From 8be853418da3175097cca737d7125288759612f2 Mon Sep 17 00:00:00 2001 From: Victor Hsieh Date: Tue, 1 Dec 2020 16:04:41 -0800 Subject: Uprev rev vm_tools/p9 to 2c3e8252c684673e83278a0124a998e997dbbcc2 Manual changes: - fuzz/OWNERS: Unrecognized email by Gerrit is commented out, otherwise the Gerrit server rejects the upload. - patches/Android.bp.patch: Add `compile_multilib: 64` to avoid touching disabled, unused 32-bit dependencies. - remove .patch from .gitignore Note that this pulls in more dependnecies: -> libchromeos-rs -> intrusive-collections -> memoffset -> rand_xorshift Bug: 174797066 Test: m Change-Id: I4a810e7b05d8180e80ba15811a66c51d9d069d8d --- .gitignore | 1 - Android.bp | 40 +- Cargo.toml | 1 + METADATA | 4 +- fuzz/OWNERS | 3 + patches/Android.bp.patch | 27 ++ src/protocol/wire_format.rs | 9 +- src/server/mod.rs | 832 ++++++++++++++++++++++-------------------- src/server/tests.rs | 320 +++++++++------- wire_format_derive/Android.bp | 2 +- 10 files changed, 700 insertions(+), 539 deletions(-) create mode 100644 fuzz/OWNERS create mode 100644 patches/Android.bp.patch diff --git a/.gitignore b/.gitignore index 0c60586..de0d2e3 100644 --- a/.gitignore +++ b/.gitignore @@ -47,7 +47,6 @@ tags_sorted_by_file # Patch files. *.diff -*.patch *.orig *.rej diff --git a/Android.bp b/Android.bp index 1db119e..2372a15 100644 --- a/Android.bp +++ b/Android.bp @@ -1,4 +1,4 @@ -// This file is generated by cargo2android.py --run --device --tests --dependencies. +// This file is generated by cargo2android.py --run --device --tests --dependencies --patch=patches/Android.bp.patch. rust_library { name: "libp9", @@ -8,6 +8,7 @@ rust_library { edition: "2018", rustlibs: [ "liblibc", + "liblibchromeos", ], proc_macros: [ "libwire_format_derive", @@ -16,6 +17,12 @@ rust_library { "//apex_available:platform", "com.android.virt", ], + // This library depends on libdata_model that is is part of crosvm project. + // Projects within crosvm on Android have only 64-bit target build enabled. + // As a result, we need to manually limit this build to 64-bit only, too. + // This is fine because this library is only used by crosvm now (thus 64-bit + // only). + compile_multilib: "64", } rust_defaults { @@ -27,10 +34,9 @@ rust_defaults { edition: "2018", rustlibs: [ "liblibc", + "liblibchromeos", ], - proc_macros: [ - "libwire_format_derive", - ], + proc_macros: ["libwire_format_derive"], } rust_test_host { @@ -41,11 +47,33 @@ rust_test_host { rust_test { name: "p9_device_test_src_lib", defaults: ["p9_defaults"], + // Manually limit to 64-bit to avoid depending on non-existing 32-bit build + // of libdata_model currently. + compile_multilib: "64", } // dependent_library ["feature_list"] -// libc-0.2.79 "default,std" +// ../../crosvm/assertions/src/lib.rs +// ../../crosvm/data_model/src/lib.rs +// ../../libchromeos-rs/src/lib.rs +// autocfg-1.0.1 +// cfg-if-0.1.10 +// futures-0.3.8 "alloc" +// futures-channel-0.3.8 "alloc,futures-sink,sink" +// futures-core-0.3.8 "alloc" +// futures-io-0.3.8 +// futures-sink-0.3.8 "alloc" +// futures-task-0.3.8 "alloc" +// futures-util-0.3.8 "alloc,futures-sink,sink" +// intrusive-collections-0.9.0 "alloc,default" +// libc-0.2.81 "default,std" +// log-0.4.11 +// memoffset-0.5.6 "default" +// pin-project-1.0.2 +// pin-project-internal-1.0.2 +// pin-utils-0.1.0 // proc-macro2-1.0.24 "default,proc-macro" +// protobuf-2.18.1 // quote-1.0.7 "default,proc-macro" -// syn-1.0.45 "clone-impls,default,derive,parsing,printing,proc-macro,quote" +// syn-1.0.54 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut" // unicode-xid-0.2.1 "default" diff --git a/Cargo.toml b/Cargo.toml index abb7a45..11ea913 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] libc = "*" +libchromeos = { path = "../../libchromeos-rs" } # provided by ebuild wire_format_derive = { path = "wire_format_derive", version = "*" } [features] diff --git a/METADATA b/METADATA index 0feb0d2..4626851 100644 --- a/METADATA +++ b/METADATA @@ -8,7 +8,7 @@ third_party { type: GIT value: "https://chromium.googlesource.com/chromiumos/platform2" } - version: "8739fdb7d25a53496536f328d2675824c7199e17" - last_upgrade_date { year: 2020 month: 10 day: 6 } + version: "2c3e8252c684673e83278a0124a998e997dbbcc2" + last_upgrade_date { year: 2020 month: 12 day: 3 } license_type: NOTICE } diff --git a/fuzz/OWNERS b/fuzz/OWNERS new file mode 100644 index 0000000..cef66ae --- /dev/null +++ b/fuzz/OWNERS @@ -0,0 +1,3 @@ +# ANDROID: Remove because Gerrit does not recognize these emails and reject the upload. +#chirantan@chromium.org +#dgreid@chromium.org \ No newline at end of file diff --git a/patches/Android.bp.patch b/patches/Android.bp.patch new file mode 100644 index 0000000..ccc2338 --- /dev/null +++ b/patches/Android.bp.patch @@ -0,0 +1,27 @@ +diff --git a/Android.bp b/Android.bp +index 0b5bcde..2372a15 100644 +--- a/Android.bp ++++ b/Android.bp +@@ -17,6 +17,12 @@ rust_library { + "//apex_available:platform", + "com.android.virt", + ], ++ // This library depends on libdata_model that is is part of crosvm project. ++ // Projects within crosvm on Android have only 64-bit target build enabled. ++ // As a result, we need to manually limit this build to 64-bit only, too. ++ // This is fine because this library is only used by crosvm now (thus 64-bit ++ // only). ++ compile_multilib: "64", + } + + rust_defaults { +@@ -41,6 +47,9 @@ rust_test_host { + rust_test { + name: "p9_device_test_src_lib", + defaults: ["p9_defaults"], ++ // Manually limit to 64-bit to avoid depending on non-existing 32-bit build ++ // of libdata_model currently. ++ compile_multilib: "64", + } + + // dependent_library ["feature_list"] diff --git a/src/protocol/wire_format.rs b/src/protocol/wire_format.rs index b787990..7120937 100644 --- a/src/protocol/wire_format.rs +++ b/src/protocol/wire_format.rs @@ -223,17 +223,14 @@ mod test { 0xef as u8, WireFormat::decode(&mut Cursor::new(&buf)).unwrap() ); - assert_eq!( - 0xbeef as u16, - WireFormat::decode(&mut Cursor::new(&buf)).unwrap() - ); + assert_eq!(0xbeef as u16, u16::decode(&mut Cursor::new(&buf)).unwrap()); assert_eq!( 0xdeadbeef as u32, - WireFormat::decode(&mut Cursor::new(&buf)).unwrap() + u32::decode(&mut Cursor::new(&buf)).unwrap() ); assert_eq!( 0x8badf00d_deadbeef as u64, - WireFormat::decode(&mut Cursor::new(&buf)).unwrap() + u64::decode(&mut Cursor::new(&buf)).unwrap() ); } diff --git a/src/server/mod.rs b/src/server/mod.rs index 6a4a5c1..880a998 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -4,19 +4,22 @@ use std::cmp::min; use std::collections::{btree_map, BTreeMap}; -use std::ffi::CString; -use std::fs; +use std::ffi::{CStr, CString}; +use std::fs::File; 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 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_RDONLY: u32 = 0o00000000; const P9_WRONLY: u32 = 0o00000001; const P9_RDWR: u32 = 0o00000002; const P9_NOACCESS: u32 = 0o00000003; @@ -37,7 +40,9 @@ const _P9_CLOEXEC: u32 = 0o02000000; const P9_SYNC: u32 = 0o04000000; // Mapping from 9P flags to libc flags. -const MAPPED_FLAGS: [(u32, i32); 14] = [ +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), @@ -61,7 +66,7 @@ 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_QTSYMLINK: u8 = 0x02; const _P9_QTLINK: u8 = 0x01; const P9_QTFILE: u8 = 0x00; @@ -100,37 +105,84 @@ const P9_SETATTR_MTIME_SET: u32 = 0x00000100; 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 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. -#[derive(Debug)] struct Fid { - path: Box, - metadata: fs::Metadata, - file: Option, - dirents: Option>, + path: File, + file: Option, + filetype: FileType, } -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 - }; +impl From 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: metadata.mtime() as u32, - path: metadata.ino(), + 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 { + let mut st = MaybeUninit::::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 { + // 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::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 @@ -164,45 +216,25 @@ fn error_to_rmessage(err: io::Error) -> Rmessage { }) } -// 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, R: AsRef>( - mut buf: PathBuf, - path: P, - root: R, -) -> io::Result { - 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), +// 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, } } +} - Ok(buf) +fn ebadf() -> io::Error { + io::Error::from_raw_os_error(libc::EBADF) } pub type ServerIdMap = BTreeMap; @@ -213,12 +245,115 @@ fn map_id_from_host(map: &ServerIdMap, 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 { + 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 { + // 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, + start: File, + ascii_casefold: bool, + mds: &mut Vec, +) -> io::Result { + 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 { + 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, + 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 { - root: Box, - msize: u32, fids: BTreeMap, - uid_map: ServerUidMap, - gid_map: ServerGidMap, + proc: File, + cfg: Config, } impl Server { @@ -226,14 +361,40 @@ impl Server { root: P, uid_map: ServerUidMap, gid_map: ServerGidMap, - ) -> Server { - Server { + ) -> io::Result { + Server::with_config(Config { root: root.into(), msize: MAX_MESSAGE_SIZE, - fids: BTreeMap::new(), uid_map, gid_map, - } + ascii_casefold: false, + }) + } + + pub fn with_config(cfg: Config) -> io::Result { + // 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 { + vec![self.proc.as_raw_fd()] } pub fn handle_message( @@ -241,16 +402,12 @@ impl Server { reader: &mut R, writer: &mut W, ) -> io::Result<()> { - let request: Tframe = WireFormat::decode(&mut reader.take(self.msize as u64))?; + let Tframe { tag, msg } = WireFormat::decode(&mut reader.take(self.cfg.msize as u64))?; - if cfg!(feature = "trace") { - println!("{:?}", &request); - } - - let rmsg = match request.msg { + 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(ref walk) => self.walk(walk).map(Rmessage::Walk), + 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)), @@ -259,7 +416,7 @@ impl Server { 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::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)), @@ -276,26 +433,18 @@ impl Server { 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)) - } + 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: request.tag, + tag, msg: rmsg.unwrap_or_else(error_to_rmessage), }; - if cfg!(feature = "trace") { - println!("{:?}", &response); - } - response.encode(writer)?; writer.flush() } @@ -310,15 +459,28 @@ impl Server { // 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 { - path: self.root.to_path_buf().into_boxed_path(), - metadata: fs::metadata(&self.root)?, + // Safe because we just opened this fd. + path: root_path, file: None, - dirents: None, - }; - let response = Rattach { - qid: metadata_to_qid(&fid.metadata), + filetype: st.st_mode.into(), }; + let response = Rattach { qid: st.into() }; entry.insert(fid); Ok(response) } @@ -333,10 +495,10 @@ impl Server { // A Tversion request clunks all open fids and terminates any pending I/O. self.fids.clear(); - self.msize = min(MAX_MESSAGE_SIZE, version.msize); + self.cfg.msize = min(self.cfg.msize, version.msize); Ok(Rversion { - msize: self.msize, + msize: self.cfg.msize, version: if version.version == "9P2000.L" { String::from("9P2000.L") } else { @@ -350,54 +512,39 @@ impl Server { Ok(()) } - fn do_walk( - &self, - wnames: &[String], - mut buf: PathBuf, - mds: &mut Vec, - ) -> io::Result { - 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 { + fn walk(&mut self, walk: Twalk) -> io::Result { // `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 + let start = 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()))?; + .ok_or_else(ebadf) + .and_then(|fid| fid.path.try_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) => { + 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() == walk.wnames.len() { - // This could just be a duplication operation. - let md = if let Some(md) = mds.last() { - md.clone() - } else { - oldmd - }; - + if mds.len() == expected_len { + let st = mds.last().copied().map(Ok).unwrap_or_else(|| stat(&end))?; self.fids.insert( walk.newfid, Fid { - path: buf.into_boxed_path(), - metadata: md, + path: end, file: None, - dirents: None, + filetype: st.st_mode.into(), }, ); } @@ -411,7 +558,7 @@ impl Server { } Ok(Rwalk { - wqids: mds.iter().map(metadata_to_qid).collect(), + wqids: mds.into_iter().map(Qid::from).collect(), }) } @@ -421,7 +568,7 @@ impl Server { .fids .get_mut(&read.fid) .and_then(|fid| fid.file.as_mut()) - .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; + .ok_or_else(ebadf)?; // Use an empty Rread struct to figure out the overhead of the header. let header_size = Rframe { @@ -432,12 +579,11 @@ impl Server { } .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 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.resize(count, 0); + buf.truncate(count); Ok(Rread { data: buf }) } @@ -447,7 +593,7 @@ impl Server { .fids .get_mut(&write.fid) .and_then(|fid| fid.file.as_mut()) - .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; + .ok_or_else(ebadf)?; let count = file.write_at(&write.data, write.offset)?; Ok(Rwrite { @@ -465,44 +611,22 @@ impl Server { } } - 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 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 { - 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()); - } + 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, @@ -511,77 +635,65 @@ impl Server { 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 + // 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 { - 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; - } - } + let fid = self.fids.get_mut(&lopen.fid).ok_or_else(ebadf)?; - // 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)?; + let file = open_fid(&self.proc, &fid.path, lopen.flags)?; + let st = stat(&file)?; - fid.metadata = file.metadata()?; fid.file = Some(file); - + let iounit = st.st_blksize as u32; Ok(Rlopen { - qid: metadata_to_qid(&fid.metadata), - iounit: 0, + qid: st.into(), + iounit, }) } - fn lcreate(&mut self, lcreate: &Tlcreate) -> io::Result { - let fid = self - .fids - .get_mut(&lcreate.fid) - .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; + fn lcreate(&mut self, lcreate: Tlcreate) -> io::Result { + let fid = self.fids.get_mut(&lcreate.fid).ok_or_else(ebadf)?; - if !fid.metadata.is_dir() { + if fid.filetype != FileType::Directory { 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; + let mut flags: i32 = libc::O_CLOEXEC | libc::O_CREAT | libc::O_EXCL; for &(p9f, of) in &MAPPED_FLAGS { if (lcreate.flags & p9f) != 0 { - custom_flags |= of; + flags |= of; } } + if lcreate.flags & P9_NOACCESS == P9_RDONLY { + flags |= libc::O_RDONLY; + } - // 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)?; + 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.metadata = file.metadata()?; fid.file = Some(file); - fid.path = path.into_boxed_path(); + + // 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: metadata_to_qid(&fid.metadata), - iounit: 0, + qid: st.into(), + iounit, }) } @@ -595,26 +707,11 @@ impl Server { 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 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 { @@ -623,31 +720,27 @@ impl Server { } fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result { - let fid = self - .fids - .get_mut(&get_attr.fid) - .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; + let fid = self.fids.get_mut(&get_attr.fid).ok_or_else(ebadf)?; - // Refresh the metadata since we were explicitly asked for it. - fid.metadata = fs::metadata(&fid.path)?; + let st = stat(&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, + 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, @@ -661,12 +754,18 @@ impl Server { 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 fid = self.fids.get(&set_attr.fid).ok_or_else(ebadf)?; - let file = fs::OpenOptions::new().write(true).open(&fid.path)?; + 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)?; @@ -732,71 +831,12 @@ impl Server { } fn readdir(&mut self, readdir: &Treaddir) -> io::Result { - let fid = self - .fids - .get_mut(&readdir.fid) - .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; + let fid = self.fids.get_mut(&readdir.fid).ok_or_else(ebadf)?; - if !fid.metadata.is_dir() { + if fid.filetype != FileType::Directory { 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 { - 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 - // into a Result, E> since Result implements FromIterator>. - fid.dirents = Some(dirents.collect::>>()?); - } - - 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 { @@ -806,10 +846,27 @@ impl Server { }), } .byte_size(); - let count = min(self.msize - header_size, readdir.count); + let count = min(self.cfg.msize - header_size, readdir.count); let mut cursor = Cursor::new(Vec::with_capacity(count as usize)); - while let Some(entry) = entries.peek() { + 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 { @@ -817,8 +874,7 @@ impl Server { break; } - // Safe because we just checked that the iterator contains at least one more item. - entries.next().unwrap().encode(&mut cursor)?; + entry.encode(&mut cursor)?; } Ok(Rreaddir { @@ -831,7 +887,7 @@ impl Server { .fids .get(&fsync.fid) .and_then(|fid| fid.file.as_ref()) - .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; + .ok_or_else(ebadf)?; if fsync.datasync == 0 { file.sync_all()?; @@ -850,84 +906,68 @@ impl Server { 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)?; + 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 path = self - .fids - .get(&link.fid) - .map(|fid| &fid.path) - .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; + let dir = self.fids.get(&link.dfid).ok_or_else(ebadf)?; + let name = string_to_cstring(link.name)?; - fs::hard_link(path, &newpath)?; + // 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 { - 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)?; + fn mkdir(&mut self, mkdir: Tmkdir) -> io::Result { + 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: metadata_to_qid(&fs::metadata(&newpath)?), + qid: statat(&fid.path, &name, 0).map(Qid::from)?, }) } - 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)?; + 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 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)?; + 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(), + ) + })?; - 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)); - } + 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)?; - if md.is_dir() { - fs::remove_dir(&path)?; - } else { - fs::remove_file(&path)?; - } + syscall!(unsafe { + libc::unlinkat( + dir.path.as_raw_fd(), + name.as_ptr(), + unlink_at.flags as libc::c_int, + ) + })?; Ok(()) } diff --git a/src/server/tests.rs b/src/server/tests.rs index cc000b6..ee122dd 100644 --- a/src/server/tests.rs +++ b/src/server/tests.rs @@ -8,13 +8,13 @@ use std::borrow::Cow; use std::collections::{HashSet, VecDeque}; use std::env; use std::ffi::{CString, OsString}; -use std::fs::File; +use std::fs::{self, File}; use std::io::{self, Cursor}; use std::mem; use std::ops::Deref; -use std::os::unix::fs::MetadataExt; use std::os::unix::ffi::OsStringExt; -use std::path::{Path, PathBuf}; +use std::os::unix::fs::MetadataExt; +use std::path::{Component, Path, PathBuf}; use std::u32; // Used to indicate that there is no fid associated with this message. @@ -26,6 +26,47 @@ const ROOT_FID: u32 = 1; // How big we want the default buffer to be when running tests. const DEFAULT_BUFFER_SIZE: u32 = 4096; +// 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, R: AsRef>( + mut buf: PathBuf, + path: P, + root: R, +) -> io::Result { + 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) +} + // Automatically deletes the path it contains when it goes out of scope. struct ScopedPath>(P); @@ -107,7 +148,7 @@ fn check_qid(qid: &Qid, md: &fs::Metadata) { } else if md.is_file() { P9_QTFILE } else if md.file_type().is_symlink() { - _P9_QTSYMLINK + P9_QTSYMLINK } else { panic!("unknown file type: {:?}", md.file_type()); }; @@ -129,7 +170,7 @@ fn check_attr(server: &mut Server, fid: u32, md: &fs::Metadata) { } else if md.is_file() { P9_QTFILE } else if md.file_type().is_symlink() { - _P9_QTSYMLINK + P9_QTSYMLINK } else { panic!("unknown file type: {:?}", md.file_type()); }; @@ -191,7 +232,7 @@ fn walk>( wnames: names, }; - let rwalk = server.walk(&twalk).expect("failed to walk directoy"); + let rwalk = server.walk(twalk).expect("failed to walk directoy"); assert_eq!(mds.len(), rwalk.wqids.len()); for (md, qid) in mds.iter().zip(rwalk.wqids.iter()) { check_qid(qid, md); @@ -206,7 +247,12 @@ fn open>( fid: u32, flags: u32, ) -> io::Result { - walk(server, dir, dir_fid, fid, vec![String::from(name)]); + let wnames = if name.is_empty() { + vec![] + } else { + vec![String::from(name)] + }; + walk(server, dir, dir_fid, fid, wnames); let tlopen = Tlopen { fid, flags }; @@ -270,7 +316,7 @@ fn create>( gid: 0, }; - server.lcreate(&tlcreate) + server.lcreate(tlcreate) } struct Readdir<'a> { @@ -389,7 +435,8 @@ fn setup>(name: P) -> (ScopedPath, Server) { .symlink_metadata() .expect("failed to get metadata for root dir"); - let mut server = Server::new(&*test_dir, Default::default(), Default::default()); + let mut server = Server::new(&*test_dir, Default::default(), Default::default()) + .expect("Failed to create server"); let tversion = Tversion { msize: DEFAULT_BUFFER_SIZE, @@ -478,7 +525,7 @@ fn tree_walk() { dirs.push_back(test_dir.to_path_buf()); while let Some(dir) = dirs.pop_front() { - let fid = next_fid; + let dfid = next_fid; next_fid += 1; let wnames: Vec = dir @@ -487,13 +534,20 @@ fn tree_walk() { .components() .map(|c| Path::new(&c).to_string_lossy().to_string()) .collect(); - walk(&mut server, &*test_dir, ROOT_FID, fid, wnames); + walk(&mut server, &*test_dir, ROOT_FID, dfid, wnames); let md = dir.symlink_metadata().expect("failed to get metadata"); - check_attr(&mut server, fid, &md); + check_attr(&mut server, dfid, &md); + let fid = next_fid; + next_fid += 1; + open(&mut server, &dir, dfid, "", fid, P9_DIRECTORY).expect("Failed to open directory"); for dirent in readdir(&mut server, fid) { + if dirent.name == "." || dirent.name == ".." { + continue; + } + let entry_path = dir.join(&dirent.name); assert!( entry_path.exists(), @@ -542,14 +596,38 @@ fn create_existing_file() { .expect_err("successfully created existing file"); } -fn set_attr_test(set_fields: F) -> io::Result +enum SetAttrKind { + File, + Directory, +} + +fn set_attr_test(kind: SetAttrKind, set_fields: F) -> io::Result where F: FnOnce(&mut Tsetattr), { let (test_dir, mut server) = setup("set_attr"); let name = "existing"; - create_local_file(&test_dir, name); + match kind { + SetAttrKind::File => { + create_local_file(&test_dir, name); + } + SetAttrKind::Directory => { + let tmkdir = Tmkdir { + dfid: ROOT_FID, + name: String::from(name), + mode: 0o755, + gid: 0, + }; + + let rmkdir = server.mkdir(tmkdir).expect("failed to create directory"); + let md = fs::symlink_metadata(test_dir.join(name)) + .expect("failed to get metadata for directory"); + + assert!(md.is_dir()); + check_qid(&rmkdir.qid, &md); + } + }; let fid = ROOT_FID + 1; walk( @@ -582,7 +660,7 @@ where #[test] fn set_len() { let len = 661; - let md = set_attr_test(|tsetattr| { + let md = set_attr_test(SetAttrKind::File, |tsetattr| { tsetattr.valid = P9_SETATTR_SIZE; tsetattr.size = len; }) @@ -592,9 +670,73 @@ fn set_len() { } #[test] -fn set_mode() { +fn set_file_mode() { + let mode = 0o640; + let err = set_attr_test(SetAttrKind::File, |tsetattr| { + tsetattr.valid = P9_SETATTR_MODE; + tsetattr.mode = mode; + }) + .expect_err("successfully set mode"); + + assert_eq!(err.kind(), io::ErrorKind::PermissionDenied); +} + +#[test] +fn set_file_uid() { + let uid = 294; + let err = set_attr_test(SetAttrKind::File, |tsetattr| { + tsetattr.valid = P9_SETATTR_UID; + tsetattr.uid = uid; + }) + .expect_err("successfully set uid"); + + assert_eq!(err.kind(), io::ErrorKind::PermissionDenied); +} + +#[test] +fn set_file_gid() { + let gid = 9024; + let err = set_attr_test(SetAttrKind::File, |tsetattr| { + tsetattr.valid = P9_SETATTR_GID; + tsetattr.gid = gid; + }) + .expect_err("successfully set gid"); + + assert_eq!(err.kind(), io::ErrorKind::PermissionDenied); +} + +#[test] +fn set_file_mtime() { + let (secs, nanos) = (1245247825, 524617); + let md = set_attr_test(SetAttrKind::File, |tsetattr| { + tsetattr.valid = P9_SETATTR_MTIME | P9_SETATTR_MTIME_SET; + tsetattr.mtime_sec = secs; + tsetattr.mtime_nsec = nanos; + }) + .expect("failed to set mtime"); + + assert_eq!(md.mtime() as u64, secs); + assert_eq!(md.mtime_nsec() as u64, nanos); +} + +#[test] +fn set_file_atime() { + let (secs, nanos) = (9247605, 4016); + let md = set_attr_test(SetAttrKind::File, |tsetattr| { + tsetattr.valid = P9_SETATTR_ATIME | P9_SETATTR_ATIME_SET; + tsetattr.atime_sec = secs; + tsetattr.atime_nsec = nanos; + }) + .expect("failed to set atime"); + + assert_eq!(md.atime() as u64, secs); + assert_eq!(md.atime_nsec() as u64, nanos); +} + +#[test] +fn set_dir_mode() { let mode = 0o640; - let err = set_attr_test(|tsetattr| { + let err = set_attr_test(SetAttrKind::Directory, |tsetattr| { tsetattr.valid = P9_SETATTR_MODE; tsetattr.mode = mode; }) @@ -604,9 +746,9 @@ fn set_mode() { } #[test] -fn set_uid() { +fn set_dir_uid() { let uid = 294; - let err = set_attr_test(|tsetattr| { + let err = set_attr_test(SetAttrKind::Directory, |tsetattr| { tsetattr.valid = P9_SETATTR_UID; tsetattr.uid = uid; }) @@ -616,9 +758,9 @@ fn set_uid() { } #[test] -fn set_gid() { +fn set_dir_gid() { let gid = 9024; - let err = set_attr_test(|tsetattr| { + let err = set_attr_test(SetAttrKind::Directory, |tsetattr| { tsetattr.valid = P9_SETATTR_GID; tsetattr.gid = gid; }) @@ -628,9 +770,9 @@ fn set_gid() { } #[test] -fn set_mtime() { +fn set_dir_mtime() { let (secs, nanos) = (1245247825, 524617); - let md = set_attr_test(|tsetattr| { + let md = set_attr_test(SetAttrKind::Directory, |tsetattr| { tsetattr.valid = P9_SETATTR_MTIME | P9_SETATTR_MTIME_SET; tsetattr.mtime_sec = secs; tsetattr.mtime_nsec = nanos; @@ -642,9 +784,9 @@ fn set_mtime() { } #[test] -fn set_atime() { +fn set_dir_atime() { let (secs, nanos) = (9247605, 4016); - let md = set_attr_test(|tsetattr| { + let md = set_attr_test(SetAttrKind::Directory, |tsetattr| { tsetattr.valid = P9_SETATTR_ATIME | P9_SETATTR_ATIME_SET; tsetattr.atime_sec = secs; tsetattr.atime_nsec = nanos; @@ -663,12 +805,12 @@ fn huge_directory() { let newdir = test_dir.join(name); fs::create_dir(&newdir).expect("failed to create directory"); - let fid = ROOT_FID + 1; + let dfid = ROOT_FID + 1; walk( &mut server, &*test_dir, ROOT_FID, - fid, + dfid, vec![String::from(name)], ); @@ -680,14 +822,20 @@ fn huge_directory() { assert!(filenames.insert(name)); } + let fid = dfid + 1; + open(&mut server, &newdir, dfid, "", fid, P9_DIRECTORY).expect("Failed to open directory"); for f in readdir(&mut server, fid) { let path = newdir.join(&f.name); let md = fs::symlink_metadata(path).expect("failed to get metadata for path"); check_qid(&f.qid, &md); - assert_eq!(f.ty, libc::DT_REG); - assert!(filenames.remove(&f.name)); + if f.name == "." || f.name == ".." { + assert_eq!(f.ty, libc::DT_DIR); + } else { + assert_eq!(f.ty, libc::DT_REG); + assert!(filenames.remove(&f.name)); + } } assert!(filenames.is_empty()); @@ -705,7 +853,7 @@ fn mkdir() { gid: 0, }; - let rmkdir = server.mkdir(&tmkdir).expect("failed to create directory"); + let rmkdir = server.mkdir(tmkdir).expect("failed to create directory"); let md = fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for directory"); @@ -713,51 +861,6 @@ fn mkdir() { check_qid(&rmkdir.qid, &md); } -#[test] -fn remove_all() { - let (test_dir, mut server) = setup("readdir"); - - let mut next_fid = ROOT_FID + 1; - - let mut dirs = VecDeque::new(); - dirs.push_back(test_dir.to_path_buf()); - - // First iterate over the whole directory. - let mut fids = VecDeque::new(); - while let Some(dir) = dirs.pop_front() { - for entry in fs::read_dir(dir).expect("failed to read directory") { - let entry = entry.expect("unable to iterate over directory"); - let fid = next_fid; - next_fid += 1; - - let wnames: Vec = entry - .path() - .strip_prefix(&test_dir) - .expect("test directory is not prefix of subdir") - .components() - .map(|c| Path::new(&c).to_string_lossy().to_string()) - .collect(); - walk(&mut server, &*test_dir, ROOT_FID, fid, wnames); - - let ft = entry - .file_type() - .expect("failed to get file type for entry"); - if ft.is_dir() { - dirs.push_back(entry.path()); - } - - fids.push_back(fid); - } - } - - // Now remove everything in reverse order. - while let Some(fid) = fids.pop_back() { - let tremove = Tremove { fid }; - - server.remove(&tremove).expect("failed to remove entry"); - } -} - #[test] fn unlink_all() { let (test_dir, mut server) = setup("readdir"); @@ -816,48 +919,11 @@ fn unlink_all() { flags, }; - server.unlink_at(&tunlinkat).expect("failed to unlink path"); + server.unlink_at(tunlinkat).expect("failed to unlink path"); } } } -#[test] -fn rename() { - let (test_dir, mut server) = setup("rename"); - - let name = "oldfile"; - let content = create_local_file(&test_dir, name); - - // First walk to the file to be renamed. - let fid = ROOT_FID + 1; - walk( - &mut server, - &*test_dir, - ROOT_FID, - fid, - vec![String::from(name)], - ); - - let newname = "newfile"; - let trename = Trename { - fid, - dfid: ROOT_FID, - name: String::from(newname), - }; - - server.rename(&trename).expect("failed to rename file"); - - assert!(!test_dir.join(name).exists()); - - let mut newcontent = Vec::with_capacity(content.len()); - let size = File::open(test_dir.join(newname)) - .expect("failed to open file") - .read_to_end(&mut newcontent) - .expect("failed to read new file content"); - assert_eq!(size, content.len()); - assert_eq!(newcontent, content); -} - #[test] fn rename_at() { let (test_dir, mut server) = setup("rename"); @@ -873,7 +939,7 @@ fn rename_at() { newname: String::from(newname), }; - server.rename_at(&trename).expect("failed to rename file"); + server.rename_at(trename).expect("failed to rename file"); assert!(!test_dir.join(name).exists()); @@ -902,7 +968,7 @@ macro_rules! open_test { let md = fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for file"); check_qid(&rlopen.qid, &md); - assert_eq!(rlopen.iounit, 0); + assert_eq!(rlopen.iounit, md.blksize() as u32); check_attr(&mut server, fid, &md); @@ -940,25 +1006,25 @@ macro_rules! open_test { }; } -open_test!(read_only_file_open, _P9_RDONLY); +open_test!(read_only_file_open, P9_RDONLY); open_test!(read_write_file_open, P9_RDWR); open_test!(write_only_file_open, P9_WRONLY); -open_test!(create_read_only_file_open, P9_CREATE | _P9_RDONLY); +open_test!(create_read_only_file_open, P9_CREATE | P9_RDONLY); open_test!(create_read_write_file_open, P9_CREATE | P9_RDWR); open_test!(create_write_only_file_open, P9_CREATE | P9_WRONLY); -open_test!(append_read_only_file_open, P9_APPEND | _P9_RDONLY); +open_test!(append_read_only_file_open, P9_APPEND | P9_RDONLY); open_test!(append_read_write_file_open, P9_APPEND | P9_RDWR); open_test!(append_write_only_file_open, P9_APPEND | P9_WRONLY); -open_test!(trunc_read_only_file_open, P9_TRUNC | _P9_RDONLY); +open_test!(trunc_read_only_file_open, P9_TRUNC | P9_RDONLY); open_test!(trunc_read_write_file_open, P9_TRUNC | P9_RDWR); open_test!(trunc_write_only_file_open, P9_TRUNC | P9_WRONLY); open_test!( create_append_read_only_file_open, - P9_CREATE | P9_APPEND | _P9_RDONLY + P9_CREATE | P9_APPEND | P9_RDONLY ); open_test!( create_append_read_write_file_open, @@ -971,7 +1037,7 @@ open_test!( open_test!( create_trunc_read_only_file_open, - P9_CREATE | P9_TRUNC | _P9_RDONLY + P9_CREATE | P9_TRUNC | P9_RDONLY ); open_test!( create_trunc_read_write_file_open, @@ -984,7 +1050,7 @@ open_test!( open_test!( append_trunc_read_only_file_open, - P9_APPEND | P9_TRUNC | _P9_RDONLY + P9_APPEND | P9_TRUNC | P9_RDONLY ); open_test!( append_trunc_read_write_file_open, @@ -997,7 +1063,7 @@ open_test!( open_test!( create_append_trunc_read_only_file_open, - P9_CREATE | P9_APPEND | P9_TRUNC | _P9_RDONLY + P9_CREATE | P9_APPEND | P9_TRUNC | P9_RDONLY ); open_test!( create_append_trunc_read_write_file_open, @@ -1010,7 +1076,7 @@ open_test!( open_test!( create_excl_read_only_file_open, - P9_CREATE | P9_EXCL | _P9_RDONLY, + P9_CREATE | P9_EXCL | P9_RDONLY, io::ErrorKind::AlreadyExists ); open_test!( @@ -1037,7 +1103,7 @@ macro_rules! create_test { let md = fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for file"); - assert_eq!(rlcreate.iounit, 0); + assert_eq!(rlcreate.iounit, md.blksize() as u32); check_qid(&rlcreate.qid, &md); check_attr(&mut server, fid, &md); @@ -1068,13 +1134,13 @@ macro_rules! create_test { }; } -create_test!(read_only_file_create, _P9_RDONLY, 0o600u32); +create_test!(read_only_file_create, P9_RDONLY, 0o600u32); create_test!(read_write_file_create, P9_RDWR, 0o600u32); create_test!(write_only_file_create, P9_WRONLY, 0o600u32); create_test!( append_read_only_file_create, - P9_APPEND | _P9_RDONLY, + P9_APPEND | P9_RDONLY, 0o600u32 ); create_test!(append_read_write_file_create, P9_APPEND | P9_RDWR, 0o600u32); diff --git a/wire_format_derive/Android.bp b/wire_format_derive/Android.bp index 1604435..16c163b 100644 --- a/wire_format_derive/Android.bp +++ b/wire_format_derive/Android.bp @@ -1,4 +1,4 @@ -// This file is generated by cargo2android.py --run --device --tests --dependencies. +// This file is generated by cargo2android.py --run --device --tests --dependencies --patch=patches/Android.bp.patch. rust_proc_macro { name: "libwire_format_derive", -- cgit v1.2.3