1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! Implementation of in-memory files
use std::marker::PhantomData;
use std::mem::{size_of, MaybeUninit};
use std::os::unix::prelude::{AsRawFd, FileExt, IntoRawFd, RawFd};

use anyhow::{anyhow, Result};
use memfd::{FileSeal, Memfd, MemfdOptions};
use memmap2::{Mmap, MmapMut};
use nix::unistd::{close, dup};
use procfs::process::{FDTarget, Process};

use crate::error::{ResultExt, SystemError, TypedError, TypedResult};
use crate::shmem::{TypedMmap, TypedMmapMut};

#[derive(Debug, Clone, Copy)]
/// Internal struct for handling in-memory files
pub struct TempFile<T: Send + Clone + Sized> {
    // TODO: Consider storing a Memfd instead of a RawFd
    fd: RawFd,
    _p: PhantomData<T>,
}

impl<T: Send + Clone + Sized> TempFile<T> {
    /// Creates an in-memory file
    pub fn create<N: AsRef<str>>(name: N) -> TypedResult<Self> {
        trace!("Create TempFile \"{}\"", name.as_ref());
        let mem = MemfdOptions::default()
            .close_on_exec(false)
            .allow_sealing(true)
            .create(name)
            .typ(SystemError::Panic)?;
        mem.as_file()
            .set_len(
                size_of::<T>()
                    .try_into()
                    .expect("Could not fit usize into u64"),
            )
            .typ(SystemError::Panic)?;
        mem.add_seals(&[FileSeal::SealShrink, FileSeal::SealGrow])
            .typ(SystemError::Panic)?;

        Ok(Self {
            fd: mem.into_raw_fd(),
            _p: PhantomData,
        })
    }

    /// Converts a FD to a Memfd without borrowing ownership
    fn get_memfd(&self) -> TypedResult<Memfd> {
        // TODO: The call to dup(2) may be removed, because RawFd has no real ownership
        let fd = dup(self.fd).typ(SystemError::Panic)?;
        Memfd::try_from_fd(fd)
            .map_err(|e| {
                close(fd).ok();
                anyhow!("Could not get Memfd from {e:#?}")
            })
            .typ(SystemError::Panic)
    }

    /// Set the TempFile to read-only (prevents further seal modifications)
    pub fn seal_read_only(&self) -> TypedResult<TypedMmapMut<T>> {
        let mmap = self.get_typed_mmap_mut()?;

        self.get_memfd()?
            .add_seals(&[FileSeal::SealFutureWrite, FileSeal::SealSeal])
            .typ(SystemError::Panic)?;

        Ok(mmap)
    }

    /// Returns the raw FD of the TempFile
    pub fn fd(&self) -> RawFd {
        self.fd
    }

    /// Writes value to the TempFile (overwrites existing data, but does not
    /// clean)
    // TODO: Consider deleting the previous data?
    pub fn write(&self, value: &T) -> TypedResult<()> {
        // TODO: Use an approach without unsafe
        let bytes =
            unsafe { std::slice::from_raw_parts(value as *const T as *const u8, size_of::<T>()) };
        let file = self.get_memfd()?.into_file();
        file.write_all_at(bytes, 0)
            .map_err(anyhow::Error::from)
            .typ(SystemError::Panic)
    }

    /// Returns all of the TempFile's data
    // TODO check if this is used for sampling ports
    pub fn read(&self) -> TypedResult<T> {
        // MaybeUninit ensures we avoid alignment related UB
        let mut data = MaybeUninit::<T>::uninit();
        let bytes_required = size_of::<T>();

        // the mut buf binding to the data in the MaybeUninit allows writing to the type
        // byte-wise
        let buf =
            unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr() as *mut u8, size_of::<T>()) };

        let file = self.get_memfd()?.into_file();

        // read_at avoids confusion by moving cursors on shared file descriptors
        let bytes_read = file.read_at(buf, 0).typ(SystemError::Panic)?;

        trace!("read {bytes_read} bytes from memfd {}", self.fd);
        if bytes_read != bytes_required {
            warn!(
                "initialized {} ({bytes_required} bytes in size) with {bytes_read} bytes originating from memfd {}",
                std::any::type_name::<T>(), self.fd()
            );
        }

        Ok(unsafe { data.assume_init() })
    }

    /// Returns a mutable memory map from a TempFile
    pub fn get_typed_mmap_mut(&self) -> TypedResult<TypedMmapMut<T>> {
        let fd = dup(self.fd).typ(SystemError::Panic)?;
        unsafe {
            MmapMut::map_mut(fd)
                .map_err(|e| {
                    close(fd).ok();
                    anyhow!("Could not get Mmap from {e:#?}")
                })
                .typ(SystemError::Panic)?
                .try_into()
        }
    }

    /// Returns a memory map from a TemplFile
    pub fn get_typed_mmap(&self) -> TypedResult<TypedMmap<T>> {
        let fd = dup(self.fd).typ(SystemError::Panic)?;
        unsafe {
            Mmap::map(fd)
                .map_err(|e| {
                    close(fd).ok();
                    anyhow!("Could not get Mmap from {e:#?}")
                })
                .typ(SystemError::Panic)?
                .try_into()
        }
    }
}

impl<T: Send + Clone> TryFrom<RawFd> for TempFile<T> {
    type Error = TypedError;

    fn try_from(fd: RawFd) -> Result<Self, Self::Error> {
        let tf = Self {
            fd,
            _p: PhantomData,
        };
        let memfd = tf.get_memfd()?;
        trace!("Got Memfd from {fd}. Seals: {:?}", memfd.seals());
        Ok(tf)
    }
}

impl<T: Send + Clone + Sized> AsRawFd for TempFile<T> {
    fn as_raw_fd(&self) -> RawFd {
        self.fd
    }
}

// TODO remove this function
// This may fail if the name is the same as another name or a part of another
pub fn get_memfd(name: &str) -> TypedResult<i32> {
    let name = format!("memfd:{name}");
    Process::myself()
        .typ(SystemError::Panic)?
        .fd()
        .typ(SystemError::Panic)?
        .flatten()
        .find_map(|f| {
            if let FDTarget::Path(p) = &f.target {
                if p.to_str().expect("Got non-UTF-8 String").contains(&name) {
                    Some(f.fd)
                } else {
                    None
                }
            } else {
                None
            }
        })
        .ok_or_else(|| anyhow!("No File Descriptor with Name: {name}"))
        .typ(SystemError::Panic)
}