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