Skip to content

Commit

Permalink
Merge pull request #132 from solidiquis/feature/permissions
Browse files Browse the repository at this point in the history
Unix permissions symbolic notation
  • Loading branch information
solidiquis authored Apr 19, 2023
2 parents a1de2cc + c6f1b01 commit e0b64aa
Show file tree
Hide file tree
Showing 36 changed files with 1,960 additions and 573 deletions.
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

0 comments on commit e0b64aa

Please sign in to comment.