#![allow(dead_code)]
use std::{
collections::{HashMap, VecDeque},
ffi::OsStr,
io,
path::{Path, PathBuf},
sync::Mutex,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use crate::{
fs::{Dirent, DirentKind, FileSystem, Stat},
lock::lock,
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct OpenOptions {
pub read: bool,
pub write: bool,
pub truncate: bool,
pub create: bool,
pub create_new: bool,
}
impl OpenOptions {
pub fn read_only() -> Self {
OpenOptions {
read: true,
..Default::default()
}
}
pub fn write_only() -> Self {
OpenOptions {
write: true,
..Default::default()
}
}
pub fn read_write() -> Self {
OpenOptions {
read: true,
write: true,
..Default::default()
}
}
pub fn create_new() -> Self {
OpenOptions {
create: true,
create_new: true,
..Self::read_write()
}
}
pub fn read(self, yes: bool) -> Self {
OpenOptions { read: yes, ..self }
}
pub fn write(self, yes: bool) -> Self {
OpenOptions { write: yes, ..self }
}
pub fn truncate(self, yes: bool) -> Self {
OpenOptions {
truncate: yes,
..self
}
}
pub fn create(self, yes: bool) -> Self {
OpenOptions {
create: yes,
..self
}
}
pub fn intends_write(&self) -> bool {
self.write || self.truncate || self.create || self.create_new
}
pub fn validate(&self) -> io::Result<()> {
if !self.read && !self.write {
return Err(io::ErrorKind::InvalidInput.into());
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct SetAttr {
pub size: Option<u64>,
pub mtime: Option<std::time::SystemTime>,
pub atime: Option<std::time::SystemTime>,
pub mode: Option<u32>,
pub uid: Option<u32>,
pub gid: Option<u32>,
}
const ROOT_INODE: u64 = 1;
const EBADF: i32 = 9;
fn bad_handle() -> io::Error {
io::Error::from_raw_os_error(EBADF)
}
pub struct Posix<T: FileSystem> {
store: T,
inodes: Mutex<InodeTable>,
opens: Mutex<OpenTable>,
held: Mutex<HashMap<u64, PathBuf>>,
}
impl<T: FileSystem> Posix<T> {
pub fn new(store: T) -> Self {
Posix {
store,
inodes: Mutex::new(InodeTable::new()),
opens: Mutex::new(OpenTable::new()),
held: Mutex::new(HashMap::new()),
}
}
}
const HELD_PREFIX: &str = ".virtx-unlinked-";
pub(in crate::fs) const TTL: Duration = Duration::from_secs(1);
pub(in crate::fs) const BLOCK_SIZE: u64 = 512;
pub(in crate::fs) const NAME_MAX: u32 = 255;
pub(in crate::fs) const TOTAL_BLOCKS: u64 = (1 << 40) / BLOCK_SIZE;
pub(in crate::fs) const TOTAL_INODES: u64 = 1 << 32;
const S_IFDIR: u32 = 0o040000;
const S_IFREG: u32 = 0o100000;
pub(in crate::fs) struct Attr {
pub size: u64,
pub blocks: u64,
pub blksize: u32,
pub mode: u32,
pub nlink: u32,
pub mtime: SystemTime,
pub atime: SystemTime,
pub ctime: SystemTime,
#[cfg_attr(
not(all(feature = "mount", unix, not(target_os = "macos"))),
allow(dead_code)
)]
pub crtime: SystemTime,
}
pub(in crate::fs) fn mode_for(kind: DirentKind) -> u32 {
match kind {
DirentKind::Dir => S_IFDIR | 0o755,
DirentKind::File => S_IFREG | 0o644,
}
}
#[cfg(all(feature = "mount", unix))]
pub(in crate::fs) fn host_errno(err: &io::Error) -> i32 {
if let Some(errno) = err.raw_os_error() {
return errno;
}
match err.kind() {
io::ErrorKind::NotFound => libc::ENOENT,
io::ErrorKind::NotADirectory => libc::ENOTDIR,
io::ErrorKind::IsADirectory => libc::EISDIR,
io::ErrorKind::AlreadyExists => libc::EEXIST,
io::ErrorKind::DirectoryNotEmpty => libc::ENOTEMPTY,
io::ErrorKind::InvalidFilename | io::ErrorKind::InvalidInput => libc::EINVAL,
io::ErrorKind::FileTooLarge => libc::EFBIG,
io::ErrorKind::PermissionDenied => libc::EACCES,
io::ErrorKind::StorageFull => libc::ENOSPC,
io::ErrorKind::ReadOnlyFilesystem => libc::EROFS,
io::ErrorKind::CrossesDevices => libc::EXDEV,
io::ErrorKind::Unsupported => libc::ENOSYS,
io::ErrorKind::WriteZero => libc::EIO,
_ => libc::EIO,
}
}
pub(in crate::fs) fn attr_for(stat: &Stat) -> Attr {
let nlink = 1;
let mode = mode_for(stat.kind);
let mtime = stat.mtime.unwrap_or(UNIX_EPOCH);
Attr {
size: stat.size,
blocks: stat.size.div_ceil(BLOCK_SIZE),
blksize: BLOCK_SIZE as u32,
mode,
nlink,
mtime,
atime: stat.atime.unwrap_or(mtime),
ctime: stat.ctime.unwrap_or(mtime),
crtime: stat.created.unwrap_or(mtime),
}
}
pub(in crate::fs) fn unix_time(time: SystemTime) -> (i64, i64) {
match time.duration_since(UNIX_EPOCH) {
Ok(since) => (since.as_secs() as i64, since.subsec_nanos() as i64),
Err(_) => (0, 0),
}
}
pub(in crate::fs) struct OpenFlagBits {
pub truncate: i32,
pub create: i32,
pub create_new: i32,
}
pub(in crate::fs) fn decode_open_flags(flags: i32, bits: &OpenFlagBits) -> io::Result<OpenOptions> {
const O_ACCMODE: i32 = 3;
const O_RDONLY: i32 = 0;
const O_WRONLY: i32 = 1;
const O_RDWR: i32 = 2;
let (read, write) = match flags & O_ACCMODE {
O_RDONLY => (true, false),
O_WRONLY => (false, true),
O_RDWR => (true, true),
_ => return Err(io::ErrorKind::InvalidInput.into()),
};
Ok(OpenOptions {
read,
write,
truncate: flags & bits.truncate != 0,
create: flags & bits.create != 0,
create_new: flags & bits.create_new != 0,
})
}
impl<T: FileSystem> Posix<T> {
pub(in crate::fs) async fn lookup_child(
&self,
parent: u64,
name: &OsStr,
) -> io::Result<(u64, Stat)> {
let parent_path = self.path_of(parent)?;
let child = parent_path.join(name);
let stat = self.store.stat(&child).await?;
let inode = lock(&self.inodes).intern(child);
Ok((inode, stat))
}
pub(in crate::fs) async fn stat_inode(&self, inode: u64) -> io::Result<Stat> {
let path = self.path_of(inode)?;
self.store.stat(&path).await
}
pub(in crate::fs) async fn open_inode(
&self,
inode: u64,
options: OpenOptions,
) -> io::Result<u64> {
let path = self.path_of(inode)?;
self.realize_open(&path, options).await?;
Ok(lock(&self.opens).insert(Open { inode, options }))
}
pub(in crate::fs) async fn create_child(
&self,
parent: u64,
name: &OsStr,
options: OpenOptions,
) -> io::Result<(u64, Stat, u64)> {
let path = self.path_of(parent)?.join(name);
let stat = match self.realize_open(&path, options).await? {
Some(stat) => stat,
None => self.store.stat(&path).await?,
};
let inode = lock(&self.inodes).intern(path);
let fh = lock(&self.opens).insert(Open { inode, options });
Ok((inode, stat, fh))
}
async fn realize_open(&self, path: &Path, options: OpenOptions) -> io::Result<Option<Stat>> {
let created = if options.create {
match self.store.create(path).await {
Ok(stat) => Some(stat),
Err(err) if err.kind() == io::ErrorKind::AlreadyExists && !options.create_new => {
None
}
Err(err) => return Err(err),
}
} else {
None
};
if options.truncate && created.is_none() {
self.store.truncate(path, 0).await?;
}
Ok(created)
}
pub(in crate::fs) async fn read_handle(
&self,
fh: u64,
offset: u64,
size: u32,
) -> io::Result<Vec<u8>> {
let (path, options) = self.open_of(fh)?;
if !options.read {
return Err(bad_handle());
}
let mut buf = vec![0u8; size as usize];
let n = self.store.read_at(&path, &mut buf, offset).await?;
buf.truncate(n);
Ok(buf)
}
pub(in crate::fs) async fn write_handle(
&self,
fh: u64,
offset: u64,
data: &[u8],
) -> io::Result<usize> {
let (path, options) = self.open_of(fh)?;
if !options.write {
return Err(bad_handle());
}
let mut written = 0usize;
while written < data.len() {
let n = self
.store
.write_at(&path, &data[written..], offset + written as u64)
.await?;
if n == 0 {
return Err(io::ErrorKind::WriteZero.into());
}
written += n;
}
Ok(written)
}
pub(in crate::fs) async fn mkdir_child(
&self,
parent: u64,
name: &OsStr,
) -> io::Result<(u64, Stat)> {
let path = self.path_of(parent)?.join(name);
let stat = self.store.mkdir(&path).await?;
let inode = lock(&self.inodes).intern(path);
Ok((inode, stat))
}
pub(in crate::fs) async fn unlink_child(&self, parent: u64, name: &OsStr) -> io::Result<()> {
let dir = self.path_of(parent)?;
let path = dir.join(name);
let numbered = lock(&self.inodes).number_of(&path);
let held_by = numbered.filter(|&inode| lock(&self.opens).any_on(inode));
if let Some(inode) = held_by {
let aside = dir.join(format!("{HELD_PREFIX}{inode}"));
match self.store.rename(&path, &aside).await {
Ok(()) => {
lock(&self.inodes).rekey_subtree(&path, &aside);
lock(&self.held).insert(inode, aside);
return Ok(());
}
Err(err)
if matches!(
err.kind(),
io::ErrorKind::ReadOnlyFilesystem | io::ErrorKind::Unsupported
) => {}
Err(err) => return Err(err),
}
}
self.store.unlink(&path).await?;
lock(&self.inodes).evict_path(&path);
Ok(())
}
pub(in crate::fs) async fn rmdir_child(&self, parent: u64, name: &OsStr) -> io::Result<()> {
let path = self.path_of(parent)?.join(name);
self.store.rmdir(&path).await?;
lock(&self.inodes).evict_subtree(&path);
Ok(())
}
pub(in crate::fs) async fn rename_child(
&self,
from_parent: u64,
name: &OsStr,
to_parent: u64,
to_name: &OsStr,
) -> io::Result<()> {
let from = self.path_of(from_parent)?.join(name);
let to = self.path_of(to_parent)?.join(to_name);
self.store.rename(&from, &to).await?;
lock(&self.inodes).rekey_subtree(&from, &to);
Ok(())
}
pub(in crate::fs) async fn setattr_inode(&self, inode: u64, attr: SetAttr) -> io::Result<Stat> {
if let Some(size) = attr.size {
let path = self.path_of(inode)?;
self.store.truncate(&path, size).await?;
}
self.stat_inode(inode).await
}
pub(in crate::fs) async fn flush_handle(&self, fh: u64) -> io::Result<()> {
let (path, _) = self.open_of(fh)?;
self.store.flush(&path).await
}
pub(in crate::fs) async fn release_handle(&self, fh: u64) -> io::Result<()> {
let Some(open) = lock(&self.opens).remove(fh) else {
return Ok(());
};
if lock(&self.opens).any_on(open.inode) {
return Ok(());
}
let Some(aside) = lock(&self.held).get(&open.inode).cloned() else {
return Ok(());
};
self.store.unlink(&aside).await?;
lock(&self.held).remove(&open.inode);
Ok(())
}
pub(in crate::fs) async fn dir_entries(&self, inode: u64) -> io::Result<Vec<(u64, Dirent)>> {
let dir = self.path_of(inode)?;
let children = self.store.list(&dir).await?;
let mut entries = Vec::with_capacity(children.len() + 2);
entries.push((inode, Dirent::new(".", DirentKind::Dir)));
entries.push((inode, Dirent::new("..", DirentKind::Dir)));
let mut inodes = lock(&self.inodes);
for child in children {
if child.name.starts_with(HELD_PREFIX) {
continue;
}
let ino = inodes.number_for(dir.join(&child.name));
entries.push((ino, child));
}
Ok(entries)
}
pub(in crate::fs) async fn for_each_dirent<E>(
&self,
inode: u64,
offset: u64,
mut emit: E,
) -> io::Result<()>
where
E: FnMut(u64, &Dirent, u64) -> io::Result<bool>,
{
for (position, (child_inode, child)) in self.dir_entries(inode).await?.iter().enumerate() {
let cursor = position as u64 + 1;
if cursor <= offset {
continue;
}
if emit(*child_inode, child, cursor)? {
break;
}
}
Ok(())
}
pub(in crate::fs) fn forget_inode(&self, inode: u64, count: u64) {
lock(&self.inodes).forget(inode, count);
}
fn path_of(&self, inode: u64) -> io::Result<PathBuf> {
lock(&self.inodes)
.path_of(inode)
.ok_or_else(|| io::ErrorKind::NotFound.into())
}
fn open_of(&self, fh: u64) -> io::Result<(PathBuf, OpenOptions)> {
let open = lock(&self.opens).get(fh).ok_or_else(bad_handle)?;
Ok((self.path_of(open.inode)?, open.options))
}
}
struct InodeData {
path: PathBuf,
lookup_count: u64,
}
pub(in crate::fs) struct InodeTable {
fwd: HashMap<u64, InodeData>,
rev: HashMap<PathBuf, u64>,
next: u64,
provisional: VecDeque<u64>,
}
const MAX_PROVISIONAL_INODES: usize = 64 * 1024;
impl InodeTable {
fn new() -> Self {
let root = PathBuf::from("/");
let mut fwd = HashMap::new();
fwd.insert(
ROOT_INODE,
InodeData {
path: root.clone(),
lookup_count: 1,
},
);
let mut rev = HashMap::new();
rev.insert(root, ROOT_INODE);
InodeTable {
fwd,
rev,
next: ROOT_INODE + 1,
provisional: VecDeque::new(),
}
}
pub(in crate::fs) fn number_of(&self, path: &Path) -> Option<u64> {
self.rev.get(path).copied()
}
pub(in crate::fs) fn path_of(&self, inode: u64) -> Option<PathBuf> {
self.fwd.get(&inode).map(|data| data.path.clone())
}
pub(in crate::fs) fn evict_path(&mut self, path: &Path) {
self.rev.remove(path);
}
pub(in crate::fs) fn evict_subtree(&mut self, prefix: &Path) {
self.rev.retain(|path, _| !path.starts_with(prefix));
}
pub(in crate::fs) fn rekey_subtree(&mut self, from: &Path, to: &Path) {
if from == to || to.starts_with(from) || from.starts_with(to) {
return;
}
self.evict_subtree(to);
let rebase = |path: &Path| -> Option<PathBuf> {
let rest = path.strip_prefix(from).ok()?;
Some(if rest.as_os_str().is_empty() {
to.to_path_buf()
} else {
to.join(rest)
})
};
for data in self.fwd.values_mut() {
if let Some(moved) = rebase(&data.path) {
data.path = moved;
}
}
let moved: Vec<(PathBuf, u64)> = self
.rev
.iter()
.filter_map(|(path, &inode)| rebase(path).map(|moved| (moved, inode)))
.collect();
self.rev.retain(|path, _| !path.starts_with(from));
self.rev.extend(moved);
}
pub(in crate::fs) fn intern(&mut self, path: PathBuf) -> u64 {
if let Some(&inode) = self.rev.get(&path) {
if let Some(data) = self.fwd.get_mut(&inode) {
data.lookup_count += 1;
return inode;
}
self.rev.remove(&path);
}
let inode = self.next;
self.next += 1;
self.fwd.insert(
inode,
InodeData {
path: path.clone(),
lookup_count: 1,
},
);
self.rev.insert(path, inode);
inode
}
pub(in crate::fs) fn number_for(&mut self, path: PathBuf) -> u64 {
if let Some(&inode) = self.rev.get(&path) {
return inode;
}
let inode = self.next;
self.next += 1;
self.fwd.insert(
inode,
InodeData {
path: path.clone(),
lookup_count: 0,
},
);
self.rev.insert(path, inode);
self.provisional.push_back(inode);
if self.provisional.len() > MAX_PROVISIONAL_INODES
&& let Some(oldest) = self.provisional.pop_front()
{
self.reclaim_if_unreferenced(oldest);
}
inode
}
pub(in crate::fs) fn forget(&mut self, inode: u64, count: u64) {
if inode == ROOT_INODE {
return;
}
let Some(data) = self.fwd.get_mut(&inode) else {
return;
};
data.lookup_count = data.lookup_count.saturating_sub(count);
self.reclaim_if_unreferenced(inode);
}
fn reclaim_if_unreferenced(&mut self, inode: u64) {
let Some(data) = self.fwd.get(&inode) else {
return;
};
if data.lookup_count != 0 {
return;
}
let path = data.path.clone();
self.fwd.remove(&inode);
if self.rev.get(&path) == Some(&inode) {
self.rev.remove(&path);
}
}
}
#[derive(Clone, Copy)]
struct Open {
inode: u64,
options: OpenOptions,
}
pub(in crate::fs) struct OpenTable {
open: HashMap<u64, Open>,
next: u64,
}
impl OpenTable {
fn new() -> Self {
OpenTable {
open: HashMap::new(),
next: 1,
}
}
fn insert(&mut self, open: Open) -> u64 {
let fh = self.next;
self.next += 1;
self.open.insert(fh, open);
fh
}
fn get(&self, fh: u64) -> Option<Open> {
self.open.get(&fh).copied()
}
fn remove(&mut self, fh: u64) -> Option<Open> {
self.open.remove(&fh)
}
fn any_on(&self, inode: u64) -> bool {
self.open.values().any(|open| open.inode == inode)
}
fn len(&self) -> usize {
self.open.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
const BITS: OpenFlagBits = OpenFlagBits {
truncate: 0o1000,
create: 0o100,
create_new: 0o200,
};
#[test]
fn an_append_style_open_is_not_permission_to_write() {
const O_APPEND: i32 = 0o2000;
let options = decode_open_flags(O_APPEND, &BITS).expect("`O_RDONLY` is an access mode");
assert!(options.read && !options.write);
assert!(!options.intends_write());
}
#[test]
fn every_flag_but_read_means_modification() {
let ro = OpenOptions::read_only();
assert!(!ro.intends_write(), "reading modifies nothing");
for (flag, options) in [
("write", ro.write(true)),
("truncate", ro.truncate(true)),
("create", ro.create(true)),
("create_new", OpenOptions::create_new()),
] {
assert!(options.intends_write(), "{flag}");
}
}
}