use std::collections::{btree_map::Values, BTreeMap, BTreeSet};use std::error::Error;use std::fmt;use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};use std::sync::{Arc, Condvar, Mutex, MutexGuard}; use crate::vfs::VirtualStat; pub const MAX_FDS_PER_PROCESS: usize = 256; pub const O_RDONLY: u32 = 0;pub const O_WRONLY: u32 = 1;pub const O_RDWR: u32 = 2;pub const O_CREAT: u32 = 0o100;pub const O_EXCL: u32 = 0o200;pub const O_TRUNC: u32 = 0o1000;pub const O_APPEND: u32 = 0o2000;pub const O_NONBLOCK: u32 = 0o4000;pub const O_DIRECT: u32 = 0o40000;pub const O_DIRECTORY: u32 = 0o200000;pub const O_NOFOLLOW: u32 = 0o400000;pub const F_DUPFD: u32 = 0;pub const F_GETFD: u32 = 1;pub const F_SETFD: u32 = 2;pub const F_GETFL: u32 = 3;pub const F_SETFL: u32 = 4;pub const FD_CLOEXEC: u32 = 1;pub const LOCK_SH: u32 = 1;pub const LOCK_EX: u32 = 2;pub const LOCK_NB: u32 = 4;pub const LOCK_UN: u32 = 8;const DEFAULT_MAX_RECORD_LOCKS: usize = 4096; pub const FILETYPE_UNKNOWN: u8 = 0;pub const FILETYPE_BLOCK_DEVICE: u8 = 1;pub const FILETYPE_CHARACTER_DEVICE: u8 = 2;pub const FILETYPE_DIRECTORY: u8 = 3;pub const FILETYPE_REGULAR_FILE: u8 = 4;pub const FILETYPE_SOCKET_DGRAM: u8 = 5;pub const FILETYPE_SOCKET_STREAM: u8 = 6;pub const FILETYPE_PIPE: u8 = FILETYPE_SOCKET_STREAM;pub const FILETYPE_SYMBOLIC_LINK: u8 = 7; pub type FdResult<T> = Result<T, FdTableError>;pub type SharedFileDescription = Arc<FileDescription>; // Every kernel subsystem keys pipes, PTYs, sockets, locks, and poll state by// open-file-description identity. Allocate that identity from one global// monotonic domain: per-subsystem counters can eventually overlap and make an// unrelated regular file look like a stale socket or pipe.static NEXT_FILE_DESCRIPTION_ID: AtomicU64 = AtomicU64::new(1); pub(crate) fn allocate_file_description_id() -> u64 { NEXT_FILE_DESCRIPTION_ID .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |id| id.checked_add(1)) .expect("open-file-description id space exhausted")} #[derive(Debug, Default)]pub struct AnonymousFileUsage { bytes: AtomicU64, inodes: AtomicUsize,} impl AnonymousFileUsage { pub fn bytes(&self) -> u64 { self.bytes.load(Ordering::SeqCst) } pub fn inodes(&self) -> usize { self.inodes.load(Ordering::SeqCst) } fn add_file(&self, size: u64) { self.bytes.fetch_add(size, Ordering::SeqCst); self.inodes.fetch_add(1, Ordering::SeqCst); } fn remove_file(&self, size: u64) { self.bytes.fetch_sub(size, Ordering::SeqCst); self.inodes.fetch_sub(1, Ordering::SeqCst); } fn resize_file(&self, old_size: u64, new_size: u64) { if new_size >= old_size { self.bytes .fetch_add(new_size.saturating_sub(old_size), Ordering::SeqCst); } else { self.bytes .fetch_sub(old_size.saturating_sub(new_size), Ordering::SeqCst); } }} #[derive(Debug)]pub struct AnonymousFile { pub data: Vec<u8>, pub stat: VirtualStat, usage: Arc<AnonymousFileUsage>,} impl AnonymousFile { pub fn new(data: Vec<u8>, stat: VirtualStat, usage: Arc<AnonymousFileUsage>) -> Self { usage.add_file(stat.size); Self { data, stat, usage } }} impl Drop for AnonymousFile { fn drop(&mut self) { self.usage.remove_file(self.stat.size); }} pub type SharedAnonymousFile = Arc<Mutex<AnonymousFile>>; #[derive(Debug)]enum FileBacking { Path(String), LinkedAlias { former_path: String, live_path: String, }, Anonymous { former_path: String, file: SharedAnonymousFile, }, DetachedDirectory { former_path: String, stat: VirtualStat, },} #[derive(Debug, Clone, PartialEq, Eq)]pub struct FdTableError { code: &'static str, message: String,} impl FdTableError { pub fn code(&self) -> &'static str { self.code } fn bad_file_descriptor(fd: u32) -> Self { Self { code: "EBADF", message: format!("bad file descriptor {fd}"), } } fn too_many_open_files() -> Self { Self { code: "EMFILE", message: String::from("too many open files"), } } fn no_memory(message: impl Into<String>) -> Self { Self { code: "ENOMEM", message: message.into(), } } fn invalid_argument(message: impl Into<String>) -> Self { Self { code: "EINVAL", message: message.into(), } } fn would_block(message: impl Into<String>) -> Self { Self { code: "EWOULDBLOCK", message: message.into(), } } fn deadlock(message: impl Into<String>) -> Self { Self { code: "EDEADLK", message: message.into(), } }} impl fmt::Display for FdTableError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", self.code, self.message) }} impl Error for FdTableError {} #[derive(Debug)]pub struct FileDescription { id: u64, backing: Mutex<FileBacking>, lock_target: Option<FileLockTarget>, cursor: AtomicU64, flags: AtomicU32, ref_count: AtomicUsize,} impl FileDescription { pub fn new(id: u64, path: impl Into<String>, flags: u32) -> Self { Self::with_ref_count_and_lock(id, path, flags, 1, None) } pub fn new_with_lock( id: u64, path: impl Into<String>, flags: u32, lock_target: Option<FileLockTarget>, ) -> Self { Self::with_ref_count_and_lock(id, path, flags, 1, lock_target) } pub fn with_ref_count(id: u64, path: impl Into<String>, flags: u32, ref_count: usize) -> Self { Self::with_ref_count_and_lock(id, path, flags, ref_count, None) } pub fn with_ref_count_and_lock( id: u64, path: impl Into<String>, flags: u32, ref_count: usize, lock_target: Option<FileLockTarget>, ) -> Self { Self { id, backing: Mutex::new(FileBacking::Path(path.into())), lock_target, cursor: AtomicU64::new(0), flags: AtomicU32::new(flags), ref_count: AtomicUsize::new(ref_count), } } pub fn id(&self) -> u64 { self.id } pub fn path(&self) -> String { match &*lock_or_recover(&self.backing) { FileBacking::Path(path) => path.clone(), FileBacking::LinkedAlias { live_path, .. } => live_path.clone(), FileBacking::Anonymous { former_path, .. } => former_path.clone(), FileBacking::DetachedDirectory { former_path, .. } => former_path.clone(), } } /// Linux renders an unlinked open file description through procfs with a /// ` (deleted)` suffix while the description itself remains usable. pub fn proc_display_path(&self) -> String { match &*lock_or_recover(&self.backing) { FileBacking::Path(path) => path.clone(), FileBacking::LinkedAlias { former_path, .. } | FileBacking::Anonymous { former_path, .. } | FileBacking::DetachedDirectory { former_path, .. } => { format!("{former_path} (deleted)") } } } pub fn is_path_backed_by(&self, expected: &str) -> bool { match &*lock_or_recover(&self.backing) { FileBacking::Path(path) => path == expected, FileBacking::LinkedAlias { live_path, .. } => live_path == expected, FileBacking::Anonymous { .. } | FileBacking::DetachedDirectory { .. } => false, } } pub fn rename_path_prefix(&self, old_path: &str, new_path: &str) { let mut backing = lock_or_recover(&self.backing); let path = match &mut *backing { FileBacking::Path(path) => path, FileBacking::LinkedAlias { live_path, .. } => live_path, FileBacking::Anonymous { .. } | FileBacking::DetachedDirectory { .. } => return, }; if path == old_path { *path = new_path.to_string(); } else if let Some(suffix) = path .strip_prefix(old_path) .filter(|value| value.starts_with('/')) { *path = format!("{new_path}{suffix}"); } } pub fn detach_path(&self, expected: &str, file: SharedAnonymousFile) -> bool { let mut backing = lock_or_recover(&self.backing); let former_path = match &*backing { FileBacking::Path(path) if path == expected => expected.to_string(), FileBacking::LinkedAlias { former_path, live_path, } if live_path == expected => former_path.clone(), _ => return false, }; *backing = FileBacking::Anonymous { former_path, file }; true } /// Keep an unlinked open description attached to the same inode through a /// surviving hard-link name. The former name remains visible through /// procfs, while descriptor operations use the live alias. pub fn rebind_deleted_path(&self, expected: &str, live_path: &str) -> bool { let mut backing = lock_or_recover(&self.backing); let former_path = match &*backing { FileBacking::Path(path) if path == expected => expected.to_string(), FileBacking::LinkedAlias { former_path, live_path: current, } if current == expected => former_path.clone(), _ => return false, }; *backing = FileBacking::LinkedAlias { former_path, live_path: live_path.to_string(), }; true } pub fn detach_directory(&self, _expected: &str, stat: VirtualStat) -> bool { let mut backing = lock_or_recover(&self.backing); let FileBacking::Path(former_path) = &*backing else { return false; }; let former_path = former_path.clone(); *backing = FileBacking::DetachedDirectory { former_path, stat }; true } pub fn detached_directory_stat(&self) -> Option<VirtualStat> { match &*lock_or_recover(&self.backing) { FileBacking::DetachedDirectory { stat, .. } => Some(stat.clone()), FileBacking::Path(_) | FileBacking::LinkedAlias { .. } | FileBacking::Anonymous { .. } => None, } } pub fn anonymous_stat(&self) -> Option<VirtualStat> { let file = match &*lock_or_recover(&self.backing) { FileBacking::Anonymous { file, .. } => Arc::clone(file), FileBacking::Path(_) | FileBacking::LinkedAlias { .. } | FileBacking::DetachedDirectory { .. } => return None, }; let stat = lock_or_recover(&file).stat.clone(); Some(stat) } pub fn anonymous_pread(&self, offset: u64, length: usize) -> Option<Vec<u8>> { let file = match &*lock_or_recover(&self.backing) { FileBacking::Anonymous { file, .. } => Arc::clone(file), FileBacking::Path(_) | FileBacking::LinkedAlias { .. } | FileBacking::DetachedDirectory { .. } => return None, }; let file = lock_or_recover(&file); let start = usize::try_from(offset).unwrap_or(usize::MAX); if start >= file.data.len() { return Some(Vec::new()); } let end = start.saturating_add(length).min(file.data.len()); Some(file.data[start..end].to_vec()) } pub fn anonymous_pwrite(&self, offset: u64, data: &[u8]) -> Option<FdResult<u64>> { let file = match &*lock_or_recover(&self.backing) { FileBacking::Anonymous { file, .. } => Arc::clone(file), FileBacking::Path(_) | FileBacking::LinkedAlias { .. } | FileBacking::DetachedDirectory { .. } => return None, }; let mut file = lock_or_recover(&file); let Ok(start) = usize::try_from(offset) else { return Some(Err(FdTableError::invalid_argument( "anonymous file offset exceeds host address space", ))); }; let Some(end) = start.checked_add(data.len()) else { return Some(Err(FdTableError::invalid_argument( "anonymous file write range overflows", ))); }; if end > file.data.len() { let additional = end - file.data.len(); if file.data.try_reserve(additional).is_err() { return Some(Err(FdTableError::no_memory( "anonymous file write allocation failed", ))); } file.data.resize(end, 0); } file.data[start..end].copy_from_slice(data); let old_size = file.stat.size; file.stat.size = file.data.len() as u64; file.stat.blocks = file.stat.size.div_ceil(512); file.usage.resize_file(old_size, file.stat.size); Some(Ok(file.stat.size)) } pub fn anonymous_truncate(&self, length: u64) -> Option<FdResult<()>> { let file = match &*lock_or_recover(&self.backing) { FileBacking::Anonymous { file, .. } => Arc::clone(file), FileBacking::Path(_) | FileBacking::LinkedAlias { .. } | FileBacking::DetachedDirectory { .. } => return None, }; let Ok(length) = usize::try_from(length) else { return Some(Err(FdTableError::invalid_argument( "anonymous file length exceeds host address space", ))); }; let mut file = lock_or_recover(&file); let additional = length.saturating_sub(file.data.len()); if additional > 0 && file.data.try_reserve(additional).is_err() { return Some(Err(FdTableError::no_memory( "anonymous file truncate allocation failed", ))); } file.data.resize(length, 0); let old_size = file.stat.size; file.stat.size = length as u64; file.stat.blocks = file.stat.size.div_ceil(512); file.usage.resize_file(old_size, file.stat.size); Some(Ok(())) } pub fn detached_chmod(&self, mode: u32) -> bool { let mut backing = lock_or_recover(&self.backing); match &mut *backing { FileBacking::Anonymous { file, .. } => { let mut file = lock_or_recover(file); file.stat.mode = (file.stat.mode & !0o7777) | (mode & 0o7777); true } FileBacking::DetachedDirectory { stat, .. } => { stat.mode = (stat.mode & !0o7777) | (mode & 0o7777); true } FileBacking::Path(_) | FileBacking::LinkedAlias { .. } => false, } } pub fn detached_chown(&self, uid: u32, gid: u32, changed_mode: Option<u32>) -> bool { let mut backing = lock_or_recover(&self.backing); match &mut *backing { FileBacking::Anonymous { file, .. } => { let mut file = lock_or_recover(file); file.stat.uid = uid; file.stat.gid = gid; if let Some(mode) = changed_mode { file.stat.mode = mode; } true } FileBacking::DetachedDirectory { stat, .. } => { stat.uid = uid; stat.gid = gid; true } FileBacking::Path(_) | FileBacking::LinkedAlias { .. } => false, } } pub fn lock_target(&self) -> Option<FileLockTarget> { self.lock_target } pub fn cursor(&self) -> u64 { self.cursor.load(Ordering::SeqCst) } pub fn set_cursor(&self, cursor: u64) { self.cursor.store(cursor, Ordering::SeqCst); } pub fn flags(&self) -> u32 { self.flags.load(Ordering::SeqCst) } pub fn update_flags(&self, mask: u32, flags: u32) -> u32 { let mut current = self.flags(); loop { let next = (current & !mask) | (flags & mask); match self .flags .compare_exchange(current, next, Ordering::SeqCst, Ordering::SeqCst) { Ok(_) => return next, Err(observed) => current = observed, } } } Lines 1–500 of 1942. Load more
use std::collections::{btree_map::Values, BTreeMap, BTreeSet}; use std::error::Error; use std::fmt; use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex, MutexGuard}; use crate::vfs::VirtualStat; pub const MAX_FDS_PER_PROCESS: usize = 256; pub const O_RDONLY: u32 = 0; pub const O_WRONLY: u32 = 1; pub const O_RDWR: u32 = 2; pub const O_CREAT: u32 = 0o100; pub const O_EXCL: u32 = 0o200; pub const O_TRUNC: u32 = 0o1000; pub const O_APPEND: u32 = 0o2000; pub const O_NONBLOCK: u32 = 0o4000; pub const O_DIRECT: u32 = 0o40000; pub const O_DIRECTORY: u32 = 0o200000; pub const O_NOFOLLOW: u32 = 0o400000; pub const F_DUPFD: u32 = 0; pub const F_GETFD: u32 = 1; pub const F_SETFD: u32 = 2; pub const F_GETFL: u32 = 3; pub const F_SETFL: u32 = 4; pub const FD_CLOEXEC: u32 = 1; pub const LOCK_SH: u32 = 1; pub const LOCK_EX: u32 = 2; pub const LOCK_NB: u32 = 4; pub const LOCK_UN: u32 = 8; const DEFAULT_MAX_RECORD_LOCKS: usize = 4096; pub const FILETYPE_UNKNOWN: u8 = 0; pub const FILETYPE_BLOCK_DEVICE: u8 = 1; pub const FILETYPE_CHARACTER_DEVICE: u8 = 2; pub const FILETYPE_DIRECTORY: u8 = 3; pub const FILETYPE_REGULAR_FILE: u8 = 4; pub const FILETYPE_SOCKET_DGRAM: u8 = 5; pub const FILETYPE_SOCKET_STREAM: u8 = 6; pub const FILETYPE_PIPE: u8 = FILETYPE_SOCKET_STREAM; pub const FILETYPE_SYMBOLIC_LINK: u8 = 7; pub type FdResult<T> = Result<T, FdTableError>; pub type SharedFileDescription = Arc<FileDescription>; // Every kernel subsystem keys pipes, PTYs, sockets, locks, and poll state by // open-file-description identity. Allocate that identity from one global // monotonic domain: per-subsystem counters can eventually overlap and make an // unrelated regular file look like a stale socket or pipe. static NEXT_FILE_DESCRIPTION_ID: AtomicU64 = AtomicU64::new(1); pub(crate) fn allocate_file_description_id() -> u64 { NEXT_FILE_DESCRIPTION_ID .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |id| id.checked_add(1)) .expect("open-file-description id space exhausted") } #[derive(Debug, Default)] pub struct AnonymousFileUsage { bytes: AtomicU64, inodes: AtomicUsize, } impl AnonymousFileUsage { pub fn bytes(&self) -> u64 { self.bytes.load(Ordering::SeqCst) } pub fn inodes(&self) -> usize { self.inodes.load(Ordering::SeqCst) } fn add_file(&self, size: u64) { self.bytes.fetch_add(size, Ordering::SeqCst); self.inodes.fetch_add(1, Ordering::SeqCst); } fn remove_file(&self, size: u64) { self.bytes.fetch_sub(size, Ordering::SeqCst); self.inodes.fetch_sub(1, Ordering::SeqCst); } fn resize_file(&self, old_size: u64, new_size: u64) { if new_size >= old_size { self.bytes .fetch_add(new_size.saturating_sub(old_size), Ordering::SeqCst); } else { self.bytes .fetch_sub(old_size.saturating_sub(new_size), Ordering::SeqCst); } } } #[derive(Debug)] pub struct AnonymousFile { pub data: Vec<u8>, pub stat: VirtualStat, usage: Arc<AnonymousFileUsage>, } impl AnonymousFile { pub fn new(data: Vec<u8>, stat: VirtualStat, usage: Arc<AnonymousFileUsage>) -> Self { usage.add_file(stat.size); Self { data, stat, usage } } } impl Drop for AnonymousFile { fn drop(&mut self) { self.usage.remove_file(self.stat.size); } } pub type SharedAnonymousFile = Arc<Mutex<AnonymousFile>>; #[derive(Debug)] enum FileBacking { Path(String), LinkedAlias { former_path: String, live_path: String, }, Anonymous { former_path: String, file: SharedAnonymousFile, }, DetachedDirectory { former_path: String, stat: VirtualStat, }, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct FdTableError { code: &'static str, message: String, } impl FdTableError { pub fn code(&self) -> &'static str { self.code } fn bad_file_descriptor(fd: u32) -> Self { Self { code: "EBADF", message: format!("bad file descriptor {fd}"), } } fn too_many_open_files() -> Self { Self { code: "EMFILE", message: String::from("too many open files"), } } fn no_memory(message: impl Into<String>) -> Self { Self { code: "ENOMEM", message: message.into(), } } fn invalid_argument(message: impl Into<String>) -> Self { Self { code: "EINVAL", message: message.into(), } } fn would_block(message: impl Into<String>) -> Self { Self { code: "EWOULDBLOCK", message: message.into(), } } fn deadlock(message: impl Into<String>) -> Self { Self { code: "EDEADLK", message: message.into(), } } } impl fmt::Display for FdTableError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", self.code, self.message) } } impl Error for FdTableError {} #[derive(Debug)] pub struct FileDescription { id: u64, backing: Mutex<FileBacking>, lock_target: Option<FileLockTarget>, cursor: AtomicU64, flags: AtomicU32, ref_count: AtomicUsize, } impl FileDescription { pub fn new(id: u64, path: impl Into<String>, flags: u32) -> Self { Self::with_ref_count_and_lock(id, path, flags, 1, None) } pub fn new_with_lock( id: u64, path: impl Into<String>, flags: u32, lock_target: Option<FileLockTarget>, ) -> Self { Self::with_ref_count_and_lock(id, path, flags, 1, lock_target) } pub fn with_ref_count(id: u64, path: impl Into<String>, flags: u32, ref_count: usize) -> Self { Self::with_ref_count_and_lock(id, path, flags, ref_count, None) } pub fn with_ref_count_and_lock( id: u64, path: impl Into<String>, flags: u32, ref_count: usize, lock_target: Option<FileLockTarget>, ) -> Self { Self { id, backing: Mutex::new(FileBacking::Path(path.into())), lock_target, cursor: AtomicU64::new(0), flags: AtomicU32::new(flags), ref_count: AtomicUsize::new(ref_count), } } pub fn id(&self) -> u64 { self.id } pub fn path(&self) -> String { match &*lock_or_recover(&self.backing) { FileBacking::Path(path) => path.clone(), FileBacking::LinkedAlias { live_path, .. } => live_path.clone(), FileBacking::Anonymous { former_path, .. } => former_path.clone(), FileBacking::DetachedDirectory { former_path, .. } => former_path.clone(), } } /// Linux renders an unlinked open file description through procfs with a /// ` (deleted)` suffix while the description itself remains usable. pub fn proc_display_path(&self) -> String { match &*lock_or_recover(&self.backing) { FileBacking::Path(path) => path.clone(), FileBacking::LinkedAlias { former_path, .. } | FileBacking::Anonymous { former_path, .. } | FileBacking::DetachedDirectory { former_path, .. } => { format!("{former_path} (deleted)") } } } pub fn is_path_backed_by(&self, expected: &str) -> bool { match &*lock_or_recover(&self.backing) { FileBacking::Path(path) => path == expected, FileBacking::LinkedAlias { live_path, .. } => live_path == expected, FileBacking::Anonymous { .. } | FileBacking::DetachedDirectory { .. } => false, } } pub fn rename_path_prefix(&self, old_path: &str, new_path: &str) { let mut backing = lock_or_recover(&self.backing); let path = match &mut *backing { FileBacking::Path(path) => path, FileBacking::LinkedAlias { live_path, .. } => live_path, FileBacking::Anonymous { .. } | FileBacking::DetachedDirectory { .. } => return, }; if path == old_path { *path = new_path.to_string(); } else if let Some(suffix) = path .strip_prefix(old_path) .filter(|value| value.starts_with('/')) { *path = format!("{new_path}{suffix}"); } } pub fn detach_path(&self, expected: &str, file: SharedAnonymousFile) -> bool { let mut backing = lock_or_recover(&self.backing); let former_path = match &*backing { FileBacking::Path(path) if path == expected => expected.to_string(), FileBacking::LinkedAlias { former_path, live_path, } if live_path == expected => former_path.clone(), _ => return false, }; *backing = FileBacking::Anonymous { former_path, file }; true } /// Keep an unlinked open description attached to the same inode through a /// surviving hard-link name. The former name remains visible through /// procfs, while descriptor operations use the live alias. pub fn rebind_deleted_path(&self, expected: &str, live_path: &str) -> bool { let mut backing = lock_or_recover(&self.backing); let former_path = match &*backing { FileBacking::Path(path) if path == expected => expected.to_string(), FileBacking::LinkedAlias { former_path, live_path: current, } if current == expected => former_path.clone(), _ => return false, }; *backing = FileBacking::LinkedAlias { former_path, live_path: live_path.to_string(), }; true } pub fn detach_directory(&self, _expected: &str, stat: VirtualStat) -> bool { let mut backing = lock_or_recover(&self.backing); let FileBacking::Path(former_path) = &*backing else { return false; }; let former_path = former_path.clone(); *backing = FileBacking::DetachedDirectory { former_path, stat }; true } pub fn detached_directory_stat(&self) -> Option<VirtualStat> { match &*lock_or_recover(&self.backing) { FileBacking::DetachedDirectory { stat, .. } => Some(stat.clone()), FileBacking::Path(_) | FileBacking::LinkedAlias { .. } | FileBacking::Anonymous { .. } => None, } } pub fn anonymous_stat(&self) -> Option<VirtualStat> { let file = match &*lock_or_recover(&self.backing) { FileBacking::Anonymous { file, .. } => Arc::clone(file), FileBacking::Path(_) | FileBacking::LinkedAlias { .. } | FileBacking::DetachedDirectory { .. } => return None, }; let stat = lock_or_recover(&file).stat.clone(); Some(stat) } pub fn anonymous_pread(&self, offset: u64, length: usize) -> Option<Vec<u8>> { let file = match &*lock_or_recover(&self.backing) { FileBacking::Anonymous { file, .. } => Arc::clone(file), FileBacking::Path(_) | FileBacking::LinkedAlias { .. } | FileBacking::DetachedDirectory { .. } => return None, }; let file = lock_or_recover(&file); let start = usize::try_from(offset).unwrap_or(usize::MAX); if start >= file.data.len() { return Some(Vec::new()); } let end = start.saturating_add(length).min(file.data.len()); Some(file.data[start..end].to_vec()) } pub fn anonymous_pwrite(&self, offset: u64, data: &[u8]) -> Option<FdResult<u64>> { let file = match &*lock_or_recover(&self.backing) { FileBacking::Anonymous { file, .. } => Arc::clone(file), FileBacking::Path(_) | FileBacking::LinkedAlias { .. } | FileBacking::DetachedDirectory { .. } => return None, }; let mut file = lock_or_recover(&file); let Ok(start) = usize::try_from(offset) else { return Some(Err(FdTableError::invalid_argument( "anonymous file offset exceeds host address space", ))); }; let Some(end) = start.checked_add(data.len()) else { return Some(Err(FdTableError::invalid_argument( "anonymous file write range overflows", ))); }; if end > file.data.len() { let additional = end - file.data.len(); if file.data.try_reserve(additional).is_err() { return Some(Err(FdTableError::no_memory( "anonymous file write allocation failed", ))); } file.data.resize(end, 0); } file.data[start..end].copy_from_slice(data); let old_size = file.stat.size; file.stat.size = file.data.len() as u64; file.stat.blocks = file.stat.size.div_ceil(512); file.usage.resize_file(old_size, file.stat.size); Some(Ok(file.stat.size)) } pub fn anonymous_truncate(&self, length: u64) -> Option<FdResult<()>> { let file = match &*lock_or_recover(&self.backing) { FileBacking::Anonymous { file, .. } => Arc::clone(file), FileBacking::Path(_) | FileBacking::LinkedAlias { .. } | FileBacking::DetachedDirectory { .. } => return None, }; let Ok(length) = usize::try_from(length) else { return Some(Err(FdTableError::invalid_argument( "anonymous file length exceeds host address space", ))); }; let mut file = lock_or_recover(&file); let additional = length.saturating_sub(file.data.len()); if additional > 0 && file.data.try_reserve(additional).is_err() { return Some(Err(FdTableError::no_memory( "anonymous file truncate allocation failed", ))); } file.data.resize(length, 0); let old_size = file.stat.size; file.stat.size = length as u64; file.stat.blocks = file.stat.size.div_ceil(512); file.usage.resize_file(old_size, file.stat.size); Some(Ok(())) } pub fn detached_chmod(&self, mode: u32) -> bool { let mut backing = lock_or_recover(&self.backing); match &mut *backing { FileBacking::Anonymous { file, .. } => { let mut file = lock_or_recover(file); file.stat.mode = (file.stat.mode & !0o7777) | (mode & 0o7777); true } FileBacking::DetachedDirectory { stat, .. } => { stat.mode = (stat.mode & !0o7777) | (mode & 0o7777); true } FileBacking::Path(_) | FileBacking::LinkedAlias { .. } => false, } } pub fn detached_chown(&self, uid: u32, gid: u32, changed_mode: Option<u32>) -> bool { let mut backing = lock_or_recover(&self.backing); match &mut *backing { FileBacking::Anonymous { file, .. } => { let mut file = lock_or_recover(file); file.stat.uid = uid; file.stat.gid = gid; if let Some(mode) = changed_mode { file.stat.mode = mode; } true } FileBacking::DetachedDirectory { stat, .. } => { stat.uid = uid; stat.gid = gid; true } FileBacking::Path(_) | FileBacking::LinkedAlias { .. } => false, } } pub fn lock_target(&self) -> Option<FileLockTarget> { self.lock_target } pub fn cursor(&self) -> u64 { self.cursor.load(Ordering::SeqCst) } pub fn set_cursor(&self, cursor: u64) { self.cursor.store(cursor, Ordering::SeqCst); } pub fn flags(&self) -> u32 { self.flags.load(Ordering::SeqCst) } pub fn update_flags(&self, mask: u32, flags: u32) -> u32 { let mut current = self.flags(); loop { let next = (current & !mask) | (flags & mask); match self .flags .compare_exchange(current, next, Ordering::SeqCst, Ordering::SeqCst) { Ok(_) => return next, Err(observed) => current = observed, } } }