-
-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #132 from solidiquis/feature/permissions
Unix permissions symbolic notation
- Loading branch information
Showing
36 changed files
with
1,960 additions
and
573 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, "---"), | ||
}, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
)) | ||
} | ||
} |
Oops, something went wrong.