mirror of
https://github.com/mgree/ffs.git
synced 2024-08-17 07:10:35 +03:00
Implement setattr
(#22)
The `setattr` interface implements `chmod`, `chown`, `truncate`, and some timing related things (for, e.g., `touch`). It's not clear I've covered _every_ base, but it certainly seems to allow for common use cases in bash without any error messages. Comes with some nice refactors (metadata on each `Inode`, `FS::attr` is now `Inode::attr`, which simplifies some weird code that made the only the borrow checker happy---nobody else).
This commit is contained in:
parent
2ec8fa6991
commit
4639d31cc6
@ -1,8 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH="$(pwd)/target/debug:$PATH"
|
||||
RUST_LOG="ffs=info"
|
||||
export RUST_LOG
|
||||
|
||||
TOTAL=0
|
||||
FAILED=0
|
||||
@ -16,7 +14,7 @@ for test in *.sh
|
||||
do
|
||||
tname="$(basename ${test%*.sh})"
|
||||
printf "========== STARTING TEST: $tname\n"
|
||||
(./${test} >$LOG/$tname.out 2>$LOG/$tname.nerr; echo $?>$LOG/$tname.ec) &
|
||||
(RUST_LOG="ffs=debug"; export RUST_LOG; ./${test} >$LOG/$tname.out 2>$LOG/$tname.nerr; echo $?>$LOG/$tname.ec) &
|
||||
: $((TOTAL += 1))
|
||||
|
||||
# don't slam 'em
|
||||
@ -46,5 +44,6 @@ do
|
||||
done
|
||||
|
||||
printf "$((TOTAL - FAILED))/$((TOTAL)) tests passed\n"
|
||||
|
||||
rm -r $LOG
|
||||
[ $FAILED -eq 0 ] || exit 1
|
||||
|
@ -1,3 +1,4 @@
|
||||
use fuser::FileType;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::format::Format;
|
||||
@ -6,7 +7,6 @@ use super::format::Format;
|
||||
pub struct Config {
|
||||
pub input_format: Format,
|
||||
pub output_format: Format,
|
||||
pub timestamp: std::time::SystemTime,
|
||||
pub uid: u32,
|
||||
pub gid: u32,
|
||||
pub filemode: u16,
|
||||
@ -43,6 +43,15 @@ impl Config {
|
||||
.replace("=", "equal")
|
||||
.replace(" ", "space")
|
||||
}
|
||||
|
||||
/// Determines the default mode of a file
|
||||
pub fn mode(&self, kind: FileType) -> u16 {
|
||||
if kind == FileType::Directory {
|
||||
self.dirmode
|
||||
} else {
|
||||
self.filemode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -50,7 +59,6 @@ impl Default for Config {
|
||||
Config {
|
||||
input_format: Format::Json,
|
||||
output_format: Format::Json,
|
||||
timestamp: std::time::SystemTime::now(),
|
||||
uid: 501,
|
||||
gid: 501,
|
||||
filemode: 0o644,
|
||||
|
@ -282,11 +282,7 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
inodes[inum as usize] = Some(Inode {
|
||||
parent,
|
||||
inum,
|
||||
entry,
|
||||
});
|
||||
inodes[inum as usize] = Some(Inode::new(parent, inum, entry, config));
|
||||
}
|
||||
assert_eq!(inodes.len() as u64, next_id);
|
||||
}
|
||||
|
430
src/fs.rs
430
src/fs.rs
@ -13,7 +13,7 @@ use fuser::{
|
||||
#[cfg(target_os = "macos")]
|
||||
use fuser::ReplyXTimes;
|
||||
|
||||
use tracing::{debug, info, instrument, warn};
|
||||
use tracing::{info, instrument, trace, warn};
|
||||
|
||||
use super::config::{Config, Output};
|
||||
|
||||
@ -39,11 +39,39 @@ const TTL: Duration = Duration::from_secs(300);
|
||||
/// An inode, the core structure in the filesystem.
|
||||
#[derive(Debug)]
|
||||
pub struct Inode {
|
||||
/// Inode number of the parent of the current inode.
|
||||
///
|
||||
/// For the root, it will be `FUSE_ROOT_ID`, i.e., itself.
|
||||
pub parent: u64,
|
||||
/// Inode number of this node. Will not be 0.
|
||||
pub inum: u64,
|
||||
/// User ID of the owner
|
||||
pub uid: u32,
|
||||
/// Group ID of the owner,
|
||||
pub gid: u32,
|
||||
/// Mode of this inode. Defaults to values set in `FS.config`, but calls to
|
||||
/// `mknod` and `mkdir` and `setattr` (as `chmod`) can change this.
|
||||
pub mode: u16,
|
||||
/// Time of last access
|
||||
pub atime: SystemTime,
|
||||
/// Time of last modification
|
||||
pub mtime: SystemTime,
|
||||
/// Time of last change
|
||||
pub ctime: SystemTime,
|
||||
/// Time of creation (macOS only)
|
||||
pub crtime: SystemTime,
|
||||
/// The actual file contents.
|
||||
pub entry: Entry,
|
||||
}
|
||||
|
||||
/// File contents. Either a `File` containing bytes or a `Directory`, mapping
|
||||
/// names to entries (see `DirEntry`)
|
||||
///
|
||||
/// Directories come in two kinds (per `DirType`): `DirType::Named` directories
|
||||
/// are conventional mappings of names to entries, but `DirType::List`
|
||||
/// directories only use name in the filesystem, and most of those names will be
|
||||
/// generated (see `format::fs_from_value`). When writing a `DirType::List`
|
||||
/// directory back out, only the sort order of the name matters.
|
||||
#[derive(Debug)]
|
||||
pub enum Entry {
|
||||
// TODO 2021-06-14 need a 'written' flag to determine whether or not to
|
||||
@ -52,6 +80,8 @@ pub enum Entry {
|
||||
Directory(DirType, HashMap<String, DirEntry>),
|
||||
}
|
||||
|
||||
/// Directory entries. We record the kind and inode (for faster
|
||||
/// `Filesystem::readdir`).
|
||||
#[derive(Debug)]
|
||||
pub struct DirEntry {
|
||||
pub kind: FileType,
|
||||
@ -80,28 +110,26 @@ impl FS {
|
||||
}
|
||||
}
|
||||
|
||||
fn fresh_inode(&mut self, parent: u64, entry: Entry) -> u64 {
|
||||
fn fresh_inode(&mut self, parent: u64, entry: Entry, uid: u32, gid: u32, mode: u32) -> u64 {
|
||||
self.dirty.set(true);
|
||||
|
||||
let inum = self.inodes.len() as u64;
|
||||
let mode = (mode & 0o777) as u16;
|
||||
|
||||
self.inodes.push(Some(Inode {
|
||||
parent,
|
||||
inum,
|
||||
entry,
|
||||
}));
|
||||
self.inodes
|
||||
.push(Some(Inode::with_mode(parent, inum, entry, uid, gid, mode)));
|
||||
|
||||
inum
|
||||
}
|
||||
|
||||
fn check_access(&self, req: &Request) -> bool {
|
||||
req.uid() == self.config.uid
|
||||
req.uid() == 0 || req.uid() == self.config.uid
|
||||
}
|
||||
|
||||
pub fn get(&self, inum: u64) -> Result<&Inode, FSError> {
|
||||
let idx = inum as usize;
|
||||
|
||||
if idx >= self.inodes.len() {
|
||||
if idx >= self.inodes.len() || idx == 0 {
|
||||
return Err(FSError::NoSuchInode(inum));
|
||||
}
|
||||
|
||||
@ -124,51 +152,6 @@ impl FS {
|
||||
}
|
||||
}
|
||||
|
||||
fn mode(&self, kind: FileType) -> u16 {
|
||||
if kind == FileType::Directory {
|
||||
self.config.dirmode
|
||||
} else {
|
||||
self.config.filemode
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the `FileAttr` of a given `Inode`. Much of this is computed each
|
||||
/// time: the size, the kind, permissions, and number of hard links.
|
||||
pub fn attr(&self, inode: &Inode) -> FileAttr {
|
||||
let size = inode.entry.size();
|
||||
let kind = inode.entry.kind();
|
||||
|
||||
let perm = self.mode(kind);
|
||||
|
||||
let nlink: u32 = match &inode.entry {
|
||||
Entry::Directory(_, files) => {
|
||||
2 + files
|
||||
.iter()
|
||||
.filter(|(_, de)| de.kind == FileType::Directory)
|
||||
.count() as u32
|
||||
}
|
||||
Entry::File(_) => 1,
|
||||
};
|
||||
|
||||
FileAttr {
|
||||
ino: inode.inum,
|
||||
atime: self.config.timestamp,
|
||||
crtime: self.config.timestamp,
|
||||
ctime: self.config.timestamp,
|
||||
mtime: self.config.timestamp,
|
||||
nlink,
|
||||
size,
|
||||
blksize: 1,
|
||||
blocks: size,
|
||||
kind,
|
||||
uid: self.config.uid,
|
||||
gid: self.config.gid,
|
||||
perm,
|
||||
rdev: 0,
|
||||
flags: 0, // weird macOS thing
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to synchronize the in-memory `FS` with its on-disk representation.
|
||||
///
|
||||
/// Depending on output conventions and the state of the `FS`, nothing may
|
||||
@ -182,7 +165,7 @@ impl FS {
|
||||
#[instrument(level = "debug", skip(self), fields(synced = self.dirty.get(), dirty = self.dirty.get()))]
|
||||
pub fn sync(&self, last_sync: bool) {
|
||||
info!("called");
|
||||
debug!("{:?}", self.inodes);
|
||||
trace!("{:?}", self.inodes);
|
||||
|
||||
if self.synced.get() && !self.dirty.get() {
|
||||
info!("skipping sync; already synced and not dirty");
|
||||
@ -203,6 +186,67 @@ impl FS {
|
||||
}
|
||||
}
|
||||
|
||||
impl Inode {
|
||||
pub fn new(parent: u64, inum: u64, entry: Entry, config: &Config) -> Self {
|
||||
let mode = config.mode(entry.kind());
|
||||
let uid = config.uid;
|
||||
let gid = config.gid;
|
||||
Inode::with_mode(parent, inum, entry, uid, gid, mode)
|
||||
}
|
||||
|
||||
pub fn with_mode(parent: u64, inum: u64, entry: Entry, uid: u32, gid: u32, mode: u16) -> Self {
|
||||
let now = SystemTime::now();
|
||||
|
||||
Inode {
|
||||
parent,
|
||||
inum,
|
||||
uid,
|
||||
gid,
|
||||
mode,
|
||||
entry,
|
||||
atime: now,
|
||||
crtime: now,
|
||||
ctime: now,
|
||||
mtime: now,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the `FileAttr` of a given `Inode`. Some of this is computed each
|
||||
/// time: the size, the kind, permissions, and number of hard links.
|
||||
pub fn attr(&self) -> FileAttr {
|
||||
let size = self.entry.size();
|
||||
let kind = self.entry.kind();
|
||||
|
||||
let nlink: u32 = match &self.entry {
|
||||
Entry::Directory(_, files) => {
|
||||
2 + files
|
||||
.iter()
|
||||
.filter(|(_, de)| de.kind == FileType::Directory)
|
||||
.count() as u32
|
||||
}
|
||||
Entry::File(_) => 1,
|
||||
};
|
||||
|
||||
FileAttr {
|
||||
ino: self.inum,
|
||||
atime: self.atime,
|
||||
crtime: self.crtime,
|
||||
ctime: self.ctime,
|
||||
mtime: self.mtime,
|
||||
nlink,
|
||||
size,
|
||||
blksize: 1,
|
||||
blocks: size,
|
||||
kind,
|
||||
uid: self.uid,
|
||||
gid: self.gid,
|
||||
perm: self.mode,
|
||||
rdev: 0,
|
||||
flags: 0, // weird macOS thing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
/// Computes the size of an entry
|
||||
///
|
||||
@ -269,7 +313,7 @@ impl Filesystem for FS {
|
||||
match self.get(inode) {
|
||||
Ok(inode) => {
|
||||
// cribbed from https://github.com/cberner/fuser/blob/4639a490f4aa7dfe8a342069a761d4cf2bd8f821/examples/simple.rs#L1703-L1736
|
||||
let attr = self.attr(inode);
|
||||
let attr = inode.attr();
|
||||
let mode = attr.perm as i32;
|
||||
|
||||
if req.uid() == 0 {
|
||||
@ -329,7 +373,7 @@ impl Filesystem for FS {
|
||||
Ok(inode) => inode,
|
||||
};
|
||||
|
||||
reply.entry(&TTL, &self.attr(file), 0);
|
||||
reply.entry(&TTL, &file.attr(), 0);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
@ -349,7 +393,186 @@ impl Filesystem for FS {
|
||||
Ok(inode) => inode,
|
||||
};
|
||||
|
||||
reply.attr(&TTL, &self.attr(file));
|
||||
reply.attr(&TTL, &file.attr());
|
||||
}
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
skip(
|
||||
self, req, reply, mode, uid, gid, size, atime, mtime, _ctime, _fh, _crtime, _chgtime,
|
||||
_bkuptime, _flags
|
||||
)
|
||||
)]
|
||||
fn setattr(
|
||||
&mut self,
|
||||
req: &Request<'_>,
|
||||
ino: u64,
|
||||
mode: Option<u32>,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
size: Option<u64>,
|
||||
atime: Option<TimeOrNow>,
|
||||
mtime: Option<TimeOrNow>,
|
||||
_ctime: Option<SystemTime>,
|
||||
_fh: Option<u64>,
|
||||
_crtime: Option<SystemTime>,
|
||||
_chgtime: Option<SystemTime>,
|
||||
_bkuptime: Option<SystemTime>,
|
||||
_flags: Option<u32>,
|
||||
reply: ReplyAttr,
|
||||
) {
|
||||
info!("called");
|
||||
|
||||
if !self.check_access(req) {
|
||||
reply.error(libc::EPERM);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(mode) = mode {
|
||||
info!("chmod to {:o}", mode);
|
||||
|
||||
if mode != mode & 0o777 {
|
||||
info!("truncating mode {:o} to {:o}", mode, mode & 0o777);
|
||||
}
|
||||
let mode = (mode as u16) & 0o777;
|
||||
|
||||
match self.get_mut(ino) {
|
||||
Ok(inode) => {
|
||||
inode.mode = mode;
|
||||
reply.attr(&TTL, &inode.attr());
|
||||
return;
|
||||
}
|
||||
Err(_) => {
|
||||
reply.error(libc::ENOENT);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// cribbing from https://github.com/cberner/fuser/blob/13557921548930afd6b70e109521044fea98c23b/examples/simple.rs#L594-L639
|
||||
if uid.is_some() || gid.is_some() {
|
||||
info!("chown called with uid {:?} guid {:?}", uid, gid);
|
||||
|
||||
// gotta be a member of the target group!
|
||||
if let Some(gid) = gid {
|
||||
let groups = groups_for(req.uid());
|
||||
if req.uid() != 0 && !groups.contains(&gid) {
|
||||
reply.error(libc::EPERM);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let inode = match self.get_mut(ino) {
|
||||
Ok(inode) => inode,
|
||||
Err(_) => {
|
||||
reply.error(libc::ENOENT);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// non-root owner can only do noop uid changes
|
||||
if let Some(uid) = uid {
|
||||
if req.uid() != 0 && !(uid == inode.uid && req.uid() == inode.uid) {
|
||||
reply.error(libc::EPERM);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// only owner may change the group
|
||||
if gid.is_some() && req.uid() != 0 && req.uid() != inode.uid {
|
||||
reply.error(libc::EPERM);
|
||||
return;
|
||||
}
|
||||
|
||||
// NB if we allowed SETUID/SETGID bits, we might need to clear them here
|
||||
if let Some(uid) = uid {
|
||||
inode.uid = uid;
|
||||
}
|
||||
|
||||
if let Some(gid) = gid {
|
||||
inode.gid = gid;
|
||||
}
|
||||
|
||||
inode.ctime = SystemTime::now();
|
||||
reply.attr(&TTL, &inode.attr());
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(size) = size {
|
||||
info!("truncate() to {}", size);
|
||||
|
||||
match self.get_mut(ino) {
|
||||
Ok(inode) => match &mut inode.entry {
|
||||
Entry::File(contents) => {
|
||||
contents.resize(size as usize, 0);
|
||||
reply.attr(&TTL, &inode.attr());
|
||||
}
|
||||
Entry::Directory(..) => {
|
||||
reply.error(libc::EISDIR);
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
reply.error(libc::ENOENT);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
self.dirty.set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
let now = SystemTime::now();
|
||||
let mut set_time = false;
|
||||
if let Some(atime) = atime {
|
||||
info!("setting atime");
|
||||
if !self.check_access(req) {
|
||||
reply.error(libc::EPERM);
|
||||
return;
|
||||
}
|
||||
match self.get_mut(ino) {
|
||||
Ok(inode) => {
|
||||
inode.atime = match atime {
|
||||
TimeOrNow::Now => now,
|
||||
TimeOrNow::SpecificTime(time) => time,
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
reply.error(libc::ENOENT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
set_time = true;
|
||||
}
|
||||
|
||||
if let Some(mtime) = mtime {
|
||||
info!("setting mtime");
|
||||
|
||||
if !self.check_access(req) {
|
||||
reply.error(libc::EPERM);
|
||||
return;
|
||||
}
|
||||
match self.get_mut(ino) {
|
||||
Ok(inode) => {
|
||||
inode.mtime = match mtime {
|
||||
TimeOrNow::Now => now,
|
||||
TimeOrNow::SpecificTime(time) => time,
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
reply.error(libc::ENOENT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
set_time = true;
|
||||
}
|
||||
|
||||
if set_time {
|
||||
reply.attr(&TTL, &self.get(ino).unwrap().attr());
|
||||
} else {
|
||||
reply.error(libc::ENOSYS);
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, _req, reply))]
|
||||
@ -514,7 +737,7 @@ impl Filesystem for FS {
|
||||
};
|
||||
|
||||
// allocate the inode (sets dirty bit)
|
||||
let inum = self.fresh_inode(parent, entry);
|
||||
let inum = self.fresh_inode(parent, entry, req.uid(), req.gid(), mode);
|
||||
|
||||
// update the parent
|
||||
// NB we can't get_mut the parent earlier due to borrowing restrictions
|
||||
@ -528,8 +751,8 @@ impl Filesystem for FS {
|
||||
},
|
||||
};
|
||||
|
||||
reply.entry(&TTL, &self.get(inum).unwrap().attr(), 0);
|
||||
assert!(self.dirty.get());
|
||||
reply.entry(&TTL, &self.attr(self.get(inum).unwrap()), 0);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, req, reply))]
|
||||
@ -544,10 +767,6 @@ impl Filesystem for FS {
|
||||
) {
|
||||
info!("called");
|
||||
|
||||
let mode = mode & 0o777; // high bits are sometimes set, e.g., 0o40755. no idea.
|
||||
if mode != self.config.dirmode as u32 {
|
||||
warn!("Given mode {:o}, using {}", mode, self.config.dirmode);
|
||||
}
|
||||
if !self.check_access(req) {
|
||||
reply.error(libc::EACCES);
|
||||
return;
|
||||
@ -587,7 +806,7 @@ impl Filesystem for FS {
|
||||
let kind = FileType::Directory;
|
||||
|
||||
// allocate the inode (sets dirty bit)
|
||||
let inum = self.fresh_inode(parent, entry);
|
||||
let inum = self.fresh_inode(parent, entry, req.uid(), req.gid(), mode);
|
||||
|
||||
// update the parent
|
||||
// NB we can't get_mut the parent earlier due to borrowing restrictions
|
||||
@ -601,8 +820,8 @@ impl Filesystem for FS {
|
||||
},
|
||||
};
|
||||
|
||||
reply.entry(&TTL, &self.get(inum).unwrap().attr(), 0);
|
||||
assert!(self.dirty.get());
|
||||
reply.entry(&TTL, &self.attr(self.get(inum).unwrap()), 0);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, req, reply))]
|
||||
@ -1046,30 +1265,6 @@ impl Filesystem for FS {
|
||||
#[instrument(level = "debug", skip(self, _req))]
|
||||
fn forget(&mut self, _req: &Request<'_>, _ino: u64, _nlookup: u64) {}
|
||||
|
||||
#[instrument(level = "debug", skip(self, _req, reply))]
|
||||
fn setattr(
|
||||
&mut self,
|
||||
_req: &Request<'_>,
|
||||
_ino: u64,
|
||||
_mode: Option<u32>,
|
||||
_uid: Option<u32>,
|
||||
_gid: Option<u32>,
|
||||
_size: Option<u64>,
|
||||
_atime: Option<TimeOrNow>,
|
||||
_mtime: Option<TimeOrNow>,
|
||||
_ctime: Option<SystemTime>,
|
||||
_fh: Option<u64>,
|
||||
_crtime: Option<SystemTime>,
|
||||
_chgtime: Option<SystemTime>,
|
||||
_bkuptime: Option<SystemTime>,
|
||||
_flags: Option<u32>,
|
||||
reply: ReplyAttr,
|
||||
) {
|
||||
info!("called");
|
||||
|
||||
reply.error(libc::ENOSYS);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, _req, reply))]
|
||||
fn readlink(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyData) {
|
||||
info!("called");
|
||||
@ -1334,3 +1529,58 @@ impl Filesystem for FS {
|
||||
reply.error(libc::ENOSYS);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the group IDs a user is in
|
||||
#[cfg(target_os = "macos")]
|
||||
fn groups_for(uid: u32) -> Vec<u32> {
|
||||
unsafe {
|
||||
let passwd = libc::getpwuid(uid);
|
||||
let name = (*passwd).pw_name;
|
||||
let basegid = (*passwd).pw_gid as i32;
|
||||
|
||||
// get the number of groups
|
||||
let mut ngroups = 0;
|
||||
libc::getgrouplist(name, basegid, std::ptr::null_mut(), &mut ngroups);
|
||||
|
||||
if ngroups == 0 {
|
||||
// BUG 2021-06-23 weird behavior on macos... :/
|
||||
ngroups = 50;
|
||||
}
|
||||
|
||||
let mut groups = vec![-1; ngroups as usize];
|
||||
loop {
|
||||
libc::getgrouplist(name, basegid, groups.as_mut_ptr(), &mut ngroups);
|
||||
|
||||
// if the last entry wasn't set, we're good
|
||||
if groups[groups.len() - 1] == -1 {
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise, there are more groups. oof, keep going.
|
||||
ngroups *= 2;
|
||||
groups.resize(ngroups as usize, 0);
|
||||
}
|
||||
groups
|
||||
.into_iter()
|
||||
.filter(|gid| gid != &-1)
|
||||
.map(|gid| gid as u32)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn groups_for(uid: u32) -> Vec<u32> {
|
||||
unsafe {
|
||||
let passwd = libc::getpwuid(uid);
|
||||
let name = (*passwd).pw_name;
|
||||
let basegid = (*passwd).pw_gid;
|
||||
|
||||
// get the number of groups
|
||||
let mut ngroups = 0;
|
||||
libc::getgrouplist(name, basegid, std::ptr::null_mut(), &mut ngroups);
|
||||
let mut groups = vec![0; ngroups as usize];
|
||||
let res = libc::getgrouplist(name, basegid, groups.as_mut_ptr(), &mut ngroups);
|
||||
assert_eq!(res, ngroups);
|
||||
groups
|
||||
}
|
||||
}
|
||||
|
54
tests/chmod.sh
Executable file
54
tests/chmod.sh
Executable file
@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
|
||||
fail() {
|
||||
echo FAILED: $1
|
||||
if [ "$MNT" ]
|
||||
then
|
||||
cd
|
||||
umount "$MNT"
|
||||
rmdir "$MNT"
|
||||
rm "$TGT"
|
||||
rm "$TGT2"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
MNT=$(mktemp -d)
|
||||
TGT=$(mktemp)
|
||||
TGT2=$(mktemp)
|
||||
|
||||
ffs "$MNT" ../json/object.json >"$TGT" &
|
||||
PID=$!
|
||||
sleep 2
|
||||
echo 'echo hi' >"$MNT"/script
|
||||
chmod +x "$MNT"/script
|
||||
[ "$($MNT/script)" = "hi" ] || fail exec
|
||||
umount "$MNT" || fail unmount1
|
||||
sleep 1
|
||||
kill -0 $PID >/dev/null 2>&1 && fail process1
|
||||
|
||||
# easiest to just test using ffs, but would be cool to get outside validation
|
||||
[ -f "$TGT" ] || fail output1
|
||||
[ -s "$TGT" ] || fail output2
|
||||
grep -e echo "$TGT" >/dev/null 2>&1 || fail grep
|
||||
ffs --no-output --source json "$MNT" "$TGT" >"$TGT2" &
|
||||
PID=$!
|
||||
sleep 2
|
||||
|
||||
case $(ls "$MNT") in
|
||||
(eyes*fingernails*human*name*script) ;;
|
||||
(*) fail ls;;
|
||||
esac
|
||||
|
||||
[ "$(cat $MNT/script)" = "echo hi" ] || fail contents
|
||||
|
||||
umount "$MNT" || fail unmount2
|
||||
sleep 1
|
||||
kill -0 $PID >/dev/null 2>&1 && fail process2
|
||||
|
||||
[ -f "$TGT2" ] || fail tgt2
|
||||
[ -s "$TGT2" ] && fail tgt2_nonempty
|
||||
|
||||
rmdir "$MNT" || fail mount
|
||||
rm "$TGT"
|
||||
rm "$TGT2"
|
36
tests/chown.sh
Executable file
36
tests/chown.sh
Executable file
@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
|
||||
fail() {
|
||||
echo FAILED: $1
|
||||
if [ "$MNT" ]
|
||||
then
|
||||
cd
|
||||
umount "$MNT"
|
||||
rmdir "$MNT"
|
||||
rm "$ERR"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
MNT=$(mktemp -d)
|
||||
ERR=$(mktemp)
|
||||
|
||||
RUST_LOG="ffs=debug" ffs -d --no-output "$MNT" ../json/object.json &
|
||||
PID=$!
|
||||
sleep 2
|
||||
chown :nobody "$MNT"/name 2>$ERR >&2 && fail "chgrp1: $(cat $ERR)"
|
||||
[ -s "$ERR" ] || fail "chgrp1 error: $(cat $ERR)"
|
||||
groups
|
||||
ls -l "$MNT"/name
|
||||
echo $(groups | cut -d' ' -f 1)
|
||||
chown :$(groups | cut -d' ' -f 1) "$MNT"/name 2>$ERR >&2 || fail "chgrp2: $(cat $ERR)"
|
||||
[ -s "$ERR" ] && fail "chgrp2 error: $(cat $ERR)"
|
||||
chown $(whoami) "$MNT"/name 2>$ERR >&2 || fail chown
|
||||
[ -s "$ERR" ] && fail "chown error: $(cat $ERR)"
|
||||
umount "$MNT" || fail unmount1
|
||||
sleep 1
|
||||
kill -0 $PID >/dev/null 2>&1 && fail process1
|
||||
|
||||
rmdir "$MNT" || fail mount
|
||||
rm "$ERR"
|
||||
|
@ -13,25 +13,28 @@ fail() {
|
||||
|
||||
MNT=$(mktemp -d)
|
||||
|
||||
umask 022
|
||||
|
||||
ffs --mode 666 "$MNT" ../json/object.json &
|
||||
PID=$!
|
||||
sleep 2
|
||||
cd "$MNT"
|
||||
ls -l eyes | grep -e 'rw-rw-rw-' >/dev/null 2>&1 || fail file1
|
||||
mkdir pockets
|
||||
ls -ld pockets | grep -e 'rwxrwxrwx' >/dev/null 2>&1 || fail dir1
|
||||
ls -ld pockets | grep -e 'rwxr-xr-x' >/dev/null 2>&1 || fail dir1
|
||||
cd - >/dev/null 2>&1
|
||||
umount "$MNT" || fail unmount1
|
||||
sleep 1
|
||||
kill -0 $PID >/dev/null 2>&1 && fail process1
|
||||
|
||||
umask 077
|
||||
ffs --mode 666 --dirmode 700 "$MNT" ../json/object.json &
|
||||
PID=$!
|
||||
sleep 2
|
||||
cd "$MNT"
|
||||
ls -l eyes | grep -e 'rw-rw-rw-' >/dev/null 2>&1 || fail file2
|
||||
mkdir pockets
|
||||
ls -ld pockets | grep -e 'rwx------' >/dev/null 2>&1 || fail dir2
|
||||
ls -ld pockets | grep -e 'rwx----' >/dev/null 2>&1 || fail dir2
|
||||
cd - >/dev/null 2>&1
|
||||
umount "$MNT" || fail unmount2
|
||||
sleep 1
|
||||
|
29
tests/touch.sh
Executable file
29
tests/touch.sh
Executable file
@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
|
||||
fail() {
|
||||
echo FAILED: $1
|
||||
if [ "$MNT" ]
|
||||
then
|
||||
cd
|
||||
umount "$MNT"
|
||||
rmdir "$MNT"
|
||||
rm "$ERR"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
MNT=$(mktemp -d)
|
||||
ERR=$(mktemp)
|
||||
|
||||
ffs --no-output "$MNT" ../json/object.json &
|
||||
PID=$!
|
||||
sleep 2
|
||||
touch "$MNT"/name 2>$ERR >&2 || { cat "$ERR"; fail touch; }
|
||||
[ -s "$ERR" ] && { cat "$ERR"; fail error ; }
|
||||
umount "$MNT" || fail unmount1
|
||||
sleep 1
|
||||
kill -0 $PID >/dev/null 2>&1 && fail process1
|
||||
|
||||
rmdir "$MNT" || fail mount
|
||||
rm "$ERR"
|
||||
|
56
tests/truncate.sh
Executable file
56
tests/truncate.sh
Executable file
@ -0,0 +1,56 @@
|
||||
#!/bin/sh
|
||||
|
||||
fail() {
|
||||
echo FAILED: $1
|
||||
if [ "$MNT" ]
|
||||
then
|
||||
cd
|
||||
umount "$MNT"
|
||||
rmdir "$MNT"
|
||||
rm "$TGT"
|
||||
rm "$TGT2"
|
||||
rm "$ERR"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
MNT=$(mktemp -d)
|
||||
TGT=$(mktemp)
|
||||
TGT2=$(mktemp)
|
||||
ERR=$(mktemp)
|
||||
|
||||
ffs "$MNT" ../json/object.json >"$TGT" &
|
||||
PID=$!
|
||||
sleep 2
|
||||
echo 'Mikey Indiana' >"$MNT"/name 2>"$ERR"
|
||||
[ -s "$ERR" ] && fail non-empty error
|
||||
umount "$MNT" || fail unmount1
|
||||
sleep 1
|
||||
kill -0 $PID >/dev/null 2>&1 && fail process1
|
||||
|
||||
# easiest to just test using ffs, but would be cool to get outside validation
|
||||
[ -f "$TGT" ] || fail output1
|
||||
[ -s "$TGT" ] || fail output2
|
||||
grep -e Indiana "$TGT" >/dev/null 2>&1 || fail grep
|
||||
ffs --no-output --source json "$MNT" "$TGT" >"$TGT2" &
|
||||
PID=$!
|
||||
sleep 2
|
||||
|
||||
case $(ls "$MNT") in
|
||||
(eyes*fingernails*human*name) ;;
|
||||
(*) fail ls;;
|
||||
esac
|
||||
|
||||
[ "$(cat $MNT/name)" = "Mikey Indiana" ] || fail contents
|
||||
|
||||
umount "$MNT" || fail unmount2
|
||||
sleep 1
|
||||
kill -0 $PID >/dev/null 2>&1 && fail process2
|
||||
|
||||
[ -f "$TGT2" ] || fail tgt2
|
||||
[ -s "$TGT2" ] && fail tgt2_nonempty
|
||||
|
||||
rmdir "$MNT" || fail mount
|
||||
rm "$TGT"
|
||||
rm "$TGT2"
|
||||
rm "$ERR"
|
Loading…
Reference in New Issue
Block a user