wasmer_wasix/syscalls/wasi/
fd_readdir.rs

1use super::*;
2use crate::syscalls::*;
3
4/// ### `fd_readdir()`
5/// Read data from directory specified by file descriptor
6/// Inputs:
7/// - `Fd fd`
8///     File descriptor from which directory data will be read
9/// - `void *buf`
10///     Buffer where directory entries are stored
11/// - `u32 buf_len`
12///     Length of data in `buf`
13/// - `Dircookie cookie`
14///     Where the directory reading should start from
15/// Output:
16/// - `u32 *bufused`
17///     The Number of bytes stored in `buf`; if less than `buf_len` then entire
18///     directory has been read
19#[instrument(level = "trace", skip_all, fields(%fd), ret)]
20pub fn fd_readdir<M: MemorySize>(
21    mut ctx: FunctionEnvMut<'_, WasiEnv>,
22    fd: WasiFd,
23    buf: WasmPtr<u8, M>,
24    buf_len: M::Offset,
25    cookie: Dircookie,
26    bufused: WasmPtr<M::Offset, M>,
27) -> Result<Errno, WasiError> {
28    WasiEnv::do_pending_operations(&mut ctx)?;
29
30    let env = ctx.data();
31    let (memory, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) };
32    // TODO: figure out how this is supposed to work;
33    // is it supposed to pack the buffer full every time until it can't? or do one at a time?
34
35    let buf_arr = wasi_try_mem_ok!(buf.slice(&memory, buf_len));
36    let bufused_ref = bufused.deref(&memory);
37    let working_dir = wasi_try_ok!(state.fs.get_fd(fd));
38    let mut buf_idx = 0usize;
39
40    let entries: Vec<(String, Filetype, u64)> = {
41        let guard = working_dir.inode.read();
42        match guard.deref() {
43            Kind::Dir { path, entries, .. } => {
44                trace!("reading dir {:?}", path);
45                // TODO: refactor this code
46                // we need to support multiple calls,
47                // simple and obviously correct implementation for now:
48                // maintain consistent order via lexacographic sorting
49                let fs_info = wasi_try_ok!(
50                    wasi_try_ok!(state.fs_read_dir(path))
51                        .collect::<Result<Vec<_>, _>>()
52                        .map_err(fs_error_into_wasi_err)
53                );
54                let mut entry_vec = wasi_try_ok!(
55                    fs_info
56                        .into_iter()
57                        .map(|entry| {
58                            let filename = entry.file_name().to_string_lossy().to_string();
59                            trace!("getting file: {:?}", filename);
60                            let filetype = virtual_file_type_to_wasi_file_type(
61                                entry.file_type().map_err(fs_error_into_wasi_err)?,
62                            );
63                            Ok((
64                                filename, filetype, 0, // TODO: inode
65                            ))
66                        })
67                        .collect::<Result<Vec<(String, Filetype, u64)>, _>>()
68                );
69                entry_vec.extend(entries.iter().filter(|(_, inode)| inode.is_preopened).map(
70                    |(name, inode)| {
71                        let stat = inode.stat.read().unwrap();
72                        (
73                            inode.name.read().unwrap().to_string(),
74                            stat.st_filetype,
75                            stat.st_ino,
76                        )
77                    },
78                ));
79                // adding . and .. special folders
80                // TODO: inode
81                entry_vec.push((".".to_string(), Filetype::Directory, 0));
82                entry_vec.push(("..".to_string(), Filetype::Directory, 0));
83                entry_vec.sort_by(|a, b| a.0.cmp(&b.0));
84                entry_vec
85            }
86            Kind::Root { entries } => {
87                trace!("reading root");
88                let sorted_entries = {
89                    let mut entry_vec: Vec<(String, InodeGuard)> = entries
90                        .iter()
91                        .map(|(a, b)| (a.clone(), b.clone()))
92                        .collect();
93                    entry_vec.sort_by(|a, b| a.0.cmp(&b.0));
94                    entry_vec
95                };
96                sorted_entries
97                    .into_iter()
98                    .map(|(name, inode)| {
99                        let stat = inode.stat.read().unwrap();
100                        (
101                            format!("/{}", inode.name.read().unwrap().as_ref()),
102                            stat.st_filetype,
103                            stat.st_ino,
104                        )
105                    })
106                    .collect()
107            }
108            Kind::File { .. }
109            | Kind::Symlink { .. }
110            | Kind::Buffer { .. }
111            | Kind::Socket { .. }
112            | Kind::PipeRx { .. }
113            | Kind::PipeTx { .. }
114            | Kind::DuplexPipe { .. }
115            | Kind::EventNotifications { .. }
116            | Kind::Epoll { .. } => return Ok(Errno::Notdir),
117        }
118    };
119
120    for (cur_cookie, (entry_path_str, wasi_file_type, ino)) in
121        (cookie + 1..).zip(entries.iter().skip(cookie as usize))
122    {
123        let namlen = entry_path_str.len();
124        trace!("returning dirent for {}", entry_path_str);
125        let dirent = Dirent {
126            d_next: cur_cookie,
127            d_ino: *ino,
128            d_namlen: namlen as u32,
129            d_type: *wasi_file_type,
130        };
131        let dirent_bytes = dirent_to_le_bytes(&dirent);
132        let buf_len: u64 = buf_len.into();
133        let upper_limit = std::cmp::min(
134            (buf_len - buf_idx as u64) as usize,
135            std::mem::size_of::<Dirent>(),
136        );
137        for (i, b) in dirent_bytes.iter().enumerate().take(upper_limit) {
138            wasi_try_mem_ok!(buf_arr.index((i + buf_idx) as u64).write(*b));
139        }
140        buf_idx += upper_limit;
141        if upper_limit != std::mem::size_of::<Dirent>() {
142            break;
143        }
144        let upper_limit = std::cmp::min((buf_len - buf_idx as u64) as usize, namlen);
145        for (i, b) in entry_path_str.bytes().take(upper_limit).enumerate() {
146            wasi_try_mem_ok!(buf_arr.index((i + buf_idx) as u64).write(b));
147        }
148        buf_idx += upper_limit;
149        if upper_limit != namlen {
150            break;
151        }
152    }
153
154    let buf_idx: M::Offset = wasi_try_ok!(buf_idx.try_into().map_err(|_| Errno::Overflow));
155    wasi_try_mem_ok!(bufused_ref.write(buf_idx));
156    Ok(Errno::Success)
157}