use super::{Backend, FileKind, QIDMapper, Result};
use crate::server::{
DirEntry, FileType, IsFlush, LinuxFileType, LinuxOpenMode, LinuxStat, LinuxStatValidity, Lock,
LockCommand, LockKind, LockStatus, Metadata, PlainStat, ProtocolVersion, SimpleOpenMode, Stat,
Tag, UnixStat, FID, QID,
};
use lawn_constants::logger::{AsLogStr, Logger};
use lawn_constants::Error;
use lawn_fs::backend as fsbackend;
use std::convert::TryInto;
use std::fs;
use std::os::unix::fs::{FileTypeExt, MetadataExt};
use std::str::FromStr;
use std::sync::Arc;
use std::time::SystemTime;
const MAGIC_KIND: u16 = 0xfeff;
const MAGIC_DEV: u32 = 0xc0c1c2c3;
#[allow(clippy::unnecessary_cast)]
#[cfg(target_os = "linux")]
fn major_minor(dev: u64) -> (u32, u32) {
(
rustix::fs::major(dev as rustix::fs::Dev) as u32,
rustix::fs::minor(dev as rustix::fs::Dev) as u32,
)
}
#[cfg(target_os = "macos")]
fn major_minor(dev: u64) -> (u32, u32) {
let major = dev >> 24;
let minor = dev & 0x00ffffff;
(major as u32, minor as u32)
}
#[cfg(target_os = "freebsd")]
fn major_minor(dev: u64) -> (u32, u32) {
let major = ((dev >> 32) & 0xffffff00) | ((dev >> 8) & 0xff);
let minor = ((dev >> 24) & 0xff00) | (dev & 0xffff00ff);
(major as u32, minor as u32)
}
#[cfg(target_os = "netbsd")]
fn major_minor(dev: u64) -> (u32, u32) {
let major = (dev & 0x000fff00) >> 8;
let minor = ((dev & 0xfff00000) >> 12) | (dev & 0xff);
(major as u32, minor as u32)
}
trait FromMetadata: Sized {
fn from_metadata(meta: &fs::Metadata) -> Option<Self>;
}
impl FromMetadata for PlainStat {
fn from_metadata(meta: &fs::Metadata) -> Option<Self> {
let len = Self::FIXED_SIZE - 2;
Some(Self {
size: len.try_into().ok()?,
kind: MAGIC_KIND,
dev: MAGIC_DEV,
qid: QID([0u8; 13]),
mode: FileType::from_metadata(meta).bits(),
atime: meta.atime().try_into().unwrap_or(0),
mtime: meta.mtime().try_into().unwrap_or(0),
length: meta.len(),
name: Vec::new(),
uid: Vec::new(),
gid: Vec::new(),
muid: Vec::new(),
})
}
}
impl FromMetadata for UnixStat {
fn from_metadata(meta: &fs::Metadata) -> Option<Self> {
let ft = meta.file_type();
let extension = if ft.is_block_device() {
let (maj, min) = major_minor(meta.rdev());
format!("b {} {}", maj, min).into_bytes()
} else if ft.is_char_device() {
let (maj, min) = major_minor(meta.rdev());
format!("c {} {}", maj, min).into_bytes()
} else {
Vec::new()
};
let len = Self::FIXED_SIZE + extension.len() - 2;
Some(Self {
size: len.try_into().ok()?,
kind: MAGIC_KIND,
dev: MAGIC_DEV,
qid: QID([0u8; 13]),
mode: FileType::from_metadata(meta).bits(),
atime: meta.atime().try_into().unwrap_or(0),
mtime: meta.mtime().try_into().unwrap_or(0),
length: meta.len(),
name: Vec::new(),
uid: Vec::new(),
gid: Vec::new(),
muid: Vec::new(),
extension,
nuid: meta.uid(),
ngid: meta.gid(),
nmuid: u32::MAX,
})
}
}
impl FromMetadata for LinuxStat {
fn from_metadata(meta: &fs::Metadata) -> Option<Self> {
let mode = LinuxFileType::from_unix(meta.mode());
Some(Self {
qid: QID([0u8; 13]),
mode: mode.bits(),
uid: meta.uid(),
gid: meta.gid(),
nlink: Some(meta.nlink()),
rdev: Some(meta.rdev()),
length: meta.len(),
blksize: meta.blksize(),
blocks: Some(meta.blocks()),
atime_sec: meta.atime().try_into().ok(),
atime_nsec: meta.atime_nsec().try_into().ok(),
mtime_sec: meta.mtime().try_into().ok(),
mtime_nsec: meta.mtime_nsec().try_into().ok(),
ctime_sec: meta.ctime().try_into().ok(),
ctime_nsec: meta.ctime_nsec().try_into().ok(),
btime_sec: None,
btime_nsec: None,
gen: None,
data_version: None,
})
}
}
#[allow(clippy::type_complexity)]
pub struct LibcBackend {
max_size: u32,
backend: Arc<dyn fsbackend::Backend + Send + Sync>,
qidmapper: QIDMapper,
logger: Arc<dyn Logger + Send + Sync>,
}
impl LibcBackend {
pub fn new(
logger: Arc<dyn Logger + Send + Sync>,
backend: Arc<dyn fsbackend::Backend + Send + Sync>,
max_size: u32,
) -> LibcBackend {
Self {
max_size,
backend,
qidmapper: QIDMapper::new(),
logger,
}
}
fn temp_backend_fid(&self, fid: FID, offset: u32) -> fsbackend::FID {
fsbackend::FID::new(((offset as u64) << 32) | u32::from_le_bytes(fid.0) as u64)
}
#[allow(clippy::unnecessary_cast)]
#[allow(unused_unsafe)]
fn parse_major_minor(&self, s: &[u8]) -> Result<(fsbackend::FileType, u32, u32)> {
let mut items = s.split(|b| *b == b' ');
let mmode = match items.next() {
Some(b"b") => fsbackend::FileType::S_IFBLK,
Some(b"c") => fsbackend::FileType::S_IFCHR,
_ => return Err(Error::EINVAL),
};
let smaj = items.next();
let smin = items.next();
let (smaj, smin) = match (smaj, smin) {
(Some(smaj), Some(smin)) => (smaj, smin),
_ => return Err(Error::EINVAL),
};
let (smaj, smin) = match (std::str::from_utf8(smaj), std::str::from_utf8(smin)) {
(Ok(maj), Ok(min)) => (maj, min),
_ => return Err(Error::EINVAL),
};
let (maj, min) = match (smaj.parse(), smin.parse()) {
(Ok(maj), Ok(min)) => (maj, min),
_ => return Err(Error::EINVAL),
};
Ok((mmode, maj, min))
}
}
impl Backend for LibcBackend {
fn version(&self, _meta: &Metadata, max_size: u32, version: &[u8]) -> Result<(u32, Vec<u8>)> {
let max_size = std::cmp::min(max_size, self.max_size);
let proto = match std::str::from_utf8(version).map(ProtocolVersion::from_str) {
Ok(Ok(ProtocolVersion::Original)) => ProtocolVersion::Original,
Ok(Ok(ProtocolVersion::Unix)) => ProtocolVersion::Unix,
Ok(Ok(ProtocolVersion::Linux)) => ProtocolVersion::Linux,
_ => return Err(Error::EOPNOTSUPP),
};
Ok((max_size, proto.to_str().as_bytes().into()))
}
fn auth(
&self,
meta: &Metadata,
afid: FID,
uname: &[u8],
aname: &[u8],
nuname: Option<u32>,
) -> Result<QID> {
let meta = meta.into();
self.backend
.auth(&meta, afid.into(), uname, aname, nuname)
.map(|qid| self.qidmapper.qid(qid))
}
fn attach(
&self,
meta: &Metadata,
fid: FID,
afid: FID,
uname: &[u8],
aname: &[u8],
nuname: Option<u32>,
) -> Result<QID> {
let meta = meta.into();
let afid = match afid {
FID([0xff, 0xff, 0xff, 0xff]) => None,
f => Some(f.into()),
};
self.backend
.attach(&meta, fid.into(), afid, uname, aname, nuname)
.map(|qid| self.qidmapper.qid(qid))
}
fn clunk(&self, meta: &Metadata, fid: FID) -> Result<()> {
let meta = meta.into();
self.backend.clunk(&meta, fid.into())
}
fn clunk_all(&self, meta: &Metadata) -> Result<()> {
let meta = meta.into();
self.backend.clunk_all(&meta)
}
fn flush(&self, meta: &Metadata, tag: Tag) -> Result<()> {
let meta = meta.into();
self.backend.flush(&meta, tag.into())
}
fn open(&self, meta: &Metadata, fid: FID, mode: SimpleOpenMode) -> Result<(QID, u32)> {
let meta = meta.into();
let (qid, iounit) = self.backend.open(&meta, fid.into(), mode.into())?;
Ok((self.qidmapper.qid(qid), iounit.unwrap_or(0)))
}
fn lopen(&self, meta: &Metadata, fid: FID, mode: LinuxOpenMode) -> Result<(QID, u32)> {
let meta = meta.into();
let (qid, iounit) = self.backend.open(&meta, fid.into(), mode)?;
Ok((self.qidmapper.qid(qid), iounit.unwrap_or(0)))
}
#[allow(clippy::unnecessary_cast)]
fn create(
&self,
meta: &Metadata,
fid: FID,
name: &[u8],
perm: FileType,
omode: SimpleOpenMode,
extension: Option<&[u8]>,
) -> Result<(QID, u32)> {
let meta = meta.into();
trace!(
self.logger,
"9P create: fid {} name {} perm {:?} omode {:?}",
fid,
name.as_log_str(),
perm,
omode
);
let mut mode = perm.bits() & 0o777;
if perm.contains(FileType::DMSETUID) {
mode |= 0o4000;
}
if perm.contains(FileType::DMSETGID) {
mode |= 0o2000;
}
if perm
.contains(FileType::DMAPPEND | FileType::DMMOUNT | FileType::DMAUTH | FileType::DMTMP)
{
return Err(Error::EINVAL);
}
trace!(self.logger, "9P create: unix mode {:04o}", mode);
let (mmode, mmajor, mminor) = match (
perm & !(FileType::DMACCMODE | FileType::DMSETUID | FileType::DMSETGID),
extension,
) {
(FileType::DMDIR, None) => {
trace!(self.logger, "9P create: directory: {}", name.as_log_str());
let mode = fsbackend::FileType::from_bits(mode).ok_or(Error::EINVAL)?;
let qid = self.backend.mkdir(&meta, fid.into(), name, mode, None)?;
trace!(
self.logger,
"9P create: directory creation OK, updating fid"
);
self.backend
.resolve(&meta, fid.into(), fid.into(), &[name])?;
return Ok((self.qidmapper.qid(qid), 0));
}
(FileType::DMSYMLINK, Some(dest)) => {
trace!(
self.logger,
"9P create: symlink: {} {}",
name.as_log_str(),
dest.as_log_str()
);
let qid = self.backend.symlink(&meta, fid.into(), name, dest, None)?;
self.backend
.resolve(&meta, fid.into(), fid.into(), &[name])?;
return Ok((self.qidmapper.qid(qid), 0));
}
(x, None) if x == FileType::empty() => {
trace!(self.logger, "9P create: file: {}", name.as_log_str());
let mode = fsbackend::FileType::from_bits(mode).ok_or(Error::EINVAL)?;
let qid = self.backend.create(
&meta,
fid.into(),
fid.into(),
name,
omode.into(),
mode,
None,
)?;
return Ok((self.qidmapper.qid(qid.0), 0));
}
(FileType::DMDEVICE, Some(kind)) => self.parse_major_minor(kind)?,
(FileType::DMSOCKET, None) => (fsbackend::FileType::S_IFSOCK, 0, 0),
(FileType::DMNAMEDPIPE, None) => (fsbackend::FileType::S_IFIFO, 0, 0),
_ => return Err(Error::EINVAL),
};
let mode = fsbackend::FileType::from_bits(mode).ok_or(Error::EINVAL)? | mmode;
trace!(self.logger, "9P create: mknod: {}", name.as_log_str());
let qid = self
.backend
.mknod(&meta, fid.into(), name, mode, mmajor, mminor, None)?;
self.backend
.resolve(&meta, fid.into(), fid.into(), &[name])?;
Ok((self.qidmapper.qid(qid), 0))
}
fn lcreate(
&self,
meta: &Metadata,
fid: FID,
name: &[u8],
flags: u32,
mode: u32,
_gid: u32,
) -> Result<(QID, u32)> {
let meta = meta.into();
let mode = fsbackend::FileType::from_bits(mode).ok_or(Error::EINVAL)?;
let flags = fsbackend::OpenMode::from_bits(flags).ok_or(Error::EINVAL)?
| fsbackend::OpenMode::O_CREAT;
let qid = self
.backend
.create(&meta, fid.into(), fid.into(), name, flags, mode, None)?;
Ok((self.qidmapper.qid(qid.0), 0))
}
fn read(&self, meta: &Metadata, fid: FID, offset: u64, data: &mut [u8]) -> Result<u32> {
let bmeta = meta.into();
if data.len() > u32::MAX as usize {
return Err(Error::EINVAL);
}
let st = self
.backend
.getattr(&bmeta, fid.into(), fsbackend::StatValidity::BASIC)?;
let is_open = self.backend.is_open(&bmeta, fid.into())?;
let mode = fsbackend::FileType::from_bits(st.mode).ok_or(Error::EIO)?;
match (mode & fsbackend::FileType::S_IFMT, is_open) {
(fsbackend::FileType::S_IFDIR, true) => {
type BoxDirEntryFn = Box<dyn FnMut(&fsbackend::DirEntry) -> usize>;
let (offsetf, lenf): (BoxDirEntryFn, BoxDirEntryFn) = match meta.protocol {
ProtocolVersion::Original => (
Box::new(|de| PlainStat::FIXED_SIZE + de.name.len()),
Box::new(|de| PlainStat::FIXED_SIZE + de.name.len()),
),
ProtocolVersion::Unix | ProtocolVersion::Linux => (
Box::new(|de| {
UnixStat::FIXED_SIZE
+ de.name.len()
+ de.extension.as_ref().map(|e| e.len()).unwrap_or_default()
}),
Box::new(|de| {
UnixStat::FIXED_SIZE
+ de.name.len()
+ de.extension.as_ref().map(|e| e.len()).unwrap_or_default()
}),
),
};
let entries = self.backend.readdir(
&bmeta,
fid.into(),
offset,
data.len() as u32,
offsetf,
lenf,
)?;
let mut size = 0;
for entry in entries {
let fsmeta = &entry.metadata;
match meta.protocol {
ProtocolVersion::Original => {
let mut st = PlainStat::from_metadata(fsmeta).ok_or(Error::EIO)?;
st.qid = self.qidmapper.qid(entry.qid);
st.size += entry.name.len() as u16;
st.name = entry.name;
let st = st.to_bytes().ok_or(Error::EIO)?;
data[size..size + st.len()].copy_from_slice(&st);
size += st.len();
}
ProtocolVersion::Unix | ProtocolVersion::Linux => {
let mut st = UnixStat::from_metadata(fsmeta).ok_or(Error::EIO)?;
st.qid = self.qidmapper.qid(entry.qid);
st.size += entry.name.len() as u16;
st.name = entry.name;
st.extension = entry.extension.unwrap_or_default();
let st = st.to_bytes().ok_or(Error::EIO)?;
data[size..size + st.len()].copy_from_slice(&st);
size += st.len();
}
}
}
Ok(size as u32)
}
(_, true) => match self.backend.read(&bmeta, fid.into(), offset, data) {
Ok(len) => Ok(len),
Err(e) => Err(e),
},
(_, false) => Err(Error::EBADF),
}
}
fn write(&self, meta: &Metadata, fid: FID, offset: u64, data: &[u8]) -> Result<u32> {
let meta = meta.into();
trace!(
self.logger,
"9P write: fid {} offset {} bytes {}",
fid,
offset,
data.len()
);
if data.len() > u32::MAX as usize {
return Err(Error::EINVAL);
}
trace!(self.logger, "9P write: validated data");
self.backend.write(&meta, fid.into(), offset, data)
}
fn remove(&self, meta: &Metadata, fid: FID) -> Result<()> {
let meta = meta.into();
trace!(self.logger, "9P remove: fid {}", fid);
let r = self.backend.remove(&meta, fid.into());
let _ = self.backend.clunk(&meta, fid.into());
r
}
fn fsync(&self, meta: &Metadata, fid: FID) {
let meta = meta.into();
self.backend.fsync(&meta, fid.into());
}
fn stat(&self, meta: &Metadata, fid: FID) -> Result<Box<dyn Stat>> {
trace!(self.logger, "9P stat: fid {}", fid);
let bmeta = meta.into();
let st = self
.backend
.getattr(&bmeta, fid.into(), fsbackend::StatValidity::BASIC)?;
match meta.protocol {
ProtocolVersion::Original => {
trace!(self.logger, "9P stat: found fid {}", fid);
if st.qid.kind() == fsbackend::QIDKind::Authentication {
let qid = self.qidmapper.qid(st.qid);
return Ok(Box::new(PlainStat {
size: UnixStat::FIXED_SIZE as u16,
kind: 0,
dev: 0,
qid,
mode: FileType::DMAUTH.bits(),
atime: 0,
mtime: 0,
length: 0,
name: Vec::new(),
uid: Vec::new(),
gid: Vec::new(),
muid: Vec::new(),
}));
}
let name = self
.backend
.pathname(&bmeta, fid.into())?
.pop()
.ok_or(Error::EIO)?;
let pst = PlainStat {
size: (PlainStat::FIXED_SIZE - 2 + name.len()) as u16,
kind: MAGIC_KIND,
dev: MAGIC_DEV,
qid: self.qidmapper.qid(st.qid),
mode: FileType::from_unix(st.mode).bits(),
atime: st.atime_sec.and_then(|t| t.try_into().ok()).unwrap_or(0),
mtime: st.mtime_sec.and_then(|t| t.try_into().ok()).unwrap_or(0),
length: st.length,
name,
uid: Vec::new(),
gid: Vec::new(),
muid: Vec::new(),
};
trace!(
self.logger,
"9P stat: created plain stat of {} bytes",
pst.size + 2
);
Ok(Box::new(pst))
}
ProtocolVersion::Unix => {
trace!(self.logger, "9P stat: found fid {}", fid);
if st.qid.kind() == fsbackend::QIDKind::Authentication {
let qid = self.qidmapper.qid(st.qid);
return Ok(Box::new(UnixStat {
size: UnixStat::FIXED_SIZE as u16,
kind: 0,
dev: 0,
qid,
mode: FileType::DMAUTH.bits(),
atime: 0,
mtime: 0,
length: 0,
name: Vec::new(),
uid: Vec::new(),
gid: Vec::new(),
muid: Vec::new(),
extension: Vec::new(),
nuid: u32::MAX,
ngid: u32::MAX,
nmuid: u32::MAX,
}));
}
let name = self
.backend
.pathname(&bmeta, fid.into())?
.pop()
.ok_or(Error::EIO)?;
let extension = if st.qid.kind() == fsbackend::QIDKind::Symlink {
self.backend.readlink(&bmeta, fid.into())?
} else {
Vec::new()
};
let ust = UnixStat {
size: (UnixStat::FIXED_SIZE - 2 + name.len() + extension.len()) as u16,
kind: MAGIC_KIND,
dev: MAGIC_DEV,
qid: self.qidmapper.qid(st.qid),
mode: FileType::from_unix(st.mode).bits(),
atime: st.atime_sec.and_then(|t| t.try_into().ok()).unwrap_or(0),
mtime: st.mtime_sec.and_then(|t| t.try_into().ok()).unwrap_or(0),
length: st.length,
name,
uid: Vec::new(),
gid: Vec::new(),
muid: Vec::new(),
nuid: u32::MAX,
ngid: u32::MAX,
nmuid: u32::MAX,
extension,
};
trace!(
self.logger,
"9P stat: created unix stat of {} bytes",
ust.size + 2
);
Ok(Box::new(ust))
}
_ => Err(Error::EOPNOTSUPP),
}
}
fn wstat(&self, meta: &Metadata, fid: FID, stat: &dyn Stat) -> Result<()> {
let bmeta = meta.into();
match meta.protocol {
ProtocolVersion::Original | ProtocolVersion::Unix => {
let is_symlink = stat.qid().0[0] == FileKind::Symlink as u8;
if !stat.kind().is_flush()
|| !stat.dev().unwrap_or(u32::MAX).is_flush()
|| (!is_symlink && stat.extension().is_some())
{
return Err(Error::EINVAL);
}
if !stat.name().is_empty() {
let tempfid = self.temp_backend_fid(fid, 1);
if let Err(e) =
self.backend
.walk(&bmeta, fid.into(), tempfid, &[b".." as &[u8]])
{
let _ = self.backend.clunk(&bmeta, tempfid);
return Err(e);
}
let r = self
.backend
.rename(&bmeta, fid.into(), tempfid, stat.name());
let _ = self.backend.clunk(&bmeta, tempfid);
r?;
}
if is_symlink && stat.extension().is_some() {
return Err(Error::EOPNOTSUPP);
}
let gid = match stat.ngid() {
Some(stgid) if !stgid.is_flush() => Some(stgid),
_ => None,
};
let mode = stat.mode().map(|stmode| {
let mut mode = stmode.bits() & 0o777;
if stmode.contains(FileType::DMSETUID) {
mode |= 0o4000;
}
if stmode.contains(FileType::DMSETGID) {
mode |= 0o2000;
}
mode
});
let size = if stat.length().is_flush() {
None
} else {
Some(stat.length())
};
self.backend.setattr(
&bmeta,
fid.into(),
mode,
None,
gid,
size,
None,
None,
false,
false,
)
}
_ => Err(Error::EOPNOTSUPP),
}
}
fn walk(&self, meta: &Metadata, fid: FID, newfid: FID, name: &[&[u8]]) -> Result<Vec<QID>> {
trace!(
self.logger,
"9P walk: fid {} newfid {} components {}",
fid,
newfid,
name.len()
);
let meta = meta.into();
self.backend
.walk(&meta, fid.into(), newfid.into(), name)
.map(|qids| qids.iter().map(|q| self.qidmapper.qid(*q)).collect())
}
fn symlink(
&self,
meta: &Metadata,
fid: FID,
name: &[u8],
target: &[u8],
gid: u32,
) -> Result<QID> {
let meta = meta.into();
self.backend
.symlink(&meta, fid.into(), name, target, Some(gid))
.map(|q| self.qidmapper.qid(q))
}
fn mknod(
&self,
meta: &Metadata,
fid: FID,
name: &[u8],
mode: u32,
major: u32,
minor: u32,
gid: u32,
) -> Result<QID> {
let meta = meta.into();
let ft = fsbackend::FileType::from_bits(mode).ok_or(Error::EINVAL)?;
self.backend
.mknod(&meta, fid.into(), name, ft, major, minor, Some(gid))
.map(|q| self.qidmapper.qid(q))
}
fn rename(&self, meta: &Metadata, fid: FID, dfid: FID, newname: &[u8]) -> Result<()> {
let meta = meta.into();
self.backend.rename(&meta, fid.into(), dfid.into(), newname)
}
fn readlink(&self, meta: &Metadata, fid: FID) -> Result<Vec<u8>> {
let meta = meta.into();
self.backend.readlink(&meta, fid.into())
}
fn getattr(&self, meta: &Metadata, fid: FID, mask: LinuxStatValidity) -> Result<LinuxStat> {
trace!(self.logger, "9P getattr: fid {} mask {:?}", fid, mask);
let meta = meta.into();
let st = self.backend.getattr(
&meta,
fid.into(),
fsbackend::StatValidity::from_bits(mask.bits()).ok_or(Error::EINVAL)?,
)?;
Ok(LinuxStat::from_fs(&st, self.qidmapper.qid(st.qid)))
}
fn setattr(
&self,
meta: &Metadata,
fid: FID,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<SystemTime>,
mtime: Option<SystemTime>,
set_atime: bool,
set_mtime: bool,
) -> Result<()> {
trace!(
self.logger,
"9P setattr: fid {} mode {:?} uid {:?} gid {:?} size {:?} atime {:?}/{} mtime {:?}/{}",
fid,
mode,
uid,
gid,
size,
atime,
set_atime,
mtime,
set_mtime
);
let meta = meta.into();
self.backend.setattr(
&meta,
fid.into(),
mode,
uid,
gid,
size,
atime,
mtime,
set_atime,
set_mtime,
)
}
fn xattrwalk(&self, meta: &Metadata, fid: FID, newfid: FID, name: &[u8]) -> Result<u64> {
let meta = meta.into();
self.backend
.xattrwalk(&meta, fid.into(), newfid.into(), name)
}
fn xattrcreate(
&self,
meta: &Metadata,
fid: FID,
name: &[u8],
size: u64,
flags: u32,
) -> Result<()> {
let meta = meta.into();
self.backend
.xattrcreate(&meta, fid.into(), name, size, flags)
}
fn readdir(&self, meta: &Metadata, fid: FID, offset: u64, count: u32) -> Result<Vec<DirEntry>> {
let meta = meta.into();
self.backend
.readdir(
&meta,
fid.into(),
offset,
count,
Box::new(|_| 1),
Box::new(|de| 13 + 8 + 1 + 2 + de.name.len()),
)
.map(|v| {
v.iter()
.map(|de| DirEntry::from_fs(de, self.qidmapper.qid(de.qid)))
.collect()
})
}
fn lock(
&self,
meta: &Metadata,
fid: FID,
kind: LockCommand,
flags: u32,
start: u64,
length: u64,
proc_id: u32,
client_id: &[u8],
) -> Result<LockStatus> {
let meta = meta.into();
self.backend
.lock(
&meta,
fid.into(),
kind.into(),
flags,
start,
length,
proc_id,
client_id,
)
.map(|status| status.into())
}
fn getlock(
&self,
meta: &Metadata,
fid: FID,
kind: LockKind,
start: u64,
length: u64,
proc_id: u32,
client_id: &[u8],
) -> Result<Lock> {
let meta = meta.into();
self.backend
.getlock(
&meta,
fid.into(),
kind.into(),
start,
length,
proc_id,
client_id,
)
.map(|lock| lock.into())
}
fn link(&self, meta: &Metadata, dfid: FID, fid: FID, name: &[u8]) -> Result<()> {
let meta = meta.into();
self.backend.link(&meta, dfid.into(), fid.into(), name)
}
fn mkdir(&self, meta: &Metadata, dfid: FID, name: &[u8], mode: u32, gid: u32) -> Result<QID> {
let meta = meta.into();
self.backend
.mkdir(
&meta,
dfid.into(),
name,
fsbackend::FileType::from_bits(mode).ok_or(Error::EINVAL)?,
Some(gid),
)
.map(|q| self.qidmapper.qid(q))
}
fn renameat(
&self,
meta: &Metadata,
olddirfid: FID,
oldname: &[u8],
newdirfid: FID,
newname: &[u8],
) -> Result<()> {
trace!(
self.logger,
"9P renameat: {}/{} -> {}/{}",
olddirfid,
oldname.as_log_str(),
newdirfid,
newname.as_log_str()
);
let meta = meta.into();
self.backend
.renameat(&meta, olddirfid.into(), oldname, newdirfid.into(), newname)
}
fn unlinkat(&self, meta: &Metadata, dirfd: FID, name: &[u8], flags: u32) -> Result<()> {
let meta = meta.into();
self.backend.unlinkat(&meta, dirfd.into(), name, flags)
}
}
#[cfg(not(miri))]
#[cfg(test)]
mod tests {
use super::LibcBackend;
use crate::backend::{Backend, ToIdentifier};
use crate::server::{
FileType, LinuxFileType, LinuxOpenMode, LinuxStatValidity, Metadata, PlainStat,
ProtocolVersion, SimpleOpenMode, Tag, UnixStat, FID,
};
use lawn_constants::error::{Error, ExtendedError};
use lawn_constants::logger::{LogFormat, LogLevel};
use lawn_fs::auth::{AuthenticationInfo, Authenticator, AuthenticatorHandle};
use lawn_fs::backend as fsbackend;
use std::collections::HashSet;
use std::convert::TryInto;
use std::ffi::OsStr;
use std::fs;
use std::fs::FileType as StdFileType;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::Mutex;
use tempfile::TempDir;
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct AutherHandle {
user: Vec<u8>,
dir: Vec<u8>,
id: Option<u32>,
}
impl AuthenticatorHandle for AutherHandle {
fn read(&self, _data: &mut [u8]) -> Result<u32, Error> {
Err(Error::EOPNOTSUPP)
}
fn write(&self, _data: &[u8]) -> Result<u32, Error> {
Err(Error::EOPNOTSUPP)
}
fn info<'a>(&'a self) -> Option<AuthenticationInfo<'a>> {
Some(AuthenticationInfo::new(
self.id,
&*self.user,
&*self.dir,
&*self.dir,
))
}
}
impl ToIdentifier for AutherHandle {
fn to_identifier(&self) -> Vec<u8> {
let mut v = Vec::with_capacity(8 + self.user.len() + 8 + self.dir.len() + 4);
v.extend(&(self.user.len() as u64).to_le_bytes());
v.extend(&self.user);
v.extend(&(self.dir.len() as u64).to_le_bytes());
v.extend(&self.dir);
if let Some(id) = self.id {
v.extend(&id.to_le_bytes());
}
v
}
}
pub struct Auther {
user: Vec<u8>,
dir: Vec<u8>,
}
impl Authenticator for Auther {
fn create(
&self,
_meta: &fsbackend::Metadata,
_uname: &[u8],
_aname: &[u8],
nuname: Option<u32>,
) -> Box<dyn AuthenticatorHandle + Send + Sync> {
Box::new(AutherHandle {
user: self.user.clone(),
dir: self.dir.clone(),
id: nuname,
})
}
}
struct Logger {}
impl lawn_constants::logger::Logger for Logger {
fn level(&self) -> LogLevel {
LogLevel::Trace
}
fn format(&self) -> LogFormat {
LogFormat::Text
}
fn fatal(&self, _msg: &str) {}
fn error(&self, _msg: &str) {}
fn message(&self, _msg: &str) {}
fn info(&self, _msg: &str) {}
fn debug(&self, _msg: &str) {}
fn trace(&self, msg: &str) {
eprintln!("{}", msg);
}
fn script_message(&self, _tag: Option<&[u8]>, _msg: &[&[u8]]) {}
fn script_error(&self, _tag: Option<&[u8]>, _err: &dyn ExtendedError) {}
fn serialized(&self, _msg: &[u8]) {}
fn serialized_error(&self, _msg: &dyn ExtendedError) {}
}
type Server = LibcBackend;
#[allow(dead_code)]
struct TestInstance {
dir: TempDir,
root: PathBuf,
version: ProtocolVersion,
server: Server,
tag: Mutex<u16>,
}
impl TestInstance {
fn next_meta(&self) -> Metadata {
let mut g = self.tag.lock().unwrap();
let tag = *g;
*g += 1;
Metadata {
protocol: self.version,
tag: Tag(tag.to_le_bytes()),
}
}
}
fn instance(version: ProtocolVersion) -> TestInstance {
let dir = TempDir::new().unwrap();
let root = fs::canonicalize(dir.path()).unwrap();
let logger = Arc::new(Logger {});
let backend = Arc::new(lawn_fs::backend::libc::LibcBackend::new(
logger.clone(),
Arc::new(Auther {
user: "foo".into(),
dir: dir.path().as_os_str().as_bytes().into(),
}),
1024 * 1024,
));
TestInstance {
version,
server: Server::new(logger.clone(), backend, 1024 * 1024),
dir,
root,
tag: Mutex::new(0),
}
}
fn minialzette(n: u32) -> u32 {
const C: u16 = 0xb7e1;
let (mut x, mut y) = ((n >> 16) as u16, n as u16);
x = x.wrapping_add(y.rotate_right(15));
y ^= x.rotate_right(8);
x ^= C;
x = x.wrapping_add(y.rotate_right(1));
y ^= x.rotate_right(1);
x ^= C;
x = x.wrapping_add(y);
y ^= x.rotate_right(15);
x ^= C;
x = x.wrapping_add(y.rotate_right(8));
y ^= x;
x ^= C;
((x as u32) << 16) | (y as u32)
}
fn fid(n: u32) -> FID {
FID(n.to_le_bytes())
}
fn attach(inst: &mut TestInstance) {
inst.server
.version(&inst.next_meta(), 4096, inst.version.to_str().as_bytes())
.unwrap();
inst.server
.attach(
&inst.next_meta(),
fid(0),
fid(0xffffffff),
b"foo",
b"aname",
None,
)
.unwrap();
}
fn lstat_dev_ino(inst: &TestInstance, path: &Path) -> Option<(u64, u64)> {
let fd = rustix::fs::openat(
rustix::fs::cwd(),
inst.dir.path(),
rustix::fs::OFlags::RDONLY,
rustix::fs::Mode::from_bits(0o755).unwrap(),
)
.ok()?;
let st = rustix::fs::statat(fd, path, rustix::fs::AtFlags::SYMLINK_NOFOLLOW).ok()?;
Some((st.st_dev as u64, st.st_ino as u64))
}
fn verify_file_is_path<F: FnOnce(&StdFileType) -> bool>(
inst: &TestInstance,
file: &Path,
path: &Path,
f: F,
) {
let meta = std::fs::symlink_metadata(path).unwrap();
assert!(f(&meta.file_type()), "file is of correct type");
let (dev, ino) = lstat_dev_ino(&inst, file).unwrap();
assert_eq!((dev, ino), (meta.dev(), meta.ino()), "same file");
}
fn verify_dir(inst: &mut TestInstance, fid: FID, path: Option<&[u8]>) {
if let Some(fidpath) = full_path_for_fid(inst, fid).as_ref() {
let mut full_path = inst.root.clone();
full_path.push(OsStr::from_bytes(path.unwrap_or_default()));
assert_ne!(
fidpath.file_name().map(|n| n.as_bytes()),
Some(b".." as &[u8]),
"not dot-dot"
);
assert_eq!(
fs::canonicalize(fidpath).unwrap(),
fs::canonicalize(&full_path).unwrap(),
"full path is correct"
);
match path {
Some(path) => {
if path.is_empty() {
assert_eq!(full_path, inst.root, "path is root");
} else {
assert_eq!(*fidpath, full_path, "path is correct");
}
}
None => assert_eq!(full_path, inst.root, "path is root"),
}
verify_file_is_path(&inst, &fidpath, &full_path, |f| f.is_dir());
} else {
panic!("Not a directory");
}
}
fn verify_file(inst: &mut TestInstance, fid: FID, path: &[u8]) {
if let Some(fidpath) = full_path_for_fid(inst, fid).as_ref() {
let mut full_path = inst.root.clone();
full_path.push(OsStr::from_bytes(path));
assert_ne!(
fidpath.file_name().map(|n| n.as_bytes()),
Some(b".." as &[u8]),
"not dot-dot"
);
assert_eq!(
fs::canonicalize(fidpath).unwrap(),
fs::canonicalize(&full_path).unwrap(),
"full path is correct"
);
assert_eq!(*fidpath, full_path, "path is correct");
verify_file_is_path(&inst, &fidpath, &full_path, |f| f.is_file());
} else {
panic!("Not a file");
}
}
fn verify_symlink(inst: &mut TestInstance, fid: FID, path: &[u8], dest: &[u8]) {
if let Some(fidpath) = full_path_for_fid(inst, fid) {
let mut full_path = inst.root.clone();
full_path.push(OsStr::from_bytes(path));
assert_ne!(
fidpath.file_name().map(|n| n.as_bytes()),
Some(b".." as &[u8]),
"not dot-dot"
);
assert_eq!(fidpath, full_path, "path is correct");
verify_file_is_path(&inst, &fidpath, &full_path, |f| f.is_symlink());
let read_dest = fs::read_link(&full_path).unwrap();
assert_eq!(
read_dest.as_os_str().as_bytes(),
dest,
"destination is as expected"
);
} else {
panic!("Not a symlink");
}
}
fn full_path_for_fid(inst: &mut TestInstance, fid: FID) -> Option<PathBuf> {
let meta = inst.next_meta();
let meta = (&meta).into();
let seq = inst.server.backend.pathname(&meta, fid.into()).ok()?;
let mut path = inst.dir.path().to_owned();
if seq.len() == 1 && seq[0] == b"/" {
return Some(path);
}
for component in seq {
path.push(OsStr::from_bytes(&component));
}
Some(path)
}
fn verify_closed(inst: &mut TestInstance, fid: FID) {
let meta = inst.next_meta();
let meta = (&meta).into();
assert!(inst.server.backend.is_open(&meta, fid.into()).is_err());
}
fn create_fixtures(inst: &mut TestInstance) {
attach(inst);
verify_dir(inst, fid(0), None);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[])
.unwrap();
inst.server
.create(
&inst.next_meta(),
fid(1),
b"dir",
FileType::DMDIR | FileType::from_bits(0o770).unwrap(),
SimpleOpenMode::O_READ,
None,
)
.unwrap();
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(inst, fid(1));
inst.server
.walk(&inst.next_meta(), fid(0), fid(2), &[b"dir"])
.unwrap();
verify_dir(inst, fid(2), Some(b"dir"));
inst.server
.create(
&inst.next_meta(),
fid(2),
b"file",
FileType::from_bits(0o660).unwrap(),
SimpleOpenMode::O_RDWR,
None,
)
.unwrap();
verify_file(inst, fid(2), b"dir/file");
let body: &[u8] = b"Hello, world!\n";
assert_eq!(
inst.server
.write(&inst.next_meta(), fid(2), 0, body)
.unwrap() as usize,
body.len()
);
let mut actual = [0u8; 512];
let size = inst
.server
.read(&inst.next_meta(), fid(2), 0, &mut actual)
.unwrap() as usize;
assert_eq!(body, &actual[0..size]);
let size = inst
.server
.read(&inst.next_meta(), fid(2), 2, &mut actual)
.unwrap() as usize;
assert_eq!(&body[2..], &actual[0..size]);
verify_file(inst, fid(2), b"dir/file");
inst.server.clunk(&inst.next_meta(), fid(2)).unwrap();
verify_closed(inst, fid(2));
}
#[test]
fn handles_arbitrary_fids() {
for ver in &[
ProtocolVersion::Original,
ProtocolVersion::Unix,
ProtocolVersion::Linux,
] {
let mut inst = instance(*ver);
create_fixtures(&mut inst);
let seq = (0..=u32::MAX).map(minialzette);
for n in seq.take(100) {
inst.server
.walk(&inst.next_meta(), fid(0), fid(n), &[b"dir"])
.unwrap();
verify_dir(&mut inst, fid(n), Some(b"dir"));
inst.server
.create(
&inst.next_meta(),
fid(n),
format!("{:08x}", n).as_bytes(),
FileType::from_bits(0o660).unwrap(),
SimpleOpenMode::O_RDWR,
None,
)
.unwrap();
}
let seq = (0..=u32::MAX).map(minialzette);
for n in seq.take(100) {
if *ver == ProtocolVersion::Linux {
let st = inst
.server
.getattr(&inst.next_meta(), fid(n), LinuxStatValidity::BASIC)
.unwrap();
assert_eq!(st.length, 0);
} else {
let st = inst.server.stat(&inst.next_meta(), fid(n)).unwrap();
assert_eq!(st.name(), format!("{:08x}", n).as_bytes());
}
verify_file(&mut inst, fid(n), format!("dir/{:08x}", n).as_bytes());
inst.server.clunk(&inst.next_meta(), fid(n)).unwrap();
}
}
}
fn read_directory_names(inst: &mut TestInstance, f: FID) -> HashSet<Vec<u8>> {
let mut actual = HashSet::new();
let mut buffer = [0u8; 512];
match inst.version {
ProtocolVersion::Original => {
let mut offset = 0;
loop {
let read = inst
.server
.read(&inst.next_meta(), f, offset, &mut buffer)
.unwrap();
if read == 0 {
break;
}
offset += read as u64;
let buf = &buffer[0..read as usize];
let mut bufoff = 0;
while bufoff != buf.len() {
let len = u16::from_le_bytes(buf[bufoff..bufoff + 2].try_into().unwrap());
let st =
PlainStat::from_bytes(len, &buf[bufoff..bufoff + (len as usize) + 2])
.unwrap();
actual.insert(st.name);
bufoff += 2 + len as usize;
}
}
}
ProtocolVersion::Unix => {
let mut offset = 0;
loop {
let read = inst
.server
.read(&inst.next_meta(), f, offset, &mut buffer)
.unwrap();
if read == 0 {
break;
}
offset += read as u64;
let buf = &buffer[0..read as usize];
let mut bufoff = 0;
while bufoff != buf.len() {
let len = u16::from_le_bytes(buf[bufoff..bufoff + 2].try_into().unwrap());
let st =
UnixStat::from_bytes(len, &buf[bufoff..bufoff + (len as usize) + 2])
.unwrap();
actual.insert(st.name);
bufoff += 2 + len as usize;
}
}
}
ProtocolVersion::Linux => {
let mut offset = 0;
loop {
let entries = inst
.server
.readdir(&inst.next_meta(), f, offset, buffer.len() as u32)
.unwrap();
if entries.is_empty() {
break;
}
offset = entries[entries.len() - 1].offset;
for entry in entries {
actual.insert(entry.name);
}
}
}
}
actual
}
#[test]
fn readdir_remove() {
for ver in &[
ProtocolVersion::Original,
ProtocolVersion::Unix,
ProtocolVersion::Linux,
] {
let mut inst = instance(*ver);
create_fixtures(&mut inst);
let seq = (0..=u32::MAX).map(minialzette);
let mut set = HashSet::new();
for n in seq.take(100) {
inst.server
.walk(&inst.next_meta(), fid(0), fid(n), &[b"dir"])
.unwrap();
verify_dir(&mut inst, fid(n), Some(b"dir"));
let path = format!("{:08x}", n).as_bytes().to_vec();
inst.server
.create(
&inst.next_meta(),
fid(n),
&path,
FileType::from_bits(0o660).unwrap(),
SimpleOpenMode::O_RDWR,
None,
)
.unwrap();
set.insert(path);
}
set.insert(b"file".to_vec());
let f = fid(minialzette(1000));
inst.server
.walk(&inst.next_meta(), fid(0), f, &[b"dir"])
.unwrap();
match ver {
ProtocolVersion::Original | ProtocolVersion::Unix => {
inst.server
.open(&inst.next_meta(), f, SimpleOpenMode::O_READ)
.unwrap();
}
ProtocolVersion::Linux => {
inst.server
.lopen(&inst.next_meta(), f, LinuxOpenMode::O_RDONLY)
.unwrap();
}
}
let actual = read_directory_names(&mut inst, f);
assert_eq!(actual, set);
let seq = (0..=u32::MAX).map(minialzette);
for n in seq.take(100) {
if *ver == ProtocolVersion::Linux {
let st = inst
.server
.getattr(&inst.next_meta(), fid(n), LinuxStatValidity::BASIC)
.unwrap();
assert_eq!(st.length, 0);
} else {
let st = inst.server.stat(&inst.next_meta(), fid(n)).unwrap();
assert_eq!(st.name(), format!("{:08x}", n).as_bytes());
}
verify_file(&mut inst, fid(n), format!("dir/{:08x}", n).as_bytes());
inst.server.remove(&inst.next_meta(), fid(n)).unwrap();
}
let actual = read_directory_names(&mut inst, f);
let mut expected = HashSet::new();
expected.insert(b"file".to_vec());
assert_eq!(actual, expected);
}
}
#[cfg(not(target_os = "netbsd"))]
#[test]
fn truncate_64bit() {
for ver in &[
ProtocolVersion::Original,
ProtocolVersion::Unix,
ProtocolVersion::Linux,
] {
const SIZE: u64 = 4_294_967_296;
let mut inst = instance(ProtocolVersion::Original);
create_fixtures(&mut inst);
let qids = inst
.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir", b"file"])
.unwrap();
match ver {
ProtocolVersion::Original => {
let ps = PlainStat {
size: u16::MAX,
kind: u16::MAX,
dev: u32::MAX,
qid: qids[1],
mode: u32::MAX,
atime: u32::MAX,
mtime: u32::MAX,
length: SIZE,
name: Default::default(),
uid: Default::default(),
gid: Default::default(),
muid: Default::default(),
};
inst.server.wstat(&inst.next_meta(), fid(1), &ps).unwrap();
let st = inst.server.stat(&inst.next_meta(), fid(1)).unwrap();
assert_eq!(st.length(), 0x1_0000_0000);
}
ProtocolVersion::Unix => {
let ps = UnixStat {
size: u16::MAX,
kind: u16::MAX,
dev: u32::MAX,
qid: qids[1],
mode: u32::MAX,
atime: u32::MAX,
mtime: u32::MAX,
length: SIZE,
name: Default::default(),
uid: Default::default(),
gid: Default::default(),
muid: Default::default(),
extension: Default::default(),
nuid: u32::MAX,
ngid: u32::MAX,
nmuid: u32::MAX,
};
inst.server.wstat(&inst.next_meta(), fid(1), &ps).unwrap();
let st = inst.server.stat(&inst.next_meta(), fid(1)).unwrap();
assert_eq!(st.length(), 0x1_0000_0000);
}
ProtocolVersion::Linux => {
inst.server
.setattr(
&inst.next_meta(),
fid(1),
None,
None,
None,
Some(SIZE),
None,
None,
false,
false,
)
.unwrap();
let st = inst
.server
.getattr(&inst.next_meta(), fid(1), LinuxStatValidity::BASIC)
.unwrap();
assert_eq!(st.length, 0x1_0000_0000);
}
}
verify_file(&mut inst, fid(1), b"dir/file");
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(&mut inst, fid(1));
}
}
#[test]
fn chmod_orig() {
let mut inst = instance(ProtocolVersion::Original);
create_fixtures(&mut inst);
let qids = inst
.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir", b"file"])
.unwrap();
let ps = PlainStat {
size: u16::MAX,
kind: u16::MAX,
dev: u32::MAX,
qid: qids[1],
mode: 0o755,
atime: u32::MAX,
mtime: u32::MAX,
length: u64::MAX,
name: Default::default(),
uid: Default::default(),
gid: Default::default(),
muid: Default::default(),
};
inst.server.wstat(&inst.next_meta(), fid(1), &ps).unwrap();
let st = inst.server.stat(&inst.next_meta(), fid(1)).unwrap();
assert_eq!(st.mode().unwrap().bits() & 0o777, 0o755);
verify_file(&mut inst, fid(1), b"dir/file");
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(&mut inst, fid(1));
}
#[test]
fn chmod_unix() {
let mut inst = instance(ProtocolVersion::Unix);
create_fixtures(&mut inst);
let qids = inst
.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir", b"file"])
.unwrap();
let ps = UnixStat {
size: u16::MAX,
kind: u16::MAX,
dev: u32::MAX,
qid: qids[1],
mode: 0o755,
atime: u32::MAX,
mtime: u32::MAX,
length: u64::MAX,
name: Default::default(),
uid: Default::default(),
gid: Default::default(),
muid: Default::default(),
extension: Default::default(),
nuid: u32::MAX,
ngid: u32::MAX,
nmuid: u32::MAX,
};
inst.server.wstat(&inst.next_meta(), fid(1), &ps).unwrap();
let st = inst.server.stat(&inst.next_meta(), fid(1)).unwrap();
assert_eq!(st.mode().unwrap().bits() & 0o777, 0o755);
verify_file(&mut inst, fid(1), b"dir/file");
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(&mut inst, fid(1));
}
#[test]
fn chmod_linux() {
let mut inst = instance(ProtocolVersion::Unix);
create_fixtures(&mut inst);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir", b"file"])
.unwrap();
inst.server
.setattr(
&inst.next_meta(),
fid(1),
Some((LinuxFileType::S_IFREG | LinuxFileType::from_bits(0o755).unwrap()).bits()),
None,
None,
None,
None,
None,
false,
false,
)
.unwrap();
let st = inst
.server
.getattr(&inst.next_meta(), fid(1), LinuxStatValidity::BASIC)
.unwrap();
assert_eq!(st.mode & 0o777, 0o755);
verify_file(&mut inst, fid(1), b"dir/file");
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(&mut inst, fid(1));
}
#[test]
fn escape() {
for ver in &[
ProtocolVersion::Original,
ProtocolVersion::Unix,
ProtocolVersion::Linux,
] {
let mut inst = instance(*ver);
create_fixtures(&mut inst);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir"])
.unwrap();
verify_dir(&mut inst, fid(1), Some(b"dir"));
inst.server
.walk(&inst.next_meta(), fid(0), fid(2), &[b".."])
.unwrap();
verify_dir(&mut inst, fid(2), Some(b""));
inst.server
.walk(&inst.next_meta(), fid(1), fid(3), &[b"..", b".."])
.unwrap();
verify_dir(&mut inst, fid(3), Some(b""));
}
}
#[test]
fn escape_unix() {
let mut inst = instance(ProtocolVersion::Unix);
create_fixtures(&mut inst);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir"])
.unwrap();
verify_dir(&mut inst, fid(1), Some(b"dir"));
inst.server
.create(
&inst.next_meta(),
fid(1),
b"nested-dir",
FileType::DMDIR | FileType::from_bits(0o770).unwrap(),
SimpleOpenMode::O_READ,
None,
)
.unwrap();
verify_dir(&mut inst, fid(1), Some(b"dir/nested-dir"));
inst.server
.create(
&inst.next_meta(),
fid(1),
b"symlink",
FileType::DMSYMLINK | FileType::from_bits(0o777).unwrap(),
SimpleOpenMode::O_READ,
Some(b"../../.."),
)
.unwrap();
verify_symlink(&mut inst, fid(1), b"dir/nested-dir/symlink", b"../../..");
inst.server
.walk(&inst.next_meta(), fid(0), fid(2), &[b"dir", b"nested-dir"])
.unwrap();
inst.server
.create(
&inst.next_meta(),
fid(2),
b"symlink2",
FileType::DMSYMLINK | FileType::from_bits(0o777).unwrap(),
SimpleOpenMode::O_READ,
Some(b"../.."),
)
.unwrap();
assert_eq!(
inst.server
.walk(
&inst.next_meta(),
fid(0),
fid(3),
&[b"dir", b"nested-dir", b"symlink", b"foo"]
)
.unwrap()
.len(),
3
);
verify_closed(&mut inst, fid(3));
assert_eq!(
inst.server
.walk(
&inst.next_meta(),
fid(0),
fid(4),
&[b"dir", b"nested-dir", b"symlink2", b"dir", b"..", b".."]
)
.unwrap()
.len(),
6
);
verify_dir(&mut inst, fid(4), Some(b""));
}
#[test]
fn create_linux() {
let mut inst = instance(ProtocolVersion::Linux);
create_fixtures(&mut inst);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir"])
.unwrap();
verify_dir(&mut inst, fid(1), Some(b"dir"));
inst.server
.lcreate(&inst.next_meta(), fid(1), b"foo", 0, 0o600, u32::MAX)
.unwrap();
verify_file(&mut inst, fid(1), b"dir/foo");
}
#[test]
fn remove_broken_symlink() {
for ver in &[ProtocolVersion::Unix, ProtocolVersion::Linux] {
let mut inst = instance(*ver);
create_fixtures(&mut inst);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir"])
.unwrap();
if *ver == ProtocolVersion::Unix {
inst.server
.walk(&inst.next_meta(), fid(1), fid(2), &[])
.unwrap();
inst.server
.create(
&inst.next_meta(),
fid(2),
b"symlink",
FileType::DMSYMLINK | FileType::from_bits(0o777).unwrap(),
SimpleOpenMode::O_READ,
Some(b"/nonexistent"),
)
.unwrap();
verify_symlink(&mut inst, fid(2), b"dir/symlink", b"/nonexistent");
} else {
inst.server
.symlink(
&inst.next_meta(),
fid(1),
b"symlink",
b"/nonexistent",
u32::MAX,
)
.unwrap();
inst.server
.walk(&inst.next_meta(), fid(1), fid(2), &[b"symlink"])
.unwrap();
verify_symlink(&mut inst, fid(2), b"dir/symlink", b"/nonexistent");
}
inst.server.remove(&inst.next_meta(), fid(2)).unwrap();
}
}
#[test]
fn open_remove_linux() {
let mut inst = instance(ProtocolVersion::Linux);
create_fixtures(&mut inst);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir"])
.unwrap();
inst.server
.lopen(
&inst.next_meta(),
fid(1),
LinuxOpenMode::from_bits(0x18800).unwrap(),
)
.unwrap();
verify_dir(&mut inst, fid(1), Some(b"dir"));
inst.server
.walk(&inst.next_meta(), fid(0), fid(2), &[b"dir", b"file"])
.unwrap();
inst.server
.lopen(
&inst.next_meta(),
fid(2),
LinuxOpenMode::O_WRONLY
| LinuxOpenMode::O_CREAT
| LinuxOpenMode::O_NOCTTY
| LinuxOpenMode::O_LARGEFILE,
)
.unwrap();
verify_file(&mut inst, fid(2), b"dir/file");
inst.server
.walk(&inst.next_meta(), fid(2), fid(3), &[])
.unwrap();
verify_file(&mut inst, fid(3), b"dir/file");
inst.server.remove(&inst.next_meta(), fid(3)).unwrap();
verify_closed(&mut inst, fid(3));
}
#[test]
fn clone_as_directory() {
let mut inst = instance(ProtocolVersion::Linux);
create_fixtures(&mut inst);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir"])
.unwrap();
inst.server
.lopen(
&inst.next_meta(),
fid(1),
LinuxOpenMode::from_bits(0x18800).unwrap(),
)
.unwrap();
verify_dir(&mut inst, fid(1), Some(b"dir"));
inst.server
.walk(&inst.next_meta(), fid(1), fid(2), &[])
.unwrap();
inst.server
.lopen(
&inst.next_meta(),
fid(2),
LinuxOpenMode::from_bits(0x18800).unwrap(),
)
.unwrap();
verify_dir(&mut inst, fid(2), Some(b"dir"));
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(&mut inst, fid(1));
verify_dir(&mut inst, fid(2), Some(b"dir"));
inst.server.clunk(&inst.next_meta(), fid(2)).unwrap();
verify_closed(&mut inst, fid(2));
}
#[test]
fn symlink_unix() {
let mut inst = instance(ProtocolVersion::Unix);
create_fixtures(&mut inst);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir"])
.unwrap();
inst.server
.create(
&inst.next_meta(),
fid(1),
b"symlink",
FileType::DMSYMLINK | FileType::from_bits(0o777).unwrap(),
SimpleOpenMode::O_READ,
Some(b"file"),
)
.unwrap();
verify_symlink(&mut inst, fid(1), b"dir/symlink", b"file");
let st = inst.server.stat(&inst.next_meta(), fid(1)).unwrap();
assert_eq!(st.extension().unwrap(), b"file");
inst.server
.open(
&inst.next_meta(),
fid(1),
SimpleOpenMode::O_RDWR | SimpleOpenMode::O_TRUNC,
)
.unwrap();
verify_symlink(&mut inst, fid(1), b"dir/symlink", b"file");
let message = b"This is a test. This is only a test.\n";
inst.server
.write(&inst.next_meta(), fid(1), 0, message)
.unwrap();
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir", b"symlink"])
.unwrap();
verify_symlink(&mut inst, fid(1), b"dir/symlink", b"file");
inst.server
.open(&inst.next_meta(), fid(1), SimpleOpenMode::O_RDWR)
.unwrap();
let mut buf = vec![0u8; message.len()];
inst.server
.read(&inst.next_meta(), fid(1), 0, &mut buf)
.unwrap();
assert_eq!(buf, message);
inst.server
.walk(&inst.next_meta(), fid(0), fid(2), &[b"dir", b"file"])
.unwrap();
let st = inst.server.stat(&inst.next_meta(), fid(2)).unwrap();
assert_eq!(st.length(), message.len() as u64);
}
#[test]
fn symlink_linux() {
let mut inst = instance(ProtocolVersion::Linux);
create_fixtures(&mut inst);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir"])
.unwrap();
inst.server
.symlink(&inst.next_meta(), fid(1), b"symlink", b"file", 0)
.unwrap();
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir", b"symlink"])
.unwrap();
verify_symlink(&mut inst, fid(1), b"dir/symlink", b"file");
let dest = inst.server.readlink(&inst.next_meta(), fid(1)).unwrap();
assert_eq!(dest, b"file");
inst.server
.lopen(
&inst.next_meta(),
fid(1),
LinuxOpenMode::O_RDWR | LinuxOpenMode::O_TRUNC,
)
.unwrap();
verify_symlink(&mut inst, fid(1), b"dir/symlink", b"file");
let message = b"This is a test. This is only a test.\n";
inst.server
.write(&inst.next_meta(), fid(1), 0, message)
.unwrap();
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir", b"symlink"])
.unwrap();
verify_symlink(&mut inst, fid(1), b"dir/symlink", b"file");
inst.server
.lopen(&inst.next_meta(), fid(1), LinuxOpenMode::O_RDWR)
.unwrap();
verify_symlink(&mut inst, fid(1), b"dir/symlink", b"file");
let mut buf = vec![0u8; message.len()];
inst.server
.read(&inst.next_meta(), fid(1), 0, &mut buf)
.unwrap();
assert_eq!(buf, message);
inst.server
.walk(&inst.next_meta(), fid(0), fid(2), &[b"dir", b"file"])
.unwrap();
let st = inst
.server
.getattr(&inst.next_meta(), fid(2), LinuxStatValidity::ALL)
.unwrap();
assert_eq!(st.length, message.len() as u64);
}
#[test]
fn rename_orig() {
let mut inst = instance(ProtocolVersion::Original);
create_fixtures(&mut inst);
let qids = inst
.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir", b"file"])
.unwrap();
let ps = PlainStat {
size: u16::MAX,
kind: u16::MAX,
dev: u32::MAX,
qid: qids[1],
mode: u32::MAX,
atime: u32::MAX,
mtime: u32::MAX,
length: u64::MAX,
name: b"foo".to_vec(),
uid: Default::default(),
gid: Default::default(),
muid: Default::default(),
};
inst.server.wstat(&inst.next_meta(), fid(1), &ps).unwrap();
let st = inst.server.stat(&inst.next_meta(), fid(1)).unwrap();
assert_eq!(st.name(), b"foo");
verify_file(&mut inst, fid(1), b"dir/foo");
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(&mut inst, fid(1));
}
#[test]
fn rename_unix() {
let mut inst = instance(ProtocolVersion::Original);
create_fixtures(&mut inst);
let qids = inst
.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir", b"file"])
.unwrap();
let ps = UnixStat {
size: u16::MAX,
kind: u16::MAX,
dev: u32::MAX,
qid: qids[1],
mode: u32::MAX,
atime: u32::MAX,
mtime: u32::MAX,
length: u64::MAX,
name: b"foo".to_vec(),
extension: Default::default(),
uid: Default::default(),
gid: Default::default(),
muid: Default::default(),
nuid: u32::MAX,
ngid: u32::MAX,
nmuid: u32::MAX,
};
inst.server.wstat(&inst.next_meta(), fid(1), &ps).unwrap();
let st = inst.server.stat(&inst.next_meta(), fid(1)).unwrap();
assert_eq!(st.name(), b"foo");
verify_file(&mut inst, fid(1), b"dir/foo");
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(&mut inst, fid(1));
}
#[test]
fn rename_linux() {
for use_renameat in &[true, false] {
let mut inst = instance(ProtocolVersion::Linux);
create_fixtures(&mut inst);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[b"dir"])
.unwrap();
inst.server
.walk(&inst.next_meta(), fid(0), fid(2), &[b"dir", b"file"])
.unwrap();
inst.server
.rename(&inst.next_meta(), fid(2), fid(1), b"foo")
.unwrap();
verify_file(&mut inst, fid(2), b"dir/foo");
inst.server
.mkdir(&inst.next_meta(), fid(0), b"other-dir", 0o770, u32::MAX)
.unwrap();
inst.server
.walk(&inst.next_meta(), fid(0), fid(3), &[b"other-dir"])
.unwrap();
verify_dir(&mut inst, fid(3), Some(b"other-dir"));
inst.server
.mkdir(&inst.next_meta(), fid(3), b"nested-dir", 0o770, u32::MAX)
.unwrap();
inst.server
.walk(&inst.next_meta(), fid(3), fid(4), &[b"nested-dir"])
.unwrap();
verify_dir(&mut inst, fid(4), Some(b"other-dir/nested-dir"));
if *use_renameat {
inst.server
.renameat(&inst.next_meta(), fid(1), b"foo", fid(4), b"baz")
.unwrap();
inst.server
.walk(
&inst.next_meta(),
fid(0),
fid(5),
&[b"other-dir", b"nested-dir", b"baz"],
)
.unwrap();
verify_file(&mut inst, fid(5), b"other-dir/nested-dir/baz");
} else {
inst.server
.rename(&inst.next_meta(), fid(2), fid(4), b"baz")
.unwrap();
verify_file(&mut inst, fid(2), b"other-dir/nested-dir/baz");
}
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(&mut inst, fid(1));
inst.server.clunk(&inst.next_meta(), fid(2)).unwrap();
verify_closed(&mut inst, fid(2));
inst.server.clunk(&inst.next_meta(), fid(3)).unwrap();
verify_closed(&mut inst, fid(3));
inst.server.clunk(&inst.next_meta(), fid(4)).unwrap();
verify_closed(&mut inst, fid(4));
}
}
#[test]
fn files_and_directories_std() {
for ver in &[ProtocolVersion::Original, ProtocolVersion::Unix] {
let mut inst = instance(*ver);
attach(&mut inst);
verify_dir(&mut inst, fid(0), None);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[])
.unwrap();
inst.server
.create(
&inst.next_meta(),
fid(1),
b"dir",
FileType::DMDIR | FileType::from_bits(0o770).unwrap(),
SimpleOpenMode::O_READ,
None,
)
.unwrap();
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(&mut inst, fid(1));
inst.server
.walk(&inst.next_meta(), fid(0), fid(2), &[b"dir"])
.unwrap();
verify_dir(&mut inst, fid(2), Some(b"dir"));
inst.server
.create(
&inst.next_meta(),
fid(2),
b"file",
FileType::from_bits(0o660).unwrap(),
SimpleOpenMode::O_RDWR,
None,
)
.unwrap();
verify_file(&mut inst, fid(2), b"dir/file");
let body: &[u8] = b"Hello, world!\n";
assert_eq!(
inst.server
.write(&inst.next_meta(), fid(2), 0, body)
.unwrap() as usize,
body.len()
);
let mut actual = [0u8; 512];
let size = inst
.server
.read(&inst.next_meta(), fid(2), 0, &mut actual)
.unwrap() as usize;
assert_eq!(body, &actual[0..size]);
let size = inst
.server
.read(&inst.next_meta(), fid(2), 2, &mut actual)
.unwrap() as usize;
assert_eq!(&body[2..], &actual[0..size]);
verify_file(&mut inst, fid(2), b"dir/file");
inst.server.clunk(&inst.next_meta(), fid(2)).unwrap();
verify_closed(&mut inst, fid(2));
inst.server
.walk(&inst.next_meta(), fid(0), fid(3), &[b"dir"])
.unwrap();
verify_dir(&mut inst, fid(3), Some(b"dir"));
let type_bits = FileType::DMDIR
| FileType::DMSYMLINK
| FileType::DMDEVICE
| FileType::DMNAMEDPIPE
| FileType::DMSOCKET;
let setid_bits = FileType::DMSETUID | FileType::DMSETGID;
let st = inst.server.stat(&inst.next_meta(), fid(3)).unwrap();
assert_eq!(st.name(), b"dir");
assert_eq!(st.mode().unwrap() & type_bits, FileType::DMDIR);
assert_eq!(st.mode().unwrap() & setid_bits, FileType::empty());
inst.server.clunk(&inst.next_meta(), fid(3)).unwrap();
verify_closed(&mut inst, fid(3));
inst.server
.walk(&inst.next_meta(), fid(0), fid(4), &[b"dir", b"file"])
.unwrap();
verify_file(&mut inst, fid(4), b"dir/file");
let st = inst.server.stat(&inst.next_meta(), fid(4)).unwrap();
assert_eq!(st.name(), b"file");
assert_eq!(st.mode().unwrap() & type_bits, FileType::empty());
assert_eq!(st.mode().unwrap() & setid_bits, FileType::empty());
inst.server.clunk(&inst.next_meta(), fid(4)).unwrap();
verify_closed(&mut inst, fid(4));
}
}
#[test]
fn files_and_directories_linux() {
let mut inst = instance(ProtocolVersion::Linux);
attach(&mut inst);
verify_dir(&mut inst, fid(0), None);
inst.server
.walk(&inst.next_meta(), fid(0), fid(1), &[])
.unwrap();
inst.server
.mkdir(&inst.next_meta(), fid(1), b"dir", 0o770, u32::MAX)
.unwrap();
inst.server.clunk(&inst.next_meta(), fid(1)).unwrap();
verify_closed(&mut inst, fid(1));
inst.server
.walk(&inst.next_meta(), fid(0), fid(2), &[b"dir"])
.unwrap();
verify_dir(&mut inst, fid(2), Some(b"dir"));
inst.server
.create(
&inst.next_meta(),
fid(2),
b"file",
FileType::from_bits(0o660).unwrap(),
SimpleOpenMode::O_RDWR,
None,
)
.unwrap();
verify_file(&mut inst, fid(2), b"dir/file");
let body: &[u8] = b"Hello, world!\n";
assert_eq!(
inst.server
.write(&inst.next_meta(), fid(2), 0, body)
.unwrap() as usize,
body.len()
);
let mut actual = [0u8; 512];
let size = inst
.server
.read(&inst.next_meta(), fid(2), 0, &mut actual)
.unwrap() as usize;
assert_eq!(body, &actual[0..size]);
let size = inst
.server
.read(&inst.next_meta(), fid(2), 2, &mut actual)
.unwrap() as usize;
assert_eq!(&body[2..], &actual[0..size]);
verify_file(&mut inst, fid(2), b"dir/file");
inst.server.clunk(&inst.next_meta(), fid(2)).unwrap();
verify_closed(&mut inst, fid(2));
}
}