From 2813c7b33509dad08c483f61e623ecc6bbcea8f3 Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Fri, 14 Apr 2023 00:30:18 -0700 Subject: [PATCH 01/33] preliminary support for unix symbolic notation permissions --- Cargo.lock | 46 ++++++++++++++++- Cargo.toml | 3 ++ src/fs/mod.rs | 4 ++ src/fs/permissions/error.rs | 10 ++++ src/fs/permissions/file_type.rs | 54 ++++++++++++++++++++ src/fs/permissions/mod.rs | 87 +++++++++++++++++++++++++++++++++ src/fs/permissions/mode.rs | 72 +++++++++++++++++++++++++++ tests/utils/mod.rs | 10 +++- 8 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 src/fs/permissions/error.rs create mode 100644 src/fs/permissions/file_type.rs create mode 100644 src/fs/permissions/mod.rs create mode 100644 src/fs/permissions/mode.rs diff --git a/Cargo.lock b/Cargo.lock index 05047386..2f9588f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bitflags" version = "1.3.2" @@ -113,6 +119,7 @@ dependencies = [ "indoc", "is-terminal", "lscolors", + "nix", "once_cell", "regex", "strip-ansi-escapes", @@ -270,9 +277,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "linux-raw-sys" @@ -311,6 +318,29 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", + "static_assertions", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -339,6 +369,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -450,6 +486,12 @@ version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strip-ansi-escapes" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index cb5621b6..d5fbafd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,9 @@ once_cell = "1.17.0" regex = "1.7.3" thiserror = "1.0.40" +[target.'cfg(unix)'.dependencies] +nix = { version = "0.26.2", features = ["fs"] } + [dev-dependencies] indoc = "2.0.0" strip-ansi-escapes = "0.1.1" diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 73e5b5c1..f6295644 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -4,6 +4,10 @@ use std::{fs, path::PathBuf}; /// Operations pertaining to underlying inodes of files. pub mod inode; +/// Unix file permissions. +#[cfg(unix)] +pub mod permissions; + /// Returns the path to the target of the soft link. Returns `None` if provided `dir_entry` isn't a /// symlink. pub fn symlink_target(dir_entry: &DirEntry) -> Option { diff --git a/src/fs/permissions/error.rs b/src/fs/permissions/error.rs new file mode 100644 index 00000000..25a8165a --- /dev/null +++ b/src/fs/permissions/error.rs @@ -0,0 +1,10 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Unknown file type.")] + UnknownFileType, + + #[error("Failed to compute file mode.")] + UnknownMode, +} diff --git a/src/fs/permissions/file_type.rs b/src/fs/permissions/file_type.rs new file mode 100644 index 00000000..e4e319cf --- /dev/null +++ b/src/fs/permissions/file_type.rs @@ -0,0 +1,54 @@ +use super::error::Error; +use nix::sys::stat::SFlag; + +/// Unix file types. +pub enum FileType { + Directory, + File, + Symlink, + Fifo, + Socket, + CharDevice, + BlockDevice, +} + +impl FileType { + /// Unix file identifiers that you'd find in the `ls -l` command. + pub const fn identifier(&self) -> char { + match self { + Self::Directory => 'd', + Self::File => '.', + Self::Symlink => 'l', + Self::Fifo => 'p', + Self::Socket => 's', + Self::CharDevice => 'c', + Self::BlockDevice => 'b', + } + } +} + +impl TryFrom for FileType { + type Error = Error; + + fn try_from(mode: u32) -> Result { + let file_mask = mode & u32::from(SFlag::S_IFMT.bits()); + + if file_mask == u32::from(SFlag::S_IFIFO.bits()) { + Ok(Self::Fifo) + } else if file_mask == u32::from(SFlag::S_IFCHR.bits()) { + Ok(Self::CharDevice) + } else if file_mask == u32::from(SFlag::S_IFDIR.bits()) { + Ok(Self::Directory) + } else if file_mask == u32::from(SFlag::S_IFBLK.bits()) { + Ok(Self::BlockDevice) + } else if file_mask == u32::from(SFlag::S_IFREG.bits()) { + Ok(Self::File) + } else if file_mask == u32::from(SFlag::S_IFLNK.bits()) { + Ok(Self::Symlink) + } else if file_mask == u32::from(SFlag::S_IFSOCK.bits()) { + Ok(Self::Socket) + } else { + Err(Error::UnknownFileType) + } + } +} diff --git a/src/fs/permissions/mod.rs b/src/fs/permissions/mod.rs new file mode 100644 index 00000000..ada28bb3 --- /dev/null +++ b/src/fs/permissions/mod.rs @@ -0,0 +1,87 @@ +use error::Error; +use file_type::FileType; +use mode::Mode; +use nix::sys::stat::SFlag; +use std::{ + convert::TryFrom, + fmt::{self, Display}, + fs::Permissions, + os::unix::fs::PermissionsExt, +}; + +/// File permission related errors. +pub mod error; + +pub mod file_type; + +pub mod mode; + +pub struct FileMode { + file_type: FileType, + user_mode: Mode, + group_mode: Mode, + other_mode: Mode, +} + +impl FileMode { + pub const fn new( + file_type: FileType, + user_mode: Mode, + group_mode: Mode, + other_mode: Mode, + ) -> Self { + Self { + file_type, + user_mode, + group_mode, + other_mode, + } + } + + const fn file_type(&self) -> &FileType { + &self.file_type + } + + const fn user_mode(&self) -> &Mode { + &self.user_mode + } + + const fn group_mode(&self) -> &Mode { + &self.group_mode + } + + const fn other_mode(&self) -> &Mode { + &self.other_mode + } +} + +impl Display for FileMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let file_iden = self.file_type().identifier(); + let user_mode = self.user_mode(); + let group_mode = self.group_mode(); + let other_mode = self.other_mode(); + + write!(f, "{file_iden}{user_mode}{group_mode}{other_mode}") + } +} + +impl TryFrom for FileMode { + type Error = Error; + + fn try_from(permissions: Permissions) -> Result { + let mode = permissions.mode(); + let file_type = FileType::try_from(mode)?; + let mode_mask = mode & !u32::from(SFlag::S_IFMT.bits()); + let user_mode = Mode::try_user_mode_from(mode_mask)?; + let group_mode = Mode::try_group_mode_from(mode_mask)?; + let other_mode = Mode::try_other_mode_from(mode_mask)?; + + Ok(Self { + file_type, + user_mode, + group_mode, + other_mode, + }) + } +} diff --git a/src/fs/permissions/mode.rs b/src/fs/permissions/mode.rs new file mode 100644 index 00000000..fd6b596e --- /dev/null +++ b/src/fs/permissions/mode.rs @@ -0,0 +1,72 @@ +use super::error::Error; +use nix::sys::stat::Mode as SMode; +use std::fmt::{self, Display}; + +pub enum Mode { + Read, + Write, + Execute, + ReadWrite, + ReadExecute, + WriteExecute, + ReadWriteExecute, +} + +impl Mode { + pub fn try_user_mode_from(mode_mask: u32) -> Result { + let owner = mode_mask & u32::from(SMode::S_IRWXU.bits()); + + let read = owner & u32::from(SMode::S_IRUSR.bits()) != 0; + let write = owner & u32::from(SMode::S_IWUSR.bits()) != 0; + let execute = owner & u32::from(SMode::S_IXUSR.bits()) != 0; + + Self::try_mode_from_rwx(read, write, execute) + } + + pub fn try_group_mode_from(mode_mask: u32) -> Result { + let owner = mode_mask & u32::from(SMode::S_IRWXG.bits()); + + let read = owner & u32::from(SMode::S_IRGRP.bits()) != 0; + let write = owner & u32::from(SMode::S_IWGRP.bits()) != 0; + let execute = owner & u32::from(SMode::S_IXGRP.bits()) != 0; + + Self::try_mode_from_rwx(read, write, execute) + } + + pub fn try_other_mode_from(mode_mask: u32) -> Result { + let owner = mode_mask & u32::from(SMode::S_IRWXO.bits()); + + let read = owner & u32::from(SMode::S_IROTH.bits()) != 0; + let write = owner & u32::from(SMode::S_IWOTH.bits()) != 0; + let execute = owner & u32::from(SMode::S_IXOTH.bits()) != 0; + + Self::try_mode_from_rwx(read, write, execute) + } + + const fn try_mode_from_rwx(r: bool, w: bool, x: bool) -> Result { + match (r, w, x) { + (true, false, false) => Ok(Self::Read), + (false, true, false) => Ok(Self::Write), + (false, false, true) => Ok(Self::Execute), + (true, true, false) => Ok(Self::ReadWrite), + (true, false, true) => Ok(Self::ReadExecute), + (false, true, true) => Ok(Self::WriteExecute), + (true, true, true) => Ok(Self::ReadWriteExecute), + _ => Err(Error::UnknownMode), + } + } +} + +impl Display for Mode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Read => write!(f, "r--"), + Self::Write => write!(f, "-w-"), + Self::Execute => write!(f, "--x"), + Self::ReadWrite => write!(f, "rw-"), + Self::ReadExecute => write!(f, "r-x"), + Self::WriteExecute => write!(f, "-wx"), + Self::ReadWriteExecute => write!(f, "rwx"), + } + } +} diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 3301cfd5..5e5f779a 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -4,7 +4,15 @@ use strip_ansi_escapes::strip as strip_ansi_escapes; pub fn run_cmd(args: &[&str]) -> String { let mut cmd = Command::new("cargo"); - cmd.args(["run", "--", "--threads", "1", "--disk-usage", "logical", "--no-config"]); + cmd.args([ + "run", + "--", + "--threads", + "1", + "--disk-usage", + "logical", + "--no-config", + ]); for arg in args { cmd.arg(arg); From 0b7cf9a8348a4415b876ed3becf4406d8d7454bc Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Fri, 14 Apr 2023 00:39:16 -0700 Subject: [PATCH 02/33] fix bit comparison --- src/fs/permissions/mode.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/fs/permissions/mode.rs b/src/fs/permissions/mode.rs index fd6b596e..6acc95ac 100644 --- a/src/fs/permissions/mode.rs +++ b/src/fs/permissions/mode.rs @@ -16,9 +16,9 @@ impl Mode { pub fn try_user_mode_from(mode_mask: u32) -> Result { let owner = mode_mask & u32::from(SMode::S_IRWXU.bits()); - let read = owner & u32::from(SMode::S_IRUSR.bits()) != 0; - let write = owner & u32::from(SMode::S_IWUSR.bits()) != 0; - let execute = owner & u32::from(SMode::S_IXUSR.bits()) != 0; + let read = owner & u32::from(SMode::S_IRUSR.bits()) == u32::from(SMode::S_IRUSR.bits()); + let write = owner & u32::from(SMode::S_IWUSR.bits()) == u32::from(SMode::S_IWUSR.bits()); + let execute = owner & u32::from(SMode::S_IXUSR.bits()) == u32::from(SMode::S_IXUSR.bits()); Self::try_mode_from_rwx(read, write, execute) } @@ -26,9 +26,9 @@ impl Mode { pub fn try_group_mode_from(mode_mask: u32) -> Result { let owner = mode_mask & u32::from(SMode::S_IRWXG.bits()); - let read = owner & u32::from(SMode::S_IRGRP.bits()) != 0; - let write = owner & u32::from(SMode::S_IWGRP.bits()) != 0; - let execute = owner & u32::from(SMode::S_IXGRP.bits()) != 0; + let read = owner & u32::from(SMode::S_IRGRP.bits()) == u32::from(SMode::S_IRGRP.bits()); + let write = owner & u32::from(SMode::S_IWGRP.bits()) == u32::from(SMode::S_IWGRP.bits()); + let execute = owner & u32::from(SMode::S_IXGRP.bits()) == u32::from(SMode::S_IXGRP.bits()); Self::try_mode_from_rwx(read, write, execute) } @@ -36,9 +36,9 @@ impl Mode { pub fn try_other_mode_from(mode_mask: u32) -> Result { let owner = mode_mask & u32::from(SMode::S_IRWXO.bits()); - let read = owner & u32::from(SMode::S_IROTH.bits()) != 0; - let write = owner & u32::from(SMode::S_IWOTH.bits()) != 0; - let execute = owner & u32::from(SMode::S_IXOTH.bits()) != 0; + let read = owner & u32::from(SMode::S_IROTH.bits()) == u32::from(SMode::S_IROTH.bits()); + let write = owner & u32::from(SMode::S_IWOTH.bits()) == u32::from(SMode::S_IWOTH.bits()); + let execute = owner & u32::from(SMode::S_IXOTH.bits()) == u32::from(SMode::S_IXOTH.bits()); Self::try_mode_from_rwx(read, write, execute) } From 37a2c5ecdc7e5663694d24259e54db1ac2a25dc7 Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Fri, 14 Apr 2023 00:49:55 -0700 Subject: [PATCH 03/33] dry up code --- src/fs/permissions/mode.rs | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/fs/permissions/mode.rs b/src/fs/permissions/mode.rs index 6acc95ac..814d5d00 100644 --- a/src/fs/permissions/mode.rs +++ b/src/fs/permissions/mode.rs @@ -13,36 +13,43 @@ pub enum Mode { } impl Mode { - pub fn try_user_mode_from(mode_mask: u32) -> Result { - let owner = mode_mask & u32::from(SMode::S_IRWXU.bits()); + pub fn try_user_mode_from(modes_mask: u32) -> Result { + let user = modes_mask & u32::from(SMode::S_IRWXU.bits()); - let read = owner & u32::from(SMode::S_IRUSR.bits()) == u32::from(SMode::S_IRUSR.bits()); - let write = owner & u32::from(SMode::S_IWUSR.bits()) == u32::from(SMode::S_IWUSR.bits()); - let execute = owner & u32::from(SMode::S_IXUSR.bits()) == u32::from(SMode::S_IXUSR.bits()); + let read = Self::enabled(user, SMode::S_IRUSR.bits()); + let write = Self::enabled(user, SMode::S_IWUSR.bits()); + let execute = Self::enabled(user, SMode::S_IXUSR.bits()); Self::try_mode_from_rwx(read, write, execute) } - pub fn try_group_mode_from(mode_mask: u32) -> Result { - let owner = mode_mask & u32::from(SMode::S_IRWXG.bits()); + pub fn try_group_mode_from(modes_mask: u32) -> Result { + let group = modes_mask & u32::from(SMode::S_IRWXG.bits()); - let read = owner & u32::from(SMode::S_IRGRP.bits()) == u32::from(SMode::S_IRGRP.bits()); - let write = owner & u32::from(SMode::S_IWGRP.bits()) == u32::from(SMode::S_IWGRP.bits()); - let execute = owner & u32::from(SMode::S_IXGRP.bits()) == u32::from(SMode::S_IXGRP.bits()); + let read = Self::enabled(group, SMode::S_IRGRP.bits()); + let write = Self::enabled(group, SMode::S_IWGRP.bits()); + let execute = Self::enabled(group, SMode::S_IXGRP.bits()); Self::try_mode_from_rwx(read, write, execute) } - pub fn try_other_mode_from(mode_mask: u32) -> Result { - let owner = mode_mask & u32::from(SMode::S_IRWXO.bits()); + pub fn try_other_mode_from(modes_mask: u32) -> Result { + let other = modes_mask & u32::from(SMode::S_IRWXO.bits()); - let read = owner & u32::from(SMode::S_IROTH.bits()) == u32::from(SMode::S_IROTH.bits()); - let write = owner & u32::from(SMode::S_IWOTH.bits()) == u32::from(SMode::S_IWOTH.bits()); - let execute = owner & u32::from(SMode::S_IXOTH.bits()) == u32::from(SMode::S_IXOTH.bits()); + let read = Self::enabled(other, SMode::S_IROTH.bits()); + let write = Self::enabled(other, SMode::S_IWOTH.bits()); + let execute = Self::enabled(other, SMode::S_IXOTH.bits()); Self::try_mode_from_rwx(read, write, execute) } + fn enabled(class_mask: u32, mode_mask: N) -> bool + where + N: Copy + Into, + { + class_mask & mode_mask.into() == mode_mask.into() + } + const fn try_mode_from_rwx(r: bool, w: bool, x: bool) -> Result { match (r, w, x) { (true, false, false) => Ok(Self::Read), From 3c572f220cf6c5783ef8353271206b77bc1bd72d Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Fri, 14 Apr 2023 01:23:35 -0700 Subject: [PATCH 04/33] doc comments --- src/fs/permissions/file_type.rs | 1 + src/fs/permissions/mod.rs | 37 ++++++++++++++++++++++++++------- src/fs/permissions/mode.rs | 8 +++++++ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/fs/permissions/file_type.rs b/src/fs/permissions/file_type.rs index e4e319cf..345817b8 100644 --- a/src/fs/permissions/file_type.rs +++ b/src/fs/permissions/file_type.rs @@ -27,6 +27,7 @@ impl FileType { } } +/// The argument `mode` is meant to come from the `mode` method of [std::fs::Permissions]. impl TryFrom for FileType { type Error = Error; diff --git a/src/fs/permissions/mod.rs b/src/fs/permissions/mod.rs index ada28bb3..3a96d376 100644 --- a/src/fs/permissions/mod.rs +++ b/src/fs/permissions/mod.rs @@ -5,17 +5,32 @@ use nix::sys::stat::SFlag; use std::{ convert::TryFrom, fmt::{self, Display}, - fs::Permissions, os::unix::fs::PermissionsExt, }; /// File permission related errors. pub mod error; +/// For working with Unix file identifiers. pub mod file_type; +/// For working with permissions for a particular class i.e. user, group, or other. pub mod mode; +/// Trait that is used to extend [std::fs::Permissions] behavior such that it allows for `mode` to +/// be expressed in Unix's symbolic notation for file permissions. +pub trait SymbolicNotation: PermissionsExt { + /// Attempts to return a [FileMode] which implements [Display] allowing it to be presented in + /// symbolic notation for file permissions. + fn try_mode_symbolic_notation(&self) -> Result { + let mode = self.mode(); + FileMode::try_from(mode) + } +} + +/// A struct which holds information about the permissions of a particular file. [FileMode] +/// implements [Display] which allows it to be conveniently represented in symbolic notation when +/// expressing file permissions. pub struct FileMode { file_type: FileType, user_mode: Mode, @@ -24,6 +39,7 @@ pub struct FileMode { } impl FileMode { + /// Constructor for [FileMode]. pub const fn new( file_type: FileType, user_mode: Mode, @@ -38,23 +54,28 @@ impl FileMode { } } + /// Returns a reference to `file_type`. const fn file_type(&self) -> &FileType { &self.file_type } + /// Returns a reference to a [Mode] which represents the permissions of the user class. const fn user_mode(&self) -> &Mode { &self.user_mode } + /// Returns a reference to a [Mode] which represents the permissions of the group class. const fn group_mode(&self) -> &Mode { &self.group_mode } + /// Returns a reference to a [Mode] which represents the permissions of the other class. const fn other_mode(&self) -> &Mode { &self.other_mode } } +/// For representing [FileMode] in symbolic notation. impl Display for FileMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let file_iden = self.file_type().identifier(); @@ -66,16 +87,16 @@ impl Display for FileMode { } } -impl TryFrom for FileMode { +/// The argument `mode` is meant to come from the `mode` method of [std::fs::Permissions]. +impl TryFrom for FileMode { type Error = Error; - fn try_from(permissions: Permissions) -> Result { - let mode = permissions.mode(); + fn try_from(mode: u32) -> Result { let file_type = FileType::try_from(mode)?; - let mode_mask = mode & !u32::from(SFlag::S_IFMT.bits()); - let user_mode = Mode::try_user_mode_from(mode_mask)?; - let group_mode = Mode::try_group_mode_from(mode_mask)?; - let other_mode = Mode::try_other_mode_from(mode_mask)?; + let modes_mask = mode & !u32::from(SFlag::S_IFMT.bits()); + let user_mode = Mode::try_user_mode_from(modes_mask)?; + let group_mode = Mode::try_group_mode_from(modes_mask)?; + let other_mode = Mode::try_other_mode_from(modes_mask)?; Ok(Self { file_type, diff --git a/src/fs/permissions/mode.rs b/src/fs/permissions/mode.rs index 814d5d00..d7c3bdc9 100644 --- a/src/fs/permissions/mode.rs +++ b/src/fs/permissions/mode.rs @@ -2,6 +2,7 @@ use super::error::Error; use nix::sys::stat::Mode as SMode; use std::fmt::{self, Display}; +/// The set of permissions for a particular class i.e. user, group, or other. pub enum Mode { Read, Write, @@ -12,7 +13,9 @@ pub enum Mode { ReadWriteExecute, } +/// All `modes_mask` arguments represent the portions of `st_mode` which excludes the file-type. impl Mode { + /// Computes user permissions. pub fn try_user_mode_from(modes_mask: u32) -> Result { let user = modes_mask & u32::from(SMode::S_IRWXU.bits()); @@ -23,6 +26,7 @@ impl Mode { Self::try_mode_from_rwx(read, write, execute) } + /// Computes group permissions. pub fn try_group_mode_from(modes_mask: u32) -> Result { let group = modes_mask & u32::from(SMode::S_IRWXG.bits()); @@ -33,6 +37,7 @@ impl Mode { Self::try_mode_from_rwx(read, write, execute) } + /// Computes other permissions. pub fn try_other_mode_from(modes_mask: u32) -> Result { let other = modes_mask & u32::from(SMode::S_IRWXO.bits()); @@ -43,6 +48,7 @@ impl Mode { Self::try_mode_from_rwx(read, write, execute) } + /// Checks if a particular mode (read, write, or execute) is enabled. fn enabled(class_mask: u32, mode_mask: N) -> bool where N: Copy + Into, @@ -50,6 +56,7 @@ impl Mode { class_mask & mode_mask.into() == mode_mask.into() } + /// Helper function to compute permissions. const fn try_mode_from_rwx(r: bool, w: bool, x: bool) -> Result { match (r, w, x) { (true, false, false) => Ok(Self::Read), @@ -64,6 +71,7 @@ impl Mode { } } +/// The `rwx` representation of a [Mode]. impl Display for Mode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { From cb00c09ffe8965aa8b196d7083392f0d3d77d163 Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Sat, 15 Apr 2023 00:48:39 -0700 Subject: [PATCH 05/33] symbolic notation test --- src/fs/permissions/file_type.rs | 1 + src/fs/permissions/mod.rs | 14 +++++++++---- src/fs/permissions/mode.rs | 4 ++++ src/fs/permissions/test.rs | 36 +++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 src/fs/permissions/test.rs diff --git a/src/fs/permissions/file_type.rs b/src/fs/permissions/file_type.rs index 345817b8..3901e28a 100644 --- a/src/fs/permissions/file_type.rs +++ b/src/fs/permissions/file_type.rs @@ -2,6 +2,7 @@ use super::error::Error; use nix::sys::stat::SFlag; /// Unix file types. +#[derive(Debug, PartialEq)] pub enum FileType { Directory, File, diff --git a/src/fs/permissions/mod.rs b/src/fs/permissions/mod.rs index 3a96d376..b3be7af3 100644 --- a/src/fs/permissions/mod.rs +++ b/src/fs/permissions/mod.rs @@ -5,6 +5,7 @@ use nix::sys::stat::SFlag; use std::{ convert::TryFrom, fmt::{self, Display}, + fs::Permissions, os::unix::fs::PermissionsExt, }; @@ -17,6 +18,9 @@ pub mod file_type; /// For working with permissions for a particular class i.e. user, group, or other. pub mod mode; +#[cfg(test)] +mod test; + /// Trait that is used to extend [std::fs::Permissions] behavior such that it allows for `mode` to /// be expressed in Unix's symbolic notation for file permissions. pub trait SymbolicNotation: PermissionsExt { @@ -28,6 +32,8 @@ pub trait SymbolicNotation: PermissionsExt { } } +impl SymbolicNotation for Permissions {} + /// A struct which holds information about the permissions of a particular file. [FileMode] /// implements [Display] which allows it to be conveniently represented in symbolic notation when /// expressing file permissions. @@ -55,22 +61,22 @@ impl FileMode { } /// Returns a reference to `file_type`. - const fn file_type(&self) -> &FileType { + pub const fn file_type(&self) -> &FileType { &self.file_type } /// Returns a reference to a [Mode] which represents the permissions of the user class. - const fn user_mode(&self) -> &Mode { + pub const fn user_mode(&self) -> &Mode { &self.user_mode } /// Returns a reference to a [Mode] which represents the permissions of the group class. - const fn group_mode(&self) -> &Mode { + pub const fn group_mode(&self) -> &Mode { &self.group_mode } /// Returns a reference to a [Mode] which represents the permissions of the other class. - const fn other_mode(&self) -> &Mode { + pub const fn other_mode(&self) -> &Mode { &self.other_mode } } diff --git a/src/fs/permissions/mode.rs b/src/fs/permissions/mode.rs index d7c3bdc9..03906e1d 100644 --- a/src/fs/permissions/mode.rs +++ b/src/fs/permissions/mode.rs @@ -3,6 +3,7 @@ use nix::sys::stat::Mode as SMode; use std::fmt::{self, Display}; /// The set of permissions for a particular class i.e. user, group, or other. +#[derive(Debug, PartialEq)] pub enum Mode { Read, Write, @@ -11,6 +12,7 @@ pub enum Mode { ReadExecute, WriteExecute, ReadWriteExecute, + None } /// All `modes_mask` arguments represent the portions of `st_mode` which excludes the file-type. @@ -66,6 +68,7 @@ impl Mode { (true, false, true) => Ok(Self::ReadExecute), (false, true, true) => Ok(Self::WriteExecute), (true, true, true) => Ok(Self::ReadWriteExecute), + (false, false, false) => Ok(Self::None), _ => Err(Error::UnknownMode), } } @@ -82,6 +85,7 @@ impl Display for Mode { Self::ReadExecute => write!(f, "r-x"), Self::WriteExecute => write!(f, "-wx"), Self::ReadWriteExecute => write!(f, "rwx"), + Self::None => write!(f, "---"), } } } diff --git a/src/fs/permissions/test.rs b/src/fs/permissions/test.rs new file mode 100644 index 00000000..99f66ce3 --- /dev/null +++ b/src/fs/permissions/test.rs @@ -0,0 +1,36 @@ +use std::{ + error::Error, + fs::File, +}; +use super::{ + file_type::FileType, + mode::Mode, + SymbolicNotation +}; + +#[test] +fn test_symbolic_notation() -> Result<(), Box> { + let temp = std::env::temp_dir().join("yogsothoth.hpl"); + let file = File::create(temp)?; + let metadata = file.metadata()?; + let persmissions = metadata.permissions(); + + // Star of the show + let file_mode = persmissions.try_mode_symbolic_notation()?; + + let file_type = file_mode.file_type(); + let user = file_mode.user_mode(); + let group = file_mode.group_mode(); + let other = file_mode.other_mode(); + + assert_eq!(file_type, &FileType::File); + assert_eq!(user, &Mode::ReadWrite); + assert_eq!(group, &Mode::Read); + assert_eq!(other, &Mode::Read); + + let rwx_string = format!("{file_mode}"); + + assert_eq!(rwx_string, ".rw-r--r--"); + + Ok(()) +} From 1b6cbebdcea6ababe121c16ae38f131c61681d3d Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Sat, 15 Apr 2023 00:50:48 -0700 Subject: [PATCH 06/33] cargo fmt --- src/fs/permissions/error.rs | 3 --- src/fs/permissions/file_type.rs | 2 +- src/fs/permissions/mod.rs | 1 + src/fs/permissions/mode.rs | 5 ++--- src/fs/permissions/test.rs | 13 +++---------- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/fs/permissions/error.rs b/src/fs/permissions/error.rs index 25a8165a..011e13f6 100644 --- a/src/fs/permissions/error.rs +++ b/src/fs/permissions/error.rs @@ -4,7 +4,4 @@ use thiserror::Error; pub enum Error { #[error("Unknown file type.")] UnknownFileType, - - #[error("Failed to compute file mode.")] - UnknownMode, } diff --git a/src/fs/permissions/file_type.rs b/src/fs/permissions/file_type.rs index 3901e28a..ec5a191e 100644 --- a/src/fs/permissions/file_type.rs +++ b/src/fs/permissions/file_type.rs @@ -2,7 +2,7 @@ use super::error::Error; use nix::sys::stat::SFlag; /// Unix file types. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum FileType { Directory, File, diff --git a/src/fs/permissions/mod.rs b/src/fs/permissions/mod.rs index b3be7af3..dbfaa41f 100644 --- a/src/fs/permissions/mod.rs +++ b/src/fs/permissions/mod.rs @@ -37,6 +37,7 @@ impl SymbolicNotation for Permissions {} /// A struct which holds information about the permissions of a particular file. [FileMode] /// implements [Display] which allows it to be conveniently represented in symbolic notation when /// expressing file permissions. +#[derive(Debug)] pub struct FileMode { file_type: FileType, user_mode: Mode, diff --git a/src/fs/permissions/mode.rs b/src/fs/permissions/mode.rs index 03906e1d..1f7c5427 100644 --- a/src/fs/permissions/mode.rs +++ b/src/fs/permissions/mode.rs @@ -3,7 +3,7 @@ use nix::sys::stat::Mode as SMode; use std::fmt::{self, Display}; /// The set of permissions for a particular class i.e. user, group, or other. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum Mode { Read, Write, @@ -12,7 +12,7 @@ pub enum Mode { ReadExecute, WriteExecute, ReadWriteExecute, - None + None, } /// All `modes_mask` arguments represent the portions of `st_mode` which excludes the file-type. @@ -69,7 +69,6 @@ impl Mode { (false, true, true) => Ok(Self::WriteExecute), (true, true, true) => Ok(Self::ReadWriteExecute), (false, false, false) => Ok(Self::None), - _ => Err(Error::UnknownMode), } } } diff --git a/src/fs/permissions/test.rs b/src/fs/permissions/test.rs index 99f66ce3..f4d01843 100644 --- a/src/fs/permissions/test.rs +++ b/src/fs/permissions/test.rs @@ -1,12 +1,5 @@ -use std::{ - error::Error, - fs::File, -}; -use super::{ - file_type::FileType, - mode::Mode, - SymbolicNotation -}; +use super::{file_type::FileType, mode::Mode, SymbolicNotation}; +use std::{error::Error, fs::File}; #[test] fn test_symbolic_notation() -> Result<(), Box> { @@ -31,6 +24,6 @@ fn test_symbolic_notation() -> Result<(), Box> { let rwx_string = format!("{file_mode}"); assert_eq!(rwx_string, ".rw-r--r--"); - + Ok(()) } From 027c04b8dfbce828f847c493b6020d22e934260a Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Sat, 15 Apr 2023 01:23:47 -0700 Subject: [PATCH 07/33] libc instead of nix --- Cargo.lock | 43 +-------------------------------- Cargo.toml | 2 +- src/fs/permissions/file_type.rs | 17 ++++++------- src/fs/permissions/mod.rs | 3 +-- src/fs/permissions/mode.rs | 25 +++++++++---------- 5 files changed, 23 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f9588f3..69c91853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,12 +26,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - [[package]] name = "bitflags" version = "1.3.2" @@ -118,8 +112,8 @@ dependencies = [ "indextree", "indoc", "is-terminal", + "libc", "lscolors", - "nix", "once_cell", "regex", "strip-ansi-escapes", @@ -318,29 +312,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - -[[package]] -name = "nix" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" -dependencies = [ - "bitflags", - "cfg-if", - "libc", - "memoffset", - "pin-utils", - "static_assertions", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -369,12 +340,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -486,12 +451,6 @@ version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strip-ansi-escapes" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index d5fbafd4..85ecf5fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ regex = "1.7.3" thiserror = "1.0.40" [target.'cfg(unix)'.dependencies] -nix = { version = "0.26.2", features = ["fs"] } +libc = "0.2.141" [dev-dependencies] indoc = "2.0.0" diff --git a/src/fs/permissions/file_type.rs b/src/fs/permissions/file_type.rs index ec5a191e..9c409973 100644 --- a/src/fs/permissions/file_type.rs +++ b/src/fs/permissions/file_type.rs @@ -1,5 +1,4 @@ use super::error::Error; -use nix::sys::stat::SFlag; /// Unix file types. #[derive(Debug, PartialEq, Eq)] @@ -33,21 +32,21 @@ impl TryFrom for FileType { type Error = Error; fn try_from(mode: u32) -> Result { - let file_mask = mode & u32::from(SFlag::S_IFMT.bits()); + let file_mask = mode & u32::from(libc::S_IFMT); - if file_mask == u32::from(SFlag::S_IFIFO.bits()) { + if file_mask == u32::from(libc::S_IFIFO) { Ok(Self::Fifo) - } else if file_mask == u32::from(SFlag::S_IFCHR.bits()) { + } else if file_mask == u32::from(libc::S_IFCHR) { Ok(Self::CharDevice) - } else if file_mask == u32::from(SFlag::S_IFDIR.bits()) { + } else if file_mask == u32::from(libc::S_IFDIR) { Ok(Self::Directory) - } else if file_mask == u32::from(SFlag::S_IFBLK.bits()) { + } else if file_mask == u32::from(libc::S_IFBLK) { Ok(Self::BlockDevice) - } else if file_mask == u32::from(SFlag::S_IFREG.bits()) { + } else if file_mask == u32::from(libc::S_IFREG) { Ok(Self::File) - } else if file_mask == u32::from(SFlag::S_IFLNK.bits()) { + } else if file_mask == u32::from(libc::S_IFLNK) { Ok(Self::Symlink) - } else if file_mask == u32::from(SFlag::S_IFSOCK.bits()) { + } else if file_mask == u32::from(libc::S_IFSOCK) { Ok(Self::Socket) } else { Err(Error::UnknownFileType) diff --git a/src/fs/permissions/mod.rs b/src/fs/permissions/mod.rs index dbfaa41f..10ad6332 100644 --- a/src/fs/permissions/mod.rs +++ b/src/fs/permissions/mod.rs @@ -1,7 +1,6 @@ use error::Error; use file_type::FileType; use mode::Mode; -use nix::sys::stat::SFlag; use std::{ convert::TryFrom, fmt::{self, Display}, @@ -100,7 +99,7 @@ impl TryFrom for FileMode { fn try_from(mode: u32) -> Result { let file_type = FileType::try_from(mode)?; - let modes_mask = mode & !u32::from(SFlag::S_IFMT.bits()); + let modes_mask = mode & !u32::from(libc::S_IFMT); let user_mode = Mode::try_user_mode_from(modes_mask)?; let group_mode = Mode::try_group_mode_from(modes_mask)?; let other_mode = Mode::try_other_mode_from(modes_mask)?; diff --git a/src/fs/permissions/mode.rs b/src/fs/permissions/mode.rs index 1f7c5427..b9ffce52 100644 --- a/src/fs/permissions/mode.rs +++ b/src/fs/permissions/mode.rs @@ -1,5 +1,4 @@ use super::error::Error; -use nix::sys::stat::Mode as SMode; use std::fmt::{self, Display}; /// The set of permissions for a particular class i.e. user, group, or other. @@ -19,33 +18,33 @@ pub enum Mode { impl Mode { /// Computes user permissions. pub fn try_user_mode_from(modes_mask: u32) -> Result { - let user = modes_mask & u32::from(SMode::S_IRWXU.bits()); + let user = modes_mask & u32::from(libc::S_IRWXU); - let read = Self::enabled(user, SMode::S_IRUSR.bits()); - let write = Self::enabled(user, SMode::S_IWUSR.bits()); - let execute = Self::enabled(user, SMode::S_IXUSR.bits()); + let read = Self::enabled(user, libc::S_IRUSR); + let write = Self::enabled(user, libc::S_IWUSR); + let execute = Self::enabled(user, libc::S_IXUSR); Self::try_mode_from_rwx(read, write, execute) } /// Computes group permissions. pub fn try_group_mode_from(modes_mask: u32) -> Result { - let group = modes_mask & u32::from(SMode::S_IRWXG.bits()); + let group = modes_mask & u32::from(libc::S_IRWXG); - let read = Self::enabled(group, SMode::S_IRGRP.bits()); - let write = Self::enabled(group, SMode::S_IWGRP.bits()); - let execute = Self::enabled(group, SMode::S_IXGRP.bits()); + let read = Self::enabled(group, libc::S_IRGRP); + let write = Self::enabled(group, libc::S_IWGRP); + let execute = Self::enabled(group, libc::S_IXGRP); Self::try_mode_from_rwx(read, write, execute) } /// Computes other permissions. pub fn try_other_mode_from(modes_mask: u32) -> Result { - let other = modes_mask & u32::from(SMode::S_IRWXO.bits()); + let other = modes_mask & u32::from(libc::S_IRWXO); - let read = Self::enabled(other, SMode::S_IROTH.bits()); - let write = Self::enabled(other, SMode::S_IWOTH.bits()); - let execute = Self::enabled(other, SMode::S_IXOTH.bits()); + let read = Self::enabled(other, libc::S_IROTH); + let write = Self::enabled(other, libc::S_IWOTH); + let execute = Self::enabled(other, libc::S_IXOTH); Self::try_mode_from_rwx(read, write, execute) } From 3e323a70471afae3fd046ea47f5b08c0a669acd0 Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Sat, 15 Apr 2023 20:33:21 -0700 Subject: [PATCH 08/33] setuid setgid sticky --- Cargo.lock | 10 +++ Cargo.toml | 1 + install.sh | 1 + src/fs/mod.rs | 4 + src/fs/permissions/class.rs | 152 ++++++++++++++++++++++++++++++++ src/fs/permissions/error.rs | 2 +- src/fs/permissions/file_type.rs | 2 +- src/fs/permissions/mod.rs | 93 ++++++++++--------- src/fs/permissions/mode.rs | 89 ------------------- src/fs/permissions/test.rs | 93 ++++++++++++++++--- src/fs/xattr.rs | 25 ++++++ src/render/tree/error.rs | 2 +- 12 files changed, 329 insertions(+), 145 deletions(-) create mode 100755 install.sh create mode 100644 src/fs/permissions/class.rs delete mode 100644 src/fs/permissions/mode.rs create mode 100644 src/fs/xattr.rs diff --git a/Cargo.lock b/Cargo.lock index 69c91853..b7989400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,7 @@ dependencies = [ "strip-ansi-escapes", "tempfile", "thiserror", + "xattr", ] [[package]] @@ -700,3 +701,12 @@ name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "xattr" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea263437ca03c1522846a4ddafbca2542d0ad5ed9b784909d4b27b76f62bc34a" +dependencies = [ + "libc", +] diff --git a/Cargo.toml b/Cargo.toml index 85ecf5fd..dafb0b3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ thiserror = "1.0.40" [target.'cfg(unix)'.dependencies] libc = "0.2.141" +xattr = "1.0" [dev-dependencies] indoc = "2.0.0" diff --git a/install.sh b/install.sh new file mode 100755 index 00000000..7c0dffd8 --- /dev/null +++ b/install.sh @@ -0,0 +1 @@ +echo hello world diff --git a/src/fs/mod.rs b/src/fs/mod.rs index f6295644..a266260f 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -8,6 +8,10 @@ pub mod inode; #[cfg(unix)] pub mod permissions; +#[cfg(unix)] +/// Determining whether or not a file has extended attributes. +pub mod xattr; + /// Returns the path to the target of the soft link. Returns `None` if provided `dir_entry` isn't a /// symlink. pub fn symlink_target(dir_entry: &DirEntry) -> Option { diff --git a/src/fs/permissions/class.rs b/src/fs/permissions/class.rs new file mode 100644 index 00000000..5b1d44d1 --- /dev/null +++ b/src/fs/permissions/class.rs @@ -0,0 +1,152 @@ +use std::fmt::{self, Display}; + +/// The set of permissions for a particular class i.e. user, group, or other. +#[derive(Debug)] +#[allow(clippy::module_name_repetitions)] +pub struct ClassPermissions { + class: Class, + attr: Option, + pub(super) triad: PermissionsTriad, +} + +/// The class type that is associated with a permissions triad. +#[derive(Debug)] +pub enum Class { + User, + Group, + Other, +} + +/// Represents the special attributes that exist on the overall file corresponding to the setuid, +/// setgid, and the sticky bit. +#[derive(Debug, PartialEq, Eq)] +#[allow(clippy::upper_case_acronyms)] +pub enum Attribute { + SUID, + SGID, + Sticky, +} + +/// Read, write, execute permissions. +#[derive(Debug, PartialEq, Eq)] +pub enum PermissionsTriad { + Read, + Write, + Execute, + ReadWrite, + ReadExecute, + WriteExecute, + ReadWriteExecute, + None, +} + +/// All `permissions_mask` arguments represents the bits of `st_mode` which excludes the file-type +/// and the setuid, setgid, and sticky bit. +impl ClassPermissions { + /// Computes user permissions. + pub fn user_permissions_from(st_mode: u32) -> Self { + let read = Self::enabled(st_mode, libc::S_IRUSR); + let write = Self::enabled(st_mode, libc::S_IWUSR); + let execute = Self::enabled(st_mode, libc::S_IXUSR); + let suid = Self::enabled(st_mode, libc::S_ISUID).then_some(Attribute::SUID); + + Self::permissions_from_rwx(Class::User, read, write, execute, suid) + } + + /// Computes group permissions. + pub fn group_permissions_from(st_mode: u32) -> Self { + let read = Self::enabled(st_mode, libc::S_IRGRP); + let write = Self::enabled(st_mode, libc::S_IWGRP); + let execute = Self::enabled(st_mode, libc::S_IXGRP); + let sgid = Self::enabled(st_mode, libc::S_ISGID).then_some(Attribute::SGID); + + Self::permissions_from_rwx(Class::Group, read, write, execute, sgid) + } + + /// Computes other permissions. + pub fn other_permissions_from(st_mode: u32) -> Self { + let read = Self::enabled(st_mode, libc::S_IROTH); + let write = Self::enabled(st_mode, libc::S_IWOTH); + let execute = Self::enabled(st_mode, libc::S_IXOTH); + let sticky = Self::enabled(st_mode, libc::S_ISVTX).then_some(Attribute::Sticky); + + Self::permissions_from_rwx(Class::Other, read, write, execute, sticky) + } + + /// Checks if a particular mode (read, write, or execute) is enabled. + fn enabled(st_mode: u32, mask: N) -> bool + where + N: Copy + Into, + { + st_mode & mask.into() == mask.into() + } + + /// Returns `true` if sticky bit is enabled. + pub fn attr_is_sticky(&self) -> bool { + self.attr + .as_ref() + .map_or(false, |attr| attr == &Attribute::Sticky) + } + + /// Helper function to compute permissions. + const fn permissions_from_rwx( + class: Class, + r: bool, + w: bool, + x: bool, + attr: Option, + ) -> Self { + let triad = match (r, w, x) { + (true, false, false) => PermissionsTriad::Read, + (false, true, false) => PermissionsTriad::Write, + (false, false, true) => PermissionsTriad::Execute, + (true, true, false) => PermissionsTriad::ReadWrite, + (true, false, true) => PermissionsTriad::ReadExecute, + (false, true, true) => PermissionsTriad::WriteExecute, + (true, true, true) => PermissionsTriad::ReadWriteExecute, + (false, false, false) => PermissionsTriad::None, + }; + + Self { class, attr, triad } + } +} + +/// The symbolic representation of a [PermissionsTriad]. +impl Display for ClassPermissions { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.class { + Class::Other if self.attr_is_sticky() => match self.triad { + PermissionsTriad::Read => write!(f, "r-T"), + PermissionsTriad::Write => write!(f, "-wT"), + PermissionsTriad::Execute => write!(f, "--t"), + PermissionsTriad::ReadWrite => write!(f, "rwT"), + PermissionsTriad::ReadExecute => write!(f, "r-t"), + PermissionsTriad::WriteExecute => write!(f, "-wt"), + PermissionsTriad::ReadWriteExecute => write!(f, "rwt"), + PermissionsTriad::None => write!(f, "--T"), + }, + + _ if self.attr.is_some() => match self.triad { + PermissionsTriad::Read => write!(f, "r-S"), + PermissionsTriad::Write => write!(f, "-wS"), + PermissionsTriad::Execute => write!(f, "--s"), + PermissionsTriad::ReadWrite => write!(f, "rwS"), + PermissionsTriad::ReadExecute => write!(f, "r-s"), + PermissionsTriad::WriteExecute => write!(f, "-ws"), + PermissionsTriad::ReadWriteExecute => write!(f, "rws"), + PermissionsTriad::None => write!(f, "--S"), + }, + + _ => match self.triad { + PermissionsTriad::Read => write!(f, "r--"), + PermissionsTriad::Write => write!(f, "-w-"), + PermissionsTriad::Execute => write!(f, "--x"), + PermissionsTriad::ReadWrite => write!(f, "rw-"), + PermissionsTriad::ReadExecute => write!(f, "r-x"), + PermissionsTriad::WriteExecute => write!(f, "-wx"), + PermissionsTriad::ReadWriteExecute => write!(f, "rwx"), + PermissionsTriad::None => write!(f, "---"), + }, + } + } +} diff --git a/src/fs/permissions/error.rs b/src/fs/permissions/error.rs index 011e13f6..075f70f2 100644 --- a/src/fs/permissions/error.rs +++ b/src/fs/permissions/error.rs @@ -2,6 +2,6 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum Error { - #[error("Unknown file type.")] + #[error("Encountered an unknown file type.")] UnknownFileType, } diff --git a/src/fs/permissions/file_type.rs b/src/fs/permissions/file_type.rs index 9c409973..4bed7f22 100644 --- a/src/fs/permissions/file_type.rs +++ b/src/fs/permissions/file_type.rs @@ -17,7 +17,7 @@ impl FileType { pub const fn identifier(&self) -> char { match self { Self::Directory => 'd', - Self::File => '.', + Self::File => '-', Self::Symlink => 'l', Self::Fifo => 'p', Self::Socket => 's', diff --git a/src/fs/permissions/mod.rs b/src/fs/permissions/mod.rs index 10ad6332..16ac16d8 100644 --- a/src/fs/permissions/mod.rs +++ b/src/fs/permissions/mod.rs @@ -1,25 +1,26 @@ +use class::ClassPermissions; use error::Error; use file_type::FileType; -use mode::Mode; use std::{ convert::TryFrom, - fmt::{self, Display}, - fs::Permissions, + fmt::{self, Display, Octal}, os::unix::fs::PermissionsExt, }; +/// For working with permissions for a particular class i.e. user, group, or other. +pub mod class; + /// File permission related errors. pub mod error; /// For working with Unix file identifiers. pub mod file_type; -/// For working with permissions for a particular class i.e. user, group, or other. -pub mod mode; - #[cfg(test)] mod test; +impl SymbolicNotation for std::fs::Permissions {} + /// Trait that is used to extend [std::fs::Permissions] behavior such that it allows for `mode` to /// be expressed in Unix's symbolic notation for file permissions. pub trait SymbolicNotation: PermissionsExt { @@ -31,32 +32,33 @@ pub trait SymbolicNotation: PermissionsExt { } } -impl SymbolicNotation for Permissions {} - /// A struct which holds information about the permissions of a particular file. [FileMode] /// implements [Display] which allows it to be conveniently represented in symbolic notation when /// expressing file permissions. #[derive(Debug)] pub struct FileMode { + pub st_mode: u32, file_type: FileType, - user_mode: Mode, - group_mode: Mode, - other_mode: Mode, + user_permissions: ClassPermissions, + group_permissions: ClassPermissions, + other_permissions: ClassPermissions, } impl FileMode { /// Constructor for [FileMode]. pub const fn new( + st_mode: u32, file_type: FileType, - user_mode: Mode, - group_mode: Mode, - other_mode: Mode, + user_permissions: ClassPermissions, + group_permissions: ClassPermissions, + other_permissions: ClassPermissions, ) -> Self { Self { + st_mode, file_type, - user_mode, - group_mode, - other_mode, + user_permissions, + group_permissions, + other_permissions, } } @@ -65,19 +67,19 @@ impl FileMode { &self.file_type } - /// Returns a reference to a [Mode] which represents the permissions of the user class. - pub const fn user_mode(&self) -> &Mode { - &self.user_mode + /// Returns a reference to a [ClassPermissions] which represents the permissions of the user class. + pub const fn user_permissions(&self) -> &ClassPermissions { + &self.user_permissions } - /// Returns a reference to a [Mode] which represents the permissions of the group class. - pub const fn group_mode(&self) -> &Mode { - &self.group_mode + /// Returns a reference to a [ClassPermissions] which represents the permissions of the group class. + pub const fn group_permissions(&self) -> &ClassPermissions { + &self.group_permissions } - /// Returns a reference to a [Mode] which represents the permissions of the other class. - pub const fn other_mode(&self) -> &Mode { - &self.other_mode + /// Returns a reference to a [ClassPermissions] which represents the permissions of the other class. + pub const fn other_permissions(&self) -> &ClassPermissions { + &self.other_permissions } } @@ -85,30 +87,41 @@ impl FileMode { impl Display for FileMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let file_iden = self.file_type().identifier(); - let user_mode = self.user_mode(); - let group_mode = self.group_mode(); - let other_mode = self.other_mode(); + let user_permissions = self.user_permissions(); + let group_permissions = self.group_permissions(); + let other_permissions = self.other_permissions(); + + write!( + f, + "{file_iden}{user_permissions}{group_permissions}{other_permissions}" + ) + } +} - write!(f, "{file_iden}{user_mode}{group_mode}{other_mode}") +/// For the octal representation of the file type and class permisssions. +impl Octal for FileMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let modes_mask = self.st_mode & !u32::from(libc::S_IFMT); + fmt::Octal::fmt(&modes_mask, f) } } -/// The argument `mode` is meant to come from the `mode` method of [std::fs::Permissions]. +/// The argument `st_mode` is meant to come from the `mode` method of [std::fs::Permissions]. impl TryFrom for FileMode { type Error = Error; - fn try_from(mode: u32) -> Result { - let file_type = FileType::try_from(mode)?; - let modes_mask = mode & !u32::from(libc::S_IFMT); - let user_mode = Mode::try_user_mode_from(modes_mask)?; - let group_mode = Mode::try_group_mode_from(modes_mask)?; - let other_mode = Mode::try_other_mode_from(modes_mask)?; + fn try_from(st_mode: u32) -> Result { + let file_type = FileType::try_from(st_mode)?; + let user_permissions = ClassPermissions::user_permissions_from(st_mode); + let group_permissions = ClassPermissions::group_permissions_from(st_mode); + let other_permissions = ClassPermissions::other_permissions_from(st_mode); Ok(Self { + st_mode, file_type, - user_mode, - group_mode, - other_mode, + user_permissions, + group_permissions, + other_permissions, }) } } diff --git a/src/fs/permissions/mode.rs b/src/fs/permissions/mode.rs deleted file mode 100644 index b9ffce52..00000000 --- a/src/fs/permissions/mode.rs +++ /dev/null @@ -1,89 +0,0 @@ -use super::error::Error; -use std::fmt::{self, Display}; - -/// The set of permissions for a particular class i.e. user, group, or other. -#[derive(Debug, PartialEq, Eq)] -pub enum Mode { - Read, - Write, - Execute, - ReadWrite, - ReadExecute, - WriteExecute, - ReadWriteExecute, - None, -} - -/// All `modes_mask` arguments represent the portions of `st_mode` which excludes the file-type. -impl Mode { - /// Computes user permissions. - pub fn try_user_mode_from(modes_mask: u32) -> Result { - let user = modes_mask & u32::from(libc::S_IRWXU); - - let read = Self::enabled(user, libc::S_IRUSR); - let write = Self::enabled(user, libc::S_IWUSR); - let execute = Self::enabled(user, libc::S_IXUSR); - - Self::try_mode_from_rwx(read, write, execute) - } - - /// Computes group permissions. - pub fn try_group_mode_from(modes_mask: u32) -> Result { - let group = modes_mask & u32::from(libc::S_IRWXG); - - let read = Self::enabled(group, libc::S_IRGRP); - let write = Self::enabled(group, libc::S_IWGRP); - let execute = Self::enabled(group, libc::S_IXGRP); - - Self::try_mode_from_rwx(read, write, execute) - } - - /// Computes other permissions. - pub fn try_other_mode_from(modes_mask: u32) -> Result { - let other = modes_mask & u32::from(libc::S_IRWXO); - - let read = Self::enabled(other, libc::S_IROTH); - let write = Self::enabled(other, libc::S_IWOTH); - let execute = Self::enabled(other, libc::S_IXOTH); - - Self::try_mode_from_rwx(read, write, execute) - } - - /// Checks if a particular mode (read, write, or execute) is enabled. - fn enabled(class_mask: u32, mode_mask: N) -> bool - where - N: Copy + Into, - { - class_mask & mode_mask.into() == mode_mask.into() - } - - /// Helper function to compute permissions. - const fn try_mode_from_rwx(r: bool, w: bool, x: bool) -> Result { - match (r, w, x) { - (true, false, false) => Ok(Self::Read), - (false, true, false) => Ok(Self::Write), - (false, false, true) => Ok(Self::Execute), - (true, true, false) => Ok(Self::ReadWrite), - (true, false, true) => Ok(Self::ReadExecute), - (false, true, true) => Ok(Self::WriteExecute), - (true, true, true) => Ok(Self::ReadWriteExecute), - (false, false, false) => Ok(Self::None), - } - } -} - -/// The `rwx` representation of a [Mode]. -impl Display for Mode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Read => write!(f, "r--"), - Self::Write => write!(f, "-w-"), - Self::Execute => write!(f, "--x"), - Self::ReadWrite => write!(f, "rw-"), - Self::ReadExecute => write!(f, "r-x"), - Self::WriteExecute => write!(f, "-wx"), - Self::ReadWriteExecute => write!(f, "rwx"), - Self::None => write!(f, "---"), - } - } -} diff --git a/src/fs/permissions/test.rs b/src/fs/permissions/test.rs index f4d01843..aa6dba70 100644 --- a/src/fs/permissions/test.rs +++ b/src/fs/permissions/test.rs @@ -1,29 +1,96 @@ -use super::{file_type::FileType, mode::Mode, SymbolicNotation}; -use std::{error::Error, fs::File}; +use super::{class::PermissionsTriad, file_type::FileType, SymbolicNotation}; +use std::{error::Error, fs::File, os::unix::fs::PermissionsExt}; #[test] fn test_symbolic_notation() -> Result<(), Box> { let temp = std::env::temp_dir().join("yogsothoth.hpl"); + + // File is created with read + write for user and read-only for all others. let file = File::create(temp)?; let metadata = file.metadata()?; - let persmissions = metadata.permissions(); - // Star of the show - let file_mode = persmissions.try_mode_symbolic_notation()?; + let permissions = metadata.permissions(); + + let file_mode = permissions.try_mode_symbolic_notation()?; let file_type = file_mode.file_type(); - let user = file_mode.user_mode(); - let group = file_mode.group_mode(); - let other = file_mode.other_mode(); + let user = file_mode.user_permissions(); + let group = file_mode.group_permissions(); + let other = file_mode.other_permissions(); assert_eq!(file_type, &FileType::File); - assert_eq!(user, &Mode::ReadWrite); - assert_eq!(group, &Mode::Read); - assert_eq!(other, &Mode::Read); + assert_eq!(&user.triad, &PermissionsTriad::ReadWrite); + assert_eq!(&group.triad, &PermissionsTriad::Read); + assert_eq!(&other.triad, &PermissionsTriad::Read); + + let rwx = format!("{file_mode}"); + assert_eq!(rwx, "-rw-r--r--"); + + let octal = format!("{:o}", file_mode); + assert_eq!(octal, "644"); + + Ok(()) +} + +#[test] +fn test_symbolic_notation_special_attr() -> Result<(), Box> { + let temp = std::env::temp_dir().join("sub-niggurath.hpl"); + + // File is created with read + write for user and read-only for all others. + let file = File::create(temp)?; + + let metadata = file.metadata()?; + let mut permissions = metadata.permissions(); + + // Set the sticky bit + permissions.set_mode(0o101644); + + let file_mode = permissions.try_mode_symbolic_notation()?; + let rwx = format!("{file_mode}"); + assert_eq!(rwx, "-rw-r--r-T"); + + let octal = format!("{:o}", file_mode); + assert_eq!(octal, "1644"); + + // Set the getuid bit + permissions.set_mode(0o102644); + + let file_mode = permissions.try_mode_symbolic_notation()?; + let rwx = format!("{file_mode}"); + assert_eq!(rwx, "-rw-r-Sr--"); + + let octal = format!("{:o}", file_mode); + assert_eq!(octal, "2644"); + + // Set the setuid bit + permissions.set_mode(0o104644); + + let file_mode = permissions.try_mode_symbolic_notation()?; + let rwx = format!("{file_mode}"); + assert_eq!(rwx, "-rwSr--r--"); + + let octal = format!("{:o}", file_mode); + assert_eq!(octal, "4644"); + + // Set the all the attr bits + permissions.set_mode(0o107644); + + let file_mode = permissions.try_mode_symbolic_notation()?; + let rwx = format!("{file_mode}"); + assert_eq!(rwx, "-rwSr-Sr-T"); + + let octal = format!("{:o}", file_mode); + assert_eq!(octal, "7644"); + + // Set all the attr bits and give all classes execute permissions + permissions.set_mode(0o107777); - let rwx_string = format!("{file_mode}"); + let file_mode = permissions.try_mode_symbolic_notation()?; + let rwx = format!("{file_mode}"); + assert_eq!(rwx, "-rwsrwsrwt"); - assert_eq!(rwx_string, ".rw-r--r--"); + let octal = format!("{:o}", file_mode); + assert_eq!(octal, "7777"); Ok(()) } diff --git a/src/fs/xattr.rs b/src/fs/xattr.rs new file mode 100644 index 00000000..f2f97931 --- /dev/null +++ b/src/fs/xattr.rs @@ -0,0 +1,25 @@ +use ignore::DirEntry; +use std::{io, path::Path}; +use xattr::XAttrs; + +/// Allow extended attributes to be queried directly from the directory entry. +impl ExtendedAttr for DirEntry { + fn path(&self) -> &Path { + self.path() + } +} + +/// Simple trait that allows files to query extended attributes if it exists. +pub trait ExtendedAttr { + fn path(&self) -> &Path; + + /// Query the extended attribute and return an error if something goes wrong. + fn try_xattr(&self) -> io::Result { + xattr::list(self.path()) + } + + /// Query the extended attribute and return `None` if something goes. + fn xattr(&self) -> Option { + xattr::list(self.path()).ok() + } +} diff --git a/src/render/tree/error.rs b/src/render/tree/error.rs index 44fa18bc..28b23347 100644 --- a/src/render/tree/error.rs +++ b/src/render/tree/error.rs @@ -9,7 +9,7 @@ use std::io::Error as IoError; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("{0}")] - ContextError(#[from] CtxError), + Context(#[from] CtxError), #[error("{0}")] DirNotFound(String), From 75fc6a2432084c31a826f78df14a88c3568dd625 Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Sat, 15 Apr 2023 21:12:25 -0700 Subject: [PATCH 09/33] function renaming --- src/fs/permissions/mod.rs | 3 +-- src/fs/xattr.rs | 4 ++-- src/main.rs | 4 ++-- src/render/tree/mod.rs | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/fs/permissions/mod.rs b/src/fs/permissions/mod.rs index 16ac16d8..bda03fb3 100644 --- a/src/fs/permissions/mod.rs +++ b/src/fs/permissions/mod.rs @@ -33,9 +33,8 @@ pub trait SymbolicNotation: PermissionsExt { } /// A struct which holds information about the permissions of a particular file. [FileMode] -/// implements [Display] which allows it to be conveniently represented in symbolic notation when +/// implements [Display] which allows it to be conveniently presented in symbolic notation when /// expressing file permissions. -#[derive(Debug)] pub struct FileMode { pub st_mode: u32, file_type: FileType, diff --git a/src/fs/xattr.rs b/src/fs/xattr.rs index f2f97931..16838362 100644 --- a/src/fs/xattr.rs +++ b/src/fs/xattr.rs @@ -14,12 +14,12 @@ pub trait ExtendedAttr { fn path(&self) -> &Path; /// Query the extended attribute and return an error if something goes wrong. - fn try_xattr(&self) -> io::Result { + fn try_get_xattrs(&self) -> io::Result { xattr::list(self.path()) } /// Query the extended attribute and return `None` if something goes. - fn xattr(&self) -> Option { + fn get_xattrs(&self) -> Option { xattr::list(self.path()).ok() } } diff --git a/src/main.rs b/src/main.rs index 600ae721..e7607b16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,11 +62,11 @@ fn run() -> Result<(), Box> { } if ctx.report { - let tree = Tree::::init(ctx)?; + let tree = Tree::::try_init(ctx)?; println!("{tree}"); } else { render::styles::init(ctx.no_color()); - let tree = Tree::::init(ctx)?; + let tree = Tree::::try_init(ctx)?; println!("{tree}"); } diff --git a/src/render/tree/mod.rs b/src/render/tree/mod.rs index 3ad65b65..d3bccf3a 100644 --- a/src/render/tree/mod.rs +++ b/src/render/tree/mod.rs @@ -67,7 +67,7 @@ where } /// Initiates file-system traversal and [Tree construction]. - pub fn init(ctx: Context) -> Result { + pub fn try_init(ctx: Context) -> Result { let (inner, root) = Self::traverse(&ctx)?; let tree = Self::new(inner, root, ctx); From 2b07532b9592f39e3e51eded47ad581e8a7cb2cd Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Sat, 15 Apr 2023 21:33:30 -0700 Subject: [PATCH 10/33] new cli arg; rename some args --- src/fs/inode.rs | 2 +- src/render/context/mod.rs | 14 +++++++++----- src/render/tree/error.rs | 8 +++++++- src/render/tree/node/mod.rs | 20 ++++++++++++++++++-- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/fs/inode.rs b/src/fs/inode.rs index 4183906c..8f9ff6b9 100644 --- a/src/fs/inode.rs +++ b/src/fs/inode.rs @@ -1,7 +1,7 @@ use std::{convert::TryFrom, fs::Metadata}; /// Represents a file's underlying inode. -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Inode { pub ino: u64, pub dev: u64, diff --git a/src/render/context/mod.rs b/src/render/context/mod.rs index 7214bb42..6a9e13f9 100644 --- a/src/render/context/mod.rs +++ b/src/render/context/mod.rs @@ -53,18 +53,22 @@ pub struct Context { #[arg(short = 'i', long)] pub no_ignore: bool, + /// Follow symlinks and consider their disk usage + #[arg(short = 'f', long = "follow")] + pub follow_links: bool, + /// Display file icons #[arg(short = 'I', long)] pub icons: bool, - /// Follow symlinks and consider their disk usage - #[arg(short = 'L', long = "follow")] - pub follow_links: bool, - /// Maximum depth to display - #[arg(short, long, value_name = "NUM")] + #[arg(short = 'L', long, value_name = "NUM")] level: Option, + /// Show extended metadata and attributes + #[arg(short, long)] + pub long: bool, + /// Regular expression (or glob if '--glob' or '--iglob' is used) used to match files #[arg(short, long)] pub pattern: Option, diff --git a/src/render/tree/error.rs b/src/render/tree/error.rs index 28b23347..58b7ac2b 100644 --- a/src/render/tree/error.rs +++ b/src/render/tree/error.rs @@ -1,5 +1,8 @@ use super::styles::error::Error as StyleError; -use crate::render::context::error::Error as CtxError; +use crate::{ + fs::permissions::error::Error as PermissionsError, + render::context::error::Error as CtxError, +}; use ignore::Error as IgnoreError; use std::io::Error as IoError; @@ -29,6 +32,9 @@ pub enum Error { #[error("{0}")] PathCanonicalization(#[from] IoError), + #[error("{0}")] + Persmissions(#[from] PermissionsError), + #[error("{0}")] UninitializedTheme(#[from] StyleError<'static>), } diff --git a/src/render/tree/node/mod.rs b/src/render/tree/node/mod.rs index c1edaf5d..edf2a6d9 100644 --- a/src/render/tree/node/mod.rs +++ b/src/render/tree/node/mod.rs @@ -1,5 +1,8 @@ use crate::{ - fs::inode::Inode, + fs::{ + permissions::{FileMode, SymbolicNotation}, + inode::Inode, + }, icons::{self, get_default_icon, icon_from_ext, icon_from_file_name, icon_from_file_type}, render::{ context::Context, @@ -37,6 +40,7 @@ pub struct Node { style: Option