a653rs_linux_core/
file.rs

1//! Implementation of in-memory files
2use std::marker::PhantomData;
3use std::mem::{size_of, MaybeUninit};
4use std::os::unix::prelude::{AsRawFd, FileExt, IntoRawFd, RawFd};
5
6use anyhow::{anyhow, Result};
7use memfd::{FileSeal, Memfd, MemfdOptions};
8use memmap2::{Mmap, MmapMut};
9use nix::unistd::{close, dup};
10use procfs::process::{FDTarget, Process};
11
12use crate::error::{ResultExt, SystemError, TypedError, TypedResult};
13use crate::shmem::{TypedMmap, TypedMmapMut};
14
15#[derive(Debug, Clone, Copy)]
16/// Internal struct for handling in-memory files
17pub struct TempFile<T: Send + Clone + Sized> {
18    // TODO: Consider storing a Memfd instead of a RawFd
19    fd: RawFd,
20    _p: PhantomData<T>,
21}
22
23impl<T: Send + Clone + Sized> TempFile<T> {
24    /// Creates an in-memory file
25    pub fn create<N: AsRef<str>>(name: N) -> TypedResult<Self> {
26        trace!("Create TempFile \"{}\"", name.as_ref());
27        let mem = MemfdOptions::default()
28            .close_on_exec(false)
29            .allow_sealing(true)
30            .create(name)
31            .typ(SystemError::Panic)?;
32        mem.as_file()
33            .set_len(
34                size_of::<T>()
35                    .try_into()
36                    .expect("Could not fit usize into u64"),
37            )
38            .typ(SystemError::Panic)?;
39        mem.add_seals(&[FileSeal::SealShrink, FileSeal::SealGrow])
40            .typ(SystemError::Panic)?;
41
42        Ok(Self {
43            fd: mem.into_raw_fd(),
44            _p: PhantomData,
45        })
46    }
47
48    /// Converts a FD to a Memfd without borrowing ownership
49    fn get_memfd(&self) -> TypedResult<Memfd> {
50        // TODO: The call to dup(2) may be removed, because RawFd has no real ownership
51        let fd = dup(self.fd).typ(SystemError::Panic)?;
52        Memfd::try_from_fd(fd)
53            .map_err(|e| {
54                close(fd).ok();
55                anyhow!("Could not get Memfd from {e:#?}")
56            })
57            .typ(SystemError::Panic)
58    }
59
60    /// Set the TempFile to read-only (prevents further seal modifications)
61    pub fn seal_read_only(&self) -> TypedResult<TypedMmapMut<T>> {
62        let mmap = self.get_typed_mmap_mut()?;
63
64        self.get_memfd()?
65            .add_seals(&[FileSeal::SealFutureWrite, FileSeal::SealSeal])
66            .typ(SystemError::Panic)?;
67
68        Ok(mmap)
69    }
70
71    /// Returns the raw FD of the TempFile
72    pub fn fd(&self) -> RawFd {
73        self.fd
74    }
75
76    /// Writes value to the TempFile (overwrites existing data, but does not
77    /// clean)
78    // TODO: Consider deleting the previous data?
79    pub fn write(&self, value: &T) -> TypedResult<()> {
80        // TODO: Use an approach without unsafe
81        let bytes =
82            unsafe { std::slice::from_raw_parts(value as *const T as *const u8, size_of::<T>()) };
83        let file = self.get_memfd()?.into_file();
84        file.write_all_at(bytes, 0)
85            .map_err(anyhow::Error::from)
86            .typ(SystemError::Panic)
87    }
88
89    /// Returns all of the TempFile's data
90    // TODO check if this is used for sampling ports
91    pub fn read(&self) -> TypedResult<T> {
92        // MaybeUninit ensures we avoid alignment related UB
93        let mut data = MaybeUninit::<T>::uninit();
94        let bytes_required = size_of::<T>();
95
96        // the mut buf binding to the data in the MaybeUninit allows writing to the type
97        // byte-wise
98        let buf =
99            unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr() as *mut u8, size_of::<T>()) };
100
101        let file = self.get_memfd()?.into_file();
102
103        // read_at avoids confusion by moving cursors on shared file descriptors
104        let bytes_read = file.read_at(buf, 0).typ(SystemError::Panic)?;
105
106        trace!("read {bytes_read} bytes from memfd {}", self.fd);
107        if bytes_read != bytes_required {
108            warn!(
109                "initialized {} ({bytes_required} bytes in size) with {bytes_read} bytes originating from memfd {}",
110                std::any::type_name::<T>(), self.fd()
111            );
112        }
113
114        Ok(unsafe { data.assume_init() })
115    }
116
117    /// Returns a mutable memory map from a TempFile
118    pub fn get_typed_mmap_mut(&self) -> TypedResult<TypedMmapMut<T>> {
119        let fd = dup(self.fd).typ(SystemError::Panic)?;
120        unsafe {
121            MmapMut::map_mut(fd)
122                .map_err(|e| {
123                    close(fd).ok();
124                    anyhow!("Could not get Mmap from {e:#?}")
125                })
126                .typ(SystemError::Panic)?
127                .try_into()
128        }
129    }
130
131    /// Returns a memory map from a TemplFile
132    pub fn get_typed_mmap(&self) -> TypedResult<TypedMmap<T>> {
133        let fd = dup(self.fd).typ(SystemError::Panic)?;
134        unsafe {
135            Mmap::map(fd)
136                .map_err(|e| {
137                    close(fd).ok();
138                    anyhow!("Could not get Mmap from {e:#?}")
139                })
140                .typ(SystemError::Panic)?
141                .try_into()
142        }
143    }
144}
145
146impl<T: Send + Clone> TryFrom<RawFd> for TempFile<T> {
147    type Error = TypedError;
148
149    fn try_from(fd: RawFd) -> Result<Self, Self::Error> {
150        let tf = Self {
151            fd,
152            _p: PhantomData,
153        };
154        let memfd = tf.get_memfd()?;
155        trace!("Got Memfd from {fd}. Seals: {:?}", memfd.seals());
156        Ok(tf)
157    }
158}
159
160impl<T: Send + Clone + Sized> AsRawFd for TempFile<T> {
161    fn as_raw_fd(&self) -> RawFd {
162        self.fd
163    }
164}
165
166// TODO remove this function
167// This may fail if the name is the same as another name or a part of another
168pub fn get_memfd(name: &str) -> TypedResult<i32> {
169    let name = format!("memfd:{name}");
170    Process::myself()
171        .typ(SystemError::Panic)?
172        .fd()
173        .typ(SystemError::Panic)?
174        .flatten()
175        .find_map(|f| {
176            if let FDTarget::Path(p) = &f.target {
177                if p.to_str().expect("Got non-UTF-8 String").contains(&name) {
178                    Some(f.fd)
179                } else {
180                    None
181                }
182            } else {
183                None
184            }
185        })
186        .ok_or_else(|| anyhow!("No File Descriptor with Name: {name}"))
187        .typ(SystemError::Panic)
188}