use std::{ ffi::CStr, fs::File, io::Write, os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}, }; /// A file whose fd cannot be written by other processes /// /// This mechanism is useful for giving clients access to large amounts of /// information such as keymaps without them being able to write to the handle. /// /// On Linux, Android, and FreeBSD, this uses a sealed memfd. On other platforms /// it creates a POSIX shared memory object with `shm_open`, opens a read-only /// copy, and unlinks it. #[derive(Debug)] pub struct SealedFile { file: File, size: usize, } impl SealedFile { /// Create a `[SealedFile]` with the given nul-terminated C string. pub fn with_content(name: &CStr, contents: &CStr) -> Result { Self::with_data(name, contents.to_bytes_with_nul()) } /// Create a `[SealedFile]` with the given binary data. #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "android"))] pub fn with_data(name: &CStr, data: &[u8]) -> Result { use rustix::fs::{MemfdFlags, SealFlags}; use std::io::Seek; let fd = rustix::fs::memfd_create(name, MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING)?; let mut file: File = fd.into(); file.write_all(data)?; file.flush()?; file.seek(std::io::SeekFrom::Start(0))?; rustix::fs::fcntl_add_seals( &file, SealFlags::SEAL | SealFlags::SHRINK | SealFlags::GROW | SealFlags::WRITE, )?; Ok(Self { file, size: data.len(), }) } /// Create a `[SealedFile]` with the given binary data. #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "android")))] pub fn with_data(name: &CStr, data: &[u8]) -> Result { use rand::{distr::Alphanumeric, Rng}; use rustix::{ io::Errno, shm::{self, Mode}, }; let mut rng = rand::rng(); // `memfd_create` isn't available. Instead, try `shm_open` with a randomized name, and // loop a couple times if it exists. let mut n = 0; let (shm_name, mut file) = loop { let mut shm_name = name.to_bytes().to_owned(); shm_name.push(b'-'); shm_name.extend((0..7).map(|_| rng.sample(Alphanumeric))); let fd = shm::open( shm_name.as_slice(), shm::OFlags::RDWR | shm::OFlags::CREATE | shm::OFlags::EXCL, Mode::RWXU, ); if !matches!(fd, Err(Errno::EXIST)) || n > 3 { break (shm_name, File::from(fd?)); } n += 1; }; // Sealing isn't available, so re-open read-only. let fd_rdonly = shm::open(shm_name.as_slice(), shm::OFlags::RDONLY, Mode::empty())?; let file_rdonly = File::from(fd_rdonly); // Unlink so another process can't open shm file. let _ = shm::unlink(shm_name.as_slice()); file.write_all(data)?; file.flush()?; Ok(Self { file: file_rdonly, size: data.len(), }) } /// Size of the data contained in the sealed file. pub fn size(&self) -> usize { self.size } } impl AsRawFd for SealedFile { fn as_raw_fd(&self) -> RawFd { self.file.as_raw_fd() } } impl AsFd for SealedFile { fn as_fd(&self) -> BorrowedFd<'_> { self.file.as_fd() } }