umask-2.1.0/.cargo_vcs_info.json0000644000000001360000000000100121540ustar { "git": { "sha1": "6118f89067ec782118507ecf82ae2b56941d500f" }, "path_in_vcs": "" }umask-2.1.0/.github/workflows/rust.yml000064400000000000000000000003301046102023000160550ustar 00000000000000name: Rust on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose umask-2.1.0/.gitignore000064400000000000000000000006041046102023000127340ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # Specific tools (any contributor can add theirs) .bacon-locations umask-2.1.0/CHANGELOG.md000064400000000000000000000004041046102023000125530ustar 00000000000000### v2.1.0 - 2023-03-29 - implement FromStr - Thanks @flxo ### v2.0.0 - 2022-05-11 - support for setuid, setgid and sticky "extra" permission bits - Thanks @CozyPenguin ### v1.0.1 - 2022-01-28 - implement Into ### v1.0.0 - 2020-05-20 Official release umask-2.1.0/Cargo.lock0000644000000030440000000000100101300ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "proc-macro2" version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "umask" version = "2.1.0" dependencies = [ "thiserror", ] [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" umask-2.1.0/Cargo.toml0000644000000015320000000000100101530ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "umask" version = "2.1.0" authors = ["dystroy "] description = "utility to deal with unix access mode" readme = "README.md" keywords = [ "unix", "umask", "permission", "mode", ] categories = ["os::unix-apis"] license = "MIT" repository = "https://github.com/Canop/umask" [dependencies.thiserror] version = "1.0.40" umask-2.1.0/Cargo.toml.orig000064400000000000000000000005541046102023000136370ustar 00000000000000[package] name = "umask" version = "2.1.0" authors = ["dystroy "] repository = "https://github.com/Canop/umask" description = "utility to deal with unix access mode" edition = "2018" keywords = ["unix", "umask", "permission", "mode"] license = "MIT" categories = ["os::unix-apis"] readme = "README.md" [dependencies] thiserror = "1.0.40" umask-2.1.0/LICENSE000064400000000000000000000020461046102023000117530ustar 00000000000000MIT License Copyright (c) 2019 Canop Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. umask-2.1.0/README.md000064400000000000000000000041041046102023000122220ustar 00000000000000[![MIT][s2]][l2] [![Latest Version][s1]][l1] [![docs][s3]][l3] [![Chat on Miaou][s4]][l4] [s1]: https://img.shields.io/crates/v/umask.svg [l1]: https://crates.io/crates/umask [s2]: https://img.shields.io/badge/license-MIT-blue.svg [l2]: LICENSE [s3]: https://docs.rs/umask/badge.svg [l3]: https://docs.rs/umask/ [s4]: https://miaou.dystroy.org/static/shields/room.svg [l4]: https://miaou.dystroy.org/3 # umask A light utility helping with unix mode representation, with strong types to avoid misusing constants. The Mode struct implements `Display` and prints as `"rwxrwxrwx"` ### Import In Cargo.toml: umask = "'2.0" ### Usage ```rust use umask::*; // You can build from a number: assert_eq!("rw-r--r--", Mode::from(0b110100100).to_string()); assert_eq!("rw-r--r--", Mode::from(0o644).to_string()); // You may use `|` to combine class permissions: let mu = Mode::from(0o640); let mo = Mode::from(0o044); assert_eq!("rw-r--r--", (mu | mo).to_string()); assert_eq!("---r-----", (mu & mo).to_string()); // You can use more semantic constructs: let m = Mode::all() .without(ALL_EXEC); assert_eq!("rw-rw-rw-", m.to_string()); let mut m = Mode::new() .with_class_perm(ALL, READ) .with_class_perm(USER, WRITE); assert_eq!("rw-r--r--", m.to_string()); // (semantic functions can be used in const declarations) // Or m |= ALL_EXEC; assert_eq!("rwxr-xr-x", m.to_string()); let m = ALL_READ | USER_WRITE; assert_eq!("rw-r--r--", m.to_string()); // Displaying the mode can be done with the `Display` // implementation but also bit per bit for more control assert_eq!( m.to_string().chars().next().unwrap(), // first char: 'r' or '-' if m.has(USER_READ) { 'r' } else { '-' }, ); // The `Display` implementation shows the extra permission bits // (setuid, setgid and sticky): let mut m = Mode::all() .with_extra(STICKY) .with_extra(SETUID) .with_extra(SETGID); assert_eq!("rwsrwsrwt", m.to_string()); // But you can remove those bits for display if you want the // sometimes more familiar 'x' for execution: assert_eq!("rwxrwxrwx", m.without_any_extra().to_string()); ``` umask-2.1.0/examples/ls/main.rs000064400000000000000000000011761046102023000144770ustar 00000000000000/// display the content of the current directory use { std::{env, io, path::PathBuf}, umask::Mode, }; fn list_files() -> io::Result<()> { let root = env::current_dir()?; println!("Current dir: {}", root.to_string_lossy()); let mut paths: Vec = root.read_dir()? .filter_map(|e| e.ok()) .map(|e| e.path()) .collect(); paths.sort_unstable(); for path in paths { let mode = Mode::try_from(&path)?; let name = path.file_name().unwrap().to_string_lossy(); println!("{} {}", mode, name); } Ok(()) } fn main() -> io::Result<()> { list_files() } umask-2.1.0/src/lib.rs000064400000000000000000000037161046102023000126560ustar 00000000000000//! A light utility helping with unix mode representation, with strong //! types to avoid misusing constants. //! //! The Mode struct implements `Display` and prints as `"rwxrwxrwx"` //! //! ``` //! use umask::*; //! //! // You can build from a number: //! assert_eq!("rw-r--r--", Mode::from(0b110100100).to_string()); //! assert_eq!("rw-r--r--", Mode::from(0o644).to_string()); //! //! // Or from a string: //! let mode: Mode = "rw-rw-r--".parse().unwrap(); //! assert_eq!("rw-rw-r--", mode.to_string()); //! //! // You may use `|` to combine class permissions: //! let mu = Mode::from(0o640); //! let mo = Mode::from(0o044); //! assert_eq!("rw-r--r--", (mu | mo).to_string()); //! assert_eq!("---r-----", (mu & mo).to_string()); //! //! // You can use more semantic constructs: //! let m = Mode::all() //! .without(ALL_EXEC); //! assert_eq!("rw-rw-rw-", m.to_string()); //! let mut m = Mode::new() //! .with_class_perm(ALL, READ) //! .with_class_perm(USER, WRITE); //! assert_eq!("rw-r--r--", m.to_string()); //! // (semantic functions can be used in const declarations) //! //! // Or //! m |= ALL_EXEC; //! assert_eq!("rwxr-xr-x", m.to_string()); //! let m = ALL_READ | USER_WRITE; //! assert_eq!("rw-r--r--", m.to_string()); //! //! // Displaying the mode can be done with the `Display` //! // implementation but also bit per bit for more control //! assert_eq!( //! m.to_string().chars().next().unwrap(), // first char: 'r' or '-' //! if m.has(USER_READ) { 'r' } else { '-' }, //! ); //! //! // The `Display` implementation shows the extra permission bits //! // (setuid, setgid and sticky): //! let mut m = Mode::all() //! .with_extra(STICKY) //! .with_extra(SETUID) //! .with_extra(SETGID); //! assert_eq!("rwsrwsrwt", m.to_string()); //! //! // But you can remove those bits for display if you want the //! // sometimes more familiar 'x' for execution: //! assert_eq!("rwxrwxrwx", m.without_any_extra().to_string()); //! //! ``` mod mode; pub use mode::*; umask-2.1.0/src/mode.rs000064400000000000000000000316261046102023000130350ustar 00000000000000use std::{ fmt::{self, Display, Formatter, Write}, io, ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}, path::Path, }; #[cfg(unix)] use std::fs; #[cfg(unix)] use std::os::unix::fs::MetadataExt; use thiserror::Error; pub type Class = u32; pub const EXTRA: Class = 0b111000000000; pub const USER: Class = 0b111000000; pub const GROUP: Class = 0b000111000; pub const OTHERS: Class = 0b000000111; pub const ALL: Class = 0b111111111; pub type Permission = u32; pub const READ: Permission = 0b100100100; pub const WRITE: Permission = 0b010010010; pub const EXEC: Permission = 0b001001001; pub type ExtraPermission = u32; /// When the sticky bit is set on a directory, files in that directory can only be /// deleted by the owner. pub const STICKY: ExtraPermission = 0o1000; /// When the setgid bit is set on a an executable file, the file will be executed /// by with the permissions of the file's group instead of the executing user's group. /// /// When set on a directory, files and subdirectories created in it are assigned the /// same group id as the parent directory. pub const SETGID: ExtraPermission = 0o2000; /// When the setuid bit is set on a an executable file, the file will be executed by /// with the permissions of the file's owner instead of the executing user. pub const SETUID: ExtraPermission = 0o4000; pub const USER_READ: Mode = Mode::new().with_class_perm(USER, READ); pub const USER_WRITE: Mode = Mode::new().with_class_perm(USER, WRITE); pub const USER_EXEC: Mode = Mode::new().with_class_perm(USER, EXEC); pub const GROUP_READ: Mode = Mode::new().with_class_perm(GROUP, READ); pub const GROUP_WRITE: Mode = Mode::new().with_class_perm(GROUP, WRITE); pub const GROUP_EXEC: Mode = Mode::new().with_class_perm(GROUP, EXEC); pub const OTHERS_READ: Mode = Mode::new().with_class_perm(OTHERS, READ); pub const OTHERS_WRITE: Mode = Mode::new().with_class_perm(OTHERS, WRITE); pub const OTHERS_EXEC: Mode = Mode::new().with_class_perm(OTHERS, EXEC); pub const ALL_READ: Mode = Mode::new().with_class_perm(ALL, READ); pub const ALL_WRITE: Mode = Mode::new().with_class_perm(ALL, WRITE); pub const ALL_EXEC: Mode = Mode::new().with_class_perm(ALL, EXEC); #[derive(Clone, Copy, PartialEq, Eq, Default, Hash)] pub struct Mode { value: u32, } impl fmt::Debug for Mode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self) } } impl From for Mode { fn from(value: u32) -> Self { Self { value } } } impl From for u32 { fn from(mode: Mode) -> Self { mode.value } } impl From<&Mode> for u32 { fn from(mode: &Mode) -> Self { mode.value } } impl BitAnd for Mode { type Output = Self; fn bitand(self, other: Self) -> Self { Self { value: self.value & other.value, } } } impl BitAndAssign for Mode { fn bitand_assign(&mut self, other: Self) { self.value &= other.value; } } impl BitOr for Mode { type Output = Self; fn bitor(self, other: Self) -> Self { Self { value: self.value | other.value, } } } impl BitOrAssign for Mode { fn bitor_assign(&mut self, other: Self) { self.value |= other.value; } } impl Not for Mode { type Output = Self; fn not(self) -> Self::Output { Self { value: !self.value } } } impl Display for Mode { /// Formats the Mode. /// /// If you want to prevent the extra permission bits from being displayed, /// use [`Mode::without_any_extra()`] to remove them before calling format. fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_char(if self.has(USER_READ) { 'r' } else { '-' })?; f.write_char(if self.has(USER_WRITE) { 'w' } else { '-' })?; f.write_char(if self.has_extra(SETUID) && self.has(USER_EXEC) { 's' } else if self.has_extra(SETUID) { 'S' } else if self.has(USER_EXEC) { 'x' } else { '-' })?; f.write_char(if self.has(GROUP_READ) { 'r' } else { '-' })?; f.write_char(if self.has(GROUP_WRITE) { 'w' } else { '-' })?; f.write_char(if self.has_extra(SETGID) && self.has(GROUP_EXEC) { 's' } else if self.has_extra(SETGID) { 'S' } else if self.has(GROUP_EXEC) { 'x' } else { '-' })?; f.write_char(if self.has(OTHERS_READ) { 'r' } else { '-' })?; f.write_char(if self.has(OTHERS_WRITE) { 'w' } else { '-' })?; f.write_char(if self.has_extra(STICKY) && self.has(OTHERS_EXEC) { 't' } else if self.has_extra(STICKY) { 'T' } else if self.has(OTHERS_EXEC) { 'x' } else { '-' })?; Ok(()) } } /// Parsing error. #[derive(Debug, Error)] pub enum ParseError { #[error("invalid character '{0}' at position {1}")] InvalidChar(char, usize), /// Invalid length. #[error("not enough input")] NotEnoughInput, /// Trailing characters. #[error("trailing characters")] TrailingCharacters, } /// Represents Permissions for a file. /// /// # Formatting /// /// String representations of Modes include the extra permission bits, /// which modify how the executable permissions are displayed if set. /// If you don't want to include this functionality, call [`without_any_extra()`](Mode::without_any_extra()) /// before converting the Mode into a string. impl Mode { /// Build a mode with absolutely no permission #[inline(always)] pub const fn new() -> Self { Self { value: 0 } } /// Build a mode with all permissions given to everybody. /// This does not include [`ExtraPermission`] bits. #[inline(always)] pub const fn all() -> Self { Self { value: 0b111111111 } } /// Return the mode for the given path. /// On non unix platforms, return `Mode::all()` #[allow(unused_variables)] pub fn try_from(path: &Path) -> Result { #[cfg(unix)] { let metadata = fs::metadata(&path)?; Ok(Mode::from(metadata.mode())) } #[cfg(not(unix))] Ok(Self::all()) } /// Try to parse a mode from a string. pub fn parse>(s: T) -> Result { let mut result = Mode::new(); let mut i = s.as_ref().chars().enumerate(); #[inline] fn expect_single( i: &mut impl Iterator, n: char, m: Mode, ) -> Result { match i.next() { Some((_, c)) if c == n => Ok(m), Some((_, '-')) => Ok(Mode::new()), Some((pos, c)) => Err(ParseError::InvalidChar(c, pos)), None => Err(ParseError::NotEnoughInput), } } result |= expect_single(&mut i, 'r', USER_READ)?; result |= expect_single(&mut i, 'w', USER_WRITE)?; match i.next() { Some((_, 's')) => result |= Mode::from(SETUID) | USER_EXEC, Some((_, 'S')) => result |= SETUID.into(), Some((_, 'x')) => result |= USER_EXEC, Some((_, '-')) => (), Some((pos, c)) => return Err(ParseError::InvalidChar(c, pos)), None => return Err(ParseError::NotEnoughInput), } result |= expect_single(&mut i, 'r', GROUP_READ)?; result |= expect_single(&mut i, 'w', GROUP_WRITE)?; match i.next() { Some((_, 's')) => result |= Mode::from(SETGID) | GROUP_EXEC, Some((_, 'S')) => result |= SETGID.into(), Some((_, 'x')) => result |= GROUP_EXEC, Some((_, '-')) => (), Some((pos, c)) => return Err(ParseError::InvalidChar(c, pos)), None => return Err(ParseError::NotEnoughInput), } result |= expect_single(&mut i, 'r', OTHERS_READ)?; result |= expect_single(&mut i, 'w', OTHERS_WRITE)?; match i.next() { Some((_, 't')) => result |= Mode::from(STICKY) | OTHERS_EXEC, Some((_, 'T')) => result |= STICKY.into(), Some((_, 'x')) => result |= OTHERS_EXEC, Some((_, '-')) => (), Some((pos, c)) => return Err(ParseError::InvalidChar(c, pos)), None => return Err(ParseError::NotEnoughInput), } if i.next().is_none() { Ok(result) } else { Err(ParseError::TrailingCharacters) } } /// Finds if the mode indicates an executable file #[inline(always)] pub const fn is_exe(self) -> bool { (self.value & 0o111) != 0 } /// Indicates whether the passed class/permissions are all present in self #[inline(always)] pub const fn has(self, other: Self) -> bool { self.value & other.value == other.value } /// Indicates whether the passed extra permission is present in self #[inline(always)] pub const fn has_extra(self, other: ExtraPermission) -> bool { self.value & other == other } /// Return a new mode, with the extra permission set /// (does nothing if the extra permission is already set for the mode) #[inline(always)] pub const fn with_extra(self, perm: ExtraPermission) -> Self { Self { value: self.value | perm, } } /// Return a new mode, without the extra permission set /// (does nothing if the extra permission is not set for the mode) #[inline(always)] pub const fn without_extra(self, perm: ExtraPermission) -> Self { Self { value: self.value & !perm, } } /// Return a new mode, without any extra permission bits set /// (does nothing if no extra permissions are set for the mode) pub const fn without_any_extra(self) -> Self { Self { value: self.value & !EXTRA, } } /// Return a new mode, with the permission added for the class /// (does nothing if the permission is already given to that class) #[inline(always)] pub const fn with_class_perm(self, class: Class, perm: Permission) -> Self { Self { value: self.value | (class & perm), } } /// return a new mode, with the permission removed for the class /// (does nothing if the permission is already given to that class) #[inline(always)] pub const fn without_class_perm(self, class: Class, perm: Permission) -> Self { Self { value: self.value & !(class & perm), } } /// add the class/permissions of the other mode #[inline(always)] pub const fn with(self, other: Mode) -> Self { Self { value: self.value | other.value, } } /// remove the class/permissions of the other mode #[inline(always)] pub const fn without(self, other: Mode) -> Self { Self { value: self.value & !other.value, } } } impl std::str::FromStr for Mode { type Err = ParseError; fn from_str(s: &str) -> Result { Self::parse(s) } } #[test] fn test_build() { let mut m = Mode::new() .with_class_perm(ALL, READ) .with_class_perm(USER, WRITE); assert_eq!("rw-r--r--", m.to_string()); m |= ALL_EXEC; assert_eq!("rwxr-xr-x", m.to_string()); m &= !USER_READ; assert_eq!("-wxr-xr-x", m.to_string()); } #[test] fn test_print() { assert_eq!("rw-r--r--", Mode::from(0b110100100).to_string()); assert_eq!("rw-r--r--", Mode::from(0o644).to_string()); let mu = Mode::from(0o600); let mo = Mode::from(0o004); assert_eq!("rw-------", mu.to_string()); assert_eq!("------r--", mo.to_string()); let muo = mu | mo; assert_eq!("rw----r--", muo.to_string()); } #[test] fn test_extra_permissions() { let mut m = Mode::all() .with_extra(STICKY) .with_extra(SETUID) .with_extra(SETGID); assert_eq!("rwsrwsrwt", m.to_string()); m &= !Mode::from(EXEC); assert_eq!("rwSrwSrwT", m.to_string()); let m = m.without_extra(STICKY); assert_eq!("rwSrwSrw-", m.to_string()); } #[test] fn test_try_from_str() -> Result<(), ParseError> { assert_eq!(Mode::parse("---------")?, Mode::from(0o000)); assert_eq!(Mode::parse("rw-r--r--")?, Mode::from(0b110100100)); assert_eq!(Mode::parse("rw-r--r--")?, Mode::from(0o644)); assert_eq!(Mode::parse("rw-------")?, Mode::from(0o600)); assert_eq!(Mode::parse("------r--")?, Mode::from(0o004)); let expected = Mode::all() .with_extra(STICKY) .with_extra(SETUID) .with_extra(SETGID); assert_eq!(Mode::parse("rwsrwsrwt")?, expected); assert!(matches!( Mode::parse("rw-r--r---"), Err(ParseError::TrailingCharacters) )); assert!(matches!(Mode::parse(""), Err(ParseError::NotEnoughInput))); assert!(matches!( Mode::parse("rw-r--r-"), Err(ParseError::NotEnoughInput) )); assert!( matches!(Mode::parse("xw-r--r---"), Err(ParseError::InvalidChar(c, pos)) if c == 'x' && pos == 0) ); Ok(()) }