use std::collections::BTreeMap; use std::convert::{TryFrom, TryInto}; use std::os::unix::io::AsRawFd; use std::{cmp, ops}; use syscall::error::{ EACCES, EBADF, EBADFD, EEXIST, EINVAL, EIO, EISDIR, ENOENT, ENOMEM, ENOSYS, ENOTDIR, ENOTEMPTY, EOVERFLOW, }; use syscall::flag::{ O_ACCMODE, O_CREAT, O_DIRECTORY, O_EXCL, O_RDONLY, O_RDWR, O_STAT, O_TRUNC, O_WRONLY, }; use syscall::{Error, EventFlags, Map, Result, SchemeMut, Stat, StatVfs, TimeSpec}; use syscall::{MODE_DIR, MODE_FILE, MODE_PERM, MODE_TYPE, SEEK_CUR, SEEK_END, SEEK_SET}; use crate::filesystem::{self, DirEntry, File, FileData, Filesystem}; #[derive(Clone)] struct Handle { inode: usize, offset: usize, opened_as_read: bool, // opened with O_RDONLY or O_RDWR opened_as_write: bool, // opened with O_WRONLY or O_RDWR // the three first bits of mode >> 6 if uid matched, mode >> 3 if gid matched, otherwise mode // >> 0. current_perm: u8, } pub struct Scheme { scheme_name: String, handles: BTreeMap, next_fd: usize, filesystem: Filesystem, } impl Scheme { /// Create the scheme, with the name being used for `fpath`. pub fn new(scheme_name: String) -> Result { Ok(Self { scheme_name, handles: BTreeMap::new(), filesystem: Filesystem::new()?, next_fd: 0, }) } /// Remove a directory entry, where the entry can be both a file or a directory. Used by both /// `unlink` and `rmdir`. pub fn remove_dentry( &mut self, path: &[u8], uid: u32, gid: u32, directory: bool, ) -> Result { let removed_entry = { let (parent_dir_inode, name_to_delete) = self.filesystem.resolve_except_last(path, uid, gid)?; let name_to_delete = name_to_delete.ok_or(Error::new(EINVAL))?; // can't remove root let parent = self .filesystem .files .get_mut(&parent_dir_inode) .ok_or(Error::new(EIO))?; let mode = current_perm(parent, uid, gid); if mode & 0o2 == 0 { return Err(Error::new(EACCES)); } let dentries = parent.data.as_directory_mut().ok_or(Error::new(EBADF))?; let (position, entry_inode) = dentries .iter() .enumerate() .find(|(_, d)| d.name == name_to_delete) .unwrap(); let entry_inode = entry_inode.inode; let removed_entry = dentries.remove(position); if let Some(File { data: FileData::Directory(ref data), .. }) = self.filesystem.files.get(&entry_inode) { if !directory { return Err(Error::new(EISDIR)); } else if !data.is_empty() { return Err(Error::new(ENOTEMPTY)); } let parent = self .filesystem .files .get_mut(&parent_dir_inode) .ok_or(Error::new(EIO))?; parent.nlink -= 1; // '..' of subdirectory } removed_entry }; let removed_inode = self .filesystem .files .get_mut(&removed_entry.inode) .ok_or(Error::new(EIO))?; if let FileData::File(_) = removed_inode.data { if directory { return Err(Error::new(EISDIR)); } removed_inode.nlink -= 1; // only the parent entry } else { if !directory { return Err(Error::new(ENOTDIR)); } removed_inode.nlink -= 2; // both the parent entry and '.' } if removed_inode.nlink == 0 && removed_inode.open_handles == 0 { self.filesystem.files.remove(&removed_entry.inode); } Ok(0) } fn open_existing(&mut self, path: &[u8], flags: usize, uid: u32, gid: u32) -> Result { let inode = self.filesystem.resolve(path, uid, gid)?; let file = self .filesystem .files .get_mut(&inode) .ok_or(Error::new(EIO))?; if flags & O_STAT == 0 && flags & O_DIRECTORY != 0 && file.mode & MODE_TYPE != MODE_DIR { return Err(Error::new(ENOTDIR)); } // Unlike on Linux, which allows directories to be opened without O_DIRECTORY, Redox has no // getdents(2) syscall, and thus it adds the additional restriction that directories have // to be opened with O_DIRECTORY, if they aren't opened with O_STAT to check whether it's a // directory. if flags & O_STAT == 0 && flags & O_DIRECTORY == 0 && file.mode & MODE_TYPE == MODE_DIR { return Err(Error::new(EISDIR)); } let current_perm = current_perm(file, uid, gid); check_permissions(flags, current_perm)?; let opened_as_read = flags & O_ACCMODE == O_RDONLY || flags & O_ACCMODE == O_RDWR; let opened_as_write = flags & O_ACCMODE == O_WRONLY || flags & O_ACCMODE == O_RDWR; if flags & O_TRUNC == O_TRUNC && opened_as_write { match file.data { // file.data and file.mode should match FileData::Directory(_) => return Err(Error::new(EBADFD)), // If we opened an existing file with O_CREAT and O_TRUNC FileData::File(ref mut data) => data.clear(), } } file.open_handles += 1; Ok(Handle { inode, offset: 0, opened_as_read, opened_as_write, current_perm, }) } } impl SchemeMut for Scheme { fn open(&mut self, path: &str, flags: usize, uid: u32, gid: u32) -> Result { let exists = self.filesystem.resolve(path.as_bytes(), 0, 0).is_ok(); if flags & O_CREAT != 0 && flags & O_EXCL != 0 && exists { return Err(Error::new(EEXIST)); } let handle = if flags & O_CREAT != 0 && exists { self.open_existing(path.as_bytes(), flags, uid, gid)? } else if flags & O_CREAT != 0 { if flags & O_STAT != 0 { return Err(Error::new(EINVAL)); } let (parent_dir_inode, new_name) = self.filesystem .resolve_except_last(path.as_bytes(), uid, gid)?; let new_name = new_name.ok_or(Error::new(EINVAL))?; // cannot mkdir / let current_time = filesystem::current_time(); let new_inode_number = self.filesystem.next_inode_number()?; let mut mode = (flags & 0xFFFF) as u16; let new_inode = if flags & O_DIRECTORY != 0 { if mode & MODE_TYPE == 0 { mode |= MODE_DIR } if mode & MODE_TYPE != MODE_DIR { return Err(Error::new(EINVAL)); } File { atime: current_time, crtime: current_time, ctime: current_time, mtime: current_time, gid, uid, ino: new_inode_number, mode, nlink: 2, // parent entry, "." data: FileData::Directory(Vec::new()), open_handles: 1, } } else { if mode & MODE_TYPE == 0 { mode |= MODE_FILE } if mode & MODE_TYPE == MODE_DIR { return Err(Error::new(EINVAL)); } File { atime: current_time, crtime: current_time, ctime: current_time, mtime: current_time, gid, uid, ino: new_inode_number, mode, nlink: 1, data: FileData::File(Vec::new()), open_handles: 1, } }; let current_perm = current_perm(&new_inode, uid, gid); check_permissions(flags, current_perm)?; self.filesystem.files.insert(new_inode_number, new_inode); let parent_file = self .filesystem .files .get_mut(&parent_dir_inode) .ok_or(Error::new(EIO))?; match parent_file.data { FileData::File(_) => return Err(Error::new(EIO)), FileData::Directory(ref mut entries) => entries.push(DirEntry { name: new_name.to_owned(), inode: new_inode_number, }), } Handle { inode: new_inode_number, offset: 0, opened_as_read: flags & O_ACCMODE == O_RDONLY || flags & O_ACCMODE == O_RDWR, opened_as_write: flags & O_ACCMODE == O_WRONLY || flags & O_ACCMODE == O_RDWR, current_perm, } } else { self.open_existing(path.as_bytes(), flags, uid, gid)? }; let fd = self.next_fd; self.next_fd += 1; self.handles.insert(fd, handle); Ok(fd) } fn chmod(&mut self, path: &str, mode: u16, uid: u32, gid: u32) -> Result { let inode_num = self.filesystem.resolve(path.as_bytes(), uid, gid)?; let inode = self .filesystem .files .get_mut(&inode_num) .ok_or(Error::new(EIO))?; let cur_mode = inode.mode & MODE_TYPE; if inode.uid != uid && uid != 0 { return Err(Error::new(EACCES)); } if mode & MODE_TYPE != 0 { return Err(Error::new(EINVAL)); } inode.mode = mode | cur_mode; Ok(0) } fn rmdir(&mut self, path: &str, uid: u32, gid: u32) -> Result { self.remove_dentry(path.as_bytes(), uid, gid, true) } fn unlink(&mut self, path: &str, uid: u32, gid: u32) -> Result { self.remove_dentry(path.as_bytes(), uid, gid, false) } fn dup(&mut self, old_fd: usize, _buf: &[u8]) -> Result { let handle = self .handles .get_mut(&old_fd) .ok_or(Error::new(EBADF))? .clone(); let fd = self.next_fd; self.next_fd += 1; self.handles.insert(fd, handle); Ok(fd) } fn read(&mut self, fd: usize, buf: &mut [u8]) -> Result { let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; let file = self .filesystem .files .get_mut(&handle.inode) .ok_or(Error::new(EBADFD))?; if !handle.opened_as_read { return Err(Error::new(EBADF)); } match file.data { FileData::File(ref bytes) => { if file.mode & MODE_TYPE == MODE_DIR { return Err(Error::new(EBADFD)); } if handle.offset >= bytes.len() { return Ok(0); } let bytes_to_read = cmp::min(bytes.len(), buf.len() + handle.offset) - handle.offset; buf[..bytes_to_read] .copy_from_slice(&bytes[handle.offset..handle.offset + bytes_to_read]); handle.offset += bytes_to_read; Ok(bytes_to_read) } FileData::Directory(ref entries) => { if file.mode & MODE_TYPE != MODE_DIR { return Err(Error::new(EBADFD)); } // directories require the execute permission to be listed if handle.current_perm & 0o1 == 0 { return Err(Error::new(EBADF)); } let mut bytes_to_skip = handle.offset; let mut bytes_left_to_read = buf.len(); let mut bytes_read = 0; for DirEntry { name: entry_bytes, .. } in entries { // skip the whole entry if it fits if bytes_to_skip >= entry_bytes.len() { bytes_to_skip -= entry_bytes.len(); continue; } let bytes_to_read = cmp::min(entry_bytes.len() + 1 - bytes_to_skip, bytes_left_to_read); let entry_bytes = &entry_bytes[bytes_to_skip..bytes_to_skip + bytes_to_read - 1]; bytes_to_skip -= bytes_to_skip; buf[bytes_read..bytes_read + bytes_to_read - 1] .copy_from_slice(&entry_bytes[..bytes_to_read - 1]); buf[bytes_read + bytes_to_read - 1] = b'\n'; bytes_left_to_read -= bytes_to_read; bytes_read += bytes_to_read; handle.offset += bytes_read; } Ok(bytes_read) } } } fn write(&mut self, fd: usize, buf: &[u8]) -> Result { let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; let file = self .filesystem .files .get_mut(&handle.inode) .ok_or(Error::new(EBADFD))?; if let &mut FileData::File(ref mut bytes) = &mut file.data { if file.mode & MODE_TYPE == MODE_DIR { return Err(Error::new(EBADFD)); } // if there's a seek hole, fill it with 0 and continue writing. if handle.offset > bytes.len() { let additional = handle.offset - bytes.len(); bytes.try_reserve(additional).or(Err(Error::new(ENOMEM)))?; bytes.resize(handle.offset, 0u8); } bytes.extend(buf); Ok(buf.len()) } else { Err(Error::new(EISDIR)) } } fn seek(&mut self, fd: usize, pos: isize, whence: usize) -> Result { let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; let file = self .filesystem .files .get_mut(&handle.inode) .ok_or(Error::new(EBADFD))?; let old = handle.offset; handle.offset = match whence { SEEK_SET => cmp::max(0, pos), SEEK_CUR => cmp::max( 0, pos + isize::try_from(handle.offset).or(Err(Error::new(EOVERFLOW)))?, ), SEEK_END => cmp::max( 0, pos + isize::try_from(file.data.size()).or(Err(Error::new(EOVERFLOW)))?, ), _ => return Err(Error::new(EINVAL)), } as usize; Ok(handle.offset as isize) } fn fchmod(&mut self, fd: usize, mode: u16) -> Result { let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; let file = self .filesystem .files .get_mut(&handle.inode) .ok_or(Error::new(EBADFD))?; let cur_type = file.mode & MODE_TYPE; if mode & MODE_TYPE != 0 { return Err(Error::new(EINVAL)); } file.mode = mode | cur_type; Ok(0) } fn fchown(&mut self, fd: usize, uid: u32, gid: u32) -> Result { let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; let file = self .filesystem .files .get_mut(&handle.inode) .ok_or(Error::new(EBADFD))?; file.uid = uid; file.gid = gid; Ok(0) } fn fcntl(&mut self, fd: usize, _cmd: usize, _arg: usize) -> Result { if !self.handles.contains_key(&fd) { return Err(Error::new(EBADF)); } Ok(0) } fn fevent(&mut self, fd: usize, _flags: EventFlags) -> Result { if !self.handles.contains_key(&fd) { return Err(Error::new(EBADF)); } Err(Error::new(ENOSYS)) } fn fmap(&mut self, fd: usize, _map: &Map) -> Result { if !self.handles.contains_key(&fd) { return Err(Error::new(EBADF)); } // TODO Err(Error::new(ENOSYS)) } fn funmap(&mut self, _address: usize, _len: usize) -> Result { // TODO Err(Error::new(ENOSYS)) } fn fpath(&mut self, fd: usize, buf: &mut [u8]) -> Result { if !self.handles.contains_key(&fd) { return Err(Error::new(EBADF)); } // TODO Err(Error::new(ENOSYS)) } fn frename(&mut self, fd: usize, path: &str, uid: u32, gid: u32) -> Result { if !self.handles.contains_key(&fd) { return Err(Error::new(EBADF)); } // TODO Err(Error::new(ENOSYS)) } fn fstat(&mut self, fd: usize, stat: &mut Stat) -> Result { let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; let block_size = self.filesystem.block_size(); let file = self .filesystem .files .get_mut(&handle.inode) .ok_or(Error::new(EBADFD))?; let size = file.data.size().try_into().or(Err(Error::new(EOVERFLOW)))?; *stat = Stat { st_mode: file.mode, st_uid: file.uid, st_gid: file.gid, st_ino: handle.inode.try_into().or(Err(Error::new(EOVERFLOW)))?, st_nlink: file.nlink.try_into().or(Err(Error::new(EOVERFLOW)))?, st_dev: 0, st_size: size, st_blksize: block_size, st_blocks: div_round_up(size, u64::from(block_size)), st_atime: file .atime .tv_sec .try_into() .or(Err(Error::new(EOVERFLOW)))?, st_atime_nsec: file .atime .tv_nsec .try_into() .or(Err(Error::new(EOVERFLOW)))?, st_ctime: file .ctime .tv_sec .try_into() .or(Err(Error::new(EOVERFLOW)))?, st_ctime_nsec: file .ctime .tv_nsec .try_into() .or(Err(Error::new(EOVERFLOW)))?, st_mtime: file .mtime .tv_sec .try_into() .or(Err(Error::new(EOVERFLOW)))?, st_mtime_nsec: file .mtime .tv_nsec .try_into() .or(Err(Error::new(EOVERFLOW)))?, }; Ok(0) } fn fstatvfs(&mut self, fd: usize, stat: &mut StatVfs) -> Result { if !self.handles.contains_key(&fd) { return Err(Error::new(EBADF)); } syscall::fstatvfs(self.filesystem.memory_file.as_raw_fd() as usize, stat)?; Ok(0) } fn fsync(&mut self, fd: usize) -> Result { if !self.handles.contains_key(&fd) { return Err(Error::new(EBADF)); } Ok(0) } fn ftruncate(&mut self, fd: usize, size: usize) -> Result { let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; let file = self .filesystem .files .get_mut(&handle.inode) .ok_or(Error::new(EBADFD))?; if file.mode & MODE_TYPE == MODE_DIR { return Err(Error::new(EISDIR)); } match &mut file.data { &mut FileData::File(ref mut bytes) => { if size > bytes.len() { let additional = size - bytes.len(); bytes.try_reserve(additional).or(Err(Error::new(ENOMEM)))?; bytes.resize(size, 0u8) } else { bytes.resize(size, 0u8) } } &mut FileData::Directory(_) => return Err(Error::new(EBADFD)), } Ok(0) } fn futimens(&mut self, fd: usize, times: &[TimeSpec]) -> Result { let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; let file = self .filesystem .files .get_mut(&handle.inode) .ok_or(Error::new(EBADFD))?; let new_atime = *times.get(0).ok_or(Error::new(EINVAL))?; let new_mtime = *times.get(1).ok_or(Error::new(EINVAL))?; file.atime = new_atime; file.mtime = new_mtime; Ok(0) } fn close(&mut self, fd: usize) -> Result { let inode_num = self.handles.remove(&fd).ok_or(Error::new(EBADF))?.inode; let inode = self .filesystem .files .get_mut(&inode_num) .ok_or(Error::new(EIO))?; inode.open_handles -= 1; if inode.nlink == 0 && inode.open_handles == 0 { self.filesystem.files.remove(&inode_num); } Ok(0) } } fn div_round_up(numer: T, denom: T) -> T where T: Copy + ops::Add + ops::Sub + ops::Div + From, { (numer + (denom - T::from(1u8))) / denom } pub fn current_perm(file: &crate::filesystem::File, uid: u32, gid: u32) -> u8 { let perm = file.mode & MODE_PERM; if uid == 0 { // root doesn't have to be checked 0o7 } else if uid == file.uid { ((perm & 0o700) >> 6) as u8 } else if gid == file.gid { ((perm & 0o70) >> 3) as u8 } else { (perm & 0o7) as u8 } } fn check_permissions(flags: usize, single_mode: u8) -> Result<()> { if flags & O_ACCMODE == O_RDONLY && single_mode & 0o4 == 0 { return Err(Error::new(EACCES)); } else if flags & O_ACCMODE == O_WRONLY && single_mode & 0o2 == 0 { return Err(Error::new(EACCES)); } else if flags & O_ACCMODE == O_RDWR && single_mode & 0o6 != 0o6 { return Err(Error::new(EACCES)); } Ok(()) }