Skip to main content

shadow_rs/host/descriptor/
mod.rs

1//! Linux file descriptors and file descriptions (equivalent to Linux `struct file`s).
2
3use std::sync::Arc;
4
5use atomic_refcell::AtomicRefCell;
6use linux_api::errno::Errno;
7use linux_api::fcntl::{DescriptorFlags, OFlag};
8use linux_api::ioctls::IoctlRequest;
9use shadow_shim_helper_rs::syscall_types::ForeignPtr;
10
11use crate::core::worker;
12use crate::cshadow as c;
13use crate::host::descriptor::listener::{StateListenHandle, StateListenerFilter};
14use crate::host::descriptor::socket::{Socket, SocketRef, SocketRefMut};
15use crate::host::host::Host;
16use crate::host::memory_manager::MemoryManager;
17use crate::host::syscall::io::IoVec;
18use crate::host::syscall::types::{SyscallError, SyscallResult};
19use crate::utility::callback_queue::CallbackQueue;
20use crate::utility::{HostTreePointer, IsSend, IsSync, ObjectCounter};
21use shadow_shim_helper_rs::explicit_drop::ExplicitDrop;
22
23pub mod descriptor_table;
24pub mod epoll;
25pub mod eventfd;
26pub mod listener;
27pub mod pipe;
28pub mod shared_buf;
29pub mod socket;
30pub mod timerfd;
31
32bitflags::bitflags! {
33    /// These are flags that can potentially be changed from the plugin (analagous to the Linux
34    /// `filp->f_flags` status flags). Not all `O_` flags are valid here. For example file access
35    /// mode flags (ex: `O_RDWR`) are stored elsewhere, and file creation flags (ex: `O_CREAT`)
36    /// are not stored anywhere. Many of these can be represented in different ways, for example:
37    /// `O_NONBLOCK`, `SOCK_NONBLOCK`, `EFD_NONBLOCK`, `GRND_NONBLOCK`, etc, and not all have the
38    /// same value.
39    #[derive(Copy, Clone, Debug)]
40    pub struct FileStatus: i32 {
41        const NONBLOCK = OFlag::O_NONBLOCK.bits();
42        const APPEND = OFlag::O_APPEND.bits();
43        const ASYNC = OFlag::O_ASYNC.bits();
44        const DIRECT = OFlag::O_DIRECT.bits();
45        const NOATIME = OFlag::O_NOATIME.bits();
46    }
47}
48
49impl FileStatus {
50    pub fn as_o_flags(&self) -> OFlag {
51        OFlag::from_bits(self.bits()).unwrap()
52    }
53
54    /// Returns a tuple of the `FileStatus` and any remaining flags.
55    pub fn from_o_flags(flags: OFlag) -> (Self, OFlag) {
56        let status = Self::from_bits_truncate(flags.bits());
57        let remaining = flags.bits() & !status.bits();
58        (status, OFlag::from_bits(remaining).unwrap())
59    }
60}
61
62bitflags::bitflags! {
63    /// These are flags that should generally not change (analagous to the Linux `filp->f_mode`).
64    /// Since the plugin will never see these values and they're not exposed by the kernel, we
65    /// don't match the kernel `FMODE_` values here.
66    ///
67    /// Examples: https://github.com/torvalds/linux/blob/master/include/linux/fs.h#L111
68    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
69    pub struct FileMode: u32 {
70        const READ = 0b00000001;
71        const WRITE = 0b00000010;
72    }
73}
74
75impl FileMode {
76    pub fn as_o_flags(&self) -> OFlag {
77        const READ_AND_WRITE: FileMode = FileMode::READ.union(FileMode::WRITE);
78        const EMPTY: FileMode = FileMode::empty();
79
80        // https://www.gnu.org/software/libc/manual/html_node/Access-Modes.html
81        match *self {
82            READ_AND_WRITE => OFlag::O_RDWR,
83            Self::READ => OFlag::O_RDONLY,
84            Self::WRITE => OFlag::O_WRONLY,
85            // a linux-specific flag
86            EMPTY => OFlag::O_PATH,
87            _ => panic!("Invalid file mode flags"),
88        }
89    }
90
91    /// Returns a tuple of the [`FileMode`] and any remaining flags, or an empty `Err` if the flags
92    /// aren't valid (for example specifying both `O_RDWR` and `O_WRONLY`).
93    #[allow(clippy::result_unit_err)]
94    pub fn from_o_flags(flags: OFlag) -> Result<(Self, OFlag), ()> {
95        // apply the access mode mask (the O_PATH flag is not contained within the access
96        // mode mask, so we need to add it separately)
97        let mode = flags & (OFlag::O_ACCMODE | OFlag::O_PATH);
98        let remaining = flags - (OFlag::O_ACCMODE | OFlag::O_PATH);
99
100        // https://www.gnu.org/software/libc/manual/html_node/Access-Modes.html
101        let mode = match mode {
102            OFlag::O_RDONLY => FileMode::READ,
103            OFlag::O_WRONLY => FileMode::WRITE,
104            OFlag::O_RDWR => FileMode::READ | FileMode::WRITE,
105            OFlag::O_PATH => FileMode::empty(),
106            _ => return Err(()),
107        };
108
109        Ok((mode, remaining))
110    }
111}
112
113bitflags::bitflags! {
114    /// Flags representing the state of a file.
115    ///
116    /// Listeners can subscribe to state changes using [`FileRefMut::add_listener`] (or similar
117    /// methods on [`SocketRefMut`][socket::SocketRefMut], [`Pipe`][pipe::Pipe], etc).
118    ///
119    /// When setting these flags on a file, they should match the result of an epoll-wait on the
120    /// file. For example if an epoll-wait on a file would return `EPOLLIN`, then the file should
121    /// have the `READABLE` state flag. If an epoll-wait would *not* return `EPOLLIN`, then the file
122    /// should not have the `READABLE` state flag.
123    #[derive(Default, Copy, Clone, Debug)]
124    #[repr(transparent)]
125    pub struct FileState: u16 {
126        // remove this when the last reference to `FileState_NONE` has been removed from the C code
127        #[deprecated(note = "use `FileState::empty()`")]
128        const NONE = 0;
129        /// Has been initialized and it is now OK to unblock any plugin waiting on a particular
130        /// state.
131        ///
132        /// This is a legacy C state and is deprecated.
133        const ACTIVE = 1 << 0;
134        /// Can be read, i.e. there is data waiting for user.
135        const READABLE = 1 << 1;
136        /// Can be written, i.e. there is available buffer space.
137        const WRITABLE = 1 << 2;
138        /// User already called close.
139        const CLOSED = 1 << 3;
140        /// A wakeup operation occurred on a futex.
141        const FUTEX_WAKEUP = 1 << 4;
142        /// A child process had an event reportable via e.g. waitpid.
143        const CHILD_EVENT = 1 << 5;
144        /// A listening socket is allowing connections. Only applicable to connection-oriented unix
145        /// sockets.
146        const SOCKET_ALLOWING_CONNECT = 1 << 6;
147        /// "read hangup" - Stream socket peer has shut down connection for
148        /// writing (or completely closed it), as for EPOLLRDHUP.
149        const RDHUP = 1 << 7;
150    }
151}
152
153bitflags::bitflags! {
154    /// File-related signals that listeners can watch for.
155    #[derive(Default, Copy, Clone, Debug)]
156    #[repr(transparent)]
157    pub struct FileSignals: u32 {
158        /// The read buffer now has additional data available to read.
159        const READ_BUFFER_GREW = 1 << 0;
160    }
161}
162
163/// A wrapper for any type of file object.
164#[derive(Clone)]
165pub enum File {
166    Pipe(Arc<AtomicRefCell<pipe::Pipe>>),
167    EventFd(Arc<AtomicRefCell<eventfd::EventFd>>),
168    Socket(Socket),
169    TimerFd(Arc<AtomicRefCell<timerfd::TimerFd>>),
170    Epoll(Arc<AtomicRefCell<epoll::Epoll>>),
171}
172
173// will not compile if `File` is not Send + Sync
174impl IsSend for File {}
175impl IsSync for File {}
176
177impl File {
178    pub fn borrow(&self) -> FileRef<'_> {
179        match self {
180            Self::Pipe(f) => FileRef::Pipe(f.borrow()),
181            Self::EventFd(f) => FileRef::EventFd(f.borrow()),
182            Self::Socket(f) => FileRef::Socket(f.borrow()),
183            Self::TimerFd(f) => FileRef::TimerFd(f.borrow()),
184            Self::Epoll(f) => FileRef::Epoll(f.borrow()),
185        }
186    }
187
188    pub fn try_borrow(&self) -> Result<FileRef<'_>, atomic_refcell::BorrowError> {
189        Ok(match self {
190            Self::Pipe(f) => FileRef::Pipe(f.try_borrow()?),
191            Self::EventFd(f) => FileRef::EventFd(f.try_borrow()?),
192            Self::Socket(f) => FileRef::Socket(f.try_borrow()?),
193            Self::TimerFd(f) => FileRef::TimerFd(f.try_borrow()?),
194            Self::Epoll(f) => FileRef::Epoll(f.try_borrow()?),
195        })
196    }
197
198    pub fn borrow_mut(&self) -> FileRefMut<'_> {
199        match self {
200            Self::Pipe(f) => FileRefMut::Pipe(f.borrow_mut()),
201            Self::EventFd(f) => FileRefMut::EventFd(f.borrow_mut()),
202            Self::Socket(f) => FileRefMut::Socket(f.borrow_mut()),
203            Self::TimerFd(f) => FileRefMut::TimerFd(f.borrow_mut()),
204            Self::Epoll(f) => FileRefMut::Epoll(f.borrow_mut()),
205        }
206    }
207
208    pub fn try_borrow_mut(&self) -> Result<FileRefMut<'_>, atomic_refcell::BorrowMutError> {
209        Ok(match self {
210            Self::Pipe(f) => FileRefMut::Pipe(f.try_borrow_mut()?),
211            Self::EventFd(f) => FileRefMut::EventFd(f.try_borrow_mut()?),
212            Self::Socket(f) => FileRefMut::Socket(f.try_borrow_mut()?),
213            Self::TimerFd(f) => FileRefMut::TimerFd(f.try_borrow_mut()?),
214            Self::Epoll(f) => FileRefMut::Epoll(f.try_borrow_mut()?),
215        })
216    }
217
218    pub fn canonical_handle(&self) -> usize {
219        match self {
220            Self::Pipe(f) => Arc::as_ptr(f) as usize,
221            Self::EventFd(f) => Arc::as_ptr(f) as usize,
222            Self::Socket(f) => f.canonical_handle(),
223            Self::TimerFd(f) => Arc::as_ptr(f) as usize,
224            Self::Epoll(f) => Arc::as_ptr(f) as usize,
225        }
226    }
227}
228
229impl std::fmt::Debug for File {
230    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231        match self {
232            Self::Pipe(_) => write!(f, "Pipe")?,
233            Self::EventFd(_) => write!(f, "EventFd")?,
234            Self::Socket(_) => write!(f, "Socket")?,
235            Self::TimerFd(_) => write!(f, "TimerFd")?,
236            Self::Epoll(_) => write!(f, "Epoll")?,
237        }
238
239        if let Ok(file) = self.try_borrow() {
240            let state = file.state();
241            let status = file.status();
242            write!(f, "(state: {state:?}, status: {status:?})")
243        } else {
244            write!(f, "(already borrowed)")
245        }
246    }
247}
248
249/// Wraps an immutably borrowed [`File`]. Created from [`File::borrow`] or [`File::try_borrow`].
250pub enum FileRef<'a> {
251    Pipe(atomic_refcell::AtomicRef<'a, pipe::Pipe>),
252    EventFd(atomic_refcell::AtomicRef<'a, eventfd::EventFd>),
253    Socket(SocketRef<'a>),
254    TimerFd(atomic_refcell::AtomicRef<'a, timerfd::TimerFd>),
255    Epoll(atomic_refcell::AtomicRef<'a, epoll::Epoll>),
256}
257
258/// Wraps a mutably borrowed [`File`]. Created from [`File::borrow_mut`] or
259/// [`File::try_borrow_mut`].
260pub enum FileRefMut<'a> {
261    Pipe(atomic_refcell::AtomicRefMut<'a, pipe::Pipe>),
262    EventFd(atomic_refcell::AtomicRefMut<'a, eventfd::EventFd>),
263    Socket(SocketRefMut<'a>),
264    TimerFd(atomic_refcell::AtomicRefMut<'a, timerfd::TimerFd>),
265    Epoll(atomic_refcell::AtomicRefMut<'a, epoll::Epoll>),
266}
267
268impl FileRef<'_> {
269    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
270        pub fn state(&self) -> FileState
271    );
272    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
273        pub fn mode(&self) -> FileMode
274    );
275    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
276        pub fn status(&self) -> FileStatus
277    );
278    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
279        pub fn stat(&self) -> Result<linux_api::stat::stat, SyscallError>
280    );
281    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
282        pub fn has_open_file(&self) -> bool
283    );
284    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
285        pub fn supports_sa_restart(&self) -> bool
286    );
287}
288
289impl FileRefMut<'_> {
290    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
291        pub fn state(&self) -> FileState
292    );
293    enum_passthrough!(self, (off, whence), Pipe, EventFd, Socket, TimerFd, Epoll;
294        pub fn lseek(&mut self, off: linux_api::posix_types::kernel_off_t, whence: linux_api::unistd::LSeekWhence) -> Result<linux_api::posix_types::kernel_off_t, SyscallError>
295    );
296    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
297        pub fn mode(&self) -> FileMode
298    );
299    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
300        pub fn status(&self) -> FileStatus
301    );
302    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
303        pub fn stat(&self) -> Result<linux_api::stat::stat, SyscallError>
304    );
305    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
306        pub fn has_open_file(&self) -> bool
307    );
308    enum_passthrough!(self, (), Pipe, EventFd, Socket, TimerFd, Epoll;
309        pub fn supports_sa_restart(&self) -> bool
310    );
311    enum_passthrough!(self, (val), Pipe, EventFd, Socket, TimerFd, Epoll;
312        pub fn set_has_open_file(&mut self, val: bool)
313    );
314    enum_passthrough!(self, (cb_queue), Pipe, EventFd, Socket, TimerFd, Epoll;
315        pub fn close(&mut self, cb_queue: &mut CallbackQueue) -> Result<(), SyscallError>
316    );
317    enum_passthrough!(self, (status), Pipe, EventFd, Socket, TimerFd, Epoll;
318        pub fn set_status(&mut self, status: FileStatus)
319    );
320    enum_passthrough!(self, (request, arg_ptr, memory_manager), Pipe, EventFd, Socket, TimerFd, Epoll;
321        pub fn ioctl(&mut self, request: IoctlRequest, arg_ptr: ForeignPtr<()>, memory_manager: &mut MemoryManager) -> SyscallResult
322    );
323    enum_passthrough!(self, (monitoring_state, monitoring_signals, filter, notify_fn), Pipe, EventFd, Socket, TimerFd, Epoll;
324        pub fn add_listener(
325            &mut self,
326            monitoring_state: FileState,
327            monitoring_signals: FileSignals,
328            filter: StateListenerFilter,
329            notify_fn: impl Fn(FileState, FileState, FileSignals, &mut CallbackQueue) + Send + Sync + 'static,
330        ) -> StateListenHandle
331    );
332    enum_passthrough!(self, (ptr), Pipe, EventFd, Socket, TimerFd, Epoll;
333        pub fn add_legacy_listener(&mut self, ptr: HostTreePointer<c::StatusListener>)
334    );
335    enum_passthrough!(self, (ptr), Pipe, EventFd, Socket, TimerFd, Epoll;
336        pub fn remove_legacy_listener(&mut self, ptr: *mut c::StatusListener)
337    );
338    enum_passthrough!(self, (iovs, offset, flags, mem, cb_queue), Pipe, EventFd, Socket, TimerFd, Epoll;
339        pub fn readv(&mut self, iovs: &[IoVec], offset: Option<libc::off_t>, flags: libc::c_int,
340                     mem: &mut MemoryManager, cb_queue: &mut CallbackQueue) -> Result<libc::ssize_t, SyscallError>
341    );
342    enum_passthrough!(self, (iovs, offset, flags, mem, cb_queue), Pipe, EventFd, Socket, TimerFd, Epoll;
343        pub fn writev(&mut self, iovs: &[IoVec], offset: Option<libc::off_t>, flags: libc::c_int,
344                      mem: &mut MemoryManager, cb_queue: &mut CallbackQueue) -> Result<libc::ssize_t, SyscallError>
345    );
346}
347
348impl std::fmt::Debug for FileRef<'_> {
349    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350        match self {
351            Self::Pipe(_) => write!(f, "Pipe")?,
352            Self::EventFd(_) => write!(f, "EventFd")?,
353            Self::Socket(_) => write!(f, "Socket")?,
354            Self::TimerFd(_) => write!(f, "TimerFd")?,
355            Self::Epoll(_) => write!(f, "Epoll")?,
356        }
357
358        let state = self.state();
359        let status = self.status();
360        write!(f, "(state: {state:?}, status: {status:?})")
361    }
362}
363
364impl std::fmt::Debug for FileRefMut<'_> {
365    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366        match self {
367            Self::Pipe(_) => write!(f, "Pipe")?,
368            Self::EventFd(_) => write!(f, "EventFd")?,
369            Self::Socket(_) => write!(f, "Socket")?,
370            Self::TimerFd(_) => write!(f, "TimerFd")?,
371            Self::Epoll(_) => write!(f, "Epoll")?,
372        }
373
374        let state = self.state();
375        let status = self.status();
376        write!(f, "(state: {state:?}, status: {status:?})")
377    }
378}
379
380/// Represents a POSIX file description, or a Linux `struct file`.
381///
382/// An `OpenFile` wraps a reference to a [`File`]. Once there are no more `OpenFile` objects for a
383/// given `File`, the `File` will be closed. Typically this means that holding an `OpenFile` will
384/// ensure that the file remains open (the file's status will not become [`FileState::CLOSED`]), but
385/// the underlying file may close itself in extenuating circumstances (for example if the file has
386/// an internal error).
387///
388/// **Warning:** If an `OpenFile` for a specific file already exists, it is an error to create a new
389/// `OpenFile` for that file. You must clone the existing `OpenFile` object. A new `OpenFile` object
390/// should probably only ever be created for a newly created file object. Otherwise for existing
391/// file objects, it won't be clear if there are already-existing `OpenFile` objects for that file.
392///
393/// There must also not be any existing mutable borrows of the file when an `OpenFile` is created.
394#[derive(Clone, Debug)]
395pub struct OpenFile {
396    inner: Arc<OpenFileInner>,
397    _counter: ObjectCounter,
398}
399
400// will not compile if `OpenFile` is not Send + Sync
401impl IsSend for OpenFile {}
402impl IsSync for OpenFile {}
403
404impl OpenFile {
405    pub fn new(file: File) -> Self {
406        {
407            let mut file = file.borrow_mut();
408
409            if file.state().contains(FileState::CLOSED) {
410                // panic if debug assertions are enabled
411                warn_and_debug_panic!("Creating an `OpenFile` object for a closed file");
412            }
413
414            if file.has_open_file() {
415                // panic if debug assertions are enabled
416                warn_and_debug_panic!(
417                    "Creating an `OpenFile` object for a file that already has an `OpenFile` object"
418                );
419            }
420
421            file.set_has_open_file(true);
422        }
423
424        Self {
425            inner: Arc::new(OpenFileInner::new(file)),
426            _counter: ObjectCounter::new("OpenFile"),
427        }
428    }
429
430    pub fn inner_file(&self) -> &File {
431        self.inner.file.as_ref().unwrap()
432    }
433
434    /// Will close the inner `File` object if this is the last `OpenFile` for that `File`. This
435    /// behaviour is the same as simply dropping this `OpenFile` object, but allows you to pass an
436    /// event queue and get the return value of the close operation.
437    pub fn close(self, cb_queue: &mut CallbackQueue) -> Option<Result<(), SyscallError>> {
438        let OpenFile { inner, _counter } = self;
439
440        // if this is the last reference, call close() on the file
441        Arc::into_inner(inner).map(|inner| inner.close(cb_queue))
442    }
443}
444
445#[derive(Clone, Debug)]
446struct OpenFileInner {
447    file: Option<File>,
448    _counter: ObjectCounter,
449}
450
451impl OpenFileInner {
452    pub fn new(file: File) -> Self {
453        Self {
454            file: Some(file),
455            _counter: ObjectCounter::new("OpenFileInner"),
456        }
457    }
458
459    pub fn close(mut self, cb_queue: &mut CallbackQueue) -> Result<(), SyscallError> {
460        self.close_helper(cb_queue)
461    }
462
463    fn close_helper(&mut self, cb_queue: &mut CallbackQueue) -> Result<(), SyscallError> {
464        if let Some(file) = self.file.take() {
465            file.borrow_mut().close(cb_queue)?;
466        }
467        Ok(())
468    }
469}
470
471impl std::ops::Drop for OpenFileInner {
472    fn drop(&mut self) {
473        // ignore any return value
474        let _ = CallbackQueue::queue_and_run_with_legacy(|cb_queue| self.close_helper(cb_queue));
475    }
476}
477
478/// A file descriptor that reference an open file. Also contains flags that change the behaviour of
479/// this file descriptor.
480#[derive(Debug, Clone)]
481pub struct Descriptor {
482    /// The file that this descriptor points to.
483    // `Option` wrapper so that our `Drop` implementation can ensure that the
484    // file was closed or removed before drop.
485    file: Option<CompatFile>,
486    /// Descriptor flags.
487    flags: DescriptorFlags,
488    _counter: ObjectCounter,
489}
490
491// will not compile if `Descriptor` is not Send + Sync
492impl IsSend for Descriptor {}
493impl IsSync for Descriptor {}
494
495impl Descriptor {
496    pub fn new(file: CompatFile) -> Self {
497        Self {
498            file: Some(file),
499            flags: DescriptorFlags::empty(),
500            _counter: ObjectCounter::new("Descriptor"),
501        }
502    }
503
504    pub fn file(&self) -> &CompatFile {
505        self.file.as_ref().unwrap()
506    }
507
508    pub fn flags(&self) -> DescriptorFlags {
509        self.flags
510    }
511
512    pub fn set_flags(&mut self, flags: DescriptorFlags) {
513        self.flags = flags;
514    }
515
516    pub fn into_file(mut self) -> CompatFile {
517        self.file.take().unwrap()
518    }
519
520    /// Close the descriptor. The `host` option is a legacy option for legacy file.
521    pub fn close(
522        self,
523        host: &Host,
524        cb_queue: &mut CallbackQueue,
525    ) -> Option<Result<(), SyscallError>> {
526        self.into_file().close(host, cb_queue)
527    }
528
529    /// Duplicate the descriptor, with both descriptors pointing to the same `OpenFile`. In
530    /// Linux, the descriptor flags aren't typically copied to the new descriptor, so we
531    /// explicitly require a flags value to avoid confusion.
532    pub fn dup(&self, flags: DescriptorFlags) -> Self {
533        Self {
534            file: self.file.clone(),
535            flags,
536            _counter: ObjectCounter::new("Descriptor"),
537        }
538    }
539
540    pub fn into_raw(descriptor: Box<Self>) -> *mut Self {
541        Box::into_raw(descriptor)
542    }
543
544    pub fn from_raw(descriptor: *mut Self) -> Option<Box<Self>> {
545        if descriptor.is_null() {
546            return None;
547        }
548
549        unsafe { Some(Box::from_raw(descriptor)) }
550    }
551
552    /// The new descriptor takes ownership of the reference to the legacy file and does not
553    /// increment its ref count, but will decrement the ref count when this descriptor is
554    /// freed/dropped with `descriptor_free()`. The descriptor flags must be either 0 or
555    /// `O_CLOEXEC`.
556    ///
557    /// If creating a descriptor for a `TCP` object, you should use `descriptor_fromLegacyTcp`
558    /// instead. If `legacy_file` is a TCP socket, this function will panic.
559    ///
560    /// # Safety
561    ///
562    /// Takes ownership of `legacy_file`, which must be safely dereferenceable.
563    pub unsafe fn from_legacy_file(
564        legacy_file: *mut c::LegacyFile,
565        descriptor_flags: OFlag,
566    ) -> Descriptor {
567        assert!(!legacy_file.is_null());
568
569        // if it's a TCP socket, `descriptor_fromLegacyTcp` should be used instead
570        assert_ne!(
571            unsafe { c::legacyfile_getType(legacy_file) },
572            c::_LegacyFileType_DT_TCPSOCKET,
573        );
574
575        let mut descriptor = Descriptor::new(CompatFile::Legacy(LegacyFileCounter::new(
576            CountedLegacyFileRef::new(HostTreePointer::new(legacy_file)),
577        )));
578
579        let (descriptor_flags, remaining) = DescriptorFlags::from_o_flags(descriptor_flags);
580        assert!(remaining.is_empty());
581        descriptor.set_flags(descriptor_flags);
582        descriptor
583    }
584}
585
586impl Drop for Descriptor {
587    fn drop(&mut self) {
588        if self.file.is_some() {
589            warn_and_debug_panic!("Dropped descriptor without closing");
590        }
591    }
592}
593
594impl ExplicitDrop for Descriptor {
595    type ExplicitDropParam<'p> = (&'p Host, &'p mut CallbackQueue);
596    type ExplicitDropResult = Option<Result<(), SyscallError>>;
597
598    fn explicit_drop<'p>(self, param: Self::ExplicitDropParam<'p>) -> Self::ExplicitDropResult {
599        let (host, cb_queue) = param;
600        self.close(host, cb_queue)
601    }
602}
603
604/// Represents a counted reference to a legacy file object. Will decrement the legacy file's ref
605/// count when dropped.
606#[derive(Debug)]
607pub struct CountedLegacyFileRef(HostTreePointer<c::LegacyFile>);
608
609impl CountedLegacyFileRef {
610    /// Does not increment the legacy file's ref count, but will decrement the ref count when
611    /// dropped.
612    pub fn new(ptr: HostTreePointer<c::LegacyFile>) -> Self {
613        Self(ptr)
614    }
615
616    /// # Safety
617    ///
618    /// See `HostTreePointer::ptr`.
619    pub unsafe fn ptr(&self) -> *mut c::LegacyFile {
620        unsafe { self.0.ptr() }
621    }
622}
623
624impl std::clone::Clone for CountedLegacyFileRef {
625    fn clone(&self) -> Self {
626        // ref the legacy file object
627        unsafe { c::legacyfile_ref(self.0.ptr() as *mut core::ffi::c_void) };
628        Self(self.0)
629    }
630}
631
632impl Drop for CountedLegacyFileRef {
633    fn drop(&mut self) {
634        // unref the legacy file object
635        unsafe { c::legacyfile_unref(self.0.ptr() as *mut core::ffi::c_void) };
636    }
637}
638
639/// Used to track how many descriptors are open for a [`LegacyFile`][c::LegacyFile].
640///
641/// When the `close()` method is called, the legacy file's `legacyfile_close()` will only be called
642/// if this is the last descriptor for that legacy file. This is similar to an [`OpenFile`] object,
643/// but for C files.
644#[derive(Clone, Debug)]
645pub struct LegacyFileCounter {
646    file: Option<CountedLegacyFileRef>,
647    /// A count of how many open descriptors there are with reference to this legacy file.
648    open_count: Arc<()>,
649}
650
651impl LegacyFileCounter {
652    pub fn new(file: CountedLegacyFileRef) -> Self {
653        Self {
654            file: Some(file),
655            open_count: Arc::new(()),
656        }
657    }
658
659    pub fn ptr(&self) -> *mut c::LegacyFile {
660        unsafe { self.file.as_ref().unwrap().ptr() }
661    }
662
663    pub fn lseek(
664        &self,
665        off: linux_api::posix_types::kernel_off_t,
666        whence: linux_api::unistd::LSeekWhence,
667    ) -> Result<linux_api::posix_types::kernel_off_t, SyscallError> {
668        let rv = unsafe {
669            c::legacyfile_lseek(self.ptr(), off, i32::try_from(u32::from(whence)).unwrap())
670        };
671        if rv < 0 {
672            return Err(Errno::from_libc_errnum(-i32::try_from(rv).unwrap())
673                .unwrap()
674                .into());
675        }
676        Ok(rv)
677    }
678
679    pub fn stat(&self) -> Result<linux_api::stat::stat, SyscallError> {
680        let mut statbuf: linux_api::stat::stat = shadow_pod::zeroed();
681        let rv = unsafe { c::legacyfile_fstat(self.ptr(), &mut statbuf) };
682        if rv < 0 {
683            return Err(Errno::from_libc_errnum(-rv).unwrap().into());
684        }
685        Ok(statbuf)
686    }
687
688    /// Should drop `self` immediately after calling this.
689    fn close_helper(&mut self, host: &Host) {
690        // this isn't subject to race conditions since we should never access descriptors
691        // from multiple threads at the same time
692        if Arc::<()>::strong_count(&self.open_count) == 1
693            && let Some(file) = self.file.take()
694        {
695            unsafe { c::legacyfile_close(file.ptr(), host) }
696        }
697    }
698
699    /// Close the descriptor, and if this is the last descriptor pointing to its legacy file, close
700    /// the legacy file as well.
701    pub fn close(mut self, host: &Host) {
702        self.close_helper(host);
703    }
704}
705
706impl std::ops::Drop for LegacyFileCounter {
707    fn drop(&mut self) {
708        worker::Worker::with_active_host(|host| self.close_helper(host)).unwrap();
709    }
710}
711
712/// A compatibility wrapper around an [`OpenFile`] or [`LegacyFileCounter`].
713#[derive(Clone, Debug)]
714pub enum CompatFile {
715    New(OpenFile),
716    Legacy(LegacyFileCounter),
717}
718
719impl CompatFile {
720    /// Close the file. The `host` option is a legacy option for legacy files.
721    pub fn close(
722        self,
723        host: &Host,
724        cb_queue: &mut CallbackQueue,
725    ) -> Option<Result<(), SyscallError>> {
726        match self {
727            Self::New(file) => file.close(cb_queue),
728            Self::Legacy(file) => {
729                file.close(host);
730                Some(Ok(()))
731            }
732        }
733    }
734
735    pub fn lseek(
736        &self,
737        off: linux_api::posix_types::kernel_off_t,
738        whence: linux_api::unistd::LSeekWhence,
739    ) -> Result<linux_api::posix_types::kernel_off_t, SyscallError> {
740        match self {
741            CompatFile::New(file) => file.inner_file().borrow_mut().lseek(off, whence),
742            CompatFile::Legacy(file) => file.lseek(off, whence),
743        }
744    }
745
746    pub fn stat(&self) -> Result<linux_api::stat::stat, SyscallError> {
747        match self {
748            CompatFile::New(open_file) => open_file.inner_file().borrow().stat(),
749            CompatFile::Legacy(legacy_file_counter) => Ok(legacy_file_counter.stat()?),
750        }
751    }
752}
753
754mod export {
755    use super::*;
756
757    use crate::host::descriptor::socket::inet::InetSocket;
758    use crate::host::descriptor::socket::inet::legacy_tcp::LegacyTcpSocket;
759
760    /// The new descriptor takes ownership of the reference to the legacy file and does not
761    /// increment its ref count, but will decrement the ref count when this descriptor is
762    /// freed/dropped with `descriptor_free()`. The descriptor flags must be either 0 or
763    /// `O_CLOEXEC`.
764    ///
765    /// If creating a descriptor for a `TCP` object, you should use `descriptor_fromLegacyTcp`
766    /// instead. If `legacy_file` is a TCP socket, this function will panic.
767    #[unsafe(no_mangle)]
768    pub unsafe extern "C-unwind" fn descriptor_fromLegacyFile(
769        legacy_file: *mut c::LegacyFile,
770        descriptor_flags: libc::c_int,
771    ) -> *mut Descriptor {
772        let descriptor_flags = OFlag::from_bits(descriptor_flags).unwrap();
773        let descriptor = unsafe { Descriptor::from_legacy_file(legacy_file, descriptor_flags) };
774        Descriptor::into_raw(Box::new(descriptor))
775    }
776
777    /// The new descriptor takes ownership of the reference to the legacy TCP object and does not
778    /// increment its ref count, but will decrement the ref count when this descriptor is
779    /// freed/dropped with `descriptor_free()`. The descriptor flags must be either 0 or
780    /// `O_CLOEXEC`.
781    #[unsafe(no_mangle)]
782    pub unsafe extern "C-unwind" fn descriptor_fromLegacyTcp(
783        legacy_tcp: *mut c::TCP,
784        descriptor_flags: libc::c_int,
785    ) -> *mut Descriptor {
786        assert!(!legacy_tcp.is_null());
787
788        let tcp = unsafe { LegacyTcpSocket::new_from_legacy(legacy_tcp) };
789        let mut descriptor = Descriptor::new(CompatFile::New(OpenFile::new(File::Socket(
790            Socket::Inet(InetSocket::LegacyTcp(tcp)),
791        ))));
792
793        let descriptor_flags = OFlag::from_bits(descriptor_flags).unwrap();
794        let (descriptor_flags, remaining) = DescriptorFlags::from_o_flags(descriptor_flags);
795        assert!(remaining.is_empty());
796        descriptor.set_flags(descriptor_flags);
797
798        Descriptor::into_raw(Box::new(descriptor))
799    }
800
801    /// If the descriptor is a legacy file, returns a pointer to the legacy file object. Otherwise
802    /// returns NULL. The legacy file's ref count is not modified, so the pointer must not outlive
803    /// the lifetime of the descriptor.
804    #[unsafe(no_mangle)]
805    pub extern "C-unwind" fn descriptor_asLegacyFile(
806        descriptor: *const Descriptor,
807    ) -> *mut c::LegacyFile {
808        assert!(!descriptor.is_null());
809
810        let descriptor = unsafe { &*descriptor };
811
812        if let CompatFile::Legacy(d) = descriptor.file() {
813            d.ptr()
814        } else {
815            std::ptr::null_mut()
816        }
817    }
818
819    /// If the descriptor is a new/rust descriptor, returns a pointer to the reference-counted
820    /// `OpenFile` object. Otherwise returns NULL. The `OpenFile` object's ref count is not
821    /// modified, so the returned pointer must not outlive the lifetime of the descriptor.
822    #[unsafe(no_mangle)]
823    pub extern "C-unwind" fn descriptor_borrowOpenFile(
824        descriptor: *const Descriptor,
825    ) -> *const OpenFile {
826        assert!(!descriptor.is_null());
827
828        let descriptor = unsafe { &*descriptor };
829
830        match descriptor.file() {
831            CompatFile::Legacy(_) => std::ptr::null_mut(),
832            CompatFile::New(d) => d,
833        }
834    }
835
836    /// If the descriptor is a new/rust descriptor, returns a pointer to the reference-counted
837    /// `OpenFile` object. Otherwise returns NULL. The `OpenFile` object's ref count is incremented,
838    /// so the returned pointer must always later be passed to `openfile_drop()`, otherwise the
839    /// memory will leak.
840    #[unsafe(no_mangle)]
841    pub extern "C-unwind" fn descriptor_newRefOpenFile(
842        descriptor: *const Descriptor,
843    ) -> *const OpenFile {
844        assert!(!descriptor.is_null());
845
846        let descriptor = unsafe { &*descriptor };
847
848        match descriptor.file() {
849            CompatFile::Legacy(_) => std::ptr::null_mut(),
850            CompatFile::New(d) => Box::into_raw(Box::new(d.clone())),
851        }
852    }
853
854    /// The descriptor flags must be either 0 or `O_CLOEXEC`.
855    #[unsafe(no_mangle)]
856    pub extern "C-unwind" fn descriptor_setFlags(descriptor: *mut Descriptor, flags: libc::c_int) {
857        assert!(!descriptor.is_null());
858
859        let descriptor = unsafe { &mut *descriptor };
860        let flags = OFlag::from_bits(flags).unwrap();
861        let (flags, remaining_flags) = DescriptorFlags::from_o_flags(flags);
862        assert!(remaining_flags.is_empty());
863
864        descriptor.set_flags(flags);
865    }
866
867    /// Decrement the ref count of the `OpenFile` object. The pointer must not be used after calling
868    /// this function.
869    #[unsafe(no_mangle)]
870    pub extern "C-unwind" fn openfile_drop(file: *const OpenFile) {
871        assert!(!file.is_null());
872
873        drop(unsafe { Box::from_raw(file.cast_mut()) });
874    }
875
876    /// Get the state of the `OpenFile` object.
877    #[unsafe(no_mangle)]
878    pub extern "C-unwind" fn openfile_getStatus(file: *const OpenFile) -> FileState {
879        assert!(!file.is_null());
880
881        let file = unsafe { &*file };
882
883        file.inner_file().borrow().state()
884    }
885
886    /// Add a status listener to the `OpenFile` object. This will increment the status listener's
887    /// ref count, and will decrement the ref count when this status listener is removed or when the
888    /// `OpenFile` is freed/dropped.
889    #[unsafe(no_mangle)]
890    pub unsafe extern "C-unwind" fn openfile_addListener(
891        file: *const OpenFile,
892        listener: *mut c::StatusListener,
893    ) {
894        assert!(!file.is_null());
895        assert!(!listener.is_null());
896
897        let file = unsafe { &*file };
898
899        file.inner_file()
900            .borrow_mut()
901            .add_legacy_listener(HostTreePointer::new(listener));
902    }
903
904    /// Remove a listener from the `OpenFile` object.
905    #[unsafe(no_mangle)]
906    pub extern "C-unwind" fn openfile_removeListener(
907        file: *const OpenFile,
908        listener: *mut c::StatusListener,
909    ) {
910        assert!(!file.is_null());
911        assert!(!listener.is_null());
912
913        let file = unsafe { &*file };
914
915        file.inner_file()
916            .borrow_mut()
917            .remove_legacy_listener(listener);
918    }
919
920    /// Get the canonical handle for an `OpenFile` object. Two `OpenFile` objects refer to the same
921    /// underlying data if their handles are equal.
922    #[unsafe(no_mangle)]
923    pub extern "C-unwind" fn openfile_getCanonicalHandle(file: *const OpenFile) -> libc::uintptr_t {
924        assert!(!file.is_null());
925
926        let file = unsafe { &*file };
927
928        file.inner_file().canonical_handle()
929    }
930
931    /// If the descriptor is a new/rust descriptor, returns a pointer to the reference-counted
932    /// `File` object. Otherwise returns NULL. The `File` object's ref count is incremented, so the
933    /// pointer must always later be passed to `file_drop()`, otherwise the memory will leak.
934    #[unsafe(no_mangle)]
935    pub extern "C-unwind" fn descriptor_newRefFile(descriptor: *const Descriptor) -> *const File {
936        assert!(!descriptor.is_null());
937
938        let descriptor = unsafe { &*descriptor };
939
940        match descriptor.file() {
941            CompatFile::Legacy(_) => std::ptr::null_mut(),
942            CompatFile::New(d) => Box::into_raw(Box::new(d.inner_file().clone())),
943        }
944    }
945
946    /// Decrement the ref count of the `File` object. The pointer must not be used after calling
947    /// this function.
948    #[unsafe(no_mangle)]
949    pub extern "C-unwind" fn file_drop(file: *const File) {
950        assert!(!file.is_null());
951
952        drop(unsafe { Box::from_raw(file.cast_mut()) });
953    }
954
955    /// Increment the ref count of the `File` object. The returned pointer will not be the same as
956    /// the given pointer (they are distinct references), and they both must be dropped with
957    /// `file_drop` separately later.
958    #[unsafe(no_mangle)]
959    pub extern "C-unwind" fn file_cloneRef(file: *const File) -> *const File {
960        let file = unsafe { file.as_ref() }.unwrap();
961        Box::into_raw(Box::new(file.clone()))
962    }
963
964    /// Get the state of the `File` object.
965    #[unsafe(no_mangle)]
966    pub extern "C-unwind" fn file_getStatus(file: *const File) -> FileState {
967        assert!(!file.is_null());
968
969        let file = unsafe { &*file };
970
971        file.borrow().state()
972    }
973
974    /// Add a status listener to the `File` object. This will increment the status listener's ref
975    /// count, and will decrement the ref count when this status listener is removed or when the
976    /// `File` is freed/dropped.
977    #[unsafe(no_mangle)]
978    pub unsafe extern "C-unwind" fn file_addListener(
979        file: *const File,
980        listener: *mut c::StatusListener,
981    ) {
982        assert!(!file.is_null());
983        assert!(!listener.is_null());
984
985        let file = unsafe { &*file };
986
987        file.borrow_mut()
988            .add_legacy_listener(HostTreePointer::new(listener));
989    }
990
991    /// Remove a listener from the `File` object.
992    #[unsafe(no_mangle)]
993    pub extern "C-unwind" fn file_removeListener(
994        file: *const File,
995        listener: *mut c::StatusListener,
996    ) {
997        assert!(!file.is_null());
998        assert!(!listener.is_null());
999
1000        let file = unsafe { &*file };
1001
1002        file.borrow_mut().remove_legacy_listener(listener);
1003    }
1004
1005    /// Get the canonical handle for a `File` object. Two `File` objects refer to the same
1006    /// underlying data if their handles are equal.
1007    #[unsafe(no_mangle)]
1008    pub extern "C-unwind" fn file_getCanonicalHandle(file: *const File) -> libc::uintptr_t {
1009        assert!(!file.is_null());
1010
1011        let file = unsafe { &*file };
1012
1013        file.canonical_handle()
1014    }
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019    use super::*;
1020    use crate::host::syscall::Trigger;
1021    use crate::host::syscall::condition::SyscallCondition;
1022    use crate::host::syscall::types::{
1023        Blocked, Failed, SyscallError, SyscallReturn, SyscallReturnBlocked, SyscallReturnDone,
1024    };
1025
1026    #[test]
1027    // can't call foreign function: syscallcondition_new
1028    #[cfg_attr(miri, ignore)]
1029    fn test_syscallresult_roundtrip() {
1030        for val in vec![
1031            Ok(1.into()),
1032            Err(linux_api::errno::Errno::EPERM.into()),
1033            Err(SyscallError::Failed(Failed {
1034                errno: linux_api::errno::Errno::EINTR,
1035                restartable: true,
1036            })),
1037            Err(SyscallError::Failed(Failed {
1038                errno: linux_api::errno::Errno::EINTR,
1039                restartable: false,
1040            })),
1041            Err(SyscallError::Blocked(Blocked {
1042                condition: SyscallCondition::new(Trigger::from(c::Trigger {
1043                    type_: 1,
1044                    object: c::TriggerObject {
1045                        as_pointer: std::ptr::null_mut(),
1046                    },
1047                    state: FileState::CLOSED,
1048                })),
1049                restartable: true,
1050            })),
1051        ]
1052        .drain(..)
1053        {
1054            // We can't easily compare the value to the roundtripped result, since
1055            // roundtripping consumes the original value, and SyscallReturn doesn't implement Clone.
1056            // Compare their debug strings instead.
1057            let orig_debug = format!("{:?}", &val);
1058            let roundtripped = SyscallResult::from(SyscallReturn::from(val));
1059            let roundtripped_debug = format!("{roundtripped:?}");
1060            assert_eq!(orig_debug, roundtripped_debug);
1061        }
1062    }
1063
1064    #[test]
1065    // can't call foreign function: syscallcondition_new
1066    #[cfg_attr(miri, ignore)]
1067    fn test_syscallreturn_roundtrip() {
1068        let condition = SyscallCondition::new(Trigger::from(c::Trigger {
1069            type_: 1,
1070            object: c::TriggerObject {
1071                as_pointer: std::ptr::null_mut(),
1072            },
1073            state: FileState::CLOSED,
1074        }));
1075        for val in vec![
1076            SyscallReturn::Done(SyscallReturnDone {
1077                retval: 1.into(),
1078                restartable: false,
1079            }),
1080            SyscallReturn::Block(SyscallReturnBlocked {
1081                cond: condition.into_inner(),
1082                restartable: true,
1083            }),
1084            SyscallReturn::Native,
1085        ]
1086        .drain(..)
1087        {
1088            // We can't easily compare the value to the roundtripped result,
1089            // since roundtripping consumes the original value, and
1090            // SyscallReturn doesn't implement Clone. Compare their debug
1091            // strings instead.
1092            let orig_debug = format!("{:?}", &val);
1093            let roundtripped = SyscallReturn::from(SyscallResult::from(val));
1094            let roundtripped_debug = format!("{roundtripped:?}");
1095            assert_eq!(orig_debug, roundtripped_debug);
1096        }
1097    }
1098
1099    #[test]
1100    fn test_file_mode_o_flags() {
1101        // test from O flags to FileMode
1102        assert_eq!(
1103            FileMode::from_o_flags(OFlag::O_PATH),
1104            Ok((FileMode::empty(), OFlag::empty()))
1105        );
1106        assert_eq!(
1107            FileMode::from_o_flags(OFlag::O_WRONLY),
1108            Ok((FileMode::WRITE, OFlag::empty()))
1109        );
1110        assert_eq!(
1111            FileMode::from_o_flags(OFlag::O_RDWR),
1112            Ok((FileMode::READ | FileMode::WRITE, OFlag::empty()))
1113        );
1114        assert_eq!(
1115            FileMode::from_o_flags(OFlag::O_RDONLY),
1116            Ok((FileMode::READ, OFlag::empty()))
1117        );
1118        assert_eq!(
1119            FileMode::from_o_flags(OFlag::empty()),
1120            Ok((FileMode::READ, OFlag::empty()))
1121        );
1122        assert_eq!(
1123            FileMode::from_o_flags(OFlag::O_RDWR | OFlag::O_WRONLY),
1124            Err(())
1125        );
1126        assert_eq!(
1127            FileMode::from_o_flags(OFlag::O_RDWR | OFlag::O_RDONLY),
1128            Ok((FileMode::READ | FileMode::WRITE, OFlag::empty()))
1129        );
1130        assert_eq!(
1131            FileMode::from_o_flags(OFlag::O_WRONLY | OFlag::O_RDONLY),
1132            Ok((FileMode::WRITE, OFlag::empty()))
1133        );
1134        assert_eq!(
1135            FileMode::from_o_flags(OFlag::O_PATH | OFlag::O_WRONLY),
1136            Err(())
1137        );
1138        assert_eq!(
1139            FileMode::from_o_flags(OFlag::O_WRONLY | OFlag::O_CLOEXEC),
1140            Ok((FileMode::WRITE, OFlag::O_CLOEXEC))
1141        );
1142
1143        // test from FileMode to O flags
1144        assert_eq!(FileMode::as_o_flags(&FileMode::empty()), OFlag::O_PATH);
1145        assert_eq!(FileMode::as_o_flags(&FileMode::READ), OFlag::O_RDONLY);
1146        assert_eq!(FileMode::as_o_flags(&FileMode::WRITE), OFlag::O_WRONLY);
1147        assert_eq!(
1148            FileMode::as_o_flags(&(FileMode::READ | FileMode::WRITE)),
1149            OFlag::O_RDWR
1150        );
1151    }
1152}