1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
use crate::fs::{File, OpenOptions, ReadDir};
use crate::wasi::types;
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
use crate::WasiCtx;
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
use std::{io, path::Path};

/// A reference to an open directory on the filesystem.
///
/// TODO: Implement `Dir`-using versions of `std::fs`'s free functions:
/// `copy`, `create_dir`, `create_dir_all`, `hard_link`, `metadata`,
/// `read_link`, `read_to_string`, `remove_dir`, `remove_dir_all`,
/// `remove_file`, `rename`, `set_permissions`, `symlink_metadata`, and
/// `write`.
///
/// Unlike `std::fs`, this API has no `canonicalize`, because absolute paths
/// don't interoperate well with the capability-oriented security model.
pub struct Dir<'ctx> {
    ctx: &'ctx WasiCtx,
    fd: types::Fd,
}

impl<'ctx> Dir<'ctx> {
    /// Constructs a new instance of `Self` from the given raw WASI file descriptor.
    pub unsafe fn from_raw_wasi_fd(ctx: &'ctx WasiCtx, fd: types::Fd) -> Self {
        Self { ctx, fd }
    }

    /// Attempts to open a file in read-only mode.
    ///
    /// This corresponds to [`std::fs::File::open`], but only accesses paths
    /// relative to and within `self`.
    ///
    /// TODO: Not yet implemented. Refactor the hostcalls functions to split out the
    /// encoding/decoding parts from the underlying functionality, so that we can call
    /// into the underlying functionality directly.
    ///
    /// [`std::fs::File::open`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.open
    pub fn open_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<File> {
        let path = path.as_ref();
        let mut fd = types::Fd::from(0);

        // TODO: Refactor the hostcalls functions to split out the encoding/decoding
        // parts from the underlying functionality, so that we can call into the
        // underlying functionality directly.
        //
        // TODO: Set the requested rights to be readonly.
        //
        // TODO: Handle paths for non-Unix platforms which don't have `as_bytes()`
        // on `OsStrExt`.
        unimplemented!("Dir::open_file");
        /*
        wasi_errno_to_io_error(hostcalls::path_open(
            self.ctx,
            self.fd,
            wasi::__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW,
            path.as_os_str().as_bytes(),
            path.as_os_str().len(),
            0,
            !0,
            !0,
            0,
            &mut fd,
        ))?;
        */

        let ctx = self.ctx;
        Ok(unsafe { File::from_raw_wasi_fd(ctx, fd) })
    }

    /// Opens a file at `path` with the options specified by `self`.
    ///
    /// This corresponds to [`std::fs::OpenOptions::open`].
    ///
    /// Instead of being a method on `OpenOptions`, this is a method on `Dir`,
    /// and it only accesses functions relative to and within `self`.
    ///
    /// TODO: Not yet implemented.
    ///
    /// [`std::fs::OpenOptions::open`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.open
    pub fn open_file_with<P: AsRef<Path>>(
        &mut self,
        path: P,
        options: &OpenOptions,
    ) -> io::Result<File> {
        unimplemented!("Dir::open_file_with");
    }

    /// Attempts to open a directory.
    ///
    /// TODO: Not yet implemented. See the comment in `open_file`.
    pub fn open_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Self> {
        let path = path.as_ref();
        let mut fd = types::Fd::from(0);

        // TODO: See the comment in `open_file`.
        unimplemented!("Dir::open_dir");
        /*
        wasi_errno_to_io_error(hostcalls::path_open(
            self.ctx,
            self.fd,
            wasi::__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW,
            path.as_os_str().as_bytes(),
            wasi::__WASI_OFLAGS_DIRECTORY,
            !0,
            !0,
            0,
            &mut fd,
        ))?;
        */

        let ctx = self.ctx;
        Ok(unsafe { Dir::from_raw_wasi_fd(ctx, fd) })
    }

    /// Opens a file in write-only mode.
    ///
    /// This corresponds to [`std::fs::File::create`], but only accesses paths
    /// relative to and within `self`.
    ///
    /// TODO: Not yet implemented. See the comment in `open_file`.
    ///
    /// [`std::fs::File::create`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.create
    pub fn create_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<File> {
        let path = path.as_ref();
        let mut fd = types::Fd::from(0);

        // TODO: See the comments in `open_file`.
        //
        // TODO: Set the requested rights to be read+write.
        unimplemented!("Dir::create_file");
        /*
        wasi_errno_to_io_error(hostcalls::path_open(
            self.ctx,
            self.fd,
            wasi::__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW,
            path.as_os_str().as_bytes(),
            path.as_os_str().len(),
            wasi::__WASI_OFLAGS_CREAT | wasi::__WASI_OFLAGS_TRUNC,
            !0,
            !0,
            0,
            &mut fd,
        ))?;
        */

        let ctx = self.ctx;
        Ok(unsafe { File::from_raw_wasi_fd(ctx, fd) })
    }

    /// Returns an iterator over the entries within a directory.
    ///
    /// This corresponds to [`std::fs::read_dir`], but reads the directory
    /// represented by `self`.
    ///
    /// TODO: Not yet implemented. We may need to wait until we have the ability
    /// to duplicate file descriptors before we can implement read safely. For
    /// now, use `into_read` instead.
    ///
    /// [`std::fs::read_dir`]: https://doc.rust-lang.org/std/fs/fn.read_dir.html
    pub fn read(&mut self) -> io::Result<ReadDir> {
        unimplemented!("Dir::read")
    }

    /// Consumes self and returns an iterator over the entries within a directory
    /// in the manner of `read`.
    pub fn into_read(self) -> ReadDir {
        unsafe { ReadDir::from_raw_wasi_fd(self.fd) }
    }

    /// Read the entire contents of a file into a bytes vector.
    ///
    /// This corresponds to [`std::fs::read`], but only accesses paths
    /// relative to and within `self`.
    ///
    /// [`std::fs::read`]: https://doc.rust-lang.org/std/fs/fn.read.html
    pub fn read_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Vec<u8>> {
        use io::Read;
        let mut file = self.open_file(path)?;
        let mut bytes = Vec::with_capacity(initial_buffer_size(&file));
        file.read_to_end(&mut bytes)?;
        Ok(bytes)
    }

    /// Returns an iterator over the entries within a directory.
    ///
    /// This corresponds to [`std::fs::read_dir`], but only accesses paths
    /// relative to and within `self`.
    ///
    /// [`std::fs::read_dir`]: https://doc.rust-lang.org/std/fs/fn.read_dir.html
    pub fn read_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<ReadDir> {
        self.open_dir(path)?.read()
    }
}

impl<'ctx> Drop for Dir<'ctx> {
    fn drop(&mut self) {
        // Note that errors are ignored when closing a file descriptor. The
        // reason for this is that if an error occurs we don't actually know if
        // the file descriptor was closed or not, and if we retried (for
        // something like EINTR), we might close another valid file descriptor
        // opened after we closed ours.
        let _ = self.ctx.fd_close(self.fd);
    }
}

/// Indicates how large a buffer to pre-allocate before reading the entire file.
///
/// Derived from the function of the same name in libstd.
fn initial_buffer_size(file: &File) -> usize {
    // Allocate one extra byte so the buffer doesn't need to grow before the
    // final `read` call at the end of the file.  Don't worry about `usize`
    // overflow because reading will fail regardless in that case.
    file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)
}

// TODO: impl Debug for Dir