Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unix permissions symbolic notation #132

Merged
merged 33 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2813c7b
preliminary support for unix symbolic notation permissions
solidiquis Apr 14, 2023
0b7cf9a
fix bit comparison
solidiquis Apr 14, 2023
37a2c5e
dry up code
solidiquis Apr 14, 2023
3c572f2
doc comments
solidiquis Apr 14, 2023
cb00c09
symbolic notation test
solidiquis Apr 15, 2023
1b6cbeb
cargo fmt
solidiquis Apr 15, 2023
027c04b
libc instead of nix
solidiquis Apr 15, 2023
3e323a7
setuid setgid sticky
solidiquis Apr 16, 2023
75fc6a2
function renaming
solidiquis Apr 16, 2023
2b07532
new cli arg; rename some args
solidiquis Apr 16, 2023
a06ac09
plain display for permissions
solidiquis Apr 16, 2023
c81af41
fix doc comment
solidiquis Apr 16, 2023
6bad330
style adjustments for disk usage and permissions
solidiquis Apr 16, 2023
1f6c0a8
octal permissions
solidiquis Apr 16, 2023
0685f3e
placeholder character
solidiquis Apr 17, 2023
99c85e7
support nlinks
solidiquis Apr 17, 2023
ced3e49
support blocks in long output
solidiquis Apr 18, 2023
3daece6
fix nocolor
solidiquis Apr 18, 2023
445726b
move import
solidiquis Apr 18, 2023
c3d253e
old file
solidiquis Apr 18, 2023
7c1c8ea
support timestamps
solidiquis Apr 18, 2023
7ab7d25
fix some visual bugs
solidiquis Apr 18, 2023
0537493
visual bug
solidiquis Apr 18, 2023
acab8e0
another visual bug
solidiquis Apr 18, 2023
765e49e
fix tests
solidiquis Apr 18, 2023
6d02a55
long view for report
solidiquis Apr 19, 2023
8da3388
cargo fmt
solidiquis Apr 19, 2023
9f186e3
reuse the same comparator rather than re-allocating
solidiquis Apr 19, 2023
922bdd7
ditch xattrs package; talk to c
solidiquis Apr 19, 2023
5315a3e
use '.' for regular file identifier
solidiquis Apr 19, 2023
422bb8b
reduce redundant code
solidiquis Apr 19, 2023
c64b22f
fix test
solidiquis Apr 19, 2023
c6f1b01
arg renaming
solidiquis Apr 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
342 changes: 325 additions & 17 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ path = "src/main.rs"

[dependencies]
ansi_term = "0.12.1"
chrono = "0.4.24"
clap = { version = "4.1.1", features = ["derive"] }
clap_complete = "4.1.1"
filesize = "0.2.0"
Expand All @@ -36,6 +37,9 @@ once_cell = "1.17.0"
regex = "1.7.3"
thiserror = "1.0.40"

[target.'cfg(unix)'.dependencies]
libc = "0.2.141"

[dev-dependencies]
indoc = "2.0.0"
strip-ansi-escapes = "0.1.1"
Expand Down
2 changes: 1 addition & 1 deletion src/fs/inode.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
8 changes: 8 additions & 0 deletions src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ use std::{fs, path::PathBuf};
/// Operations pertaining to underlying inodes of files.
pub mod inode;

/// Unix file permissions.
#[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<PathBuf> {
Expand Down
152 changes: 152 additions & 0 deletions src/fs/permissions/class.rs
Original file line number Diff line number Diff line change
@@ -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<Attribute>,
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<N>(st_mode: u32, mask: N) -> bool
where
N: Copy + Into<u32>,
{
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<Attribute>,
) -> 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, "---"),
},
}
}
}
7 changes: 7 additions & 0 deletions src/fs/permissions/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error("Encountered an unknown file type.")]
UnknownFileType,
}
55 changes: 55 additions & 0 deletions src/fs/permissions/file_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use super::error::Error;

/// Unix file types.
#[derive(Debug, PartialEq, Eq)]
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',
}
}
}

/// The argument `mode` is meant to come from the `mode` method of [std::fs::Permissions].
impl TryFrom<u32> for FileType {
type Error = Error;

fn try_from(mode: u32) -> Result<Self, Self::Error> {
let file_mask = mode & u32::from(libc::S_IFMT);

if file_mask == u32::from(libc::S_IFIFO) {
Ok(Self::Fifo)
} else if file_mask == u32::from(libc::S_IFCHR) {
Ok(Self::CharDevice)
} else if file_mask == u32::from(libc::S_IFDIR) {
Ok(Self::Directory)
} else if file_mask == u32::from(libc::S_IFBLK) {
Ok(Self::BlockDevice)
} else if file_mask == u32::from(libc::S_IFREG) {
Ok(Self::File)
} else if file_mask == u32::from(libc::S_IFLNK) {
Ok(Self::Symlink)
} else if file_mask == u32::from(libc::S_IFSOCK) {
Ok(Self::Socket)
} else {
Err(Error::UnknownFileType)
}
}
}
126 changes: 126 additions & 0 deletions src/fs/permissions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use class::ClassPermissions;
use error::Error;
use file_type::FileType;
use std::{
convert::TryFrom,
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;

#[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 {
/// 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<FileMode, Error> {
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 presented in symbolic notation when
/// expressing file permissions.
pub struct FileMode {
pub st_mode: u32,
file_type: FileType,
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_permissions: ClassPermissions,
group_permissions: ClassPermissions,
other_permissions: ClassPermissions,
) -> Self {
Self {
st_mode,
file_type,
user_permissions,
group_permissions,
other_permissions,
}
}

/// Returns a reference to `file_type`.
pub const fn file_type(&self) -> &FileType {
&self.file_type
}

/// 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 [ClassPermissions] which represents the permissions of the group class.
pub const fn group_permissions(&self) -> &ClassPermissions {
&self.group_permissions
}

/// Returns a reference to a [ClassPermissions] which represents the permissions of the other class.
pub const fn other_permissions(&self) -> &ClassPermissions {
&self.other_permissions
}
}

/// 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();
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}"
)
}
}

/// For the octal representation of permissions
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 `st_mode` is meant to come from the `mode` method of [std::fs::Permissions].
impl TryFrom<u32> for FileMode {
type Error = Error;

fn try_from(st_mode: u32) -> Result<Self, Self::Error> {
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::new(
st_mode,
file_type,
user_permissions,
group_permissions,
other_permissions,
))
}
}
Loading