summaryrefslogtreecommitdiff
path: root/src/server/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/mod.rs')
-rw-r--r--src/server/mod.rs832
1 files changed, 436 insertions, 396 deletions
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(())
}