Skip to content

Commit 32d0cd7

Browse files
committed
Show mode_t as octal in std::fs Debug impls
1 parent d8fbe99 commit 32d0cd7

File tree

2 files changed

+186
-5
lines changed

2 files changed

+186
-5
lines changed

Diff for: std/src/sys/pal/unix/fs.rs

+115-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
// miri has some special hacks here that make things unused.
22
#![cfg_attr(miri, allow(unused))]
33

4+
#[cfg(test)]
5+
mod tests;
6+
47
use crate::os::unix::prelude::*;
58

69
use crate::ffi::{CStr, OsStr, OsString};
7-
use crate::fmt;
10+
use crate::fmt::{self, Write as _};
811
use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
912
use crate::mem;
1013
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd};
@@ -354,7 +357,7 @@ pub struct DirEntry {
354357
entry: dirent64,
355358
}
356359

357-
#[derive(Clone, Debug)]
360+
#[derive(Clone)]
358361
pub struct OpenOptions {
359362
// generic
360363
read: bool,
@@ -368,7 +371,7 @@ pub struct OpenOptions {
368371
mode: mode_t,
369372
}
370373

371-
#[derive(Clone, PartialEq, Eq, Debug)]
374+
#[derive(Clone, PartialEq, Eq)]
372375
pub struct FilePermissions {
373376
mode: mode_t,
374377
}
@@ -381,7 +384,7 @@ pub struct FileTimes {
381384
created: Option<SystemTime>,
382385
}
383386

384-
#[derive(Copy, Clone, Eq, Debug)]
387+
#[derive(Copy, Clone, Eq)]
385388
pub struct FileType {
386389
mode: mode_t,
387390
}
@@ -398,11 +401,13 @@ impl core::hash::Hash for FileType {
398401
}
399402
}
400403

401-
#[derive(Debug)]
402404
pub struct DirBuilder {
403405
mode: mode_t,
404406
}
405407

408+
#[derive(Copy, Clone)]
409+
struct Mode(mode_t);
410+
406411
cfg_has_statx! {{
407412
impl FileAttr {
408413
fn from_stat64(stat: stat64) -> Self {
@@ -673,12 +678,26 @@ impl FileType {
673678
}
674679
}
675680

681+
impl fmt::Debug for FileType {
682+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
683+
let FileType { mode } = self;
684+
f.debug_struct("FileType").field("mode", &Mode(*mode)).finish()
685+
}
686+
}
687+
676688
impl FromInner<u32> for FilePermissions {
677689
fn from_inner(mode: u32) -> FilePermissions {
678690
FilePermissions { mode: mode as mode_t }
679691
}
680692
}
681693

694+
impl fmt::Debug for FilePermissions {
695+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
696+
let FilePermissions { mode } = self;
697+
f.debug_struct("FilePermissions").field("mode", &Mode(*mode)).finish()
698+
}
699+
}
700+
682701
impl fmt::Debug for ReadDir {
683702
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684703
// This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.
@@ -1116,6 +1135,23 @@ impl OpenOptions {
11161135
}
11171136
}
11181137

1138+
impl fmt::Debug for OpenOptions {
1139+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1140+
let OpenOptions { read, write, append, truncate, create, create_new, custom_flags, mode } =
1141+
self;
1142+
f.debug_struct("OpenOptions")
1143+
.field("read", read)
1144+
.field("write", write)
1145+
.field("append", append)
1146+
.field("truncate", truncate)
1147+
.field("create", create)
1148+
.field("create_new", create_new)
1149+
.field("custom_flags", custom_flags)
1150+
.field("mode", &Mode(*mode))
1151+
.finish()
1152+
}
1153+
}
1154+
11191155
impl File {
11201156
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
11211157
run_path_with_cstr(path, &|path| File::open_c(path, opts))
@@ -1402,6 +1438,13 @@ impl DirBuilder {
14021438
}
14031439
}
14041440

1441+
impl fmt::Debug for DirBuilder {
1442+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1443+
let DirBuilder { mode } = self;
1444+
f.debug_struct("DirBuilder").field("mode", &Mode(*mode)).finish()
1445+
}
1446+
}
1447+
14051448
impl AsInner<FileDesc> for File {
14061449
#[inline]
14071450
fn as_inner(&self) -> &FileDesc {
@@ -1574,6 +1617,73 @@ impl fmt::Debug for File {
15741617
}
15751618
}
15761619

1620+
// Format in octal, followed by the mode format used in `ls -l`.
1621+
//
1622+
// References:
1623+
// https://pubs.opengroup.org/onlinepubs/009696899/utilities/ls.html
1624+
// https://www.gnu.org/software/libc/manual/html_node/Testing-File-Type.html
1625+
// https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html
1626+
//
1627+
// Example:
1628+
// 0o100664 (-rw-rw-r--)
1629+
impl fmt::Debug for Mode {
1630+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1631+
let Self(mode) = *self;
1632+
write!(f, "0o{mode:06o}")?;
1633+
1634+
let entry_type = match mode & libc::S_IFMT {
1635+
libc::S_IFDIR => 'd',
1636+
libc::S_IFBLK => 'b',
1637+
libc::S_IFCHR => 'c',
1638+
libc::S_IFLNK => 'l',
1639+
libc::S_IFIFO => 'p',
1640+
libc::S_IFREG => '-',
1641+
_ => return Ok(()),
1642+
};
1643+
1644+
f.write_str(" (")?;
1645+
f.write_char(entry_type)?;
1646+
1647+
// Owner permissions
1648+
f.write_char(if mode & libc::S_IRUSR != 0 { 'r' } else { '-' })?;
1649+
f.write_char(if mode & libc::S_IWUSR != 0 { 'w' } else { '-' })?;
1650+
let owner_executable = mode & libc::S_IXUSR != 0;
1651+
let setuid = mode as c_int & libc::S_ISUID as c_int != 0;
1652+
f.write_char(match (owner_executable, setuid) {
1653+
(true, true) => 's', // executable and setuid
1654+
(false, true) => 'S', // setuid
1655+
(true, false) => 'x', // executable
1656+
(false, false) => '-',
1657+
})?;
1658+
1659+
// Group permissions
1660+
f.write_char(if mode & libc::S_IRGRP != 0 { 'r' } else { '-' })?;
1661+
f.write_char(if mode & libc::S_IWGRP != 0 { 'w' } else { '-' })?;
1662+
let group_executable = mode & libc::S_IXGRP != 0;
1663+
let setgid = mode as c_int & libc::S_ISGID as c_int != 0;
1664+
f.write_char(match (group_executable, setgid) {
1665+
(true, true) => 's', // executable and setgid
1666+
(false, true) => 'S', // setgid
1667+
(true, false) => 'x', // executable
1668+
(false, false) => '-',
1669+
})?;
1670+
1671+
// Other permissions
1672+
f.write_char(if mode & libc::S_IROTH != 0 { 'r' } else { '-' })?;
1673+
f.write_char(if mode & libc::S_IWOTH != 0 { 'w' } else { '-' })?;
1674+
let other_executable = mode & libc::S_IXOTH != 0;
1675+
let sticky = mode as c_int & libc::S_ISVTX as c_int != 0;
1676+
f.write_char(match (entry_type, other_executable, sticky) {
1677+
('d', true, true) => 't', // searchable and restricted deletion
1678+
('d', false, true) => 'T', // restricted deletion
1679+
(_, true, _) => 'x', // executable
1680+
(_, false, _) => '-',
1681+
})?;
1682+
1683+
f.write_char(')')
1684+
}
1685+
}
1686+
15771687
pub fn readdir(path: &Path) -> io::Result<ReadDir> {
15781688
let ptr = run_path_with_cstr(path, &|p| unsafe { Ok(libc::opendir(p.as_ptr())) })?;
15791689
if ptr.is_null() {

Diff for: std/src/sys/pal/unix/fs/tests.rs

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use crate::sys::pal::unix::fs::FilePermissions;
2+
3+
#[test]
4+
fn test_debug_permissions() {
5+
for (expected, mode) in [
6+
// typical directory
7+
("FilePermissions { mode: 0o040775 (drwxrwxr-x) }", 0o04_0775),
8+
// typical text file
9+
("FilePermissions { mode: 0o100664 (-rw-rw-r--) }", 0o10_0664),
10+
// setuid executable (/usr/bin/doas)
11+
("FilePermissions { mode: 0o104755 (-rwsr-xr-x) }", 0o10_4755),
12+
// char device (/dev/zero)
13+
("FilePermissions { mode: 0o020666 (crw-rw-rw-) }", 0o02_0666),
14+
// block device (/dev/vda)
15+
("FilePermissions { mode: 0o060660 (brw-rw----) }", 0o06_0660),
16+
// symbolic link
17+
("FilePermissions { mode: 0o120777 (lrwxrwxrwx) }", 0o12_0777),
18+
// fifo
19+
("FilePermissions { mode: 0o010664 (prw-rw-r--) }", 0o01_0664),
20+
// none
21+
("FilePermissions { mode: 0o100000 (----------) }", 0o10_0000),
22+
// unrecognized
23+
("FilePermissions { mode: 0o000001 }", 1),
24+
] {
25+
assert_eq!(format!("{:?}", FilePermissions { mode }), expected);
26+
}
27+
28+
for (expected, mode) in [
29+
// owner readable
30+
("FilePermissions { mode: 0o100400 (-r--------) }", libc::S_IRUSR),
31+
// owner writable
32+
("FilePermissions { mode: 0o100200 (--w-------) }", libc::S_IWUSR),
33+
// owner executable
34+
("FilePermissions { mode: 0o100100 (---x------) }", libc::S_IXUSR),
35+
// setuid
36+
("FilePermissions { mode: 0o104000 (---S------) }", libc::S_ISUID),
37+
// owner executable and setuid
38+
("FilePermissions { mode: 0o104100 (---s------) }", libc::S_IXUSR | libc::S_ISUID),
39+
// group readable
40+
("FilePermissions { mode: 0o100040 (----r-----) }", libc::S_IRGRP),
41+
// group writable
42+
("FilePermissions { mode: 0o100020 (-----w----) }", libc::S_IWGRP),
43+
// group executable
44+
("FilePermissions { mode: 0o100010 (------x---) }", libc::S_IXGRP),
45+
// setgid
46+
("FilePermissions { mode: 0o102000 (------S---) }", libc::S_ISGID),
47+
// group executable and setgid
48+
("FilePermissions { mode: 0o102010 (------s---) }", libc::S_IXGRP | libc::S_ISGID),
49+
// other readable
50+
("FilePermissions { mode: 0o100004 (-------r--) }", libc::S_IROTH),
51+
// other writeable
52+
("FilePermissions { mode: 0o100002 (--------w-) }", libc::S_IWOTH),
53+
// other executable
54+
("FilePermissions { mode: 0o100001 (---------x) }", libc::S_IXOTH),
55+
// sticky
56+
("FilePermissions { mode: 0o101000 (----------) }", libc::S_ISVTX),
57+
// other executable and sticky
58+
("FilePermissions { mode: 0o101001 (---------x) }", libc::S_IXOTH | libc::S_ISVTX),
59+
] {
60+
assert_eq!(format!("{:?}", FilePermissions { mode: libc::S_IFREG | mode }), expected);
61+
}
62+
63+
for (expected, mode) in [
64+
// restricted deletion ("sticky") flag is set, and search permission is not granted to others
65+
("FilePermissions { mode: 0o041000 (d--------T) }", libc::S_ISVTX),
66+
// sticky and searchable
67+
("FilePermissions { mode: 0o041001 (d--------t) }", libc::S_ISVTX | libc::S_IXOTH),
68+
] {
69+
assert_eq!(format!("{:?}", FilePermissions { mode: libc::S_IFDIR | mode }), expected);
70+
}
71+
}

0 commit comments

Comments
 (0)