memfile 0.3.2

memory backed files using memfd_create with file sealing support
Documentation
use assert2::{assert, let_assert};
use memfile::{MemFile, Seal, Seals};
use std::io::{Read, Write, Seek};
use std::os::fd::OwnedFd;
use std::os::unix::io::AsRawFd;

#[test]
fn create_write_seek_read() {
	let_assert!(Ok(mut file) = MemFile::create_default("foo"));
	assert!(let Ok(()) = file.write_all(b"Hello world!"));

	let mut buffer = [0u8; 12];
	assert!(let Ok(0) = file.seek(std::io::SeekFrom::Start(0)));
	assert!(let Ok(()) = file.read_exact(&mut buffer));
	assert!(&buffer == b"Hello world!");
}

#[track_caller]
fn dup_stdout() -> OwnedFd {
	use std::os::fd::FromRawFd;
	use std::mem::ManuallyDrop;

	let stdout = ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(1) });
	let_assert!(Ok(file) = stdout.try_clone());
	file.into()
}

#[test]
fn from_fd() {
	// We should be able to wrap a MemFile as MemFile again.
	let_assert!(Ok(original) = MemFile::create_default("foo"));
	let original_fd = original.as_raw_fd();
	let_assert!(Ok(moved) = MemFile::from_fd(original.into_fd()));
	assert!(moved.as_raw_fd() == original_fd);

	// We should not be able to wrap stdout as MemFile.
	let dupped_stdout = dup_stdout();
	let dupped_fd = dupped_stdout.as_raw_fd();
	let_assert!(Err(error) = MemFile::from_fd(dupped_stdout));
	assert!(error.error().kind() == std::io::ErrorKind::InvalidInput);
	assert!(error.fd().as_raw_fd() == dupped_fd);
}

#[test]
fn try_clone() {
	let_assert!(Ok(original) = MemFile::create_default("foo"));
	let_assert!(Ok(dupped) = original.try_clone());

	// Dupped file descriptors should not have the same value.
	assert!(original.as_raw_fd() != dupped.as_raw_fd());
}

#[test]
fn set_len_stat() {
	let_assert!(Ok(file) = MemFile::create_default("foo"));
	assert!(let Ok(()) = file.set_len(12));
	let_assert!(Ok(stat) = file.metadata());
	assert!(stat.len() == 12);
}

#[test]
fn seal_seal() {
	// Create the file and resize it to 12 bytes.
	let_assert!(Ok(file) = MemFile::create_sealable("foo"));
	assert!(let Ok(()) = file.add_seal(Seal::Seal));

	let_assert!(Err(error) = file.add_seal(Seal::Grow));
	assert!(error.kind() == std::io::ErrorKind::PermissionDenied);
}

#[test]
fn seal_shrink() {
	// Create the file and resize it to 12 bytes.
	let_assert!(Ok(file) = MemFile::create_sealable("foo"));
	assert!(let Ok(()) = file.set_len(12));
	let_assert!(Ok(stat) = file.metadata());
	assert!(stat.len() == 12);

	// Seal it and try to shrink it.
	assert!(let Ok(()) = file.add_seal(Seal::Shrink));
	let_assert!(Err(error) = file.set_len(6));
	assert!(error.kind() == std::io::ErrorKind::PermissionDenied);
	let_assert!(Ok(stat) = file.metadata());
	assert!(stat.len() == 12);

	// Try to grow it.
	let_assert!(Ok(()) = file.set_len(18));
	let_assert!(Ok(stat) = file.metadata());
	assert!(stat.len() == 18);
}

#[test]
fn seal_grow() {
	// Create the file and resize it to 12 bytes.
	let_assert!(Ok(file) = MemFile::create_sealable("foo"));
	assert!(let Ok(()) = file.set_len(12));
	let_assert!(Ok(stat) = file.metadata());
	assert!(stat.len() == 12);

	// Seal it and try to grow it.
	assert!(let Ok(()) = file.add_seal(Seal::Grow));
	let_assert!(Err(error) = file.set_len(18));
	assert!(error.kind() == std::io::ErrorKind::PermissionDenied);
	let_assert!(Ok(stat) = file.metadata());
	assert!(stat.len() == 12);

	// Try to shrink it.
	let_assert!(Ok(()) = file.set_len(6));
	let_assert!(Ok(stat) = file.metadata());
	assert!(stat.len() == 6);
}

#[test]
fn seal_write() {
	let_assert!(Ok(mut file) = MemFile::create_sealable("foo"));
	assert!(let Ok(()) = file.add_seal(Seal::Write));

	let_assert!(Err(error) = file.write_all(b"Hello world!"));
	assert!(error.kind() == std::io::ErrorKind::PermissionDenied);
}

#[test]
#[cfg(target_os = "linux")]
fn seal_future_write() {
	// TODO: to properly test this, we need to create a shared writable memory mapping, and validate that it remains usable,
	// and that no new shared writable mappings can be made.
	let_assert!(Ok(mut file) = MemFile::create_sealable("foo"));
	assert!(let Ok(()) = file.add_seal(Seal::FutureWrite));

	let_assert!(Err(error) = file.write_all(b"Hello world!"));
	assert!(error.kind() == std::io::ErrorKind::PermissionDenied);
}

#[test]
fn clones_share_metadata_and_seals() {
	let_assert!(Ok(original) = MemFile::create_sealable("foo"));
	let_assert!(Ok(mut dupped) = original.try_clone());

	let_assert!(Ok(()) = original.set_len(12));
	let_assert!(Ok(stat) = dupped.metadata());
	assert!(stat.len() == 12);

	let_assert!(Ok(()) = original.add_seals(Seals::all()));
	let_assert!(Err(error) = dupped.write_all(b"Hello world!"));
	assert!(error.kind() == std::io::ErrorKind::PermissionDenied);
}

#[test]
fn sealing_must_be_enabled() {
	// Create MemFile without enabling sealing.
	let_assert!(Ok(original) = MemFile::create_default("foo"));

	// Now try to add a seal, which should fail.
	let_assert!(Err(error) = original.add_seals(Seals::all()));
	assert!(error.kind() == std::io::ErrorKind::PermissionDenied);
}