Skip to content

Commit c730b84

Browse files
committed
wall: testing
- added some argument tests. - used `cfg` directives to build (no-op) on windows. - minor readability tweaks
1 parent b9fc50d commit c730b84

3 files changed

Lines changed: 96 additions & 41 deletions

File tree

src/uu/wall/src/wall.rs

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#![cfg(unix)]
12
#![warn(clippy::all, clippy::pedantic)]
23

34
use std::{
@@ -76,7 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
7677
return Err(USimpleError::new(1, "invalid timeout argument: 0"));
7778
}
7879

79-
// get nobanner flag and check user is root
80+
// get nobanner flag and check if user is root
8081
let flag = args.get_flag("nobanner");
8182
let print_banner = if flag && unsafe { libc::geteuid() } != 0 {
8283
eprintln!("wall: --nobanner is available only for root");
@@ -94,41 +95,40 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
9495
// If we have a single input arg and it exists on disk, treat as a file.
9596
// If either is false, assume it is a literal string.
9697
// If no input given, use stdin.
97-
match args.get_many::<String>("input") {
98-
Some(v) => {
99-
let vals: Vec<&str> = v.map(String::as_str).collect();
100-
101-
let fname = vals
102-
.first()
103-
.expect("clap guarantees at least 1 value for input");
104-
105-
let p = Path::new(fname);
106-
if vals.len() == 1 && p.exists() {
107-
// When we are not root, but suid or sgid, refuse to read files
108-
// (e.g. device files) that the user may not have access to.
109-
// After all, our invoker can easily do "wall < file"
110-
// instead of "wall file".
111-
unsafe {
112-
let uid = libc::getuid();
113-
if uid > 0 && (uid != libc::geteuid() || libc::getgid() != libc::getegid()) {
114-
return Err(USimpleError::new(
115-
1,
116-
format!("will not read {fname} - use stdin"),
117-
));
118-
}
98+
if let Some(v) = args.get_many::<String>("input") {
99+
let vals: Vec<&str> = v.map(String::as_str).collect();
100+
101+
let fname = vals
102+
.first()
103+
.expect("clap guarantees at least 1 value for input");
104+
105+
let p = Path::new(fname);
106+
if vals.len() == 1 && p.exists() {
107+
// When we are not root, but suid or sgid, refuse to read files
108+
// (e.g. device files) that the user may not have access to.
109+
// After all, our invoker can easily do "wall < file" instead of "wall file".
110+
unsafe {
111+
let uid = libc::getuid();
112+
if uid > 0 && (uid != libc::geteuid() || libc::getgid() != libc::getegid()) {
113+
return Err(USimpleError::new(
114+
1,
115+
format!("will not read {fname} - use stdin"),
116+
));
119117
}
118+
}
120119

121-
let f = File::open(p)
122-
.map_err(|_| USimpleError::new(1, format!("cannot open {fname}")))?;
120+
let Ok(f) = File::open(p) else {
121+
return Err(USimpleError::new(1, format!("cannot open {fname}")));
122+
};
123123

124-
wall(f, group, timeout, print_banner);
125-
} else {
126-
let mut s = vals.as_slice().join(" ");
127-
s.push('\n');
128-
wall(s.as_bytes(), group, timeout, print_banner);
129-
}
124+
wall(f, group, timeout, print_banner);
125+
} else {
126+
let mut s = vals.as_slice().join(" ");
127+
s.push('\n');
128+
wall(s.as_bytes(), group, timeout, print_banner);
130129
}
131-
None => wall(stdin(), group, timeout, print_banner),
130+
} else {
131+
wall(stdin(), group, timeout, print_banner);
132132
}
133133

134134
Ok(())
@@ -294,9 +294,8 @@ fn sanitise_line(line: &str) -> String {
294294
}
295295

296296
// Determine if user is in specified group
297+
#[allow(clippy::cast_sign_loss)]
297298
fn is_gr_member(user: &[c_char], gid: gid_t) -> bool {
298-
#![allow(clippy::cast_sign_loss)]
299-
300299
// make sure user exists in database
301300
let pw = unsafe { libc::getpwnam(user.as_ptr()) };
302301
if pw.is_null() {
@@ -309,12 +308,14 @@ fn is_gr_member(user: &[c_char], gid: gid_t) -> bool {
309308
return true;
310309
}
311310

311+
// on macos, getgrouplist takes c_int as its group argument,
312+
// other unices should use gid_t like linux
312313
#[cfg(target_os = "macos")]
313314
let base_gid = group as libc::c_int;
314315
#[cfg(not(target_os = "macos"))]
315316
let base_gid = group;
316317

317-
// otherwise check gid is in list of groups user belongs to
318+
// otherwise check gid is in list of supplementary groups user belongs to
318319
let mut ngroups = 16;
319320
let mut groups: Vec<gid_t> = vec![0; ngroups as usize];
320321
while unsafe {
@@ -336,19 +337,19 @@ fn is_gr_member(user: &[c_char], gid: gid_t) -> bool {
336337
// Try to get corresponding group gid.
337338
fn get_group_gid(group: &String) -> UResult<gid_t> {
338339
// first we try as a group name
339-
let cname =
340-
CString::from_str(group).map_err(|_| USimpleError::new(1, "invalid group argument"))?;
340+
let Ok(cname) = CString::from_str(group) else {
341+
return Err(USimpleError::new(1, "invalid group argument"));
342+
};
341343

342344
let gr = unsafe { libc::getgrnam(cname.as_ptr()) };
343345
if !gr.is_null() {
344346
return Ok(unsafe { (*gr).gr_gid });
345347
}
346348

347349
// otherwise, try as literal gid
348-
let gid = group
349-
.parse::<gid_t>()
350-
.map_err(|_| USimpleError::new(1, "invalid group argument"))?;
351-
350+
let Ok(gid) = group.parse::<gid_t>() else {
351+
return Err(USimpleError::new(1, "invalid group argument"));
352+
};
352353
if unsafe { libc::getgrgid(gid) }.is_null() {
353354
return Err(USimpleError::new(1, format!("{group}: unknown gid")));
354355
}

tests/by-util/test_wall.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#[cfg(unix)]
2+
mod tests {
3+
use uutests::new_ucmd;
4+
5+
#[test]
6+
fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); }
7+
8+
#[test]
9+
fn test_fails_on_invalid_group() {
10+
new_ucmd!()
11+
.arg("-g")
12+
.arg("fooblywoobly") // assuming this group doesnt exist
13+
.fails()
14+
.code_is(1)
15+
.stderr_contains("wall: invalid group argument");
16+
}
17+
18+
#[test]
19+
fn test_fails_on_invalid_gid() {
20+
new_ucmd!()
21+
.arg("-g")
22+
.arg("99999") // assuming this group doesnt exist
23+
.fails()
24+
.code_is(1)
25+
.stderr_contains("wall: 99999: unknown gid");
26+
}
27+
28+
#[test]
29+
fn test_warns_on_nobanner() {
30+
new_ucmd!()
31+
.arg("-n")
32+
.arg("some text to wall")
33+
.succeeds()
34+
.code_is(0)
35+
.stderr_contains("wall: --nobanner is available only for root");
36+
}
37+
38+
#[test]
39+
fn test_fails_on_invalid_timeout() {
40+
new_ucmd!()
41+
.arg("-t")
42+
.arg("0")
43+
.fails()
44+
.code_is(1)
45+
.stderr_contains("wall: invalid timeout argument: 0");
46+
}
47+
48+
#[test]
49+
fn test_succeeds_no_stdout() { new_ucmd!().pipe_in("pipe me").succeeds().stdout_is(""); }
50+
}

tests/tests.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,7 @@ mod test_mcookie;
9090
#[cfg(feature = "uuidgen")]
9191
#[path = "by-util/test_uuidgen.rs"]
9292
mod test_uuidgen;
93+
94+
#[cfg(feature = "wall")]
95+
#[path = "by-util/test_wall.rs"]
96+
mod test_wall;

0 commit comments

Comments
 (0)