Skip to content

Commit 79bbf52

Browse files
authored
Merge pull request #8343 from TheJJ/fix-mknod-mode
mknod: set cli specified file mode even when it's 0o666
1 parent 33a3f73 commit 79bbf52

File tree

3 files changed

+84
-23
lines changed

3 files changed

+84
-23
lines changed

src/uu/mknod/src/mknod.rs

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,14 @@ impl FileType {
4949
}
5050
}
5151

52-
/// Configuration for directory creation.
52+
/// Configuration for special inode creation.
5353
pub struct Config<'a> {
54+
/// bitmask of inode mode (permissions and file type)
5455
pub mode: mode_t,
5556

57+
/// when false, the exact mode bits will be set
58+
pub use_umask: bool,
59+
5660
pub dev: dev_t,
5761

5862
/// Set `SELinux` security context.
@@ -65,18 +69,19 @@ pub struct Config<'a> {
6569
fn mknod(file_name: &str, config: Config) -> i32 {
6670
let c_str = CString::new(file_name).expect("Failed to convert to CString");
6771

68-
// the user supplied a mode
69-
let set_umask = config.mode & MODE_RW_UGO != MODE_RW_UGO;
70-
7172
unsafe {
72-
// store prev umask
73-
let last_umask = if set_umask { libc::umask(0) } else { 0 };
73+
// set umask to 0 and store previous umask
74+
let have_prev_umask = if config.use_umask {
75+
None
76+
} else {
77+
Some(libc::umask(0))
78+
};
7479

7580
let errno = libc::mknod(c_str.as_ptr(), config.mode, config.dev);
7681

7782
// set umask back to original value
78-
if set_umask {
79-
libc::umask(last_umask);
83+
if let Some(prev_umask) = have_prev_umask {
84+
libc::umask(prev_umask);
8085
}
8186

8287
if errno == -1 {
@@ -109,8 +114,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
109114
let matches = uu_app().try_get_matches_from(args)?;
110115

111116
let file_type = matches.get_one::<FileType>("type").unwrap();
112-
let mode = get_mode(matches.get_one::<String>("mode")).map_err(|e| USimpleError::new(1, e))?
113-
| file_type.as_mode();
117+
118+
let mut use_umask = true;
119+
let mode_permissions = match matches.get_one::<String>("mode") {
120+
None => MODE_RW_UGO,
121+
Some(str_mode) => {
122+
use_umask = false;
123+
parse_mode(str_mode).map_err(|e| USimpleError::new(1, e))?
124+
}
125+
};
126+
let mode = mode_permissions | file_type.as_mode();
114127

115128
let file_name = matches
116129
.get_one::<String>("name")
@@ -143,6 +156,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
143156

144157
let config = Config {
145158
mode,
159+
use_umask,
146160
dev,
147161
set_selinux_context: set_selinux_context || context.is_some(),
148162
context,
@@ -210,19 +224,21 @@ pub fn uu_app() -> Command {
210224
)
211225
}
212226

213-
fn get_mode(str_mode: Option<&String>) -> Result<mode_t, String> {
214-
match str_mode {
215-
None => Ok(MODE_RW_UGO),
216-
Some(str_mode) => uucore::mode::parse_mode(str_mode)
217-
.map_err(|e| translate!("mknod-error-invalid-mode", "error" => e))
218-
.and_then(|mode| {
219-
if mode > 0o777 {
220-
Err(translate!("mknod-error-mode-permission-bits-only"))
221-
} else {
222-
Ok(mode)
223-
}
224-
}),
225-
}
227+
fn parse_mode(str_mode: &str) -> Result<mode_t, String> {
228+
uucore::mode::parse_mode(str_mode)
229+
.map_err(|e| {
230+
translate!(
231+
"mknod-error-invalid-mode",
232+
"error" => e
233+
)
234+
})
235+
.and_then(|mode| {
236+
if mode > 0o777 {
237+
Err(translate!("mknod-error-mode-permission-bits-only"))
238+
} else {
239+
Ok(mode)
240+
}
241+
})
226242
}
227243

228244
fn parse_type(tpe: &str) -> Result<FileType, String> {

tests/by-util/test_mknod.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66
// spell-checker:ignore nconfined
77

8+
use std::os::unix::fs::PermissionsExt;
9+
810
use uutests::new_ucmd;
911
use uutests::util::TestScenario;
12+
use uutests::util::run_ucmd_as_root;
1013
use uutests::util_name;
1114

1215
#[test]
@@ -120,6 +123,35 @@ fn test_mknod_invalid_mode() {
120123
.stderr_contains("invalid mode");
121124
}
122125

126+
#[test]
127+
fn test_mknod_mode_permissions() {
128+
for test_mode in [0o0666, 0o0000, 0o0444, 0o0004, 0o0040, 0o0400, 0o0644] {
129+
let ts = TestScenario::new(util_name!());
130+
let filename = format!("null_file-{test_mode:04o}");
131+
132+
if let Ok(result) = run_ucmd_as_root(
133+
&ts,
134+
&[
135+
"--mode",
136+
&format!("{test_mode:04o}"),
137+
&filename,
138+
"c",
139+
"1",
140+
"3",
141+
],
142+
) {
143+
result.success().no_stdout();
144+
} else {
145+
print!("Test skipped; `mknod c 1 3` for null char dev requires root user");
146+
break;
147+
}
148+
149+
assert!(ts.fixtures.is_char_device(&filename));
150+
let permissions = ts.fixtures.metadata(&filename).permissions();
151+
assert_eq!(test_mode, PermissionsExt::mode(&permissions) & 0o777);
152+
}
153+
}
154+
123155
#[test]
124156
#[cfg(feature = "feat_selinux")]
125157
fn test_mknod_selinux() {

tests/uutests/src/lib/util.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,19 @@ impl AtPath {
11451145
}
11461146
}
11471147

1148+
#[cfg(not(windows))]
1149+
pub fn is_char_device(&self, char_dev: &str) -> bool {
1150+
unsafe {
1151+
let name = CString::new(self.plus_as_string(char_dev)).unwrap();
1152+
let mut stat: libc::stat = std::mem::zeroed();
1153+
if libc::stat(name.as_ptr(), &mut stat) >= 0 {
1154+
libc::S_IFCHR & stat.st_mode as libc::mode_t != 0
1155+
} else {
1156+
false
1157+
}
1158+
}
1159+
}
1160+
11481161
pub fn hard_link(&self, original: &str, link: &str) {
11491162
log_info(
11501163
"hard_link",

0 commit comments

Comments
 (0)