Skip to content

Commit 6dbb75a

Browse files
committed
wall: refactor
- put deps in appropariate places - use `uucore` methods to simplify code and remove a lot of manual `unsafe { libc::...
1 parent 32ebe55 commit 6dbb75a

File tree

4 files changed

+44
-122
lines changed

4 files changed

+44
-122
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ sysinfo = "0.38"
7676
tempfile = "3.9.0"
7777
textwrap = { version = "0.16.0", features = ["terminal_size"] }
7878
thiserror = "2.0"
79+
unicode-width = { version = "0.2.2", default-features = false }
7980
uucore = "0.2.2"
8081
uuid = { version = "1.16.0", features = ["rng-rand"] }
8182
uutests = "0.6.0"

src/uu/wall/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
name = "uu_wall"
33
version = "0.0.1"
44
edition = "2021"
5+
description = "wall ~ Write a mesage to all users."
56

67
[lib]
78
path = "src/wall.rs"
@@ -11,8 +12,8 @@ name = "wall"
1112
path = "src/main.rs"
1213

1314
[dependencies]
14-
uucore = { workspace = true }
15+
uucore = { workspace = true, features = ["process", "entries"] }
1516
clap = { workspace = true }
1617
libc = { workspace = true }
1718
chrono = { workspace = true }
18-
unicode-width = "0.2.2"
19+
unicode-width = { workspace = true }

src/uu/wall/src/wall.rs

Lines changed: 38 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,30 @@ use clap::{crate_version, Arg, ArgAction, Command};
22
use uucore::{error::UResult, format_usage, help_about, help_usage};
33

44
#[cfg(unix)]
5-
use uucore::error::USimpleError;
5+
use uucore::{
6+
entries::{Group, Locate},
7+
error::USimpleError,
8+
process,
9+
};
610

711
const ABOUT: &str = help_about!("wall.md");
812
const USAGE: &str = help_usage!("wall.md");
913

1014
#[cfg(unix)]
1115
mod unix {
12-
use super::{UResult, USimpleError};
16+
use super::process;
17+
18+
use uucore::entries::{uid2usr, Locate, Passwd};
19+
use uucore::utmpx::Utmpx;
1320

14-
use chrono::{DateTime, Local};
15-
use libc::{c_char, gid_t};
1621
use std::{
17-
ffi::{CStr, CString},
18-
fmt::Write as fw,
22+
ffi::CStr,
1923
fs::OpenOptions,
2024
io::{BufRead, BufReader, Read, Write},
21-
str::FromStr,
2225
sync::{mpsc, Arc},
23-
time::{Duration, SystemTime},
26+
time::Duration,
2427
};
25-
use unicode_width::UnicodeWidthChar;
28+
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
2629

2730
const TERM_WIDTH: usize = 79;
2831
const BLANK: &str = unsafe { str::from_utf8_unchecked(&[b' '; TERM_WIDTH]) };
@@ -35,51 +38,31 @@ mod unix {
3538
// if group is specified, only print to memebers of the group.
3639
pub fn wall<R: Read>(
3740
input: R,
38-
group: Option<gid_t>,
41+
group: Option<libc::gid_t>,
3942
timeout: Option<&u64>,
4043
print_banner: bool,
4144
) {
4245
let msg = makemsg(input, print_banner);
4346
let mut seen_ttys = Vec::with_capacity(16);
44-
loop {
45-
// get next user entry and check it is valid
46-
let entry = unsafe {
47-
let utmpptr = libc::getutxent();
48-
if utmpptr.is_null() {
49-
break;
50-
}
51-
&*utmpptr
52-
};
53-
54-
if entry.ut_user[0] == 0 || entry.ut_type != libc::USER_PROCESS {
47+
for record in Utmpx::iter_all_records() {
48+
if !record.is_user_process() {
5549
continue;
5650
}
5751

5852
// make sure device is valid
59-
let first = entry.ut_line[0].cast_unsigned();
60-
if first == 0 || first == b':' {
53+
let tty = record.tty_device();
54+
if tty.is_empty() || tty.starts_with(':') {
6155
continue;
6256
}
6357

6458
// check group membership
6559
if let Some(gid) = group {
66-
if !is_gr_member(&entry.ut_user, gid) {
67-
continue;
60+
match Passwd::locate(record.user().as_str()) {
61+
Ok(pw) if pw.gid == gid || pw.belongs_to().contains(&gid) => {}
62+
_ => continue,
6863
}
6964
}
7065

71-
// get tty
72-
let tty = unsafe {
73-
let len = entry
74-
.ut_line
75-
.iter()
76-
.position(|&c| c == 0)
77-
.unwrap_or(entry.ut_line.len());
78-
79-
let bytes = std::slice::from_raw_parts(entry.ut_line.as_ptr().cast(), len);
80-
str::from_utf8_unchecked(bytes).to_owned()
81-
};
82-
8366
// output message to device
8467
if !seen_ttys.contains(&tty) {
8568
if let Err(e) = ttymsg(&tty, msg.clone(), timeout) {
@@ -88,7 +71,6 @@ mod unix {
8871
seen_ttys.push(tty);
8972
}
9073
}
91-
unsafe { libc::endutxent() };
9274
}
9375

9476
// Create the banner and sanitise input
@@ -105,16 +87,7 @@ mod unix {
10587
}
10688
};
10789

108-
let user = unsafe {
109-
let ruid = libc::getuid();
110-
let pw = libc::getpwuid(ruid);
111-
if !pw.is_null() && !(*pw).pw_name.is_null() {
112-
CStr::from_ptr((*pw).pw_name).to_string_lossy().into_owned()
113-
} else {
114-
eprintln!("cannot get passwd uid");
115-
"<someone>".to_string()
116-
}
117-
};
90+
let user = uid2usr(process::getuid()).unwrap_or("<someone>".to_string());
11891

11992
let tty = unsafe {
12093
let tty_ptr = libc::ttyname(libc::STDOUT_FILENO);
@@ -126,14 +99,14 @@ mod unix {
12699
}
127100
};
128101

129-
let date = DateTime::<Local>::from(SystemTime::now()).format("%a %b %e %T %Y");
102+
let date = chrono::Local::now().format("%a %b %e %T %Y");
130103
let banner = format!("Broadcast message from {user}@{hostname} ({tty}) ({date}):");
131104

132105
blank(&mut buf);
133106
buf += &banner;
134107
buf.extend(std::iter::repeat_n(
135108
' ',
136-
TERM_WIDTH.saturating_sub(banner.len()),
109+
TERM_WIDTH.saturating_sub(banner.width()),
137110
));
138111
buf += "\x07\x07\r\n";
139112
}
@@ -153,6 +126,8 @@ mod unix {
153126
// - wraps lines by TERM_WIDTH
154127
// - escapes control characters
155128
fn sanitise_line(line: &str) -> String {
129+
use std::fmt::Write;
130+
156131
let mut buf = String::with_capacity(line.len());
157132
let mut col = 0;
158133

@@ -179,7 +154,7 @@ mod unix {
179154
}
180155
_ => {
181156
buf.push(ch);
182-
col += ch.width_cjk().unwrap_or_default();
157+
col += ch.width().unwrap_or_default();
183158
}
184159
}
185160

@@ -195,63 +170,6 @@ mod unix {
195170
buf + "\r\n"
196171
}
197172

198-
// Determine if user is in specified group
199-
fn is_gr_member(user: &[c_char], gid: gid_t) -> bool {
200-
// make sure user exists in database
201-
let pw = unsafe { libc::getpwnam(user.as_ptr()) };
202-
if pw.is_null() {
203-
return false;
204-
}
205-
206-
// if so, check if primary group matches
207-
let group = unsafe { (*pw).pw_gid };
208-
if gid == group {
209-
return true;
210-
}
211-
212-
// on macos, getgrouplist takes c_int as its group argument
213-
#[cfg(target_os = "macos")]
214-
let group = group.cast_signed();
215-
216-
// otherwise check gid is in list of supplementary groups user belongs to
217-
let mut ngroups = 16;
218-
let mut groups = vec![0; ngroups as usize];
219-
while unsafe {
220-
libc::getgrouplist(user.as_ptr(), group, groups.as_mut_ptr(), &raw mut ngroups)
221-
} == -1
222-
{
223-
// ret -1 means buffer was too small so we resize
224-
// according to the returned ngroups value
225-
groups.resize(ngroups as usize, 0);
226-
}
227-
228-
#[cfg(target_os = "macos")]
229-
let gid = gid.cast_signed();
230-
groups.contains(&gid)
231-
}
232-
233-
// Try to get corresponding group gid.
234-
pub fn get_group_gid(group: &String) -> UResult<gid_t> {
235-
// first we try as a group name
236-
let Ok(cname) = CString::from_str(group) else {
237-
return Err(USimpleError::new(1, "invalid group argument"));
238-
};
239-
240-
let gr = unsafe { libc::getgrnam(cname.as_ptr()) };
241-
if !gr.is_null() {
242-
return Ok(unsafe { (*gr).gr_gid });
243-
}
244-
245-
// otherwise, try as literal gid
246-
let Ok(gid) = group.parse::<gid_t>() else {
247-
return Err(USimpleError::new(1, "invalid group argument"));
248-
};
249-
if unsafe { libc::getgrgid(gid) }.is_null() {
250-
return Err(USimpleError::new(1, format!("{group}: unknown gid")));
251-
}
252-
Ok(gid)
253-
}
254-
255173
// Write to the tty device
256174
fn ttymsg(tty: &str, msg: Arc<String>, timeout: Option<&u64>) -> Result<(), &'static str> {
257175
let (tx, rx) = mpsc::channel();
@@ -337,7 +255,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
337255

338256
// get nobanner flag and check if user is root
339257
let flag = args.get_flag("nobanner");
340-
let print_banner = if flag && unsafe { libc::geteuid() } != 0 {
258+
let print_banner = if flag && process::geteuid() != 0 {
341259
eprintln!("wall: --nobanner is available only for root");
342260
true
343261
} else {
@@ -347,7 +265,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
347265
// if group exists, map to corresponding gid
348266
let group = args
349267
.get_one::<String>("group")
350-
.map(unix::get_group_gid)
268+
.map(|g| {
269+
Group::locate(g.as_str())
270+
.map(|g| g.gid)
271+
.map_err(|_| USimpleError::new(1, format!("{g}: unknown group")))
272+
})
351273
.transpose()?;
352274

353275
// If we have a single input arg and it exists on disk, treat as a file.
@@ -365,14 +287,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
365287
// When we are not root, but suid or sgid, refuse to read files
366288
// (e.g. device files) that the user may not have access to.
367289
// After all, our invoker can easily do "wall < file" instead of "wall file".
368-
unsafe {
369-
let uid = libc::getuid();
370-
if uid > 0 && (uid != libc::geteuid() || libc::getgid() != libc::getegid()) {
371-
return Err(USimpleError::new(
372-
1,
373-
format!("will not read {fname} - use stdin"),
374-
));
375-
}
290+
let uid = process::getuid();
291+
if uid > 0 && (uid != process::geteuid() || process::getgid() != process::getegid()) {
292+
return Err(USimpleError::new(
293+
1,
294+
format!("will not read {fname} - use stdin"),
295+
));
376296
}
377297

378298
let Ok(f) = File::open(p) else {

tests/by-util/test_wall.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ mod tests {
1414
.arg("fooblywoobly") // assuming this group doesnt exist
1515
.fails()
1616
.code_is(1)
17-
.stderr_is("wall: invalid group argument\n");
17+
.stderr_is("wall: fooblywoobly: unknown group\n");
1818
}
1919

2020
#[test]
@@ -24,7 +24,7 @@ mod tests {
2424
.arg("99999") // assuming this group doesnt exist
2525
.fails()
2626
.code_is(1)
27-
.stderr_is("wall: 99999: unknown gid\n");
27+
.stderr_is("wall: 99999: unknown group\n");
2828
}
2929

3030
#[test]

0 commit comments

Comments
 (0)