use super::{ Classification, ClassificationSnapshot, Duration, Errno, File, FileType, FlockOperation, HashMap, INodeNo, IgnoredWorkspace, LockFlags, Mode, OFlags, OsStr, OsStrExt, OsString, OsStringExt, Path, PathBuf, ReplyEmpty, ReplyXattr, SystemTime, TimeOrNow, Timespec, UNIX_EPOCH, UTIME_NOW, UTIME_OMIT, io, tracked_paths_from_index, }; impl IgnoredWorkspace { pub(super) fn remove(&self, parent: INodeNo, name: &OsStr, directory: bool, reply: ReplyEmpty) { let result = (|| { let path = self.child_path(parent, name)?; let snapshot = self.classifier.snapshot_for(&path); let entry = self.resolve(&snapshot, &path)?; self.root(entry.backing).unlink(&path, directory) })(); reply_result(result, reply); } pub(super) fn with_temporary_file( &self, inode: INodeNo, operation: impl FnOnce(&File) -> io::Result, ) -> io::Result { let path = self.path_for(inode)?; let snapshot = self.classifier.snapshot_for(&path); let entry = self.resolve(&snapshot, &path)?; let file = self .root(entry.backing) .open_entry(&path, OFlags::RDONLY, Mode::empty())?; operation(&file) } pub(super) fn require_index_publication_safe( &self, backing: Classification, source: &Path, snapshot: &ClassificationSnapshot, ) -> io::Result<()> { let bytes = self.root(backing).read_file(source, 512 * 1024 * 1024)?; for path in tracked_paths_from_index(&bytes)? { let path = PathBuf::from(OsString::from_vec(path)); if snapshot.is_tracked(&path) { continue; } let host = self.host.stat(&path).ok(); let isolated = self.isolated.stat(&path).ok(); if let Some(isolated) = isolated { let compatible = host.is_some_and(|host| { host.st_mode & libc::S_IFMT == isolated.st_mode & libc::S_IFMT }); if !compatible { return Err(io::Error::from_raw_os_error(libc::EBUSY)); } } } Ok(()) } } pub(super) struct ResolvedEntry { pub(super) path: PathBuf, pub(super) backing: Classification, pub(super) stat: rustix::fs::Stat, } pub(super) struct OpenFile { pub(super) file: File, pub(super) backing: Classification, pub(super) access_mode: i32, } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub(super) struct PosixLockKey { pub(super) backing: Classification, pub(super) device: u64, pub(super) inode: u64, pub(super) owner: u64, } pub(super) struct DirectoryStream { pub(super) directory: File, pub(super) entries: Vec, pub(super) _snapshot: ClassificationSnapshot, } pub(super) struct DirectoryEntry { pub(super) inode: INodeNo, pub(super) kind: FileType, pub(super) name: OsString, } pub(super) struct NodeTable { paths: HashMap, inodes: HashMap, next_inode: u64, } impl NodeTable { pub(super) fn new() -> Self { Self { paths: HashMap::from([(INodeNo::ROOT.0, PathBuf::new())]), inodes: HashMap::new(), next_inode: 2, } } pub(super) fn path(&self, inode: INodeNo) -> Option { self.paths.get(&inode.0).cloned() } pub(super) fn inode(&mut self, entry: &ResolvedEntry) -> INodeNo { let key = NodeKey { path: entry.path.clone(), backing: entry.backing, device: entry.stat.st_dev, inode: entry.stat.st_ino, }; if let Some(inode) = self.inodes.get(&key) { return INodeNo(*inode); } let inode = self.next_inode; self.next_inode = self.next_inode.saturating_add(1); self.paths.insert(inode, entry.path.clone()); self.inodes.insert(key, inode); INodeNo(inode) } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] struct NodeKey { path: PathBuf, backing: Classification, device: u64, inode: u64, } pub(super) fn open_flags(flags: i32) -> OFlags { let mut result = OFlags::from_bits_retain(u32::from_ne_bytes(flags.to_ne_bytes())); result.remove(OFlags::CREATE | OFlags::EXCL | OFlags::NOFOLLOW | OFlags::CLOEXEC); result } pub(super) fn file_type(mode: u32) -> io::Result { match mode & libc::S_IFMT { libc::S_IFREG => Ok(FileType::RegularFile), libc::S_IFDIR => Ok(FileType::Directory), libc::S_IFLNK => Ok(FileType::Symlink), libc::S_IFIFO => Ok(FileType::NamedPipe), libc::S_IFSOCK => Ok(FileType::Socket), libc::S_IFCHR => Ok(FileType::CharDevice), libc::S_IFBLK => Ok(FileType::BlockDevice), _ => Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)), } } pub(super) fn system_time(seconds: i64, nanoseconds: u64) -> SystemTime { if seconds >= 0 { UNIX_EPOCH + Duration::new( u64::try_from(seconds).unwrap_or(0), u32::try_from(nanoseconds).unwrap_or(999_999_999), ) } else { UNIX_EPOCH } } pub(super) fn timestamp(value: Option) -> Timespec { match value { Some(TimeOrNow::Now) => Timespec { tv_sec: 0, tv_nsec: UTIME_NOW, }, Some(TimeOrNow::SpecificTime(value)) => { let duration = value.duration_since(UNIX_EPOCH).unwrap_or_default(); Timespec { tv_sec: i64::try_from(duration.as_secs()).unwrap_or(i64::MAX), tv_nsec: i64::from(duration.subsec_nanos()), } } None => Timespec { tv_sec: 0, tv_nsec: UTIME_OMIT, }, } } pub(super) fn writable_xattr(name: &OsStr) -> bool { matches!( name.as_bytes(), b"system.posix_acl_access" | b"system.posix_acl_default" ) || name.as_bytes().starts_with(b"user.") } pub(super) fn unsupported_lock_flags(flags: LockFlags) -> bool { flags.bits() & !LockFlags::FUSE_LK_FLOCK.bits() != 0 } pub(super) fn bsd_lock_operation(typ: i32, sleep: bool) -> io::Result { match (typ, sleep) { (libc::F_RDLCK, true) => Ok(FlockOperation::LockShared), (libc::F_RDLCK, false) => Ok(FlockOperation::NonBlockingLockShared), (libc::F_WRLCK, true) => Ok(FlockOperation::LockExclusive), (libc::F_WRLCK, false) => Ok(FlockOperation::NonBlockingLockExclusive), (libc::F_UNLCK, true) => Ok(FlockOperation::Unlock), (libc::F_UNLCK, false) => Ok(FlockOperation::NonBlockingUnlock), _ => Err(io::Error::from_raw_os_error(libc::EINVAL)), } } pub(super) fn record_lock(start: u64, end: u64, typ: i32) -> io::Result { if start > end { return Err(io::Error::from_raw_os_error(libc::EINVAL)); } let start_u64 = start; let start = i64::try_from(start_u64).map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?; let length = if end == u64::MAX { 0 } else { let length = end .checked_sub(start_u64) .and_then(|length| length.checked_add(1)) .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?; i64::try_from(length).map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))? }; let typ = i16::try_from(typ).map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; let whence = i16::try_from(libc::SEEK_SET).map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; if !matches!( i32::from(typ), libc::F_RDLCK | libc::F_WRLCK | libc::F_UNLCK ) { return Err(io::Error::from_raw_os_error(libc::EINVAL)); } Ok(libc::flock { l_type: typ, l_whence: whence, l_start: start, l_len: length, l_pid: 0, }) } pub(super) fn fuse_lock(lock: &libc::flock) -> io::Result<(u64, u64, i32, u32)> { let start = u64::try_from(lock.l_start).map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?; let end = if lock.l_len == 0 { u64::MAX } else { let length = u64::try_from(lock.l_len).map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?; start .checked_add(length.saturating_sub(1)) .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))? }; Ok(( start, end, i32::from(lock.l_type), u32::from_ne_bytes(lock.l_pid.to_ne_bytes()), )) } pub(super) fn errno(error: &io::Error) -> Errno { Errno::from_i32(error.raw_os_error().unwrap_or(libc::EIO)) } pub(super) fn reply_result(result: io::Result<()>, reply: ReplyEmpty) { match result { Ok(()) => reply.ok(), Err(error) => reply.error(errno(&error)), } } pub(super) fn reply_xattr(result: io::Result>, size: u32, reply: ReplyXattr) { match result { Ok(bytes) if size == 0 => reply.size(u32::try_from(bytes.len()).unwrap_or(u32::MAX)), Ok(bytes) if bytes.len() <= size as usize => reply.data(&bytes), Ok(_) => reply.error(Errno::ERANGE), Err(error) => reply.error(errno(&error)), } }