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 cur_cookie = cookie;
39    let mut buf_idx = 0usize;
40
41    let entries: Vec<(String, Filetype, u64)> = {
42        let guard = working_dir.inode.read();
43        match guard.deref() {
44            Kind::Dir { path, entries, .. } => {
45                trace!("reading dir {:?}", path);
46                // TODO: refactor this code
47                // we need to support multiple calls,
48                // simple and obviously correct implementation for now:
49                // maintain consistent order via lexacographic sorting
50                let fs_info = wasi_try_ok!(
51                    wasi_try_ok!(state.fs_read_dir(path))
52                        .collect::<Result<Vec<_>, _>>()
53                        .map_err(fs_error_into_wasi_err)
54                );
55                let mut entry_vec = wasi_try_ok!(
56                    fs_info
57                        .into_iter()
58                        .map(|entry| {
59                            let filename = entry.file_name().to_string_lossy().to_string();
60                            trace!("getting file: {:?}", filename);
61                            let filetype = virtual_file_type_to_wasi_file_type(
62                                entry.file_type().map_err(fs_error_into_wasi_err)?,
63                            );
64                            Ok((
65                                filename, filetype, 0, // TODO: inode
66                            ))
67                        })
68                        .collect::<Result<Vec<(String, Filetype, u64)>, _>>()
69                );
70                entry_vec.extend(entries.iter().filter(|(_, inode)| inode.is_preopened).map(
71                    |(name, inode)| {
72                        let stat = inode.stat.read().unwrap();
73                        (
74                            inode.name.read().unwrap().to_string(),
75                            stat.st_filetype,
76                            stat.st_ino,
77                        )
78                    },
79                ));
80                // adding . and .. special folders
81                // TODO: inode
82                entry_vec.push((".".to_string(), Filetype::Directory, 0));
83                entry_vec.push(("..".to_string(), Filetype::Directory, 0));
84                entry_vec.sort_by(|a, b| a.0.cmp(&b.0));
85                entry_vec
86            }
87            Kind::Root { entries } => {
88                trace!("reading root");
89                let sorted_entries = {
90                    let mut entry_vec: Vec<(String, InodeGuard)> = entries
91                        .iter()
92                        .map(|(a, b)| (a.clone(), b.clone()))
93                        .collect();
94                    entry_vec.sort_by(|a, b| a.0.cmp(&b.0));
95                    entry_vec
96                };
97                sorted_entries
98                    .into_iter()
99                    .map(|(name, inode)| {
100                        let stat = inode.stat.read().unwrap();
101                        (
102                            format!("/{}", inode.name.read().unwrap().as_ref()),
103                            stat.st_filetype,
104                            stat.st_ino,
105                        )
106                    })
107                    .collect()
108            }
109            Kind::File { .. }
110            | Kind::Symlink { .. }
111            | Kind::Buffer { .. }
112            | Kind::Socket { .. }
113            | Kind::PipeRx { .. }
114            | Kind::PipeTx { .. }
115            | Kind::DuplexPipe { .. }
116            | Kind::EventNotifications { .. }
117            | Kind::Epoll { .. } => return Ok(Errno::Notdir),
118        }
119    };
120
121    for (entry_path_str, wasi_file_type, ino) in entries.iter().skip(cookie as usize) {
122        cur_cookie += 1;
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}