#[cfg(target_family = "unix")]
use std::io;
use std::fmt::{self, Debug, Display};
#[cfg(target_family = "unix")]
use std::path::Path;
#[cfg(target_family = "unix")]
use std::fs::{metadata, symlink_metadata, set_permissions, File};
use std::iter::FromIterator;
#[cfg(target_family = "unix")]
use std::os::unix::fs::PermissionsExt;
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
mod parser;
pub use parser::ModeParseError;
const S_IFMT: u32 = 0o170000;
const S_IFDIR: u32 = 0o040000;
const S_IFCHR: u32 = 0o020000;
const S_IFBLK: u32 = 0o060000;
const S_IFREG: u32 = 0o100000;
const S_IFIFO: u32 = 0o010000;
const S_IFLNK: u32 = 0o120000;
const S_IFSOCK: u32 = 0o140000;
const S_ISUID: u32 = 0o4000;
const S_ISGID: u32 = 0o2000;
const S_ISVTX: u32 = 0o1000;
const S_IREAD: u32 = 0o400;
const S_IWRITE: u32 = 0o200;
const S_IEXEC: u32 = 0o100;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum User {
Owner,
Group,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FileType {
Directory,
CharacterDevice,
BlockDevice,
RegularFile,
FIFO,
SymbolicLink,
Socket,
}
impl FileType {
pub fn from_mode(mode: u32) -> Option<FileType> {
use FileType::*;
match mode & S_IFMT {
S_IFDIR => Some(Directory),
S_IFCHR => Some(CharacterDevice),
S_IFBLK => Some(BlockDevice),
S_IFREG => Some(RegularFile),
S_IFIFO => Some(FIFO),
S_IFLNK => Some(SymbolicLink),
S_IFSOCK => Some(Socket),
_ => None
}
}
pub fn is_directory(&self) -> bool {
*self == FileType::Directory
}
pub fn is_character_device(&self) -> bool {
*self == FileType::CharacterDevice
}
pub fn is_block_device(&self) -> bool {
*self == FileType::BlockDevice
}
pub fn is_regular_file(&self) -> bool {
*self == FileType::RegularFile
}
pub fn is_fifo(&self) -> bool {
*self == FileType::FIFO
}
pub fn is_symbolic_link(&self) -> bool {
*self == FileType::SymbolicLink
}
pub fn is_socket(&self) -> bool {
*self == FileType::Socket
}
pub fn mode(&self) -> Mode {
use FileType::*;
let mode = match self {
Directory => S_IFDIR,
CharacterDevice => S_IFCHR,
BlockDevice => S_IFBLK,
RegularFile => S_IFREG,
FIFO => S_IFIFO,
SymbolicLink => S_IFLNK,
Socket => S_IFSOCK,
};
Mode::new(mode, S_IFMT)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProtectionBit {
Read,
Write,
Execute,
Search,
}
impl From<ProtectionBit> for Protection {
fn from(bit: ProtectionBit) -> Protection {
Protection::empty().with_set(bit)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum ExecuteOrSearch {
Execute(bool),
Search(bool),
}
#[derive(Debug, Clone)]
pub struct Protection {
read: Option<bool>,
write: Option<bool>,
execute: Option<ExecuteOrSearch>,
}
impl Protection {
pub fn empty() -> Protection {
Protection {
read: None,
write: None,
execute: None,
}
}
pub fn all_set() -> Protection {
Protection {
read: Some(true),
write: Some(true),
execute: Some(ExecuteOrSearch::Execute(true)),
}
}
pub fn all_clear() -> Protection {
Protection {
read: Some(false),
write: Some(false),
execute: Some(ExecuteOrSearch::Execute(false)),
}
}
pub fn from_mode_user(mode: &Mode, user: User) -> Protection {
let mut mask = mode.mask;
let mut dir_mask = mode.dir_mask;
let mut mode = mode.mode;
let owner_mask = S_IREAD | S_IWRITE | S_IEXEC;
let shift = Self::user_shift(user);
mode <<= shift;
mask <<= shift;
dir_mask <<= shift;
mode &= owner_mask;
mask &= owner_mask;
dir_mask &= owner_mask;
let mut ret = Protection::empty();
if mask & S_IREAD > 0 {
ret.read = Some(mode & S_IREAD > 0);
}
if mask & S_IWRITE > 0 {
ret.write = Some(mode & S_IWRITE > 0);
}
if mask & S_IEXEC > 0 {
if dir_mask & S_IEXEC > 0 {
ret.execute = Some(ExecuteOrSearch::Search(mode & S_IEXEC > 0));
} else {
ret.execute = Some(ExecuteOrSearch::Execute(mode & S_IEXEC > 0));
}
}
ret
}
pub fn is_read_set(&self) -> bool {
self.read == Some(true)
}
pub fn is_write_set(&self) -> bool {
self.write == Some(true)
}
pub fn is_execute_set(&self) -> bool {
self.execute == Some(ExecuteOrSearch::Execute(true))
}
pub fn is_search_set(&self) -> bool {
self.execute == Some(ExecuteOrSearch::Execute(true)) ||
self.execute == Some(ExecuteOrSearch::Search(true))
}
pub fn with_set(mut self, bit: ProtectionBit) -> Protection {
self.set(bit);
self
}
pub fn with_cleared(mut self, bit: ProtectionBit) -> Protection {
self.clear(bit);
self
}
pub fn with_forgotten(mut self, bit: ProtectionBit) -> Protection {
self.forget(bit);
self
}
pub fn set(&mut self, bit: ProtectionBit) {
match bit {
ProtectionBit::Read => self.read = Some(true),
ProtectionBit::Write => self.write = Some(true),
ProtectionBit::Execute => self.execute = Some(ExecuteOrSearch::Execute(true)),
ProtectionBit::Search => self.execute = Some(ExecuteOrSearch::Search(true)),
}
}
pub fn clear(&mut self, bit: ProtectionBit) {
match bit {
ProtectionBit::Read => self.read = Some(false),
ProtectionBit::Write => self.write = Some(false),
ProtectionBit::Execute => self.execute = Some(ExecuteOrSearch::Execute(false)),
ProtectionBit::Search => self.execute = Some(ExecuteOrSearch::Search(false)),
}
}
pub fn forget(&mut self, bit: ProtectionBit) {
match bit {
ProtectionBit::Read => self.read = None,
ProtectionBit::Write => self.write = None,
ProtectionBit::Execute | ProtectionBit::Search => self.execute = None,
}
}
fn user_shift(user: User) -> u32 {
match user {
User::Owner => 0,
User::Group => 3,
User::Other => 6,
}
}
pub fn for_user(&self, user: User) -> Mode {
let mut mode = Mode::empty();
if let Some(read) = self.read {
if read {
mode.mode |= S_IREAD;
} else {
mode.mode &= !S_IREAD;
}
mode.mask |= S_IREAD;
}
if let Some(write) = self.write {
if write {
mode.mode |= S_IWRITE;
} else {
mode.mode &= !S_IWRITE;
}
mode.mask |= S_IWRITE;
}
if let Some(execute) = self.execute {
match execute {
ExecuteOrSearch::Execute(execute) | ExecuteOrSearch::Search(execute) => if execute {
mode.mode |= S_IEXEC;
} else {
mode.mode &= !S_IEXEC;
}
}
mode.mask |= S_IEXEC;
if let ExecuteOrSearch::Search(_) = execute {
mode.dir_mask |= S_IEXEC;
}
}
let shift = Self::user_shift(user);
mode.mode >>= shift;
mode.mask >>= shift;
mode.dir_mask >>= shift;
mode
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SpecialBit {
SetId,
Sticky,
}
impl From<SpecialBit> for Special {
fn from(bit: SpecialBit) -> Special {
Special::empty().with_set(bit)
}
}
#[derive(Debug, Clone)]
pub struct Special {
set_id: Option<bool>,
sticky: Option<bool>,
}
impl Special {
pub fn empty() -> Special {
Special {
set_id: None,
sticky: None,
}
}
pub fn from_mode_user(mode: &Mode, user: User) -> Special {
let mut ret = Special::empty();
match user {
User::Owner if mode.mask & S_ISUID > 0 => ret.set_id = Some(mode.mode & S_ISUID > 0),
User::Group if mode.mask & S_ISGID > 0 => ret.set_id = Some(mode.mode & S_ISGID > 0),
_ => (),
}
if mode.mask & S_ISVTX > 0 {
ret.sticky = Some(mode.mode & S_ISVTX > 0);
}
ret
}
pub fn is_set_id_set(&self) -> bool {
self.set_id == Some(true)
}
pub fn is_sticky_set(&self) -> bool {
self.sticky == Some(true)
}
pub fn with_set(mut self, bit: SpecialBit) -> Special {
self.set(bit);
self
}
pub fn with_cleared(mut self, bit: SpecialBit) -> Special {
self.clear(bit);
self
}
pub fn with_forgotten(mut self, bit: SpecialBit) -> Special {
self.forget(bit);
self
}
pub fn set(&mut self, bit: SpecialBit) {
match bit {
SpecialBit::SetId => self.set_id = Some(true),
SpecialBit::Sticky => self.sticky = Some(true),
}
}
pub fn clear(&mut self, bit: SpecialBit) {
match bit {
SpecialBit::SetId => self.set_id = Some(false),
SpecialBit::Sticky => self.sticky = Some(false),
}
}
pub fn forget(&mut self, bit: SpecialBit) {
match bit {
SpecialBit::SetId => self.set_id = None,
SpecialBit::Sticky => self.sticky = None,
}
}
pub fn for_user(&self, user: User) -> Mode {
let mut mode = Mode::empty();
if let Some(sticky) = self.sticky {
if sticky {
mode.mode |= S_ISVTX;
} else {
mode.mode &= !S_ISVTX;
}
mode.mask |= S_ISVTX;
}
if let Some(set_id) = self.set_id {
match user {
User::Owner => {
if set_id {
mode.mode |= S_ISUID;
} else {
mode.mode &= !S_ISUID;
}
mode.mask |= S_ISUID;
}
User::Group => {
if set_id {
mode.mode |= S_ISGID;
} else {
mode.mode &= !S_ISGID;
}
mode.mask |= S_ISGID;
}
_ => (),
}
}
mode
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Mode {
mode: u32,
mask: u32,
dir_mask: u32,
}
impl Debug for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Mode")
.field("mode", &format_args!("{:06o}", self.mode))
.field("mask", &format_args!("{:06o}", self.mask))
.field("dirm", &format_args!("{:06o}", self.dir_mask))
.finish()
}
}
impl Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use FileType::*;
match self.file_type() {
Some(Directory) => f.write_str("d")?,
Some(CharacterDevice) => f.write_str("c")?,
Some(BlockDevice) => f.write_str("b")?,
Some(RegularFile) => f.write_str("-")?,
Some(FIFO) => f.write_str("p")?,
Some(SymbolicLink) => f.write_str("l")?,
Some(Socket) => f.write_str("s")?,
None => f.write_str("?")?,
}
use User::*;
for user in &[Owner, Group, Other] {
let special = self.user_special(*user);
let protection = self.user_protection(*user);
if protection.is_read_set() {
f.write_str("r")?;
} else {
f.write_str("-")?;
}
if protection.is_write_set() {
f.write_str("w")?;
} else {
f.write_str("-")?;
}
match user {
Owner | Group => match (special.is_set_id_set(), protection.is_execute_set(), protection.is_search_set()) {
(true, true, _) => f.write_str("s")?,
(true, false, _) => f.write_str("S")?,
(false, false, true) => f.write_str("X")?,
(false, true, _) => f.write_str("x")?,
(false, false, _) => f.write_str("-")?,
}
Other => match (special.is_sticky_set(), protection.is_execute_set(), protection.is_search_set()) {
(true, true, _) => f.write_str("t")?,
(true, false, _) => f.write_str("T")?,
(false, false, true) => f.write_str("X")?,
(false, true, _) => f.write_str("x")?,
(false, false, _) => f.write_str("-")?,
}
}
}
Ok(())
}
}
impl Mode {
pub fn new(mode: u32, mask: u32) -> Mode {
Mode {
mode: mode & (mask | S_IFMT),
mask,
dir_mask: 0,
}
}
pub fn empty() -> Mode {
Mode {
mode: 0,
mask: 0,
dir_mask: 0,
}
}
#[cfg(target_family = "unix")]
pub fn from_path(path: impl AsRef<Path>) -> Result<Mode, io::Error> {
Ok(Mode::new(metadata(path.as_ref())?.permissions().mode(), 0o7777 | S_IFMT))
}
#[cfg(target_family = "unix")]
pub fn from_path_nofollow(path: impl AsRef<Path>) -> Result<Mode, io::Error> {
Ok(Mode::new(symlink_metadata(path.as_ref())?.permissions().mode(), 0o7777 | S_IFMT))
}
#[cfg(target_family = "unix")]
pub fn from_file(file: &File) -> Result<Mode, io::Error> {
Ok(Mode::new(file.metadata()?.permissions().mode(), 0o7777 | S_IFMT))
}
pub fn mode(&self) -> u32 {
self.mode_mask().0
}
pub fn mode_mask(&self) -> (u32, u32) {
if let Some(true) = self.file_type().map(|m| m.is_directory()) {
(self.mode, self.mask)
} else {
(self.mode & !self.dir_mask, self.mask & !self.dir_mask)
}
}
pub fn file_type(&self) -> Option<FileType> {
FileType::from_mode(self.mode)
}
pub fn user_protection(&self, user: User) -> Protection {
Protection::from_mode_user(self, user)
}
pub fn user_special(&self, user: User) -> Special {
Special::from_mode_user(self, user)
}
pub fn add(&mut self, mode: &Mode) {
self.mode |= mode.mode;
self.mask |= mode.mask;
self.dir_mask |= mode.dir_mask;
}
pub fn sub(&mut self, mode: &Mode) {
self.mode &= !mode.mode;
self.mask |= mode.mask;
self.dir_mask |= mode.dir_mask;
}
pub fn set(&mut self, mode: &Mode) {
self.mode &= !mode.mask;
self.mode |= mode.mode;
self.mask |= mode.mask;
self.dir_mask |= mode.dir_mask;
}
pub fn forget(&mut self, mode: &Mode) {
self.mode &= !mode.mask;
self.mask &= !mode.mask;
self.dir_mask &= !mode.dir_mask;
}
pub fn with_file_type(mut self, file_type: FileType) -> Mode {
self.set(&file_type.mode());
self
}
pub fn with_protection(mut self, user: User, protection: &Protection) -> Mode {
self.set(&protection.for_user(user));
self
}
pub fn with_special(mut self, user: User, special: &Special) -> Mode {
self.set(&special.for_user(user));
self
}
pub fn set_file_type(&mut self, file_type: FileType) {
self.set(&file_type.mode())
}
pub fn set_protection(&mut self, user: User, protection: &Protection) {
self.set(&protection.for_user(user))
}
pub fn set_special(&mut self, user: User, special: &Special) {
self.set(&special.for_user(user))
}
pub fn set_str(&mut self, mode_str: &str) -> Result<(), ModeParseError> {
self.set_str_umask(mode_str, umask())
}
pub fn set_str_umask(&mut self, mode_str: &str, umask: u32) -> Result<(), ModeParseError> {
parser::mode_set_from_str(self, mode_str, umask)
}
pub fn apply_umask(&mut self, mut umask: u32) {
umask &= 0o777;
self.mode &= !umask;
}
pub fn apply_to(&self, mut mode: u32) -> u32 {
let (amode, amask) = if let Some(true) = FileType::from_mode(mode).map(|m| m.is_directory()) {
(self.mode, self.mask)
} else {
(self.mode & !self.dir_mask, self.mask & !self.dir_mask)
};
mode &= !amask;
mode | amode
}
#[cfg(target_family = "unix")]
pub fn apply_to_path(&self, path: impl AsRef<Path>) -> Result<u32, io::Error> {
let file_mode = metadata(path.as_ref())?.permissions().mode();
Ok(self.apply_to(file_mode))
}
#[cfg(target_family = "unix")]
pub fn apply_to_path_nofollow(&self, path: impl AsRef<Path>) -> Result<u32, io::Error> {
let file_mode = symlink_metadata(path.as_ref())?.permissions().mode();
Ok(self.apply_to(file_mode))
}
#[cfg(target_family = "unix")]
pub fn set_mode_path(&self, path: impl AsRef<Path>) -> Result<u32, io::Error> {
let path = path.as_ref();
let mut perms = metadata(path)?.permissions();
let mode = self.apply_to(perms.mode());
perms.set_mode(mode);
set_permissions(path, perms)?;
Ok(mode)
}
#[cfg(target_family = "unix")]
pub fn set_mode_path_nofollow(&self, path: impl AsRef<Path>) -> Result<u32, io::Error> {
let path = path.as_ref();
let mut perms = symlink_metadata(path)?.permissions();
let mode = self.apply_to(perms.mode());
perms.set_mode(mode);
set_permissions(path, perms)?;
Ok(mode)
}
#[cfg(target_family = "unix")]
pub fn set_mode_file(&self, file: &File) -> Result<u32, io::Error> {
let mut perms = file.metadata()?.permissions();
let mode = self.apply_to(perms.mode());
perms.set_mode(mode);
file.set_permissions(perms)?;
Ok(mode)
}
}
impl From<u32> for Mode {
fn from(mode: u32) -> Mode {
Mode::new(mode, 0o7777)
}
}
impl FromIterator<Mode> for Mode {
fn from_iter<I: IntoIterator<Item=Mode>>(iter: I) -> Self {
let mut mode = Mode::empty();
for m in iter {
mode.set(&m);
}
mode
}
}
#[cfg(target_family = "unix")]
mod unix {
use super::*;
use std::error::Error;
use std::convert::Infallible;
#[derive(Debug)]
pub enum ModeError {
ModeParseError(ModeParseError),
IoError(io::Error),
}
impl Display for ModeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ModeError::*;
match self {
ModeParseError(_) => write!(f, "error parsing file mode string"),
IoError(_) => write!(f, "I/O error setting file mode"),
}
}
}
impl Error for ModeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use ModeError::*;
match self {
ModeParseError(err) => Some(err),
IoError(err) => Some(err),
}
}
}
impl From<ModeParseError> for ModeError {
fn from(err: ModeParseError) -> ModeError {
ModeError::ModeParseError(err)
}
}
impl From<io::Error> for ModeError {
fn from(err: io::Error) -> ModeError {
ModeError::IoError(err)
}
}
impl From<Infallible> for ModeError {
fn from(_err: Infallible) -> ModeError {
unreachable!()
}
}
pub enum SetMode<'s> {
Value(Mode),
Str(&'s str),
}
impl<'s, M: Into<Mode>> From<M> for SetMode<'s> {
fn from(val: M) -> SetMode<'s> {
SetMode::Value(val.into())
}
}
impl<'s> From<&'s str> for SetMode<'s> {
fn from(val: &'s str) -> SetMode<'s> {
SetMode::Str(val)
}
}
pub trait ModePath {
fn mode(&self) -> Result<Mode, io::Error>;
fn set_mode<'s, M: Into<SetMode<'s>>>(&self, mode: M) -> Result<u32, ModeError>;
}
impl<T: AsRef<Path>> ModePath for T {
fn mode(&self) -> Result<Mode, io::Error> {
Mode::from_path(self.as_ref())
}
fn set_mode<'s, M: Into<SetMode<'s>>>(&self, mode: M) -> Result<u32, ModeError> {
let mut m = Mode::from_path(self)?;
match mode.into() {
SetMode::Value(val) => m.set(&val),
SetMode::Str(val) => m.set_str(val)?,
}
Ok(m.set_mode_path(self)?)
}
}
pub trait ModeFile {
fn mode(&self) -> Result<Mode, io::Error>;
fn set_mode<'s, M: Into<SetMode<'s>>>(&self, mode: M) -> Result<u32, ModeError>;
}
impl ModeFile for File {
fn mode(&self) -> Result<Mode, io::Error> {
Mode::from_file(&self)
}
fn set_mode<'s, M: Into<SetMode<'s>>>(&self, mode: M) -> Result<u32, ModeError> {
let mut m = Mode::from_file(self)?;
match mode.into() {
SetMode::Value(val) => m.set(&val),
SetMode::Str(val) => m.set_str(val)?,
}
Ok(m.set_mode_file(self)?)
}
}
pub fn set_umask(umask: u32) -> u32 {
unsafe {
libc::umask(umask as libc::mode_t) as u32
}
}
pub fn umask() -> u32 {
let m = set_umask(0);
set_umask(m);
m
}
}
#[cfg(target_family = "unix")]
pub use unix::*;
#[cfg(not(target_family = "unix"))]
mod non_unix {
use std::sync::atomic::{AtomicU32, Ordering};
static UMASK: AtomicU32 = AtomicU32::new(0o002);
pub fn set_umask(umask: u32) -> u32 {
UMASK.swap(umask, Ordering::Release)
}
pub fn umask() -> u32 {
UMASK.load(Ordering::Acquire)
}
}
#[cfg(not(target_family = "unix"))]
pub use non_unix::*;
#[cfg(test)]
mod tests {
use super::*;
use parking_lot::Mutex;
const UMASK_LOCK: Mutex<()> = parking_lot::const_mutex(());
#[test]
fn test_mode() {
assert_eq!(Mode::new(0o700, 0o700).apply_to(0o412), 0o712);
assert_eq!(Mode::new(0o700, 0o400).apply_to(0o412), 0o412);
assert_eq!(Mode::new(0o000, 0o700).apply_to(0o412), 0o012);
assert_eq!(Mode::new(0o000, 0o111).apply_to(0o412), 0o402);
assert_eq!(Mode::new(0o111, 0o111).apply_to(0o412), 0o513);
assert_eq!(Mode::new(0o777, 0o111).apply_to(0o412), 0o513);
}
#[test]
fn test_protection() {
use ProtectionBit::*;
use User::*;
let mut o_700 = Protection::empty();
o_700.set(Read);
assert!(o_700.is_read_set());
assert!(!o_700.is_write_set());
assert!(!o_700.is_execute_set());
o_700.set(Write);
assert!(o_700.is_read_set());
assert!(o_700.is_write_set());
assert!(!o_700.is_execute_set());
o_700.set(Execute);
assert!(o_700.is_read_set());
assert!(o_700.is_write_set());
assert!(o_700.is_execute_set());
assert_eq!(o_700.for_user(Owner), Mode::new(0o700, 0o700));
assert_eq!(o_700.for_user(Group), Mode::new(0o070, 0o070));
assert_eq!(o_700.for_user(Other), Mode::new(0o007, 0o007));
let mut o_100 = Protection::empty();
o_100.set(Execute);
assert_eq!(o_100.for_user(Owner), Mode::new(0o100, 0o100));
assert_eq!(o_100.for_user(Group), Mode::new(0o010, 0o010));
assert_eq!(o_100.for_user(Other), Mode::new(0o001, 0o001));
let mut o_200 = Protection::empty();
o_200.set(Write);
assert_eq!(o_200.for_user(Owner), Mode::new(0o200, 0o200));
assert_eq!(o_200.for_user(Group), Mode::new(0o020, 0o020));
assert_eq!(o_200.for_user(Other), Mode::new(0o002, 0o002));
let mut o_600 = o_700.clone();
o_600.clear(Execute);
assert_eq!(o_600.for_user(Owner), Mode::new(0o600, 0o700));
let mut o_777 = o_700.for_user(Owner);
o_777.set(&o_700.for_user(Group));
o_777.set(&o_700.for_user(Other));
assert_eq!(o_777, Mode::new(0o777, 0o777));
let mut o_123 = Mode::empty();
o_123.set_protection(Owner, &Protection::empty().with_set(Execute));
o_123.set_protection(Group, &Protection::empty().with_set(Write));
o_123.set_protection(Other, &Protection::empty().with_set(Execute).with_set(Write));
assert_eq!(o_123, Mode::new(0o123, 0o123));
let mut o_102 = o_123;
o_102.set_protection(Group, &Protection::empty().with_cleared(Write));
o_102.set_protection(Other, &Protection::empty().with_cleared(Execute));
assert_eq!(o_102, Mode::new(0o102, 0o123));
}
#[test]
fn test_special() {
use SpecialBit::*;
let mut mode = Mode::empty();
mode.set_special(User::Owner, &SetId.into());
assert_eq!(mode, Mode::new(0o4000, 0o4000));
mode.set_special(User::Group, &SetId.into());
assert_eq!(mode, Mode::new(0o6000, 0o6000));
let mut mode = Mode::empty();
mode.set_special(User::Other, &SetId.into());
assert_eq!(mode, Mode::new(0o0000, 0o0000));
let mut mode = Mode::empty();
mode.set_special(User::Owner, &Sticky.into());
assert_eq!(mode, Mode::new(0o1000, 0o1000));
mode.set_special(User::Group, &Sticky.into());
assert_eq!(mode, Mode::new(0o1000, 0o1000));
mode.set_special(User::Other, &Sticky.into());
assert_eq!(mode, Mode::new(0o1000, 0o1000));
mode.set_special(User::Group, &SetId.into());
assert_eq!(mode, Mode::new(0o3000, 0o3000));
mode.set_special(User::Owner, &SetId.into());
assert_eq!(mode, Mode::new(0o7000, 0o7000));
let mut mode = Mode::empty();
let special = Special::empty();
mode.set_special(User::Owner, &special);
assert_eq!(mode, Mode::new(0o0000, 0o0000));
let special = mode.user_special(User::Owner);
assert!(!special.is_set_id_set());
assert!(!special.is_sticky_set());
mode.set_special(User::Group, &special);
assert_eq!(mode, Mode::new(0o0000, 0o0000));
let special = mode.user_special(User::Owner);
assert!(!special.is_set_id_set());
assert!(!special.is_sticky_set());
let mut mode = Mode::empty();
let mut special = Special::empty();
special.set(SetId);
special.set(Sticky);
mode.set_special(User::Owner, &special);
assert_eq!(mode, Mode::new(0o5000, 0o5000));
let special = mode.user_special(User::Owner);
assert!(special.is_set_id_set());
assert!(special.is_sticky_set());
mode.set_special(User::Group, &special);
assert_eq!(mode, Mode::new(0o7000, 0o7000));
let special = mode.user_special(User::Owner);
assert!(special.is_set_id_set());
assert!(special.is_sticky_set());
}
#[test]
fn test_set_str_umask_symbolic() {
let mut mode = Mode::empty();
mode.set_str("u=rwx,g=rw,o+x").unwrap();
assert_eq!(mode, Mode::new(0o761, 0o771));
let mut mode = Mode::new(0o2777, 0o2777);
mode.set_str_umask("o=t", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o3770, 0o3777));
let mut mode = Mode::new(0o1770, 0o1770);
mode.set_str_umask("g+s", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o3770, 0o3770));
let mut mode = Mode::new(0o0, 0o0);
mode.set_str_umask("=rwx", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o0775, 0o0777));
let mut mode = Mode::new(0o0, 0o0);
mode.set_str_umask("=s", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o6000, 0o6777));
let mut mode = Mode::new(0o0, 0o0);
mode.set_str_umask("=", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o0000, 0o0777));
let mut mode = Mode::new(0o777, 0o7777);
mode.set_str_umask("+w", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o777, 0o7777));
let mut mode = Mode::new(0o000, 0o7777);
mode.set_str_umask("+w", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o220, 0o7777));
let mut mode = Mode::new(0o700, 0o700);
mode.set_str_umask("o=u", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o707, 0o707));
let mut mode = Mode::new(0o100, 0o100);
mode.set_str_umask("o=u", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o101, 0o107));
let mut mode = Mode::new(0o000, 0o000);
mode.set_str_umask("u=rw,og=u", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o666, 0o777));
let mut mode = Mode::new(0o700, 0o700);
mode.set_str_umask("o=u,g=o", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o777, 0o777));
let mut mode = Mode::new(0o604, 0o777);
mode.set_str("u+r,g+u").unwrap();
assert_eq!(mode.apply_to(0o664), 0o664);
let mut mode = Mode::new(0o664, 0o777);
mode.set_str("u+r,g-u").unwrap();
assert_eq!(mode.apply_to(0o664), 0o604);
let dir = 0o040664;
let file = 0o100664;
let mut mode = Mode::empty();
mode.set_str("u+x,g+X").unwrap();
assert_eq!(mode.apply_to(dir), 0o040774);
assert_eq!(mode.apply_to(file), 0o100764);
}
#[test]
fn test_set_str_umask_octal() {
let mut mode = Mode::new(0o000,0o000);
mode.set_str_umask("=777", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o777, 0o7777));
let mut mode = Mode::new(0o000,0o000);
mode.set_str_umask("+777,-111", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o666, 0o7777));
let mut mode = Mode::new(0o000,0o000);
mode.set_str_umask("=0,+7,-1", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o006, 0o7777));
let mut mode = Mode::new(0o000,0o000);
mode.set_str_umask("a=t,ug=s,+7,-1", 0o002).unwrap();
assert_eq!(mode, Mode::new(0o7006, 0o7777));
}
#[test]
fn test_from_str() {
let lock = UMASK_LOCK;
let guard = lock.lock();
let umask = umask();
set_umask(0o002);
let mut mode = Mode::empty();
mode.set_str("=rwx").unwrap();
assert_eq!(mode, Mode::new(0o0775, 0o0777));
set_umask(0o022);
let mut mode = Mode::empty();
mode.set_str("=rwx").unwrap();
assert_eq!(mode, Mode::new(0o0755, 0o0777));
set_umask(0o002);
let mut mode = Mode::empty();
mode.set_str("+w").unwrap();
assert_eq!(mode, Mode::new(0o0220, 0o0222));
set_umask(umask);
drop(guard);
}
#[test]
fn test_file_type() {
assert_eq!(Mode::new(0o040775, 0o177777).file_type(), Some(FileType::Directory));
assert_eq!(Mode::new(0o020666, 0o177777).file_type(), Some(FileType::CharacterDevice));
assert_eq!(Mode::new(0o060660, 0o177777).file_type(), Some(FileType::BlockDevice));
assert_eq!(Mode::new(0o100644, 0o177777).file_type(), Some(FileType::RegularFile));
assert_eq!(Mode::new(0o010664, 0o177777).file_type(), Some(FileType::FIFO));
assert_eq!(Mode::new(0o120777, 0o177777).file_type(), Some(FileType::SymbolicLink));
assert_eq!(Mode::new(0o140777, 0o177777).file_type(), Some(FileType::Socket));
}
#[test]
fn test_apply_to() {
let dir = 0o040664;
let file = 0o100664;
let mut search = Mode::empty();
search.set_protection(User::Owner, &ProtectionBit::Execute.into());
search.set_protection(User::Group, &ProtectionBit::Search.into());
assert_eq!(search.apply_to(dir), 0o040774);
assert_eq!(search.apply_to(file), 0o100764);
}
#[test]
fn test_to_string() {
assert_eq!(&Mode::new(0o040775, 0o177777).to_string(), "drwxrwxr-x");
assert_eq!(&Mode::new(0o100644, 0o177777).to_string(), "-rw-r--r--");
assert_eq!(&Mode::new(0o104755, 0o177777).to_string(), "-rwsr-xr-x");
assert_eq!(&Mode::new(0o041777, 0o177777).to_string(), "drwxrwxrwt");
assert_eq!(&Mode::new(0o7000, 0o7777).to_string(), "?--S--S--T");
let mut mode = Mode::new(0o7000, 0o7777);
mode.set_file_type(FileType::Directory);
assert_eq!(&mode.to_string(), "d--S--S--T");
let mut mode = Mode::empty();
mode.set_str("u+x,g+X").unwrap();
assert_eq!(&mode.to_string(), "?--x--X---");
}
#[test]
fn test_mode_debug() {
let mode = Mode::new(0o040775, 0o177777);
eprintln!("{}", mode);
eprintln!("{:?}", mode);
eprintln!("{:#?}", mode);
}
#[test]
#[cfg(target_family = "unix")]
fn test_set_mode_traits() {
let file = Path::new("LICENSE");
file.set_mode(0o600).unwrap();
assert_eq!(file.set_mode("+r").unwrap() & 0o777, 0o644);
let file = File::open(file).unwrap();
file.set_mode(0o600).unwrap();
assert_eq!(file.set_mode("+r").unwrap() & 0o777, 0o644);
assert_eq!(file.set_mode("g-u").unwrap() & 0o777, 0o604);
assert_eq!(file.set_mode("g+u").unwrap() & 0o777, 0o664);
}
}