1942 lines · 59.2 KB · Rust
RawDownload
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.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t