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