summaryrefslogtreecommitdiff
path: root/src/server/tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/tests.rs')
-rw-r--r--src/server/tests.rs1351
1 files changed, 1351 insertions, 0 deletions
diff --git a/src/server/tests.rs b/src/server/tests.rs
new file mode 100644
index 0000000..0c0e73d
--- /dev/null
+++ b/src/server/tests.rs
@@ -0,0 +1,1351 @@
+// Copyright 2019 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::borrow::Cow;
+use std::collections::HashSet;
+use std::collections::VecDeque;
+use std::env;
+use std::ffi::CString;
+use std::ffi::OsString;
+use std::fs;
+use std::fs::File;
+use std::io;
+use std::io::Cursor;
+use std::mem;
+use std::ops::Deref;
+use std::os::unix::ffi::OsStringExt;
+use std::os::unix::fs::symlink;
+use std::os::unix::fs::MetadataExt;
+use std::path::Component;
+use std::path::Path;
+use std::path::PathBuf;
+use std::u32;
+
+use super::*;
+
+// Used to indicate that there is no fid associated with this message.
+const P9_NOFID: u32 = u32::MAX;
+
+// The fid associated with the root directory of the server.
+const ROOT_FID: u32 = 1;
+
+// The pid of the server process, cannot be 1 since that's the kernel init
+const SERVER_PID: u32 = 5;
+
+// How big we want the default buffer to be when running tests.
+const DEFAULT_BUFFER_SIZE: u32 = 4096;
+
+// How big we want to make randomly generated files
+const LOCAL_FILE_LEN: u64 = 200;
+
+// 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);
+
+impl<P: AsRef<Path>> AsRef<Path> for ScopedPath<P> {
+ fn as_ref(&self) -> &Path {
+ self.0.as_ref()
+ }
+}
+
+impl<P: AsRef<Path>> Deref for ScopedPath<P> {
+ type Target = Path;
+
+ fn deref(&self) -> &Self::Target {
+ self.0.as_ref()
+ }
+}
+
+impl<P: AsRef<Path>> Drop for ScopedPath<P> {
+ fn drop(&mut self) {
+ if let Err(e) = fs::remove_dir_all(&**self) {
+ println!("Failed to remove {}: {}", self.display(), e);
+ }
+ }
+}
+
+enum DirEntry<'a> {
+ File {
+ name: &'a str,
+ content: &'a [u8],
+ },
+ Directory {
+ name: &'a str,
+ entries: &'a [DirEntry<'a>],
+ },
+ Symlink {
+ name: &'a str,
+ target: &'a str,
+ },
+}
+
+impl<'a> DirEntry<'a> {
+ // Creates `self` in the path given by `dir`.
+ // TODO(b/228627457): clippy is warning about the `Cow` below, but it is necessary
+ #[allow(clippy::ptr_arg)]
+ fn create(&self, dir: &mut Cow<Path>) {
+ match *self {
+ DirEntry::File { name, content } => {
+ let mut f = File::create(dir.join(name)).expect("failed to create file");
+ f.write_all(content).expect("failed to write file content");
+ }
+ DirEntry::Directory { name, entries } => {
+ dir.to_mut().push(name);
+
+ fs::create_dir_all(&**dir).expect("failed to create directory");
+ for e in entries {
+ e.create(dir);
+ }
+
+ assert!(dir.to_mut().pop());
+ }
+ DirEntry::Symlink { name, target } => {
+ symlink(target, dir.join(name)).expect("failed to create symlink");
+ }
+ }
+ }
+}
+
+// Creates a file with `name` in `dir` and fills it with random
+// content.
+fn create_local_file<P: AsRef<Path>>(dir: P, name: &str) -> Vec<u8> {
+ let mut content = Vec::new();
+ File::open("/dev/urandom")
+ .and_then(|f| f.take(LOCAL_FILE_LEN).read_to_end(&mut content))
+ .expect("failed to read from /dev/urandom");
+
+ let f = DirEntry::File {
+ name,
+ content: &content,
+ };
+ f.create(&mut Cow::from(dir.as_ref()));
+
+ content
+}
+
+// Create a symlink named `name` that links to `target`.
+fn create_local_symlink<P: AsRef<Path>>(dir: P, name: &str, target: &str) {
+ let f = DirEntry::Symlink { name, target };
+ f.create(&mut Cow::from(dir.as_ref()));
+}
+
+fn check_qid(qid: &Qid, md: &fs::Metadata) {
+ let ty = if md.is_dir() {
+ P9_QTDIR
+ } else if md.is_file() {
+ P9_QTFILE
+ } else if md.file_type().is_symlink() {
+ P9_QTSYMLINK
+ } else {
+ panic!("unknown file type: {:?}", md.file_type());
+ };
+ assert_eq!(qid.ty, ty);
+ assert_eq!(qid.version, md.mtime() as u32);
+ assert_eq!(qid.path, md.ino());
+}
+
+fn check_attr(server: &mut Server, fid: u32, md: &fs::Metadata) {
+ let tgetattr = Tgetattr {
+ fid,
+ request_mask: P9_GETATTR_BASIC,
+ };
+
+ let rgetattr = server.get_attr(&tgetattr).expect("failed to call get_attr");
+
+ let ty = if md.is_dir() {
+ P9_QTDIR
+ } else if md.is_file() {
+ P9_QTFILE
+ } else if md.file_type().is_symlink() {
+ P9_QTSYMLINK
+ } else {
+ panic!("unknown file type: {:?}", md.file_type());
+ };
+ assert_eq!(rgetattr.valid, P9_GETATTR_BASIC);
+ assert_eq!(rgetattr.qid.ty, ty);
+ assert_eq!(rgetattr.qid.version, md.mtime() as u32);
+ assert_eq!(rgetattr.qid.path, md.ino());
+ assert_eq!(rgetattr.mode, md.mode());
+ assert_eq!(rgetattr.uid, md.uid());
+ assert_eq!(rgetattr.gid, md.gid());
+ assert_eq!(rgetattr.nlink, md.nlink());
+ assert_eq!(rgetattr.rdev, md.rdev());
+ assert_eq!(rgetattr.size, md.size());
+ assert_eq!(rgetattr.atime_sec, md.atime() as u64);
+ assert_eq!(rgetattr.atime_nsec, md.atime_nsec() as u64);
+ assert_eq!(rgetattr.mtime_sec, md.mtime() as u64);
+ assert_eq!(rgetattr.mtime_nsec, md.mtime_nsec() as u64);
+ assert_eq!(rgetattr.ctime_sec, md.ctime() as u64);
+ assert_eq!(rgetattr.ctime_nsec, md.ctime_nsec() as u64);
+ assert_eq!(rgetattr.btime_sec, 0);
+ assert_eq!(rgetattr.btime_nsec, 0);
+ assert_eq!(rgetattr.gen, 0);
+ assert_eq!(rgetattr.data_version, 0);
+}
+
+fn check_content(server: &mut Server, content: &[u8], fid: u32) {
+ for offset in 0..content.len() {
+ let tread = Tread {
+ fid,
+ offset: offset as u64,
+ count: DEFAULT_BUFFER_SIZE,
+ };
+
+ let rread = server.read(&tread).expect("failed to read file");
+ assert_eq!(content[offset..], rread.data[..]);
+ }
+}
+
+fn walk<P: Into<PathBuf>>(
+ server: &mut Server,
+ start: P,
+ fid: u32,
+ newfid: u32,
+ names: Vec<String>,
+) {
+ let mut mds = Vec::with_capacity(names.len());
+ let mut buf = start.into();
+ for name in &names {
+ buf.push(name);
+ mds.push(
+ buf.symlink_metadata()
+ .expect("failed to get metadata for path"),
+ );
+ }
+
+ let twalk = Twalk {
+ fid,
+ newfid,
+ wnames: names,
+ };
+
+ 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);
+ }
+}
+
+fn open<P: Into<PathBuf>>(
+ server: &mut Server,
+ dir: P,
+ dir_fid: u32,
+ name: &str,
+ fid: u32,
+ flags: u32,
+) -> io::Result<Rlopen> {
+ let wnames = if name.is_empty() {
+ vec![]
+ } else {
+ vec![String::from(name)]
+ };
+ walk(server, dir, dir_fid, fid, wnames);
+
+ let tlopen = Tlopen { fid, flags };
+
+ server.lopen(&tlopen)
+}
+
+fn write<P: AsRef<Path>>(server: &mut Server, dir: P, name: &str, fid: u32, flags: u32) {
+ let file_path = dir.as_ref().join(name);
+ let file_len = if file_path.exists() {
+ fs::symlink_metadata(&file_path)
+ .expect("unable to get metadata for file")
+ .len() as usize
+ } else {
+ 0usize
+ };
+ let mut new_content = Vec::new();
+ File::open("/dev/urandom")
+ .and_then(|f| f.take(200).read_to_end(&mut new_content))
+ .expect("failed to read from /dev/urandom");
+
+ let twrite = Twrite {
+ fid,
+ offset: 0,
+ data: Data(new_content),
+ };
+
+ let rwrite = server.write(&twrite).expect("failed to write file");
+ assert_eq!(rwrite.count, twrite.data.len() as u32);
+
+ let tfsync = Tfsync { fid, datasync: 0 };
+ server.fsync(&tfsync).expect("failed to sync file contents");
+
+ let actual_content = fs::read(file_path).expect("failed to read back content from file");
+
+ // If the file was opened append-only, then the content should have been
+ // written to the end even though the offset was 0.
+ let idx = if flags & P9_APPEND == 0 { 0 } else { file_len };
+ assert_eq!(actual_content[idx..], twrite.data[..]);
+}
+
+fn create<P: Into<PathBuf>>(
+ server: &mut Server,
+ dir: P,
+ dir_fid: u32,
+ fid: u32,
+ name: &str,
+ flags: u32,
+ mode: u32,
+) -> io::Result<Rlcreate> {
+ // The `fid` in the lcreate call initially points to the directory
+ // but is supposed to point to the newly created file after the call
+ // completes. Duplicate the fid so that we don't end up consuming the
+ // directory fid.
+ walk(server, dir, dir_fid, fid, Vec::new());
+
+ let tlcreate = Tlcreate {
+ fid,
+ name: String::from(name),
+ flags,
+ mode,
+ gid: 0,
+ };
+
+ server.lcreate(tlcreate)
+}
+
+struct Readdir<'a> {
+ server: &'a mut Server,
+ fid: u32,
+ offset: u64,
+ cursor: Cursor<Vec<u8>>,
+}
+
+impl<'a> Iterator for Readdir<'a> {
+ type Item = Dirent;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.cursor.position() >= self.cursor.get_ref().len() as u64 {
+ let treaddir = Treaddir {
+ fid: self.fid,
+ offset: self.offset,
+ count: DEFAULT_BUFFER_SIZE,
+ };
+
+ let Rreaddir { data } = self
+ .server
+ .readdir(&treaddir)
+ .expect("failed to read directory");
+ if data.is_empty() {
+ // No more entries.
+ return None;
+ }
+
+ mem::drop(mem::replace(&mut self.cursor, Cursor::new(data.0)));
+ }
+
+ let dirent: Dirent = WireFormat::decode(&mut self.cursor).expect("failed to decode dirent");
+ self.offset = dirent.offset;
+
+ Some(dirent)
+ }
+}
+
+fn readdir(server: &mut Server, fid: u32) -> Readdir {
+ Readdir {
+ server,
+ fid,
+ offset: 0,
+ cursor: Cursor::new(Vec::new()),
+ }
+}
+
+// Sets up the server to start handling messages. Creates a new temporary
+// directory to act as the server root and sends an initial Tattach message.
+// At the end of setup, fid 1 points to the root of the server.
+fn setup<P: AsRef<Path>>(name: P) -> (ScopedPath<OsString>, Server) {
+ let mut test_dir = env::var_os("T")
+ .map(PathBuf::from)
+ .unwrap_or_else(env::temp_dir);
+ test_dir.push(name);
+
+ let mut os_str = OsString::from(test_dir);
+ os_str.push(".XXXXXX");
+
+ // Create a c string and release ownership. This seems like the only way
+ // to get a *mut c_char.
+ let buf = CString::new(os_str.into_vec())
+ .expect("failed to create CString")
+ .into_raw();
+
+ // Safe because this will only modify the contents of `buf`.
+ let ret = unsafe { libc::mkdtemp(buf) };
+
+ // Take ownership of the buffer back before checking the result. Safe because
+ // this was created by a call to into_raw() above and mkdtemp will not overwrite
+ // the trailing '\0'.
+ let buf = unsafe { CString::from_raw(buf) };
+
+ assert!(!ret.is_null());
+
+ let test_dir = ScopedPath(OsString::from_vec(buf.into_bytes()));
+
+ // Create a basic file system hierarchy.
+ let entries = [
+ DirEntry::Directory {
+ name: "subdir",
+ entries: &[
+ DirEntry::File {
+ name: "b",
+ content: b"hello, world!",
+ },
+ DirEntry::Directory {
+ name: "nested",
+ entries: &[DirEntry::File {
+ name: "Огонь по готовности!",
+ content: &[
+ 0xe9u8, 0xbeu8, 0x8du8, 0xe3u8, 0x81u8, 0x8cu8, 0xe6u8, 0x88u8, 0x91u8,
+ 0xe3u8, 0x81u8, 0x8cu8, 0xe6u8, 0x95u8, 0xb5u8, 0xe3u8, 0x82u8, 0x92u8,
+ 0xe5u8, 0x96u8, 0xb0u8, 0xe3u8, 0x82u8, 0x89u8, 0xe3u8, 0x81u8, 0x86u8,
+ 0x21u8,
+ ],
+ }],
+ },
+ ],
+ },
+ DirEntry::File {
+ name: "世界.txt",
+ content: &[
+ 0xe3u8, 0x81u8, 0x93u8, 0xe3u8, 0x82u8, 0x93u8, 0xe3u8, 0x81u8, 0xabu8, 0xe3u8,
+ 0x81u8, 0xa1u8, 0xe3u8, 0x81u8, 0xafu8,
+ ],
+ },
+ ];
+
+ for e in &entries {
+ e.create(&mut Cow::from(&*test_dir));
+ }
+
+ let md = test_dir
+ .symlink_metadata()
+ .expect("failed to get metadata for root dir");
+
+ let mut server = Server::new(&*test_dir, Default::default(), Default::default())
+ .expect("Failed to create server");
+
+ let tversion = Tversion {
+ msize: DEFAULT_BUFFER_SIZE,
+ version: String::from("9P2000.L"),
+ };
+
+ let rversion = server
+ .version(&tversion)
+ .expect("failed to get version from server");
+ assert_eq!(rversion.msize, DEFAULT_BUFFER_SIZE);
+ assert_eq!(rversion.version, "9P2000.L");
+
+ let tattach = Tattach {
+ fid: ROOT_FID,
+ afid: P9_NOFID,
+ uname: String::from("unittest"),
+ aname: String::from(""),
+ n_uname: 1000,
+ };
+
+ let rattach = server.attach(&tattach).expect("failed to attach to server");
+ check_qid(&rattach.qid, &md);
+
+ (test_dir, server)
+}
+
+#[test]
+fn path_joins() {
+ let root = PathBuf::from("/a/b/c");
+ let path = PathBuf::from("/a/b/c/d/e/f");
+
+ assert_eq!(
+ &join_path(path.clone(), "nested", &root).expect("normal"),
+ Path::new("/a/b/c/d/e/f/nested")
+ );
+
+ let p1 = join_path(path, "..", &root).expect("parent 1");
+ assert_eq!(&p1, Path::new("/a/b/c/d/e/"));
+
+ let p2 = join_path(p1, "..", &root).expect("parent 2");
+ assert_eq!(&p2, Path::new("/a/b/c/d/"));
+
+ let p3 = join_path(p2, "..", &root).expect("parent 3");
+ assert_eq!(&p3, Path::new("/a/b/c/"));
+
+ let p4 = join_path(p3, "..", &root).expect("parent of root");
+ assert_eq!(&p4, Path::new("/a/b/c/"));
+}
+
+#[test]
+fn invalid_joins() {
+ let root = PathBuf::from("/a");
+ let path = PathBuf::from("/a/b");
+
+ join_path(path.clone(), ".", &root).expect_err("current directory");
+ join_path(path.clone(), "c/d/e", &root).expect_err("too many components");
+ join_path(path, "/c/d/e", &root).expect_err("absolute path");
+}
+
+#[test]
+fn clunk() {
+ let (_test_dir, mut server) = setup("clunk");
+
+ let tclunk = Tclunk { fid: ROOT_FID };
+ server.clunk(&tclunk).expect("failed to clunk root fid");
+}
+
+#[test]
+fn get_attr() {
+ let (test_dir, mut server) = setup("get_attr");
+
+ let md = test_dir
+ .symlink_metadata()
+ .expect("failed to get metadata for test dir");
+
+ check_attr(&mut server, ROOT_FID, &md);
+}
+
+#[test]
+fn tree_walk() {
+ 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());
+
+ while let Some(dir) = dirs.pop_front() {
+ let dfid = next_fid;
+ next_fid += 1;
+
+ let wnames: Vec<String> = dir
+ .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, dfid, wnames);
+
+ let md = dir.symlink_metadata().expect("failed to get metadata");
+
+ 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(),
+ "directory entry \"{}\" does not exist",
+ entry_path.display()
+ );
+ let md = fs::symlink_metadata(&entry_path).expect("failed to get metadata for entry");
+
+ let ty = if md.is_dir() {
+ dirs.push_back(dir.join(dirent.name));
+ libc::DT_DIR
+ } else if md.is_file() {
+ libc::DT_REG
+ } else if md.file_type().is_symlink() {
+ libc::DT_LNK
+ } else {
+ panic!("unknown file type: {:?}", md.file_type());
+ };
+
+ assert_eq!(dirent.ty, ty);
+ check_qid(&dirent.qid, &md);
+ }
+
+ let tclunk = Tclunk { fid };
+ server.clunk(&tclunk).expect("failed to clunk fid");
+ }
+}
+
+#[test]
+fn create_existing_file() {
+ let (test_dir, mut server) = setup("create_existing");
+
+ let name = "existing";
+ create_local_file(&test_dir, name);
+
+ let fid = ROOT_FID + 1;
+ create(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ fid,
+ name,
+ P9_APPEND,
+ 0o644,
+ )
+ .expect_err("successfully created existing file");
+}
+
+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";
+ 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(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ fid,
+ vec![String::from(name)],
+ );
+
+ let mut tsetattr = Tsetattr {
+ fid,
+ valid: 0,
+ mode: 0,
+ uid: 0,
+ gid: 0,
+ size: 0,
+ atime_sec: 0,
+ atime_nsec: 0,
+ mtime_sec: 0,
+ mtime_nsec: 0,
+ };
+
+ set_fields(&mut tsetattr);
+ server.set_attr(&tsetattr)?;
+
+ fs::symlink_metadata(test_dir.join(name))
+}
+
+#[test]
+fn set_len() {
+ let len = 661;
+ let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+ tsetattr.valid = P9_SETATTR_SIZE;
+ tsetattr.size = len;
+ })
+ .expect("failed to run set length of file");
+
+ assert_eq!(md.size(), len);
+}
+
+#[test]
+fn set_file_mode() {
+ let mode = 0o640;
+ let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+ tsetattr.valid = P9_SETATTR_MODE;
+ tsetattr.mode = mode;
+ })
+ .expect("failed to set mode");
+
+ assert_eq!(md.mode() & 0o777, mode);
+}
+
+#[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 md = set_attr_test(SetAttrKind::Directory, |tsetattr| {
+ tsetattr.valid = P9_SETATTR_MODE;
+ tsetattr.mode = mode;
+ })
+ .expect("failed to set mode");
+
+ assert_eq!(md.mode() & 0o777, mode);
+}
+
+#[test]
+fn set_dir_mtime() {
+ let (secs, nanos) = (1245247825, 524617);
+ 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;
+ })
+ .expect("failed to set mtime");
+
+ assert_eq!(md.mtime() as u64, secs);
+ assert_eq!(md.mtime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_dir_atime() {
+ let (secs, nanos) = (9247605, 4016);
+ 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;
+ })
+ .expect("failed to set atime");
+
+ assert_eq!(md.atime() as u64, secs);
+ assert_eq!(md.atime_nsec() as u64, nanos);
+}
+
+#[test]
+fn huge_directory() {
+ let (test_dir, mut server) = setup("huge_directory");
+
+ let name = "newdir";
+ let newdir = test_dir.join(name);
+ fs::create_dir(&newdir).expect("failed to create directory");
+
+ let dfid = ROOT_FID + 1;
+ walk(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ dfid,
+ vec![String::from(name)],
+ );
+
+ // Create ~4K files in the directory and then attempt to read them all.
+ let mut filenames = HashSet::with_capacity(4096);
+ for i in 0..4096 {
+ let name = format!("file_{}", i);
+ create_local_file(&newdir, &name);
+ 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);
+
+ 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());
+}
+
+#[test]
+fn mkdir() {
+ let (test_dir, mut server) = setup("mkdir");
+
+ let name = "conan";
+ 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);
+}
+
+#[test]
+fn unlink_all() {
+ let (test_dir, mut server) = setup("readdir");
+
+ let mut next_fid = ROOT_FID + 1;
+
+ let mut dirs = VecDeque::new();
+ dirs.push_back((ROOT_FID, test_dir.to_path_buf()));
+
+ // First iterate over the whole directory.
+ let mut unlinks = VecDeque::new();
+ while let Some((dfid, dir)) = dirs.pop_front() {
+ let mut names = VecDeque::new();
+ for entry in fs::read_dir(dir).expect("failed to read directory") {
+ let entry = entry.expect("unable to iterate over directory");
+ let ft = entry
+ .file_type()
+ .expect("failed to get file type for entry");
+ if ft.is_dir() {
+ 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);
+ dirs.push_back((fid, entry.path()));
+ }
+
+ names.push_back((
+ entry
+ .file_name()
+ .into_string()
+ .expect("failed to convert entry name to string"),
+ if ft.is_dir() {
+ libc::AT_REMOVEDIR as u32
+ } else {
+ 0
+ },
+ ));
+ }
+
+ unlinks.push_back((dfid, names));
+ }
+
+ // Now remove everything in reverse order.
+ while let Some((dfid, names)) = unlinks.pop_back() {
+ for (name, flags) in names {
+ let tunlinkat = Tunlinkat {
+ dirfd: dfid,
+ name,
+ flags,
+ };
+
+ server.unlink_at(tunlinkat).expect("failed to unlink path");
+ }
+ }
+}
+
+#[test]
+fn rename_at() {
+ let (test_dir, mut server) = setup("rename");
+
+ let name = "oldfile";
+ let content = create_local_file(&test_dir, name);
+
+ let newname = "newfile";
+ let trename = Trenameat {
+ olddirfid: ROOT_FID,
+ oldname: String::from(name),
+ newdirfid: ROOT_FID,
+ newname: String::from(newname),
+ };
+
+ server.rename_at(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);
+}
+
+fn setlk_tlock(fid: u32, len: u64, start: u64, type_: i32) -> Tlock {
+ Tlock {
+ fid,
+ type_: type_ as u8,
+ flags: 0,
+ start,
+ length: len,
+ proc_id: SERVER_PID,
+ client_id: String::from("test-server"),
+ }
+}
+
+fn getlk_tgetlock(fid: u32, type_: i32) -> Tgetlock {
+ Tgetlock {
+ fid,
+ type_: type_ as u8,
+ start: 0,
+ length: 0,
+ proc_id: SERVER_PID,
+ client_id: String::from("test-server"),
+ }
+}
+
+fn setup_simple_lock_no_open() -> Server {
+ let (test_dir, server) = setup("simple lock");
+
+ let filename = "file";
+ create_local_file(&test_dir, filename);
+
+ server
+}
+
+fn setup_simple_lock(flags: u32) -> Server {
+ let (test_dir, mut server) = setup("simple lock");
+
+ let filename = "file";
+ create_local_file(&test_dir, filename);
+
+ open(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ filename,
+ ROOT_FID + 1,
+ flags,
+ )
+ .expect("failed to open file");
+
+ server
+}
+
+#[test]
+fn lock_rdlck_no_open_file() {
+ let mut server = setup_simple_lock_no_open();
+
+ let tlock = setlk_tlock(ROOT_FID + 1, 8, 0, libc::F_RDLCK);
+
+ server.lock(&tlock).expect_err("Bad file descriptor");
+}
+
+#[test]
+fn lock_rdlck() {
+ let mut server = setup_simple_lock(P9_RDWR);
+
+ let tlock = setlk_tlock(ROOT_FID + 1, 8, 0, libc::F_RDLCK);
+
+ server.lock(&tlock).expect("failed to lock file");
+}
+#[test]
+fn lock_wrlck_no_open_file() {
+ let mut server = setup_simple_lock_no_open();
+
+ let tlock = setlk_tlock(ROOT_FID + 1, 8, 0, libc::F_WRLCK);
+
+ server.lock(&tlock).expect_err("Bad file descriptor");
+}
+#[test]
+fn lock_wrlck() {
+ let mut server = setup_simple_lock(P9_RDWR);
+
+ let tlock = setlk_tlock(ROOT_FID + 1, 8, 0, libc::F_WRLCK);
+
+ server.lock(&tlock).expect("failed to lock file");
+}
+
+#[test]
+fn lock_unlck_no_lock() {
+ let mut server = setup_simple_lock(P9_RDWR);
+
+ let tlock = setlk_tlock(ROOT_FID + 1, 0, 0, libc::F_UNLCK);
+
+ server.lock(&tlock).expect("failed to lock file");
+}
+
+#[test]
+fn lock_unlck() {
+ let mut server = setup_simple_lock(P9_RDWR);
+
+ let tlock = setlk_tlock(ROOT_FID + 1, LOCAL_FILE_LEN / 2, 0, libc::F_RDLCK);
+
+ server.lock(&tlock).expect("failed to lock file");
+
+ let tlock = setlk_tlock(ROOT_FID + 1, 0, 0, libc::F_UNLCK);
+
+ server.lock(&tlock).expect("failed to lock file");
+}
+
+#[test]
+fn lock_unlck_relock() {
+ let mut server = setup_simple_lock(P9_RDWR);
+
+ let tlock = setlk_tlock(ROOT_FID + 1, LOCAL_FILE_LEN / 2, 0, libc::F_RDLCK);
+
+ server.lock(&tlock).expect("failed to lock file");
+
+ let tlock = setlk_tlock(ROOT_FID + 1, 0, 0, libc::F_UNLCK);
+
+ server.lock(&tlock).expect("failed to lock file");
+
+ let tlock = setlk_tlock(ROOT_FID + 1, LOCAL_FILE_LEN / 2, 0, libc::F_RDLCK);
+
+ server.lock(&tlock).expect("failed to lock file");
+}
+
+#[test]
+fn getlock_rdlck_nolock() {
+ let mut server = setup_simple_lock(P9_RDWR);
+
+ let tgetlock = getlk_tgetlock(ROOT_FID + 1, libc::F_RDLCK);
+
+ server
+ .get_lock(&tgetlock)
+ .expect("failed to get lock on file");
+}
+
+#[test]
+fn getlock_wrlck() {
+ let mut server = setup_simple_lock(P9_RDWR);
+
+ let tlock = setlk_tlock(ROOT_FID + 1, LOCAL_FILE_LEN / 2, 0, libc::F_WRLCK);
+
+ server.lock(&tlock).expect("failed to lock file");
+
+ let tgetlock = getlk_tgetlock(ROOT_FID + 1, libc::F_WRLCK);
+
+ server
+ .get_lock(&tgetlock)
+ .expect("failed to get lock on file");
+}
+
+#[test]
+fn getlock_rdlck() {
+ let mut server = setup_simple_lock(P9_RDWR);
+
+ let tlock = setlk_tlock(ROOT_FID + 1, LOCAL_FILE_LEN / 2, 0, libc::F_RDLCK);
+
+ server.lock(&tlock).expect("failed to lock file");
+
+ let tgetlock = getlk_tgetlock(ROOT_FID + 1, libc::F_RDLCK);
+
+ server
+ .get_lock(&tgetlock)
+ .expect("failed to get lock on file");
+}
+
+macro_rules! open_test {
+ ($name:ident, $flags:expr) => {
+ #[test]
+ fn $name() {
+ let (test_dir, mut server) = setup("open");
+
+ let fid = ROOT_FID + 1;
+ let name = "test.txt";
+ let content = create_local_file(&test_dir, name);
+
+ let rlopen = open(&mut server, &*test_dir, ROOT_FID, name, fid, $flags as u32)
+ .expect("failed to open file");
+
+ 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);
+
+ check_attr(&mut server, fid, &md);
+
+ // Check that the file has the proper contents as long as we didn't
+ // truncate it first.
+ if $flags & P9_TRUNC == 0 && $flags & P9_WRONLY == 0 {
+ check_content(&mut server, &content, fid);
+ }
+
+ // Check that we can write to the file.
+ if $flags & P9_RDWR != 0 || $flags & P9_WRONLY != 0 {
+ write(&mut server, &test_dir, name, fid, $flags);
+ }
+
+ let tclunk = Tclunk { fid };
+ server.clunk(&tclunk).expect("Unable to clunk file");
+ }
+ };
+ ($name:ident, $flags:expr, $expected_err:expr) => {
+ #[test]
+ fn $name() {
+ let (test_dir, mut server) = setup("open_fail");
+
+ let fid = ROOT_FID + 1;
+ let name = "test.txt";
+ create_local_file(&test_dir, name);
+
+ let err = open(&mut server, &*test_dir, ROOT_FID, name, fid, $flags as u32)
+ .expect_err("successfully opened file");
+ assert_eq!(err.kind(), $expected_err);
+
+ let tclunk = Tclunk { fid };
+ server.clunk(&tclunk).expect("Unable to clunk file");
+ }
+ };
+}
+
+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_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_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_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
+);
+open_test!(
+ create_append_read_write_file_open,
+ P9_CREATE | P9_APPEND | P9_RDWR
+);
+open_test!(
+ create_append_wronly_file_open,
+ P9_CREATE | P9_APPEND | P9_WRONLY
+);
+
+open_test!(
+ create_trunc_read_only_file_open,
+ P9_CREATE | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+ create_trunc_read_write_file_open,
+ P9_CREATE | P9_TRUNC | P9_RDWR
+);
+open_test!(
+ create_trunc_wronly_file_open,
+ P9_CREATE | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+ append_trunc_read_only_file_open,
+ P9_APPEND | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+ append_trunc_read_write_file_open,
+ P9_APPEND | P9_TRUNC | P9_RDWR
+);
+open_test!(
+ append_trunc_wronly_file_open,
+ P9_APPEND | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+ create_append_trunc_read_only_file_open,
+ P9_CREATE | P9_APPEND | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+ create_append_trunc_read_write_file_open,
+ P9_CREATE | P9_APPEND | P9_TRUNC | P9_RDWR
+);
+open_test!(
+ create_append_trunc_wronly_file_open,
+ P9_CREATE | P9_APPEND | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+ create_excl_read_only_file_open,
+ P9_CREATE | P9_EXCL | P9_RDONLY,
+ io::ErrorKind::AlreadyExists
+);
+open_test!(
+ create_excl_read_write_file_open,
+ P9_CREATE | P9_EXCL | P9_RDWR,
+ io::ErrorKind::AlreadyExists
+);
+open_test!(
+ create_excl_wronly_file_open,
+ P9_CREATE | P9_EXCL | P9_WRONLY,
+ io::ErrorKind::AlreadyExists
+);
+
+macro_rules! create_test {
+ ($name:ident, $flags:expr, $mode:expr) => {
+ #[test]
+ fn $name() {
+ let (test_dir, mut server) = setup("create");
+
+ let name = "foo.txt";
+ let fid = ROOT_FID + 1;
+ let rlcreate = create(&mut server, &*test_dir, ROOT_FID, fid, name, $flags, $mode)
+ .expect("failed to create file");
+
+ let md =
+ fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for file");
+ assert_eq!(rlcreate.iounit, 0);
+ check_qid(&rlcreate.qid, &md);
+ check_attr(&mut server, fid, &md);
+
+ // Check that we can write to the file.
+ if $flags & P9_RDWR != 0 || $flags & P9_WRONLY != 0 {
+ write(&mut server, &test_dir, name, fid, $flags);
+ }
+
+ let tclunk = Tclunk { fid };
+ server.clunk(&tclunk).expect("Unable to clunk file");
+ }
+ };
+ ($name:ident, $flags:expr, $mode:expr, $expected_err:expr) => {
+ #[test]
+ fn $name() {
+ let (test_dir, mut server) = setup("create_fail");
+
+ let name = "foo.txt";
+ // The `fid` in the lcreate call initially points to the directory
+ // but is supposed to point to the newly created file after the call
+ // completes. Duplicate the fid so that we don't end up consuming the
+ // root fid.
+ let fid = ROOT_FID + 1;
+ let err = create(&mut server, &*test_dir, ROOT_FID, fid, name, $flags, $mode)
+ .expect_err("successfully created file");
+ assert_eq!(err.kind(), $expected_err);
+ }
+ };
+}
+
+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,
+ 0o600u32
+);
+create_test!(append_read_write_file_create, P9_APPEND | P9_RDWR, 0o600u32);
+create_test!(append_wronly_file_create, P9_APPEND | P9_WRONLY, 0o600u32);
+
+#[test]
+fn lcreate_set_len() {
+ let (test_dir, mut server) = setup("lcreate_set_len");
+
+ let name = "foo.txt";
+ let fid = ROOT_FID + 1;
+ create(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ fid,
+ name,
+ P9_RDWR,
+ 0o600u32,
+ )
+ .expect("failed to create file");
+
+ let tsetattr = Tsetattr {
+ fid,
+ valid: 0x8, // P9_SETATTR_SIZE
+ size: 100,
+ // The other fields are not used because the relevant flags aren't set in `valid`.
+ mode: 0,
+ uid: 0,
+ gid: 0,
+ atime_sec: 0,
+ atime_nsec: 0,
+ mtime_sec: 0,
+ mtime_nsec: 0,
+ };
+ server
+ .set_attr(&tsetattr)
+ .expect("failed to set file length after lcreate");
+
+ let tclunk = Tclunk { fid };
+ server.clunk(&tclunk).expect("Unable to clunk file");
+}
+
+#[test]
+fn readlink() {
+ let (test_dir, mut server) = setup("readlink");
+ create_local_symlink(&test_dir, "symlink", "target/of/symlink");
+
+ let fid = ROOT_FID + 1;
+ walk(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ fid,
+ vec!["symlink".into()],
+ );
+
+ let treadlink = Treadlink { fid };
+
+ let rreadlink = server.readlink(&treadlink).expect("failed to readlink");
+
+ assert_eq!(rreadlink.target, "target/of/symlink");
+}