diff options
Diffstat (limited to 'src/server/mod.rs')
-rw-r--r-- | src/server/mod.rs | 977 |
1 files changed, 0 insertions, 977 deletions
diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index 880a998..0000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,977 +0,0 @@ -// 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::{CStr, CString}; -use std::fs::File; -use std::io::{self, Cursor, Read, Write}; -use std::mem::{self, MaybeUninit}; -use std::ops::Deref; -use std::os::unix::ffi::OsStrExt; -use std::os::unix::fs::FileExt; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::path::Path; - -use libchromeos::{read_dir, syscall}; - -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); 16] = [ - (P9_WRONLY, libc::O_WRONLY), - (P9_RDWR, libc::O_RDWR), - (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; - -#[derive(PartialEq, Eq)] -enum FileType { - Regular, - Directory, - Other, -} - -impl From<libc::mode_t> for FileType { - fn from(mode: libc::mode_t) -> Self { - match mode & libc::S_IFMT { - libc::S_IFREG => FileType::Regular, - libc::S_IFDIR => FileType::Directory, - _ => FileType::Other, - } - } -} - -// 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. -struct Fid { - path: File, - file: Option<File>, - filetype: FileType, -} - -impl From<libc::stat64> for Qid { - fn from(st: libc::stat64) -> Qid { - let ty = match st.st_mode & libc::S_IFMT { - libc::S_IFDIR => P9_QTDIR, - libc::S_IFREG => P9_QTFILE, - libc::S_IFLNK => P9_QTSYMLINK, - _ => 0, - }; - - Qid { - ty, - // TODO: deal with the 2038 problem before 2038 - version: st.st_mtime as u32, - path: st.st_ino, - } - } -} - -fn statat(d: &File, name: &CStr, flags: libc::c_int) -> io::Result<libc::stat64> { - let mut st = MaybeUninit::<libc::stat64>::zeroed(); - - // Safe because the kernel will only write data in `st` and we check the return - // value. - let res = unsafe { - libc::fstatat64( - d.as_raw_fd(), - name.as_ptr(), - st.as_mut_ptr(), - flags | libc::AT_SYMLINK_NOFOLLOW, - ) - }; - if res >= 0 { - // Safe because the kernel guarantees that the struct is now fully initialized. - Ok(unsafe { st.assume_init() }) - } else { - Err(io::Error::last_os_error()) - } -} - -fn stat(f: &File) -> io::Result<libc::stat64> { - // Safe because this is a constant value and a valid C string. - let pathname = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") }; - - statat(f, pathname, libc::AT_EMPTY_PATH) -} - -fn string_to_cstring(s: String) -> io::Result<CString> { - CString::new(s).map_err(|_| io::Error::from_raw_os_error(libc::EINVAL)) -} - -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, - }) -} - -// Sigh.. Cow requires the underlying type to implement Clone. -enum MaybeOwned<'b, T> { - Borrowed(&'b T), - Owned(T), -} - -impl<'a, T> Deref for MaybeOwned<'a, T> { - type Target = T; - fn deref(&self) -> &Self::Target { - use MaybeOwned::*; - match *self { - Borrowed(borrowed) => borrowed, - Owned(ref owned) => owned, - } - } -} - -fn ebadf() -> io::Error { - io::Error::from_raw_os_error(libc::EBADF) -} - -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()) -} - -// Performs an ascii case insensitive lookup and returns an O_PATH fd for the entry, if found. -fn ascii_casefold_lookup(proc: &File, parent: &File, name: &[u8]) -> io::Result<File> { - let mut dir = open_fid(proc, &parent, P9_DIRECTORY)?; - let mut dirents = read_dir(&mut dir, 0)?; - - while let Some(entry) = dirents.next().transpose()? { - if name.eq_ignore_ascii_case(entry.name.to_bytes()) { - return lookup(parent, entry.name); - } - } - - Err(io::Error::from_raw_os_error(libc::ENOENT)) -} - -fn lookup(parent: &File, name: &CStr) -> io::Result<File> { - // Safe because this doesn't modify any memory and we check the return value. - let fd = syscall!(unsafe { - libc::openat( - parent.as_raw_fd(), - name.as_ptr(), - libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC, - ) - })?; - - // Safe because we just opened this fd. - Ok(unsafe { File::from_raw_fd(fd) }) -} - -fn do_walk( - proc: &File, - wnames: Vec<String>, - start: File, - ascii_casefold: bool, - mds: &mut Vec<libc::stat64>, -) -> io::Result<File> { - let mut current = start; - - for wname in wnames { - let name = string_to_cstring(wname)?; - current = lookup(¤t, &name).or_else(|e| { - if ascii_casefold { - if let Some(libc::ENOENT) = e.raw_os_error() { - return ascii_casefold_lookup(proc, ¤t, name.to_bytes()); - } - } - - Err(e) - })?; - mds.push(stat(¤t)?); - } - - Ok(current) -} - -fn open_fid(proc: &File, path: &File, p9_flags: u32) -> io::Result<File> { - let pathname = string_to_cstring(format!("self/fd/{}", path.as_raw_fd()))?; - - // We always open files with O_CLOEXEC. - let mut flags: i32 = libc::O_CLOEXEC; - for &(p9f, of) in &MAPPED_FLAGS { - if (p9_flags & p9f) != 0 { - flags |= of; - } - } - - if p9_flags & P9_NOACCESS == P9_RDONLY { - flags |= libc::O_RDONLY; - } - - // Safe because this doesn't modify any memory and we check the return value. We need to - // clear the O_NOFOLLOW flag because we want to follow the proc symlink. - let fd = syscall!(unsafe { - libc::openat( - proc.as_raw_fd(), - pathname.as_ptr(), - flags & !libc::O_NOFOLLOW, - ) - })?; - - // Safe because we just opened this fd and we know it is valid. - Ok(unsafe { File::from_raw_fd(fd) }) -} - -#[derive(Clone)] -pub struct Config { - pub root: Box<Path>, - pub msize: u32, - - pub uid_map: ServerUidMap, - pub gid_map: ServerGidMap, - - pub ascii_casefold: bool, -} - -impl Default for Config { - fn default() -> Config { - Config { - root: Path::new("/").into(), - msize: MAX_MESSAGE_SIZE, - uid_map: Default::default(), - gid_map: Default::default(), - ascii_casefold: false, - } - } -} -pub struct Server { - fids: BTreeMap<u32, Fid>, - proc: File, - cfg: Config, -} - -impl Server { - pub fn new<P: Into<Box<Path>>>( - root: P, - uid_map: ServerUidMap, - gid_map: ServerGidMap, - ) -> io::Result<Server> { - Server::with_config(Config { - root: root.into(), - msize: MAX_MESSAGE_SIZE, - uid_map, - gid_map, - ascii_casefold: false, - }) - } - - pub fn with_config(cfg: Config) -> io::Result<Server> { - // Safe because this is a valid c-string. - let proc_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(b"/proc\0") }; - - // Safe because this doesn't modify any memory and we check the return value. - let fd = syscall!(unsafe { - libc::openat( - libc::AT_FDCWD, - proc_cstr.as_ptr(), - libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC, - ) - })?; - - // Safe because we just opened this fd and we know it is valid. - let proc = unsafe { File::from_raw_fd(fd) }; - Ok(Server { - fids: BTreeMap::new(), - proc, - cfg, - }) - } - - pub fn keep_fds(&self) -> Vec<RawFd> { - vec![self.proc.as_raw_fd()] - } - - pub fn handle_message<R: Read, W: Write>( - &mut self, - reader: &mut R, - writer: &mut W, - ) -> io::Result<()> { - let Tframe { tag, msg } = WireFormat::decode(&mut reader.take(self.cfg.msize as u64))?; - - let rmsg = match msg { - Tmessage::Version(ref version) => self.version(version).map(Rmessage::Version), - Tmessage::Flush(ref flush) => self.flush(flush).and(Ok(Rmessage::Flush)), - Tmessage::Walk(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(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(link) => self.link(link).and(Ok(Rmessage::Link)), - Tmessage::Mkdir(mkdir) => self.mkdir(mkdir).map(Rmessage::Mkdir), - Tmessage::RenameAt(rename_at) => self.rename_at(rename_at).and(Ok(Rmessage::RenameAt)), - Tmessage::UnlinkAt(unlink_at) => self.unlink_at(unlink_at).and(Ok(Rmessage::UnlinkAt)), - }; - - // Errors while handling requests are never fatal. - let response = Rframe { - tag, - msg: rmsg.unwrap_or_else(error_to_rmessage), - }; - - 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 root = CString::new(self.cfg.root.as_os_str().as_bytes()) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - - // Safe because this doesn't modify any memory and we check the return value. - let fd = syscall!(unsafe { - libc::openat( - libc::AT_FDCWD, - root.as_ptr(), - libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC, - ) - })?; - - let root_path = unsafe { File::from_raw_fd(fd) }; - let st = stat(&root_path)?; - - let fid = Fid { - // Safe because we just opened this fd. - path: root_path, - file: None, - filetype: st.st_mode.into(), - }; - let response = Rattach { qid: st.into() }; - 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.cfg.msize = min(self.cfg.msize, version.msize); - - Ok(Rversion { - msize: self.cfg.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 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 start = self - .fids - .get(&walk.fid) - .ok_or_else(ebadf) - .and_then(|fid| fid.path.try_clone())?; - - // Now walk the tree and break on the first error, if any. - let expected_len = walk.wnames.len(); - let mut mds = Vec::with_capacity(expected_len); - match do_walk( - &self.proc, - walk.wnames, - start, - self.cfg.ascii_casefold, - &mut mds, - ) { - Ok(end) => { - // Store the new fid if the full walk succeeded. - if mds.len() == expected_len { - let st = mds.last().copied().map(Ok).unwrap_or_else(|| stat(&end))?; - self.fids.insert( - walk.newfid, - Fid { - path: end, - file: None, - filetype: st.st_mode.into(), - }, - ); - } - } - Err(e) => { - // Only return an error if it occurred on the first component. - if mds.is_empty() { - return Err(e); - } - } - } - - Ok(Rwalk { - wqids: mds.into_iter().map(Qid::from).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(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.cfg.msize - header_size, read.count); - let mut buf = Data(vec![0u8; capacity as usize]); - - let count = file.read_at(&mut buf, read.offset)?; - buf.truncate(count); - - 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(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<()> { - // Since a file could be linked into multiple locations, there is no way to know exactly - // which path we are supposed to unlink. Linux uses unlink_at anyway, so we can just return - // an error here. - Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)) - } - - fn statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rstatfs> { - let fid = self.fids.get(&statfs.fid).ok_or_else(ebadf)?; - let mut buf = MaybeUninit::zeroed(); - - // Safe because this will only modify `out` and we check the return value. - syscall!(unsafe { libc::fstatfs64(fid.path.as_raw_fd(), buf.as_mut_ptr()) })?; - - // Safe because this only has integer types and any value is valid. - let out = unsafe { buf.assume_init() }; - 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, - // Safe because the fsid has only integer fields and the compiler will verify that is - // the same width as the `fsid` field in Rstatfs. - fsid: unsafe { mem::transmute(out.f_fsid) }, - 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(ebadf)?; - - let file = open_fid(&self.proc, &fid.path, lopen.flags)?; - let st = stat(&file)?; - - fid.file = Some(file); - let iounit = st.st_blksize as u32; - Ok(Rlopen { - qid: st.into(), - iounit, - }) - } - - fn lcreate(&mut self, lcreate: Tlcreate) -> io::Result<Rlcreate> { - let fid = self.fids.get_mut(&lcreate.fid).ok_or_else(ebadf)?; - - if fid.filetype != FileType::Directory { - return Err(io::Error::from_raw_os_error(libc::ENOTDIR)); - } - - let mut flags: i32 = libc::O_CLOEXEC | libc::O_CREAT | libc::O_EXCL; - for &(p9f, of) in &MAPPED_FLAGS { - if (lcreate.flags & p9f) != 0 { - flags |= of; - } - } - if lcreate.flags & P9_NOACCESS == P9_RDONLY { - flags |= libc::O_RDONLY; - } - - let name = string_to_cstring(lcreate.name)?; - - // Safe because this doesn't modify any memory and we check the return value. - let fd = syscall!(unsafe { - libc::openat(fid.path.as_raw_fd(), name.as_ptr(), flags, lcreate.mode) - })?; - - // Safe because we just opened this fd and we know it is valid. - let file = unsafe { File::from_raw_fd(fd) }; - let st = stat(&file)?; - let iounit = st.st_blksize as u32; - - fid.file = Some(file); - - // This fid now refers to the newly created file so we need to update the O_PATH fd for it - // as well. - fid.path = lookup(&fid.path, &name)?; - - Ok(Rlcreate { - qid: st.into(), - iounit, - }) - } - - 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<()> { - // We cannot support this as an inode may be linked into multiple directories but we don't - // know which one the client wants us to rename. Linux uses rename_at anyway, so we don't - // need to worry about this. - Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)) - } - - 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(ebadf)?; - - let st = stat(&fid.path)?; - - Ok(Rgetattr { - valid: P9_GETATTR_BASIC, - qid: st.into(), - mode: st.st_mode, - uid: map_id_from_host(&self.cfg.uid_map, st.st_uid), - gid: map_id_from_host(&self.cfg.gid_map, st.st_gid), - nlink: st.st_nlink as u64, - rdev: st.st_rdev, - size: st.st_size as u64, - blksize: st.st_blksize as u64, - blocks: st.st_blocks as u64, - atime_sec: st.st_atime as u64, - atime_nsec: st.st_atime_nsec as u64, - mtime_sec: st.st_mtime as u64, - mtime_nsec: st.st_mtime_nsec as u64, - ctime_sec: st.st_ctime as u64, - ctime_nsec: st.st_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(&set_attr.fid).ok_or_else(ebadf)?; - - let file = if let Some(ref file) = fid.file { - MaybeOwned::Borrowed(file) - } else { - let flags = match fid.filetype { - FileType::Regular => P9_RDWR, - FileType::Directory => P9_RDONLY | P9_DIRECTORY, - FileType::Other => P9_RDWR, - }; - MaybeOwned::Owned(open_fid(&self.proc, &fid.path, P9_NONBLOCK | flags)?) - }; - - 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(ebadf)?; - - if fid.filetype != FileType::Directory { - return Err(io::Error::from_raw_os_error(libc::ENOTDIR)); - } - - // 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.cfg.msize - header_size, readdir.count); - let mut cursor = Cursor::new(Vec::with_capacity(count as usize)); - - let dir = fid.file.as_mut().ok_or_else(ebadf)?; - let mut dirents = read_dir(dir, readdir.offset as libc::off64_t)?; - while let Some(dirent) = dirents.next().transpose()? { - let st = statat(&fid.path, &dirent.name, 0)?; - - let name = dirent - .name - .to_str() - .map(String::from) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; - - let entry = Dirent { - qid: st.into(), - offset: dirent.offset, - ty: dirent.type_, - name, - }; - - 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; - } - - entry.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(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 target = self.fids.get(&link.fid).ok_or_else(ebadf)?; - let path = string_to_cstring(format!("self/fd/{}", target.path.as_raw_fd()))?; - - let dir = self.fids.get(&link.dfid).ok_or_else(ebadf)?; - let name = string_to_cstring(link.name)?; - - // Safe because this doesn't modify any memory and we check the return value. - syscall!(unsafe { - libc::linkat( - self.proc.as_raw_fd(), - path.as_ptr(), - dir.path.as_raw_fd(), - name.as_ptr(), - libc::AT_SYMLINK_FOLLOW, - ) - })?; - Ok(()) - } - - fn mkdir(&mut self, mkdir: Tmkdir) -> io::Result<Rmkdir> { - let fid = self.fids.get(&mkdir.dfid).ok_or_else(ebadf)?; - let name = string_to_cstring(mkdir.name)?; - - // Safe because this doesn't modify any memory and we check the return value. - syscall!(unsafe { libc::mkdirat(fid.path.as_raw_fd(), name.as_ptr(), mkdir.mode) })?; - Ok(Rmkdir { - qid: statat(&fid.path, &name, 0).map(Qid::from)?, - }) - } - - fn rename_at(&mut self, rename_at: Trenameat) -> io::Result<()> { - let olddir = self.fids.get(&rename_at.olddirfid).ok_or_else(ebadf)?; - let oldname = string_to_cstring(rename_at.oldname)?; - - let newdir = self.fids.get(&rename_at.newdirfid).ok_or_else(ebadf)?; - let newname = string_to_cstring(rename_at.newname)?; - - // Safe because this doesn't modify any memory and we check the return value. - syscall!(unsafe { - libc::renameat( - olddir.path.as_raw_fd(), - oldname.as_ptr(), - newdir.path.as_raw_fd(), - newname.as_ptr(), - ) - })?; - - Ok(()) - } - - fn unlink_at(&mut self, unlink_at: Tunlinkat) -> io::Result<()> { - let dir = self.fids.get(&unlink_at.dirfd).ok_or_else(ebadf)?; - let name = string_to_cstring(unlink_at.name)?; - - syscall!(unsafe { - libc::unlinkat( - dir.path.as_raw_fd(), - name.as_ptr(), - unlink_at.flags as libc::c_int, - ) - })?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests; |