🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
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, std::io::Error> {
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<Self, std::io::Error> {
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<Self, std::io::Error> {
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()
}
}