summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Hsieh <victorhsieh@google.com>2021-01-08 18:17:05 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-01-08 18:17:05 +0000
commit69e8868888e782f62f05e2a22d815b31c7216eb4 (patch)
tree8c9847dc87c59c236d7b109592e025bcf8d11366
parent7c9ecf6f6db94efcbfffca3f90b7d084469f614e (diff)
parent8be853418da3175097cca737d7125288759612f2 (diff)
downloadp9-69e8868888e782f62f05e2a22d815b31c7216eb4.tar.gz
Uprev rev vm_tools/p9 to 2c3e8252c684673e83278a0124a998e997dbbcc2 am: 8be853418d
Original change: https://android-review.googlesource.com/c/platform/external/vm_tools/p9/+/1518677 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: Iad09587fa4cbe011158f89434c8d82d8a018ca5c
-rw-r--r--.gitignore1
-rw-r--r--Android.bp40
-rw-r--r--Cargo.toml1
-rw-r--r--METADATA4
-rw-r--r--fuzz/OWNERS3
-rw-r--r--patches/Android.bp.patch27
-rw-r--r--src/protocol/wire_format.rs9
-rw-r--r--src/server/mod.rs832
-rw-r--r--src/server/tests.rs320
-rw-r--r--wire_format_derive/Android.bp2
10 files changed, 700 insertions, 539 deletions
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<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.
-#[derive(Debug)]
struct Fid {
- path: Box<Path>,
- metadata: fs::Metadata,
- file: Option<fs::File>,
- dirents: Option<Vec<Dirent>>,
+ path: File,
+ file: Option<File>,
+ 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<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: 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<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
@@ -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<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),
+// 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<T> = BTreeMap<T, T>;
@@ -213,12 +245,115 @@ 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(&current, &name).or_else(|e| {
+ if ascii_casefold {
+ if let Some(libc::ENOENT) = e.raw_os_error() {
+ return ascii_casefold_lookup(proc, &current, name.to_bytes());
+ }
+ }
+
+ Err(e)
+ })?;
+ mds.push(stat(&current)?);
+ }
+
+ 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 {
- root: Box<Path>,
- msize: u32,
fids: BTreeMap<u32, Fid>,
- 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> {
+ 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<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>(
@@ -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<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> {
+ 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
+ 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<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());
- }
+ 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<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;
- }
- }
+ 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<Rlcreate> {
- 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<Rlcreate> {
+ 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<Rreadlink> {
@@ -623,31 +720,27 @@ impl Server {
}
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))?;
+ 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<Rreaddir> {
- 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<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 {
@@ -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<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)?;
+ 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: 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<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)
+}
+
// Automatically deletes the path it contains when it goes out of scope.
struct ScopedPath<P: AsRef<Path>>(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<P: Into<PathBuf>>(
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<P: Into<PathBuf>>(
fid: u32,
flags: u32,
) -> io::Result<Rlopen> {
- 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<P: Into<PathBuf>>(
gid: 0,
};
- server.lcreate(&tlcreate)
+ server.lcreate(tlcreate)
}
struct Readdir<'a> {
@@ -389,7 +435,8 @@ fn setup<P: AsRef<Path>>(name: P) -> (ScopedPath<OsString>, 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<String> = 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<F>(set_fields: F) -> io::Result<fs::Metadata>
+enum SetAttrKind {
+ File,
+ Directory,
+}
+
+fn set_attr_test<F>(kind: SetAttrKind, set_fields: F) -> io::Result<fs::Metadata>
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");
@@ -714,51 +862,6 @@ fn mkdir() {
}
#[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<String> = 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,49 +919,12 @@ 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",