use std::path::PathBuf;
use crate::{
errors::{RvResult, VfsError},
sys::{Entry, VfsEntry},
};
pub struct Chmod {
pub(crate) opts: ChmodOpts,
pub(crate) exec: Box<dyn Fn(ChmodOpts) -> RvResult<()>>, }
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ChmodOpts {
pub(crate) path: PathBuf, pub(crate) dirs: u32, pub(crate) files: u32, pub(crate) follow: bool, pub(crate) recursive: bool, pub(crate) sym: String, }
impl Chmod {
pub fn all(mut self, mode: u32) -> Self {
self.opts.dirs = mode;
self.opts.files = mode;
self
}
pub fn dirs(mut self, mode: u32) -> Self {
self.opts.dirs = mode;
self
}
pub fn files(mut self, mode: u32) -> Self {
self.opts.files = mode;
self
}
pub fn follow(mut self) -> Self {
self.opts.follow = true;
self
}
pub fn readonly(mut self) -> Self {
self.opts.sym = "f:a+r,f:a-wx".to_string();
self
}
pub fn recurse(mut self) -> Self {
self.opts.recursive = true;
self
}
pub fn no_recurse(mut self) -> Self {
self.opts.recursive = false;
self
}
pub fn secure(mut self) -> Self {
self.opts.sym = "a:go-rwx".to_string();
self
}
pub fn sym(mut self, symbolic: &str) -> Self {
self.opts.sym = symbolic.into();
self
}
pub fn exec(&self) -> RvResult<()> {
(self.exec)(self.opts.clone())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum State {
Target,
Group,
Perms,
}
pub(crate) fn mode(entry: &VfsEntry, octal: u32, sym: &str) -> RvResult<u32> {
if octal != 0 {
return Ok(octal);
}
if sym.is_empty() {
return Ok(0);
}
let mut mode = entry.mode();
let mut group = 0;
let mut op = '0';
let mut chars: Vec<char> = sym.chars().rev().collect();
let mut state = State::Target;
while let Some(mut c) = chars.pop() {
match state {
State::Target => {
group = 0; op = '0';
loop {
if c != 'd' && c != 'f' && c != 'a' && c != ':' {
return Err(VfsError::InvalidChmodTarget(sym.to_string()).into());
}
if entry.is_symlink() || (c == 'd' && !entry.is_dir()) || (c == 'f' && !entry.is_file()) {
return Ok(mode); } else if c == ':' {
state = State::Group;
break;
}
c = _pop(&mut chars, sym)?;
}
},
State::Group => {
loop {
match c {
'u' => group |= 0o0700,
'g' => group |= 0o0070,
'o' => group |= 0o0007,
'a' => group |= 0o0777,
'-' | '+' | '=' => {
op = c;
state = State::Perms;
break;
},
_ => return Err(VfsError::InvalidChmodGroup(sym.to_string()).into()),
}
c = _pop(&mut chars, sym)?;
}
if group == 0 {
return Err(VfsError::InvalidChmodGroup(sym.to_string()).into());
}
if op == '0' {
return Err(VfsError::InvalidChmodOp(sym.to_string()).into());
}
},
State::Perms => {
let mut perm = 0;
while state == State::Perms {
match c {
'r' | 'w' | 'x' => {
match c {
'r' => perm |= 0o0444,
'w' => perm |= 0o0222,
_ => perm |= 0o0111,
}
if !chars.is_empty() {
c = chars.pop().unwrap();
} else {
break;
}
},
',' => {
state = State::Target;
},
_ => return Err(VfsError::InvalidChmodPermissions(sym.to_string()).into()),
}
}
if perm == 0 {
return Err(VfsError::InvalidChmodPermissions(sym.to_string()).into());
}
match op {
'-' => mode &= !(group & perm),
'+' => mode |= group & perm,
_ => mode = (!group & mode) | (group & perm),
}
},
}
}
Ok(mode)
}
fn _pop(chars: &mut Vec<char>, sym: &str) -> RvResult<char> {
if !chars.is_empty() {
Ok(chars.pop().unwrap())
} else {
Err(VfsError::InvalidChmod(sym.to_string()).into())
}
}
pub(crate) fn revoking_mode(old: u32, new: u32) -> bool {
old & 0o0500 > new & 0o0500 || old & 0o0050 > new & 0o0050 || old & 0o0005 > new & 0o0005
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
#[test]
fn test_vfs_chmod() {
test_chmod(assert_vfs_setup!(Vfs::memfs()));
test_chmod(assert_vfs_setup!(Vfs::stdfs()));
}
fn test_chmod((vfs, tmpdir): (Vfs, PathBuf)) {
let file1 = tmpdir.mash("file1");
assert_vfs_mkfile!(vfs, &file1);
assert!(vfs.chmod(&file1, 0o644).is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100644);
assert!(vfs.chmod(&file1, 0o555).is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100555);
assert_vfs_remove_all!(vfs, &tmpdir);
}
#[test]
fn test_vfs_chmod_b() {
test_chmod_b(assert_vfs_setup!(Vfs::memfs()));
test_chmod_b(assert_vfs_setup!(Vfs::stdfs()));
}
fn test_chmod_b((vfs, tmpdir): (Vfs, PathBuf)) {
let dir1 = tmpdir.mash("dir1");
let file1 = dir1.mash("file1");
let dir2 = dir1.mash("dir2");
let file2 = dir2.mash("file2");
assert_eq!(vfs.mkdir_p(&dir1).unwrap(), dir1);
assert_eq!(vfs.mkdir_p(&dir2).unwrap(), dir2);
assert_eq!(vfs.mkfile_m(&file1, 0o644).unwrap(), file1);
assert_eq!(vfs.mkfile_m(&file2, 0o644).unwrap(), file2);
assert!(vfs.chmod_b(&dir1).unwrap().all(0o600).exec().is_ok());
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40600);
assert!(vfs.chmod_b(&dir1).unwrap().dirs(0o755).exec().is_ok());
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40755);
assert_eq!(vfs.mode(&file1).unwrap(), 0o100600);
assert_eq!(vfs.mode(&dir2).unwrap(), 0o40755);
assert_eq!(vfs.mode(&file2).unwrap(), 0o100600);
assert!(vfs.chmod_b(&dir1).unwrap().files(0o644).exec().is_ok());
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40755);
assert_eq!(vfs.mode(&file1).unwrap(), 0o100644);
assert_eq!(vfs.mode(&dir2).unwrap(), 0o40755);
assert_eq!(vfs.mode(&file2).unwrap(), 0o100644);
assert!(vfs.chmod_b(&dir1).unwrap().all(0o600).exec().is_ok());
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40600);
assert!(vfs.chmod_b(&dir1).unwrap().dirs(0o755).files(0o644).exec().is_ok());
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40755);
assert_eq!(vfs.mode(&file1).unwrap(), 0o100644);
assert_eq!(vfs.mode(&dir2).unwrap(), 0o40755);
assert_eq!(vfs.mode(&file2).unwrap(), 0o100644);
assert!(vfs.chmod_b("bogus").unwrap().all(0o644).exec().is_err());
assert!(vfs.chmod_b("").is_err());
assert_vfs_remove_all!(vfs, &tmpdir);
}
#[test]
fn test_vfs_chmod_b_symbolic() {
test_chmod_b_symbolic(assert_vfs_setup!(Vfs::memfs()));
test_chmod_b_symbolic(assert_vfs_setup!(Vfs::stdfs()));
}
fn test_chmod_b_symbolic((vfs, tmpdir): (Vfs, PathBuf)) {
let file1 = tmpdir.mash("file1");
assert!(vfs.mkfile_m(&file1, 0o644).is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100644);
assert_eq!(vfs.is_exec(&file1), false);
assert!(vfs.chmod_b(&file1).unwrap().sym("f:a+x").exec().is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100755);
assert_eq!(vfs.is_exec(&file1), true);
assert!(vfs.chmod_b(&file1).unwrap().sym("f:a-x").exec().is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100644);
assert_eq!(vfs.is_exec(&file1), false);
assert!(vfs.chmod_b(&file1).unwrap().sym("f:a-w").exec().is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100444);
assert_eq!(vfs.is_readonly(&file1), true);
assert!(vfs.chmod_b(&file1).unwrap().sym("f:a+w").exec().is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100666);
assert_eq!(vfs.is_readonly(&file1), false);
assert!(vfs.chmod_b(&file1).unwrap().sym("f:a-r").exec().is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100222);
assert!(vfs.chmod_b(&file1).unwrap().sym("f:a+r").exec().is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100666);
assert!(vfs.chmod_b(&file1).unwrap().sym("f:a+rwx").exec().is_ok());
assert!(vfs.chmod_b(&file1).unwrap().secure().exec().is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100700);
assert!(vfs.chmod_b(&file1).unwrap().readonly().exec().is_ok());
assert_eq!(vfs.mode(&file1).unwrap(), 0o100444);
assert_eq!(vfs.is_readonly(&file1), true);
assert_vfs_remove_all!(vfs, &tmpdir);
}
#[test]
fn test_vfs_chmod_follow() {
test_chmod_follow(assert_vfs_setup!(Vfs::memfs()));
test_chmod_follow(assert_vfs_setup!(Vfs::stdfs()));
}
fn test_chmod_follow((vfs, tmpdir): (Vfs, PathBuf)) {
let dir1 = tmpdir.mash("dir1");
let file1 = dir1.mash("file1");
let link1 = tmpdir.mash("link1");
assert_eq!(vfs.mkdir_m(&dir1, 0o777).unwrap(), dir1);
assert_eq!(vfs.mkfile_m(&file1, 0o777).unwrap(), file1);
assert_eq!(vfs.symlink(&link1, &dir1).unwrap(), link1);
assert_eq!(vfs.mode(&link1).unwrap(), 0o120777);
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40777);
assert!(vfs.chmod(&link1, 0o555).is_ok());
assert_eq!(vfs.mode(&link1).unwrap(), 0o120777);
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40777);
assert_eq!(vfs.mode(&file1).unwrap(), 0o100777);
assert!(vfs.chmod_b(&link1).unwrap().no_recurse().dirs(0o755).files(0o444).exec().is_ok());
assert_eq!(vfs.mode(&link1).unwrap(), 0o120777);
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40777);
assert_eq!(vfs.mode(&file1).unwrap(), 0o100777);
assert!(vfs.chmod_b(&link1).unwrap().follow().dirs(0o755).files(0o444).exec().is_ok());
assert_eq!(vfs.mode(&link1).unwrap(), 0o120777);
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40755);
assert_eq!(vfs.mode(&file1).unwrap(), 0o100444);
assert_vfs_remove_all!(vfs, &tmpdir);
}
#[test]
fn test_vfs_chmod_recurse() {
test_chmod_recurse(assert_vfs_setup!(Vfs::memfs()));
test_chmod_recurse(assert_vfs_setup!(Vfs::stdfs()));
}
fn test_chmod_recurse((vfs, tmpdir): (Vfs, PathBuf)) {
let dir1 = tmpdir.mash("dir1");
let file1 = dir1.mash("file1");
let dir2 = dir1.mash("dir2");
let file2 = dir2.mash("file2");
assert_eq!(vfs.mkdir_m(&dir2, 0o777).unwrap(), dir2);
assert_eq!(vfs.mkfile_m(&file1, 0o777).unwrap(), file1);
assert_eq!(vfs.mkfile_m(&file2, 0o777).unwrap(), file2);
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40777);
assert_eq!(vfs.mode(&file1).unwrap(), 0o100777);
assert_eq!(vfs.mode(&dir2).unwrap(), 0o40777);
assert_eq!(vfs.mode(&file2).unwrap(), 0o100777);
assert!(vfs.chmod_b(&dir1).unwrap().no_recurse().dirs(0o755).files(0o644).exec().is_ok());
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40755);
assert_eq!(vfs.mode(&file1).unwrap(), 0o100777);
assert_eq!(vfs.mode(&dir2).unwrap(), 0o40777);
assert_eq!(vfs.mode(&file2).unwrap(), 0o100777);
assert!(vfs.chmod_b(&dir1).unwrap().dirs(0o755).files(0o644).exec().is_ok());
assert_eq!(vfs.mode(&dir1).unwrap(), 0o40755);
assert_eq!(vfs.mode(&file1).unwrap(), 0o100644);
assert_eq!(vfs.mode(&dir2).unwrap(), 0o40755);
assert_eq!(vfs.mode(&file2).unwrap(), 0o100644);
assert_vfs_remove_all!(vfs, &tmpdir);
}
#[test]
fn test_vfs_chmod_symbolic() {
test_chmod_symbolic(
Box::new(|m: u32| -> VfsEntry {
let mut entry = MemfsEntry::opts(PathBuf::new()).dir().build();
entry.mode = m;
entry.upcast()
}),
Box::new(|m: u32| -> VfsEntry {
let mut entry = MemfsEntry::opts(PathBuf::new()).file().build();
entry.mode = m;
entry.upcast()
}),
);
test_chmod_symbolic(
Box::new(|m: u32| -> VfsEntry {
StdfsEntry {
path: PathBuf::new(),
alt: PathBuf::new(),
rel: PathBuf::new(),
dir: true,
file: false,
link: false,
mode: m,
follow: false,
cached: false,
}
.upcast()
}),
Box::new(|m: u32| -> VfsEntry {
StdfsEntry {
path: PathBuf::new(),
alt: PathBuf::new(),
rel: PathBuf::new(),
dir: false,
file: true,
link: false,
mode: m,
follow: false,
cached: false,
}
.upcast()
}),
);
}
fn test_chmod_symbolic(d: Box<dyn Fn(u32) -> VfsEntry>, f: Box<dyn Fn(u32) -> VfsEntry>) {
assert_eq!(0o0700 & 0o0444 | 0o0000, 0o0400); assert_eq!(0o0770 & 0o0444 | 0o0000, 0o0440); assert_eq!(!(0o0700 & 0o0444) & 0o0444, 0o0044); assert_eq!(!(0o0770 & 0o0444) & 0o0444, 0o0004);
assert_eq!(sys::mode(&f(0o0000), 0, "a:a=rwx,a:g=rw,a:o=r").unwrap(), 0o0764);
assert_eq!(sys::mode(&f(0o0077), 0, "f:u=rwx,f:g=rw,f:o=r").unwrap(), 0o0764);
assert_eq!(sys::mode(&f(0o0077), 0, "f:u=rwx,f:g=rw,f:o-rwx").unwrap(), 0o0760);
assert_eq!(
sys::mode(&f(0o0300), 0, "sf:u+r").unwrap_err().to_string(),
"Invalid chmod target given: sf:u+r"
);
assert_eq!(sys::mode(&d(0o0300), 0, "f:u+r").unwrap(), 0o0300);
assert_eq!(sys::mode(&f(0o0300), 0, "d:u+r").unwrap(), 0o0300);
assert_eq!(sys::mode(&d(0o0300), 0, "a:u+r").unwrap(), 0o0700);
assert_eq!(sys::mode(&f(0o0300), 0, "a:u+r").unwrap(), 0o0700);
assert_eq!(sys::mode(&d(0o0300), 0, "ad:u+r").unwrap(), 0o0700);
assert_eq!(sys::mode(&f(0o0300), 0, "af:u+r").unwrap(), 0o0700);
assert_eq!(sys::mode(&d(0o0300), 0, "d:u+r").unwrap(), 0o0700);
assert_eq!(sys::mode(&d(0o0300), 0, "d:g+r").unwrap(), 0o0340);
assert_eq!(sys::mode(&d(0o0300), 0, "d:o+r").unwrap(), 0o0304);
assert_eq!(sys::mode(&f(0o0300), 0, "f:u+r").unwrap(), 0o0700);
assert_eq!(sys::mode(&f(0o0300), 0, "f:g+r").unwrap(), 0o0340);
assert_eq!(sys::mode(&f(0o0300), 0, "f:o+r").unwrap(), 0o0304);
assert_eq!(sys::mode(&f(0o0300), 0, "f:ug+r").unwrap(), 0o0740);
assert_eq!(sys::mode(&f(0o0300), 0, "f:uo+r").unwrap(), 0o0704);
assert_eq!(sys::mode(&f(0o0300), 0, "f:go+r").unwrap(), 0o0344);
assert_eq!(sys::mode(&f(0o0300), 0, "f:gu+r").unwrap(), 0o0740);
assert_eq!(sys::mode(&f(0o0300), 0, "f:gugugu+r").unwrap(), 0o0740);
assert_eq!(sys::mode(&f(0o0300), 0, "f:a+r").unwrap(), 0o0744);
assert_eq!(sys::mode(&f(0o0300), 0, "f:ugo+r").unwrap(), 0o0744);
assert_eq!(sys::mode(&f(0o0300), 0, "f:ugoa+r").unwrap(), 0o0744);
assert_eq!(sys::mode(&f(0o0000), 0, "f:u=rw").unwrap(), 0o0600);
assert_eq!(sys::mode(&f(0o0100), 0, "f:u=rw").unwrap(), 0o0600);
assert_eq!(sys::mode(&f(0o0200), 0, "f:u=rw").unwrap(), 0o0600);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u=rw").unwrap(), 0o0600);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u=rwrw").unwrap(), 0o0600);
assert_eq!(sys::mode(&f(0o0000), 0, "f:u-x").unwrap(), 0o0000);
assert_eq!(sys::mode(&f(0o0100), 0, "f:u-x").unwrap(), 0o0000);
assert_eq!(sys::mode(&f(0o0200), 0, "f:u-x").unwrap(), 0o0200);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u-x").unwrap(), 0o0400);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u-xx").unwrap(), 0o0400);
assert_eq!(sys::mode(&f(0o0000), 0, "f:u-w").unwrap(), 0o0000);
assert_eq!(sys::mode(&f(0o0100), 0, "f:u-w").unwrap(), 0o0100);
assert_eq!(sys::mode(&f(0o0200), 0, "f:u-w").unwrap(), 0o0000);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u-w").unwrap(), 0o0400);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u-www").unwrap(), 0o0400);
assert_eq!(sys::mode(&f(0o0000), 0, "f:u+w").unwrap(), 0o0200);
assert_eq!(sys::mode(&f(0o0100), 0, "f:u+w").unwrap(), 0o0300);
assert_eq!(sys::mode(&f(0o0200), 0, "f:u+w").unwrap(), 0o0200);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u+w").unwrap(), 0o0600);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u+www").unwrap(), 0o0600);
assert_eq!(sys::mode(&f(0o0000), 0, "f:u+x").unwrap(), 0o0100);
assert_eq!(sys::mode(&f(0o0100), 0, "f:u+x").unwrap(), 0o0100);
assert_eq!(sys::mode(&f(0o0200), 0, "f:u+x").unwrap(), 0o0300);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u+x").unwrap(), 0o0500);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u+xx").unwrap(), 0o0500);
assert_eq!(sys::mode(&f(0o0000), 0, "f:u+rwx").unwrap(), 0o0700);
assert_eq!(sys::mode(&f(0o0100), 0, "f:u+rwx").unwrap(), 0o0700);
assert_eq!(sys::mode(&f(0o0200), 0, "f:u+rwx").unwrap(), 0o0700);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u+rwx").unwrap(), 0o0700);
assert_eq!(sys::mode(&f(0o0400), 0, "f:u+rwxrwx").unwrap(), 0o0700);
}
#[test]
fn test_revoking_mode() {
assert_eq!(sys::revoking_mode(0o0777, 0o0777), false);
assert_eq!(sys::revoking_mode(0o0776, 0o0775), false);
assert_eq!(sys::revoking_mode(0o0770, 0o0771), false);
assert_eq!(sys::revoking_mode(0o0776, 0o0772), true);
assert_eq!(sys::revoking_mode(0o0775, 0o0776), true);
assert_eq!(sys::revoking_mode(0o0775, 0o0774), true);
assert_eq!(sys::revoking_mode(0o0777, 0o0777), false);
assert_eq!(sys::revoking_mode(0o0767, 0o0757), false);
assert_eq!(sys::revoking_mode(0o0707, 0o0717), false);
assert_eq!(sys::revoking_mode(0o0767, 0o0727), true);
assert_eq!(sys::revoking_mode(0o0757, 0o0767), true);
assert_eq!(sys::revoking_mode(0o0757, 0o0747), true);
assert_eq!(sys::revoking_mode(0o0777, 0o0777), false);
assert_eq!(sys::revoking_mode(0o0677, 0o0577), false);
assert_eq!(sys::revoking_mode(0o0077, 0o0177), false);
assert_eq!(sys::revoking_mode(0o0677, 0o0277), true);
assert_eq!(sys::revoking_mode(0o0577, 0o0677), true);
assert_eq!(sys::revoking_mode(0o0577, 0o0477), true);
assert_eq!(sys::revoking_mode(0o0577, 0o0177), true);
}
}