| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::cmp::min; |
| use std::collections::{btree_map, BTreeMap}; |
| use std::ffi::CString; |
| use std::fs; |
| use std::io::{self, Cursor, Read, Write}; |
| use std::mem; |
| use std::os::unix::fs::MetadataExt; |
| use std::os::unix::fs::{DirBuilderExt, FileExt, OpenOptionsExt}; |
| use std::os::unix::io::AsRawFd; |
| use std::path::{Component, Path, PathBuf}; |
| |
| use crate::protocol::*; |
| |
| // Tlopen and Tlcreate flags. Taken from "include/net/9p/9p.h" in the linux tree. |
| const _P9_RDONLY: u32 = 0o00000000; |
| const P9_WRONLY: u32 = 0o00000001; |
| const P9_RDWR: u32 = 0o00000002; |
| const P9_NOACCESS: u32 = 0o00000003; |
| const P9_CREATE: u32 = 0o00000100; |
| const P9_EXCL: u32 = 0o00000200; |
| const P9_NOCTTY: u32 = 0o00000400; |
| const P9_TRUNC: u32 = 0o00001000; |
| const P9_APPEND: u32 = 0o00002000; |
| const P9_NONBLOCK: u32 = 0o00004000; |
| const P9_DSYNC: u32 = 0o00010000; |
| const P9_FASYNC: u32 = 0o00020000; |
| const P9_DIRECT: u32 = 0o00040000; |
| const P9_LARGEFILE: u32 = 0o00100000; |
| const P9_DIRECTORY: u32 = 0o00200000; |
| const P9_NOFOLLOW: u32 = 0o00400000; |
| const P9_NOATIME: u32 = 0o01000000; |
| const _P9_CLOEXEC: u32 = 0o02000000; |
| const P9_SYNC: u32 = 0o04000000; |
| |
| // Mapping from 9P flags to libc flags. |
| const MAPPED_FLAGS: [(u32, i32); 14] = [ |
| (P9_CREATE, libc::O_CREAT), |
| (P9_EXCL, libc::O_EXCL), |
| (P9_NOCTTY, libc::O_NOCTTY), |
| (P9_TRUNC, libc::O_TRUNC), |
| (P9_APPEND, libc::O_APPEND), |
| (P9_NONBLOCK, libc::O_NONBLOCK), |
| (P9_DSYNC, libc::O_DSYNC), |
| (P9_FASYNC, 0), // Unsupported |
| (P9_DIRECT, libc::O_DIRECT), |
| (P9_LARGEFILE, libc::O_LARGEFILE), |
| (P9_DIRECTORY, libc::O_DIRECTORY), |
| (P9_NOFOLLOW, libc::O_NOFOLLOW), |
| (P9_NOATIME, libc::O_NOATIME), |
| (P9_SYNC, libc::O_SYNC), |
| ]; |
| |
| // 9P Qid types. Taken from "include/net/9p/9p.h" in the linux tree. |
| const P9_QTDIR: u8 = 0x80; |
| const _P9_QTAPPEND: u8 = 0x40; |
| const _P9_QTEXCL: u8 = 0x20; |
| const _P9_QTMOUNT: u8 = 0x10; |
| const _P9_QTAUTH: u8 = 0x08; |
| const _P9_QTTMP: u8 = 0x04; |
| const _P9_QTSYMLINK: u8 = 0x02; |
| const _P9_QTLINK: u8 = 0x01; |
| const P9_QTFILE: u8 = 0x00; |
| |
| // Bitmask values for the getattr request. |
| const _P9_GETATTR_MODE: u64 = 0x00000001; |
| const _P9_GETATTR_NLINK: u64 = 0x00000002; |
| const _P9_GETATTR_UID: u64 = 0x00000004; |
| const _P9_GETATTR_GID: u64 = 0x00000008; |
| const _P9_GETATTR_RDEV: u64 = 0x00000010; |
| const _P9_GETATTR_ATIME: u64 = 0x00000020; |
| const _P9_GETATTR_MTIME: u64 = 0x00000040; |
| const _P9_GETATTR_CTIME: u64 = 0x00000080; |
| const _P9_GETATTR_INO: u64 = 0x00000100; |
| const _P9_GETATTR_SIZE: u64 = 0x00000200; |
| const _P9_GETATTR_BLOCKS: u64 = 0x00000400; |
| |
| const _P9_GETATTR_BTIME: u64 = 0x00000800; |
| const _P9_GETATTR_GEN: u64 = 0x00001000; |
| const _P9_GETATTR_DATA_VERSION: u64 = 0x00002000; |
| |
| const P9_GETATTR_BASIC: u64 = 0x000007ff; /* Mask for fields up to BLOCKS */ |
| const _P9_GETATTR_ALL: u64 = 0x00003fff; /* Mask for All fields above */ |
| |
| // Bitmask values for the setattr request. |
| const P9_SETATTR_MODE: u32 = 0x00000001; |
| const P9_SETATTR_UID: u32 = 0x00000002; |
| const P9_SETATTR_GID: u32 = 0x00000004; |
| const P9_SETATTR_SIZE: u32 = 0x00000008; |
| const P9_SETATTR_ATIME: u32 = 0x00000010; |
| const P9_SETATTR_MTIME: u32 = 0x00000020; |
| const P9_SETATTR_CTIME: u32 = 0x00000040; |
| const P9_SETATTR_ATIME_SET: u32 = 0x00000080; |
| const P9_SETATTR_MTIME_SET: u32 = 0x00000100; |
| |
| // Minimum and maximum message size that we'll expect from the client. |
| const MIN_MESSAGE_SIZE: u32 = 256; |
| const MAX_MESSAGE_SIZE: u32 = ::std::u16::MAX as u32; |
| |
| // Represents state that the server is holding on behalf of a client. Fids are somewhat like file |
| // descriptors but are not restricted to open files and directories. Fids are identified by a unique |
| // 32-bit number chosen by the client. Most messages sent by clients include a fid on which to |
| // operate. The fid in a Tattach message represents the root of the file system tree that the client |
| // is allowed to access. A client can create more fids by walking the directory tree from that fid. |
| #[derive(Debug)] |
| struct Fid { |
| path: Box<Path>, |
| metadata: fs::Metadata, |
| file: Option<fs::File>, |
| dirents: Option<Vec<Dirent>>, |
| } |
| |
| fn metadata_to_qid(metadata: &fs::Metadata) -> Qid { |
| let ty = if metadata.is_dir() { |
| P9_QTDIR |
| } else if metadata.is_file() { |
| P9_QTFILE |
| } else { |
| // Unknown file type... |
| 0 |
| }; |
| |
| Qid { |
| ty, |
| // TODO: deal with the 2038 problem before 2038 |
| version: metadata.mtime() as u32, |
| path: metadata.ino(), |
| } |
| } |
| |
| fn error_to_rmessage(err: io::Error) -> Rmessage { |
| let errno = if let Some(errno) = err.raw_os_error() { |
| errno |
| } else { |
| // Make a best-effort guess based on the kind. |
| match err.kind() { |
| io::ErrorKind::NotFound => libc::ENOENT, |
| io::ErrorKind::PermissionDenied => libc::EPERM, |
| io::ErrorKind::ConnectionRefused => libc::ECONNREFUSED, |
| io::ErrorKind::ConnectionReset => libc::ECONNRESET, |
| io::ErrorKind::ConnectionAborted => libc::ECONNABORTED, |
| io::ErrorKind::NotConnected => libc::ENOTCONN, |
| io::ErrorKind::AddrInUse => libc::EADDRINUSE, |
| io::ErrorKind::AddrNotAvailable => libc::EADDRNOTAVAIL, |
| io::ErrorKind::BrokenPipe => libc::EPIPE, |
| io::ErrorKind::AlreadyExists => libc::EEXIST, |
| io::ErrorKind::WouldBlock => libc::EWOULDBLOCK, |
| io::ErrorKind::InvalidInput => libc::EINVAL, |
| io::ErrorKind::InvalidData => libc::EINVAL, |
| io::ErrorKind::TimedOut => libc::ETIMEDOUT, |
| io::ErrorKind::WriteZero => libc::EIO, |
| io::ErrorKind::Interrupted => libc::EINTR, |
| io::ErrorKind::Other => libc::EIO, |
| io::ErrorKind::UnexpectedEof => libc::EIO, |
| _ => libc::EIO, |
| } |
| }; |
| |
| Rmessage::Lerror(Rlerror { |
| ecode: errno as u32, |
| }) |
| } |
| |
| // Joins `path` to `buf`. If `path` is '..', removes the last component from `buf` |
| // only if `buf` != `root` but does nothing if `buf` == `root`. Pushes `path` onto |
| // `buf` if it is a normal path component. |
| // |
| // Returns an error if `path` is absolute, has more than one component, or contains |
| // a '.' component. |
| fn join_path<P: AsRef<Path>, R: AsRef<Path>>( |
| mut buf: PathBuf, |
| path: P, |
| root: R, |
| ) -> io::Result<PathBuf> { |
| let path = path.as_ref(); |
| let root = root.as_ref(); |
| debug_assert!(buf.starts_with(root)); |
| |
| if path.components().count() > 1 { |
| return Err(io::Error::from_raw_os_error(libc::EINVAL)); |
| } |
| |
| for component in path.components() { |
| match component { |
| // Prefix should only appear on windows systems. |
| Component::Prefix(_) => return Err(io::Error::from_raw_os_error(libc::EINVAL)), |
| // Absolute paths are not allowed. |
| Component::RootDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)), |
| // '.' elements are not allowed. |
| Component::CurDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)), |
| Component::ParentDir => { |
| // We only remove the parent path if we are not already at the root of the |
| // file system. |
| if buf != root { |
| buf.pop(); |
| } |
| } |
| Component::Normal(element) => buf.push(element), |
| } |
| } |
| |
| Ok(buf) |
| } |
| |
| pub type ServerIdMap<T> = BTreeMap<T, T>; |
| pub type ServerUidMap = ServerIdMap<libc::uid_t>; |
| pub type ServerGidMap = ServerIdMap<libc::gid_t>; |
| |
| fn map_id_from_host<T: Clone + Ord>(map: &ServerIdMap<T>, id: T) -> T { |
| map.get(&id).map_or(id.clone(), |v| v.clone()) |
| } |
| |
| pub struct Server { |
| root: Box<Path>, |
| msize: u32, |
| fids: BTreeMap<u32, Fid>, |
| uid_map: ServerUidMap, |
| gid_map: ServerGidMap, |
| } |
| |
| impl Server { |
| pub fn new<P: Into<Box<Path>>>( |
| root: P, |
| uid_map: ServerUidMap, |
| gid_map: ServerGidMap, |
| ) -> Server { |
| Server { |
| root: root.into(), |
| msize: MAX_MESSAGE_SIZE, |
| fids: BTreeMap::new(), |
| uid_map, |
| gid_map, |
| } |
| } |
| |
| pub fn handle_message<R: Read, W: Write>( |
| &mut self, |
| reader: &mut R, |
| writer: &mut W, |
| ) -> io::Result<()> { |
| let request: Tframe = WireFormat::decode(&mut reader.take(self.msize as u64))?; |
| |
| if cfg!(feature = "trace") { |
| println!("{:?}", &request); |
| } |
| |
| let rmsg = match request.msg { |
| Tmessage::Version(ref version) => self.version(version).map(Rmessage::Version), |
| Tmessage::Flush(ref flush) => self.flush(flush).and(Ok(Rmessage::Flush)), |
| Tmessage::Walk(ref walk) => self.walk(walk).map(Rmessage::Walk), |
| Tmessage::Read(ref read) => self.read(read).map(Rmessage::Read), |
| Tmessage::Write(ref write) => self.write(write).map(Rmessage::Write), |
| Tmessage::Clunk(ref clunk) => self.clunk(clunk).and(Ok(Rmessage::Clunk)), |
| Tmessage::Remove(ref remove) => self.remove(remove).and(Ok(Rmessage::Remove)), |
| Tmessage::Attach(ref attach) => self.attach(attach).map(Rmessage::Attach), |
| Tmessage::Auth(ref auth) => self.auth(auth).map(Rmessage::Auth), |
| Tmessage::Statfs(ref statfs) => self.statfs(statfs).map(Rmessage::Statfs), |
| Tmessage::Lopen(ref lopen) => self.lopen(lopen).map(Rmessage::Lopen), |
| Tmessage::Lcreate(ref lcreate) => self.lcreate(lcreate).map(Rmessage::Lcreate), |
| Tmessage::Symlink(ref symlink) => self.symlink(symlink).map(Rmessage::Symlink), |
| Tmessage::Mknod(ref mknod) => self.mknod(mknod).map(Rmessage::Mknod), |
| Tmessage::Rename(ref rename) => self.rename(rename).and(Ok(Rmessage::Rename)), |
| Tmessage::Readlink(ref readlink) => self.readlink(readlink).map(Rmessage::Readlink), |
| Tmessage::GetAttr(ref get_attr) => self.get_attr(get_attr).map(Rmessage::GetAttr), |
| Tmessage::SetAttr(ref set_attr) => self.set_attr(set_attr).and(Ok(Rmessage::SetAttr)), |
| Tmessage::XattrWalk(ref xattr_walk) => { |
| self.xattr_walk(xattr_walk).map(Rmessage::XattrWalk) |
| } |
| Tmessage::XattrCreate(ref xattr_create) => self |
| .xattr_create(xattr_create) |
| .and(Ok(Rmessage::XattrCreate)), |
| Tmessage::Readdir(ref readdir) => self.readdir(readdir).map(Rmessage::Readdir), |
| Tmessage::Fsync(ref fsync) => self.fsync(fsync).and(Ok(Rmessage::Fsync)), |
| Tmessage::Lock(ref lock) => self.lock(lock).map(Rmessage::Lock), |
| Tmessage::GetLock(ref get_lock) => self.get_lock(get_lock).map(Rmessage::GetLock), |
| Tmessage::Link(ref link) => self.link(link).and(Ok(Rmessage::Link)), |
| Tmessage::Mkdir(ref mkdir) => self.mkdir(mkdir).map(Rmessage::Mkdir), |
| Tmessage::RenameAt(ref rename_at) => { |
| self.rename_at(rename_at).and(Ok(Rmessage::RenameAt)) |
| } |
| Tmessage::UnlinkAt(ref unlink_at) => { |
| self.unlink_at(unlink_at).and(Ok(Rmessage::UnlinkAt)) |
| } |
| }; |
| |
| // Errors while handling requests are never fatal. |
| let response = Rframe { |
| tag: request.tag, |
| msg: rmsg.unwrap_or_else(error_to_rmessage), |
| }; |
| |
| if cfg!(feature = "trace") { |
| println!("{:?}", &response); |
| } |
| |
| response.encode(writer)?; |
| writer.flush() |
| } |
| |
| fn auth(&mut self, _auth: &Tauth) -> io::Result<Rauth> { |
| // Returning an error for the auth message means that the server does not require |
| // authentication. |
| Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)) |
| } |
| |
| fn attach(&mut self, attach: &Tattach) -> io::Result<Rattach> { |
| // TODO: Check attach parameters |
| match self.fids.entry(attach.fid) { |
| btree_map::Entry::Vacant(entry) => { |
| let fid = Fid { |
| path: self.root.to_path_buf().into_boxed_path(), |
| metadata: fs::metadata(&self.root)?, |
| file: None, |
| dirents: None, |
| }; |
| let response = Rattach { |
| qid: metadata_to_qid(&fid.metadata), |
| }; |
| entry.insert(fid); |
| Ok(response) |
| } |
| btree_map::Entry::Occupied(_) => Err(io::Error::from_raw_os_error(libc::EBADF)), |
| } |
| } |
| |
| fn version(&mut self, version: &Tversion) -> io::Result<Rversion> { |
| if version.msize < MIN_MESSAGE_SIZE { |
| return Err(io::Error::from_raw_os_error(libc::EINVAL)); |
| } |
| |
| // A Tversion request clunks all open fids and terminates any pending I/O. |
| self.fids.clear(); |
| self.msize = min(MAX_MESSAGE_SIZE, version.msize); |
| |
| Ok(Rversion { |
| msize: self.msize, |
| version: if version.version == "9P2000.L" { |
| String::from("9P2000.L") |
| } else { |
| String::from("unknown") |
| }, |
| }) |
| } |
| |
| fn flush(&mut self, _flush: &Tflush) -> io::Result<()> { |
| // TODO: Since everything is synchronous we can't actually flush requests. |
| Ok(()) |
| } |
| |
| fn do_walk( |
| &self, |
| wnames: &[String], |
| mut buf: PathBuf, |
| mds: &mut Vec<fs::Metadata>, |
| ) -> io::Result<PathBuf> { |
| for wname in wnames { |
| let name = Path::new(wname); |
| buf = join_path(buf, name, &*self.root)?; |
| mds.push(fs::metadata(&buf)?); |
| } |
| |
| Ok(buf) |
| } |
| |
| fn walk(&mut self, walk: &Twalk) -> io::Result<Rwalk> { |
| // `newfid` must not currently be in use unless it is the same as `fid`. |
| if walk.fid != walk.newfid && self.fids.contains_key(&walk.newfid) { |
| return Err(io::Error::from_raw_os_error(libc::EBADF)); |
| } |
| |
| // We need to walk the tree. First get the starting path. |
| let (buf, oldmd) = self |
| .fids |
| .get(&walk.fid) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF)) |
| .map(|fid| (fid.path.to_path_buf(), fid.metadata.clone()))?; |
| |
| // Now walk the tree and break on the first error, if any. |
| let mut mds = Vec::with_capacity(walk.wnames.len()); |
| match self.do_walk(&walk.wnames, buf, &mut mds) { |
| Ok(buf) => { |
| // Store the new fid if the full walk succeeded. |
| if mds.len() == walk.wnames.len() { |
| // This could just be a duplication operation. |
| let md = if let Some(md) = mds.last() { |
| md.clone() |
| } else { |
| oldmd |
| }; |
| |
| self.fids.insert( |
| walk.newfid, |
| Fid { |
| path: buf.into_boxed_path(), |
| metadata: md, |
| file: None, |
| dirents: None, |
| }, |
| ); |
| } |
| } |
| Err(e) => { |
| // Only return an error if it occurred on the first component. |
| if mds.is_empty() { |
| return Err(e); |
| } |
| } |
| } |
| |
| Ok(Rwalk { |
| wqids: mds.iter().map(metadata_to_qid).collect(), |
| }) |
| } |
| |
| fn read(&mut self, read: &Tread) -> io::Result<Rread> { |
| // Thankfully, `read` cannot be used to read directories in 9P2000.L. |
| let file = self |
| .fids |
| .get_mut(&read.fid) |
| .and_then(|fid| fid.file.as_mut()) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| |
| // Use an empty Rread struct to figure out the overhead of the header. |
| let header_size = Rframe { |
| tag: 0, |
| msg: Rmessage::Read(Rread { |
| data: Data(Vec::new()), |
| }), |
| } |
| .byte_size(); |
| |
| let capacity = min(self.msize - header_size, read.count); |
| let mut buf = Data(Vec::with_capacity(capacity as usize)); |
| buf.resize(capacity as usize, 0); |
| |
| let count = file.read_at(&mut buf, read.offset)?; |
| buf.resize(count, 0); |
| |
| Ok(Rread { data: buf }) |
| } |
| |
| fn write(&mut self, write: &Twrite) -> io::Result<Rwrite> { |
| let file = self |
| .fids |
| .get_mut(&write.fid) |
| .and_then(|fid| fid.file.as_mut()) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| |
| let count = file.write_at(&write.data, write.offset)?; |
| Ok(Rwrite { |
| count: count as u32, |
| }) |
| } |
| |
| fn clunk(&mut self, clunk: &Tclunk) -> io::Result<()> { |
| match self.fids.entry(clunk.fid) { |
| btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)), |
| btree_map::Entry::Occupied(entry) => { |
| entry.remove(); |
| Ok(()) |
| } |
| } |
| } |
| |
| fn remove(&mut self, remove: &Tremove) -> io::Result<()> { |
| match self.fids.entry(remove.fid) { |
| btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)), |
| btree_map::Entry::Occupied(o) => { |
| let (_, fid) = o.remove_entry(); |
| |
| if fid.metadata.is_dir() { |
| fs::remove_dir(&fid.path)?; |
| } else { |
| fs::remove_file(&fid.path)?; |
| } |
| |
| Ok(()) |
| } |
| } |
| } |
| |
| fn statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rstatfs> { |
| let fid = self |
| .fids |
| .get(&statfs.fid) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| let path = fid |
| .path |
| .to_str() |
| .and_then(|path| CString::new(path).ok()) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?; |
| |
| // Safe because we are zero-initializing a C struct with only primitive |
| // data members. |
| let mut out: libc::statfs64 = unsafe { mem::zeroed() }; |
| |
| // Safe because we know that `path` is valid and we have already initialized `out`. |
| let ret = unsafe { libc::statfs64(path.as_ptr(), &mut out) }; |
| if ret != 0 { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| Ok(Rstatfs { |
| ty: out.f_type as u32, |
| bsize: out.f_bsize as u32, |
| blocks: out.f_blocks, |
| bfree: out.f_bfree, |
| bavail: out.f_bavail, |
| files: out.f_files, |
| ffree: out.f_ffree, |
| fsid: 0, // No way to get the fields of a libc::fsid_t |
| namelen: out.f_namelen as u32, |
| }) |
| } |
| |
| fn lopen(&mut self, lopen: &Tlopen) -> io::Result<Rlopen> { |
| let fid = self |
| .fids |
| .get_mut(&lopen.fid) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| |
| // We always open files with O_CLOEXEC. |
| let mut custom_flags: i32 = libc::O_CLOEXEC; |
| for &(p9f, of) in &MAPPED_FLAGS { |
| if (lopen.flags & p9f) != 0 { |
| custom_flags |= of; |
| } |
| } |
| |
| // MAPPED_FLAGS will handle append, create[_new], and truncate. |
| let file = fs::OpenOptions::new() |
| .read((lopen.flags & P9_NOACCESS) == 0 || (lopen.flags & P9_RDWR) != 0) |
| .write((lopen.flags & P9_WRONLY) != 0 || (lopen.flags & P9_RDWR) != 0) |
| .custom_flags(custom_flags) |
| .open(&fid.path)?; |
| |
| fid.metadata = file.metadata()?; |
| fid.file = Some(file); |
| |
| Ok(Rlopen { |
| qid: metadata_to_qid(&fid.metadata), |
| iounit: 0, |
| }) |
| } |
| |
| fn lcreate(&mut self, lcreate: &Tlcreate) -> io::Result<Rlcreate> { |
| let fid = self |
| .fids |
| .get_mut(&lcreate.fid) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| |
| if !fid.metadata.is_dir() { |
| return Err(io::Error::from_raw_os_error(libc::ENOTDIR)); |
| } |
| |
| let name = Path::new(&lcreate.name); |
| let path = join_path(fid.path.to_path_buf(), name, &*self.root)?; |
| |
| let mut custom_flags: i32 = libc::O_CLOEXEC; |
| for &(p9f, of) in &MAPPED_FLAGS { |
| if (lcreate.flags & p9f) != 0 { |
| custom_flags |= of; |
| } |
| } |
| |
| // Set O_CREAT|O_EXCL, MAPPED_FLAGS will handle append and truncate. |
| custom_flags |= libc::O_CREAT | libc::O_EXCL; |
| let file = fs::OpenOptions::new() |
| .read((lcreate.flags & P9_NOACCESS) == 0 || (lcreate.flags & P9_RDWR) != 0) |
| .write((lcreate.flags & P9_WRONLY) != 0 || (lcreate.flags & P9_RDWR) != 0) |
| .custom_flags(custom_flags) |
| .mode(lcreate.mode & 0o755) |
| .open(&path)?; |
| |
| fid.metadata = file.metadata()?; |
| fid.file = Some(file); |
| fid.path = path.into_boxed_path(); |
| |
| Ok(Rlcreate { |
| qid: metadata_to_qid(&fid.metadata), |
| iounit: 0, |
| }) |
| } |
| |
| fn symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rsymlink> { |
| // symlinks are not allowed. |
| Err(io::Error::from_raw_os_error(libc::EACCES)) |
| } |
| |
| fn mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmknod> { |
| // No nodes either. |
| Err(io::Error::from_raw_os_error(libc::EACCES)) |
| } |
| |
| fn rename(&mut self, rename: &Trename) -> io::Result<()> { |
| let newname = Path::new(&rename.name); |
| let buf = self |
| .fids |
| .get(&rename.dfid) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF)) |
| .map(|dfid| dfid.path.to_path_buf())?; |
| let newpath = join_path(buf, newname, &*self.root)?; |
| |
| let fid = self |
| .fids |
| .get_mut(&rename.fid) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?; |
| |
| fs::rename(&fid.path, &newpath)?; |
| |
| // TODO: figure out if the client expects |fid.path| to point to |
| // the renamed path. |
| fid.path = newpath.into_boxed_path(); |
| Ok(()) |
| } |
| |
| fn readlink(&mut self, _readlink: &Treadlink) -> io::Result<Rreadlink> { |
| // symlinks are not allowed |
| Err(io::Error::from_raw_os_error(libc::EACCES)) |
| } |
| |
| fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rgetattr> { |
| let fid = self |
| .fids |
| .get_mut(&get_attr.fid) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| |
| // Refresh the metadata since we were explicitly asked for it. |
| fid.metadata = fs::metadata(&fid.path)?; |
| |
| Ok(Rgetattr { |
| valid: P9_GETATTR_BASIC, |
| qid: metadata_to_qid(&fid.metadata), |
| mode: fid.metadata.mode(), |
| uid: map_id_from_host(&self.uid_map, fid.metadata.uid()), |
| gid: map_id_from_host(&self.gid_map, fid.metadata.gid()), |
| nlink: fid.metadata.nlink(), |
| rdev: fid.metadata.rdev(), |
| size: fid.metadata.size(), |
| blksize: fid.metadata.blksize(), |
| blocks: fid.metadata.blocks(), |
| atime_sec: fid.metadata.atime() as u64, |
| atime_nsec: fid.metadata.atime_nsec() as u64, |
| mtime_sec: fid.metadata.mtime() as u64, |
| mtime_nsec: fid.metadata.mtime_nsec() as u64, |
| ctime_sec: fid.metadata.ctime() as u64, |
| ctime_nsec: fid.metadata.ctime_nsec() as u64, |
| btime_sec: 0, |
| btime_nsec: 0, |
| gen: 0, |
| data_version: 0, |
| }) |
| } |
| |
| fn set_attr(&mut self, set_attr: &Tsetattr) -> io::Result<()> { |
| let blocked_ops = P9_SETATTR_MODE | P9_SETATTR_UID | P9_SETATTR_GID; |
| if set_attr.valid & blocked_ops != 0 { |
| return Err(io::Error::from_raw_os_error(libc::EPERM)); |
| } |
| |
| let fid = self |
| .fids |
| .get_mut(&set_attr.fid) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| |
| let file = fs::OpenOptions::new().write(true).open(&fid.path)?; |
| |
| if set_attr.valid & P9_SETATTR_SIZE != 0 { |
| file.set_len(set_attr.size)?; |
| } |
| |
| if set_attr.valid & (P9_SETATTR_ATIME | P9_SETATTR_MTIME) != 0 { |
| let times = [ |
| libc::timespec { |
| tv_sec: set_attr.atime_sec as _, |
| tv_nsec: if set_attr.valid & P9_SETATTR_ATIME == 0 { |
| libc::UTIME_OMIT |
| } else if set_attr.valid & P9_SETATTR_ATIME_SET == 0 { |
| libc::UTIME_NOW |
| } else { |
| set_attr.atime_nsec as _ |
| }, |
| }, |
| libc::timespec { |
| tv_sec: set_attr.mtime_sec as _, |
| tv_nsec: if set_attr.valid & P9_SETATTR_MTIME == 0 { |
| libc::UTIME_OMIT |
| } else if set_attr.valid & P9_SETATTR_MTIME_SET == 0 { |
| libc::UTIME_NOW |
| } else { |
| set_attr.mtime_nsec as _ |
| }, |
| }, |
| ]; |
| |
| // Safe because file is valid and we have initialized times fully. |
| let ret = unsafe { libc::futimens(file.as_raw_fd(), × as *const libc::timespec) }; |
| if ret < 0 { |
| return Err(io::Error::last_os_error()); |
| } |
| } |
| |
| // The ctime would have been updated by any of the above operations so we only |
| // need to change it if it was the only option given. |
| if set_attr.valid & P9_SETATTR_CTIME != 0 && set_attr.valid & (!P9_SETATTR_CTIME) == 0 { |
| // Setting -1 as the uid and gid will not actually change anything but will |
| // still update the ctime. |
| let ret = unsafe { |
| libc::fchown( |
| file.as_raw_fd(), |
| libc::uid_t::max_value(), |
| libc::gid_t::max_value(), |
| ) |
| }; |
| if ret < 0 { |
| return Err(io::Error::last_os_error()); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rxattrwalk> { |
| Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)) |
| } |
| |
| fn xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<()> { |
| Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)) |
| } |
| |
| fn readdir(&mut self, readdir: &Treaddir) -> io::Result<Rreaddir> { |
| let fid = self |
| .fids |
| .get_mut(&readdir.fid) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| |
| if !fid.metadata.is_dir() { |
| return Err(io::Error::from_raw_os_error(libc::ENOTDIR)); |
| } |
| |
| // The p9 client implementation in the kernel doesn't fully read all the contents |
| // of the directory. This means that if some application performs a getdents() |
| // call, followed by removing some files, followed by another getdents() call, |
| // the offset that we get from the kernel is completely meaningless. Instead |
| // we fully read the contents of the directory here and only re-read the directory |
| // if the offset we get from the client is 0. Any other offset is served from the |
| // directory entries in memory. This ensures consistency even if the directory |
| // changes in between Treaddir messages. |
| if readdir.offset == 0 { |
| let mut offset = 0; |
| let iter = fs::read_dir(&fid.path)?; |
| let dirents = iter.map(|item| -> io::Result<Dirent> { |
| let entry = item?; |
| |
| let md = entry.metadata()?; |
| let qid = metadata_to_qid(&md); |
| |
| let ty = if md.is_dir() { |
| libc::DT_DIR |
| } else if md.is_file() { |
| libc::DT_REG |
| } else { |
| libc::DT_UNKNOWN |
| }; |
| |
| let name = entry |
| .file_name() |
| .into_string() |
| .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; |
| |
| let mut out = Dirent { |
| qid, |
| offset: 0, // set below |
| ty, |
| name, |
| }; |
| |
| offset += out.byte_size() as u64; |
| out.offset = offset; |
| |
| Ok(out) |
| }); |
| |
| // This is taking advantage of the fact that we can turn a Iterator of Result<T, E> |
| // into a Result<FromIterator<T>, E> since Result implements FromIterator<Result<T, E>>. |
| fid.dirents = Some(dirents.collect::<io::Result<Vec<Dirent>>>()?); |
| } |
| |
| let mut entries = fid |
| .dirents |
| .as_ref() |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))? |
| .iter() |
| .skip_while(|entry| entry.offset <= readdir.offset) |
| .peekable(); |
| |
| // Use an empty Rreaddir struct to figure out the maximum number of bytes that |
| // can be returned. |
| let header_size = Rframe { |
| tag: 0, |
| msg: Rmessage::Readdir(Rreaddir { |
| data: Data(Vec::new()), |
| }), |
| } |
| .byte_size(); |
| let count = min(self.msize - header_size, readdir.count); |
| let mut cursor = Cursor::new(Vec::with_capacity(count as usize)); |
| |
| while let Some(entry) = entries.peek() { |
| let byte_size = entry.byte_size() as usize; |
| |
| if cursor.get_ref().capacity() - cursor.get_ref().len() < byte_size { |
| // No more room in the buffer. |
| break; |
| } |
| |
| // Safe because we just checked that the iterator contains at least one more item. |
| entries.next().unwrap().encode(&mut cursor)?; |
| } |
| |
| Ok(Rreaddir { |
| data: Data(cursor.into_inner()), |
| }) |
| } |
| |
| fn fsync(&mut self, fsync: &Tfsync) -> io::Result<()> { |
| let file = self |
| .fids |
| .get(&fsync.fid) |
| .and_then(|fid| fid.file.as_ref()) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| |
| if fsync.datasync == 0 { |
| file.sync_all()?; |
| } else { |
| file.sync_data()?; |
| } |
| Ok(()) |
| } |
| |
| fn lock(&mut self, _lock: &Tlock) -> io::Result<Rlock> { |
| // File locking is not supported. |
| Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)) |
| } |
| fn get_lock(&mut self, _get_lock: &Tgetlock) -> io::Result<Rgetlock> { |
| // File locking is not supported. |
| Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)) |
| } |
| |
| fn link(&mut self, link: &Tlink) -> io::Result<()> { |
| let newname = Path::new(&link.name); |
| let buf = self |
| .fids |
| .get(&link.dfid) |
| .map(|dfid| dfid.path.to_path_buf()) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| let newpath = join_path(buf, newname, &*self.root)?; |
| |
| let path = self |
| .fids |
| .get(&link.fid) |
| .map(|fid| &fid.path) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| |
| fs::hard_link(path, &newpath)?; |
| Ok(()) |
| } |
| |
| fn mkdir(&mut self, mkdir: &Tmkdir) -> io::Result<Rmkdir> { |
| let fid = self |
| .fids |
| .get(&mkdir.dfid) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| |
| let name = Path::new(&mkdir.name); |
| let newpath = join_path(fid.path.to_path_buf(), name, &*self.root)?; |
| |
| fs::DirBuilder::new() |
| .recursive(false) |
| .mode(mkdir.mode & 0o755) |
| .create(&newpath)?; |
| |
| Ok(Rmkdir { |
| qid: metadata_to_qid(&fs::metadata(&newpath)?), |
| }) |
| } |
| |
| fn rename_at(&mut self, rename_at: &Trenameat) -> io::Result<()> { |
| let oldname = Path::new(&rename_at.oldname); |
| let oldbuf = self |
| .fids |
| .get(&rename_at.olddirfid) |
| .map(|dfid| dfid.path.to_path_buf()) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| let oldpath = join_path(oldbuf, oldname, &*self.root)?; |
| |
| let newname = Path::new(&rename_at.newname); |
| let newbuf = self |
| .fids |
| .get(&rename_at.newdirfid) |
| .map(|dfid| dfid.path.to_path_buf()) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| let newpath = join_path(newbuf, newname, &*self.root)?; |
| |
| fs::rename(&oldpath, &newpath)?; |
| Ok(()) |
| } |
| |
| fn unlink_at(&mut self, unlink_at: &Tunlinkat) -> io::Result<()> { |
| let name = Path::new(&unlink_at.name); |
| let buf = self |
| .fids |
| .get(&unlink_at.dirfd) |
| .map(|fid| fid.path.to_path_buf()) |
| .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| let path = join_path(buf, name, &*self.root)?; |
| |
| let md = fs::metadata(&path)?; |
| if md.is_dir() && (unlink_at.flags & (libc::AT_REMOVEDIR as u32)) == 0 { |
| return Err(io::Error::from_raw_os_error(libc::EISDIR)); |
| } |
| |
| if md.is_dir() { |
| fs::remove_dir(&path)?; |
| } else { |
| fs::remove_file(&path)?; |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests; |