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