diff --git a/library/std/src/sys/pal/unix/fs.rs b/library/std/src/sys/pal/unix/fs.rs index b968f8df34c23..024e36848dd72 100644 --- a/library/std/src/sys/pal/unix/fs.rs +++ b/library/std/src/sys/pal/unix/fs.rs @@ -1,12 +1,16 @@ // miri has some special hacks here that make things unused. #![cfg_attr(miri, allow(unused))] +#[cfg(test)] +mod tests; + use crate::os::unix::prelude::*; use crate::ffi::{CStr, OsStr, OsString}; -use crate::fmt; +use crate::fmt::{self, Write as _}; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; use crate::mem; +use crate::ops::{BitAnd, BitAndAssign, BitOrAssign}; use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd}; use crate::path::{Path, PathBuf}; use crate::ptr; @@ -365,12 +369,12 @@ pub struct OpenOptions { create_new: bool, // system-specific custom_flags: i32, - mode: mode_t, + mode: Mode, } #[derive(Clone, PartialEq, Eq, Debug)] pub struct FilePermissions { - mode: mode_t, + mode: Mode, } #[derive(Copy, Clone, Debug, Default)] @@ -383,7 +387,7 @@ pub struct FileTimes { #[derive(Copy, Clone, Eq, Debug)] pub struct FileType { - mode: mode_t, + mode: Mode, } impl PartialEq for FileType { @@ -400,9 +404,12 @@ impl core::hash::Hash for FileType { #[derive(Debug)] pub struct DirBuilder { - mode: mode_t, + mode: Mode, } +#[derive(Copy, Clone, PartialEq, Eq)] +struct Mode(mode_t); + cfg_has_statx! {{ impl FileAttr { fn from_stat64(stat: stat64) -> Self { @@ -452,11 +459,11 @@ impl FileAttr { self.stat.st_size as u64 } pub fn perm(&self) -> FilePermissions { - FilePermissions { mode: (self.stat.st_mode as mode_t) } + FilePermissions { mode: Mode(self.stat.st_mode as mode_t) } } pub fn file_type(&self) -> FileType { - FileType { mode: self.stat.st_mode as mode_t } + FileType { mode: Mode(self.stat.st_mode as mode_t) } } } @@ -634,7 +641,7 @@ impl FilePermissions { } } pub fn mode(&self) -> u32 { - self.mode as u32 + self.mode.0 as u32 } } @@ -669,13 +676,13 @@ impl FileType { } fn masked(&self) -> mode_t { - self.mode & libc::S_IFMT + self.mode.0 & libc::S_IFMT } } impl FromInner for FilePermissions { fn from_inner(mode: u32) -> FilePermissions { - FilePermissions { mode: mode as mode_t } + FilePermissions { mode: Mode(mode as mode_t) } } } @@ -922,13 +929,13 @@ impl DirEntry { )))] pub fn file_type(&self) -> io::Result { match self.entry.d_type { - libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }), - libc::DT_FIFO => Ok(FileType { mode: libc::S_IFIFO }), - libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }), - libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }), - libc::DT_SOCK => Ok(FileType { mode: libc::S_IFSOCK }), - libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }), - libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }), + libc::DT_CHR => Ok(FileType { mode: Mode(libc::S_IFCHR) }), + libc::DT_FIFO => Ok(FileType { mode: Mode(libc::S_IFIFO) }), + libc::DT_LNK => Ok(FileType { mode: Mode(libc::S_IFLNK) }), + libc::DT_REG => Ok(FileType { mode: Mode(libc::S_IFREG) }), + libc::DT_SOCK => Ok(FileType { mode: Mode(libc::S_IFSOCK) }), + libc::DT_DIR => Ok(FileType { mode: Mode(libc::S_IFDIR) }), + libc::DT_BLK => Ok(FileType { mode: Mode(libc::S_IFBLK) }), _ => self.metadata().map(|m| m.file_type()), } } @@ -1050,7 +1057,7 @@ impl OpenOptions { create_new: false, // system-specific custom_flags: 0, - mode: 0o666, + mode: Mode(0o666), } } @@ -1077,7 +1084,7 @@ impl OpenOptions { self.custom_flags = flags; } pub fn mode(&mut self, mode: u32) { - self.mode = mode as mode_t; + self.mode.0 = mode as mode_t; } fn get_access_mode(&self) -> io::Result { @@ -1130,7 +1137,7 @@ impl File { // some platforms (like macOS, where `open64` is actually `open`), `mode_t` is `u16`. // However, since this is a variadic function, C integer promotion rules mean that on // the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms). - let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?; + let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode.0 as c_int) })?; Ok(File(unsafe { FileDesc::from_raw_fd(fd) })) } @@ -1294,7 +1301,7 @@ impl File { } pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> { - cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?; + cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode.0) })?; Ok(()) } @@ -1390,15 +1397,15 @@ impl File { impl DirBuilder { pub fn new() -> DirBuilder { - DirBuilder { mode: 0o777 } + DirBuilder { mode: Mode(0o777) } } pub fn mkdir(&self, p: &Path) -> io::Result<()> { - run_path_with_cstr(p, &|p| cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) }).map(|_| ())) + run_path_with_cstr(p, &|p| cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode.0) }).map(|_| ())) } pub fn set_mode(&mut self, mode: u32) { - self.mode = mode as mode_t; + self.mode.0 = mode as mode_t; } } @@ -1574,6 +1581,87 @@ impl fmt::Debug for File { } } +// Format in octal, followed by the mode format used in `ls -l`. +// +// References: +// https://pubs.opengroup.org/onlinepubs/009696899/utilities/ls.html +// https://www.gnu.org/software/libc/manual/html_node/Testing-File-Type.html +// https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html +// +// Example: +// 0o100664 (-rw-rw-r--) +impl fmt::Debug for Mode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self(mode) = self; + write!(f, "0o{mode:06o}")?; + + let entry_type = match mode & libc::S_IFMT { + libc::S_IFDIR => 'd', + libc::S_IFBLK => 'b', + libc::S_IFCHR => 'c', + libc::S_IFLNK => 'l', + libc::S_IFIFO => 'p', + libc::S_IFREG => '-', + _ => return Ok(()), + }; + + f.write_str(" (")?; + f.write_char(entry_type)?; + + // Owner permissions + f.write_char(if mode & libc::S_IRUSR != 0 { 'r' } else { '-' })?; + f.write_char(if mode & libc::S_IWUSR != 0 { 'w' } else { '-' })?; + f.write_char(match (mode & libc::S_IXUSR != 0, mode & libc::S_ISUID != 0) { + (true, true) => 's', // executable and setuid + (false, true) => 'S', // setuid + (true, false) => 'x', // executable + (false, false) => '-', + })?; + + // Group permissions + f.write_char(if mode & libc::S_IRGRP != 0 { 'r' } else { '-' })?; + f.write_char(if mode & libc::S_IWGRP != 0 { 'w' } else { '-' })?; + f.write_char(match (mode & libc::S_IXGRP != 0, mode & libc::S_ISGID != 0) { + (true, true) => 's', // executable and setgid + (false, true) => 'S', // setgid + (true, false) => 'x', // executable + (false, false) => '-', + })?; + + // Other permissions + f.write_char(if mode & libc::S_IROTH != 0 { 'r' } else { '-' })?; + f.write_char(if mode & libc::S_IWOTH != 0 { 'w' } else { '-' })?; + f.write_char(if mode & libc::S_IXOTH != 0 { 'x' } else { '-' })?; + + f.write_char(')') + } +} + +impl BitAnd for Mode { + type Output = Mode; + fn bitand(self, rhs: mode_t) -> Self::Output { + Mode(self.0 & rhs) + } +} + +impl BitAndAssign for Mode { + fn bitand_assign(&mut self, rhs: mode_t) { + self.0 &= rhs; + } +} + +impl BitOrAssign for Mode { + fn bitor_assign(&mut self, rhs: mode_t) { + self.0 |= rhs; + } +} + +impl PartialEq for Mode { + fn eq(&self, rhs: &mode_t) -> bool { + self.0 == *rhs + } +} + pub fn readdir(path: &Path) -> io::Result { let ptr = run_path_with_cstr(path, &|p| unsafe { Ok(libc::opendir(p.as_ptr())) })?; if ptr.is_null() { @@ -1598,7 +1686,9 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { } pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { - run_path_with_cstr(p, &|p| cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ())) + run_path_with_cstr(p, &|p| { + cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode.0) }).map(|_| ()) + }) } pub fn rmdir(p: &Path) -> io::Result<()> { diff --git a/library/std/src/sys/pal/unix/fs/tests.rs b/library/std/src/sys/pal/unix/fs/tests.rs new file mode 100644 index 0000000000000..9e64f9fe3d701 --- /dev/null +++ b/library/std/src/sys/pal/unix/fs/tests.rs @@ -0,0 +1,59 @@ +use crate::sys::pal::unix::fs::FilePermissions; +use crate::sys_common::FromInner; + +#[test] +fn test_debug_permissions() { + for (expected, mode) in [ + // typical directory + ("FilePermissions { mode: 0o040775 (drwxrwxr-x) }", 0o04_0775), + // typical text file + ("FilePermissions { mode: 0o100664 (-rw-rw-r--) }", 0o10_0664), + // setuid executable (/usr/bin/doas) + ("FilePermissions { mode: 0o104755 (-rwsr-xr-x) }", 0o10_4755), + // char device (/dev/zero) + ("FilePermissions { mode: 0o020666 (crw-rw-rw-) }", 0o02_0666), + // block device (/dev/vda) + ("FilePermissions { mode: 0o060660 (brw-rw----) }", 0o06_0660), + // symbolic link + ("FilePermissions { mode: 0o120777 (lrwxrwxrwx) }", 0o12_0777), + // fifo + ("FilePermissions { mode: 0o010664 (prw-rw-r--) }", 0o01_0664), + // none + ("FilePermissions { mode: 0o100000 (----------) }", 0o10_0000), + // unrecognized + ("FilePermissions { mode: 0o000001 }", 1), + ] { + assert_eq!(format!("{:?}", FilePermissions::from_inner(mode)), expected); + } + + for (expected, mode) in [ + // owner readable + ("FilePermissions { mode: 0o100400 (-r--------) }", libc::S_IRUSR), + // owner writable + ("FilePermissions { mode: 0o100200 (--w-------) }", libc::S_IWUSR), + // owner executable + ("FilePermissions { mode: 0o100100 (---x------) }", libc::S_IXUSR), + // setuid + ("FilePermissions { mode: 0o104000 (---S------) }", libc::S_ISUID), + // owner executable and setuid + ("FilePermissions { mode: 0o104100 (---s------) }", libc::S_IXUSR | libc::S_ISUID), + // group readable + ("FilePermissions { mode: 0o100040 (----r-----) }", libc::S_IRGRP), + // group writable + ("FilePermissions { mode: 0o100020 (-----w----) }", libc::S_IWGRP), + // group executable + ("FilePermissions { mode: 0o100010 (------x---) }", libc::S_IXGRP), + // setgid + ("FilePermissions { mode: 0o102000 (------S---) }", libc::S_ISGID), + // group executable and setgid + ("FilePermissions { mode: 0o102010 (------s---) }", libc::S_IXGRP | libc::S_ISGID), + // other readable + ("FilePermissions { mode: 0o100004 (-------r--) }", libc::S_IROTH), + // other writeable + ("FilePermissions { mode: 0o100002 (--------w-) }", libc::S_IWOTH), + // other executable + ("FilePermissions { mode: 0o100001 (---------x) }", libc::S_IXOTH), + ] { + assert_eq!(format!("{:?}", FilePermissions::from_inner(libc::S_IFREG | mode)), expected); + } +}