wasi_common/sync/
file.rs

1use crate::{
2    file::{Advice, FdFlags, FileType, Filestat, WasiFile},
3    Error, ErrorExt,
4};
5use cap_fs_ext::MetadataExt;
6use fs_set_times::{SetTimes, SystemTimeSpec};
7use io_lifetimes::AsFilelike;
8use std::any::Any;
9use std::io::{self, IsTerminal};
10use system_interface::{
11    fs::{FileIoExt, GetSetFdFlags},
12    io::{IoExt, ReadReady},
13};
14
15pub struct File(cap_std::fs::File);
16
17impl File {
18    pub fn from_cap_std(file: cap_std::fs::File) -> Self {
19        File(file)
20    }
21}
22
23#[wiggle::async_trait]
24impl WasiFile for File {
25    fn as_any(&self) -> &dyn Any {
26        self
27    }
28    #[cfg(unix)]
29    fn pollable(&self) -> Option<rustix::fd::BorrowedFd> {
30        Some(self.0.as_fd())
31    }
32    #[cfg(windows)]
33    fn pollable(&self) -> Option<io_extras::os::windows::RawHandleOrSocket> {
34        Some(self.0.as_raw_handle_or_socket())
35    }
36    async fn datasync(&self) -> Result<(), Error> {
37        self.0.sync_data()?;
38        Ok(())
39    }
40    async fn sync(&self) -> Result<(), Error> {
41        self.0.sync_all()?;
42        Ok(())
43    }
44    async fn get_filetype(&self) -> Result<FileType, Error> {
45        let meta = self.0.metadata()?;
46        Ok(filetype_from(&meta.file_type()))
47    }
48    async fn get_fdflags(&self) -> Result<FdFlags, Error> {
49        let fdflags = get_fd_flags(&self.0)?;
50        Ok(fdflags)
51    }
52    async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
53        if fdflags.intersects(
54            crate::file::FdFlags::DSYNC | crate::file::FdFlags::SYNC | crate::file::FdFlags::RSYNC,
55        ) {
56            return Err(Error::invalid_argument().context("cannot set DSYNC, SYNC, or RSYNC flag"));
57        }
58        let set_fd_flags = self.0.new_set_fd_flags(to_sysif_fdflags(fdflags))?;
59        self.0.set_fd_flags(set_fd_flags)?;
60        Ok(())
61    }
62    async fn get_filestat(&self) -> Result<Filestat, Error> {
63        let meta = self.0.metadata()?;
64        Ok(Filestat {
65            device_id: meta.dev(),
66            inode: meta.ino(),
67            filetype: filetype_from(&meta.file_type()),
68            nlink: meta.nlink(),
69            size: meta.len(),
70            atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
71            mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
72            ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
73        })
74    }
75    async fn set_filestat_size(&self, size: u64) -> Result<(), Error> {
76        self.0.set_len(size)?;
77        Ok(())
78    }
79    async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
80        self.0.advise(offset, len, convert_advice(advice))?;
81        Ok(())
82    }
83    async fn set_times(
84        &self,
85        atime: Option<crate::SystemTimeSpec>,
86        mtime: Option<crate::SystemTimeSpec>,
87    ) -> Result<(), Error> {
88        self.0
89            .set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
90        Ok(())
91    }
92    async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result<u64, Error> {
93        let n = self.0.read_vectored(bufs)?;
94        Ok(n.try_into()?)
95    }
96    async fn read_vectored_at<'a>(
97        &self,
98        bufs: &mut [io::IoSliceMut<'a>],
99        offset: u64,
100    ) -> Result<u64, Error> {
101        let n = self.0.read_vectored_at(bufs, offset)?;
102        Ok(n.try_into()?)
103    }
104    async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
105        let n = self.0.write_vectored(bufs)?;
106        Ok(n.try_into()?)
107    }
108    async fn write_vectored_at<'a>(
109        &self,
110        bufs: &[io::IoSlice<'a>],
111        offset: u64,
112    ) -> Result<u64, Error> {
113        if bufs.iter().map(|i| i.len()).sum::<usize>() == 0 {
114            return Ok(0);
115        }
116        let n = self.0.write_vectored_at(bufs, offset)?;
117        Ok(n.try_into()?)
118    }
119    async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
120        Ok(self.0.seek(pos)?)
121    }
122    async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
123        let n = self.0.peek(buf)?;
124        Ok(n.try_into()?)
125    }
126    fn num_ready_bytes(&self) -> Result<u64, Error> {
127        Ok(self.0.num_ready_bytes()?)
128    }
129    fn isatty(&self) -> bool {
130        #[cfg(unix)]
131        return self.0.as_fd().is_terminal();
132        #[cfg(windows)]
133        return self.0.as_handle().is_terminal();
134    }
135}
136
137pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType {
138    use cap_fs_ext::FileTypeExt;
139    if ft.is_dir() {
140        FileType::Directory
141    } else if ft.is_symlink() {
142        FileType::SymbolicLink
143    } else if ft.is_socket() {
144        if ft.is_block_device() {
145            FileType::SocketDgram
146        } else {
147            FileType::SocketStream
148        }
149    } else if ft.is_block_device() {
150        FileType::BlockDevice
151    } else if ft.is_char_device() {
152        FileType::CharacterDevice
153    } else if ft.is_file() {
154        FileType::RegularFile
155    } else {
156        FileType::Unknown
157    }
158}
159
160#[cfg(windows)]
161use io_lifetimes::{AsHandle, BorrowedHandle};
162#[cfg(windows)]
163impl AsHandle for File {
164    fn as_handle(&self) -> BorrowedHandle<'_> {
165        self.0.as_handle()
166    }
167}
168
169#[cfg(windows)]
170use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket};
171#[cfg(windows)]
172impl AsRawHandleOrSocket for File {
173    #[inline]
174    fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket {
175        self.0.as_raw_handle_or_socket()
176    }
177}
178
179#[cfg(unix)]
180use io_lifetimes::{AsFd, BorrowedFd};
181
182#[cfg(unix)]
183impl AsFd for File {
184    fn as_fd(&self) -> BorrowedFd<'_> {
185        self.0.as_fd()
186    }
187}
188
189pub(crate) fn convert_systimespec(t: Option<crate::SystemTimeSpec>) -> Option<SystemTimeSpec> {
190    match t {
191        Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t.into_std())),
192        Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
193        None => None,
194    }
195}
196
197pub(crate) fn to_sysif_fdflags(f: crate::file::FdFlags) -> system_interface::fs::FdFlags {
198    let mut out = system_interface::fs::FdFlags::empty();
199    if f.contains(crate::file::FdFlags::APPEND) {
200        out |= system_interface::fs::FdFlags::APPEND;
201    }
202    if f.contains(crate::file::FdFlags::DSYNC) {
203        out |= system_interface::fs::FdFlags::DSYNC;
204    }
205    if f.contains(crate::file::FdFlags::NONBLOCK) {
206        out |= system_interface::fs::FdFlags::NONBLOCK;
207    }
208    if f.contains(crate::file::FdFlags::RSYNC) {
209        out |= system_interface::fs::FdFlags::RSYNC;
210    }
211    if f.contains(crate::file::FdFlags::SYNC) {
212        out |= system_interface::fs::FdFlags::SYNC;
213    }
214    out
215}
216
217/// Return the file-descriptor flags for a given file-like object.
218///
219/// This returns the flags needed to implement [`WasiFile::get_fdflags`].
220pub fn get_fd_flags<Filelike: AsFilelike>(f: Filelike) -> io::Result<crate::file::FdFlags> {
221    let f = f.as_filelike().get_fd_flags()?;
222    let mut out = crate::file::FdFlags::empty();
223    if f.contains(system_interface::fs::FdFlags::APPEND) {
224        out |= crate::file::FdFlags::APPEND;
225    }
226    if f.contains(system_interface::fs::FdFlags::DSYNC) {
227        out |= crate::file::FdFlags::DSYNC;
228    }
229    if f.contains(system_interface::fs::FdFlags::NONBLOCK) {
230        out |= crate::file::FdFlags::NONBLOCK;
231    }
232    if f.contains(system_interface::fs::FdFlags::RSYNC) {
233        out |= crate::file::FdFlags::RSYNC;
234    }
235    if f.contains(system_interface::fs::FdFlags::SYNC) {
236        out |= crate::file::FdFlags::SYNC;
237    }
238    Ok(out)
239}
240
241fn convert_advice(advice: Advice) -> system_interface::fs::Advice {
242    match advice {
243        Advice::Normal => system_interface::fs::Advice::Normal,
244        Advice::Sequential => system_interface::fs::Advice::Sequential,
245        Advice::Random => system_interface::fs::Advice::Random,
246        Advice::WillNeed => system_interface::fs::Advice::WillNeed,
247        Advice::DontNeed => system_interface::fs::Advice::DontNeed,
248        Advice::NoReuse => system_interface::fs::Advice::NoReuse,
249    }
250}