Skip to content

Commit 41462d8

Browse files
committed
who and friends: MacOS testing and POSIX compliance fixes
All who POSIX compliance issues have been fixed: Fixes Applied: | Issue | Status | Cross-Platform | |-------------------------------|--------|---------------------------------------| | who -m / who am i crash | FIXED | ✓ Uses POSIX ttyname() | | Wrong timezone (UTC vs local) | FIXED | ✓ Uses chrono::Local | | -T terminal state | FIXED | ✓ Uses std::os::unix::fs::MetadataExt | | -u idle time | FIXED | ✓ Uses atime() from MetadataExt | | -q format (horizontal) | FIXED | ✓ Pure Rust | | -H header (NAME not USER) | FIXED | ✓ Pure Rust | Files Modified: - plib/src/curuser.rs - tty() returns Option<String> - sys/who.rs - All POSIX fixes - users/tty.rs - Handle Option - users/write.rs - Handle Option - users/newgrp.rs - Handle Option
1 parent 0099a92 commit 41462d8

File tree

5 files changed

+136
-49
lines changed

5 files changed

+136
-49
lines changed

plib/src/curuser.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub fn login_name() -> String {
3232
username
3333
}
3434

35-
pub fn tty() -> String {
35+
pub fn tty() -> Option<String> {
3636
// Try to get the tty name from STDIN, STDOUT, STDERR in that order
3737
for fd in [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO].iter() {
3838
unsafe {
@@ -42,12 +42,12 @@ pub fn tty() -> String {
4242
let c_str = CStr::from_ptr(c_str);
4343

4444
return match c_str.to_str() {
45-
Ok(s) => s.to_owned(),
46-
Err(e) => panic!("Failed to convert tty name to a Rust String: {}", e),
45+
Ok(s) => Some(s.to_owned()),
46+
Err(_) => None,
4747
};
4848
}
4949
}
5050
}
5151

52-
panic!("Failed to get tty name from any file descriptor");
52+
None
5353
}

sys/who.rs

Lines changed: 123 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
//
99
// TODO:
1010
// - implement -f option (requires updates to utmpx module)
11-
// - implement -T, -u options
1211
//
1312

13+
use std::os::unix::fs::MetadataExt;
1414
use std::path::PathBuf;
1515

1616
use clap::Parser;
@@ -82,47 +82,130 @@ struct Args {
8282
file: Option<PathBuf>,
8383
}
8484

85-
// convert timestamp into POSIX-specified strftime format
85+
// convert timestamp into POSIX-specified strftime format (local time)
8686
fn fmt_timestamp(ts: libc::time_t) -> String {
87-
let dt = chrono::DateTime::from_timestamp(ts, 0).unwrap();
87+
use chrono::{Local, TimeZone};
88+
let dt = Local
89+
.timestamp_opt(ts, 0)
90+
.single()
91+
.unwrap_or_else(Local::now);
8892
dt.format("%b %e %H:%M").to_string()
8993
}
9094

91-
fn print_fmt_short(entry: &Utmpx, line: &str) {
92-
println!(
93-
"{:<16} {:<12} {}",
94-
entry.user,
95-
line,
96-
fmt_timestamp(entry.timestamp)
97-
);
95+
// Get terminal state for -T option: + (write allowed), - (write denied), ? (unknown)
96+
fn get_terminal_state(line: &str) -> char {
97+
if line == "system boot" {
98+
return ' ';
99+
}
100+
let path = format!("/dev/{}", line);
101+
match std::fs::metadata(&path) {
102+
Ok(meta) => {
103+
let mode = meta.mode();
104+
// Check group write permission (o+w would be 0o002, g+w is 0o020)
105+
if mode & 0o020 != 0 {
106+
'+'
107+
} else {
108+
'-'
109+
}
110+
}
111+
Err(_) => '?',
112+
}
98113
}
99114

100-
fn print_fmt_term(entry: &Utmpx, line: &str) {
101-
let term_state = '?';
102-
println!(
103-
"{:<16} {} {:<12} {}",
104-
entry.user,
105-
term_state,
106-
line,
107-
fmt_timestamp(entry.timestamp)
108-
);
115+
// Get idle time for -u option
116+
fn get_idle_time(line: &str) -> String {
117+
if line == "system boot" {
118+
return " .".to_string();
119+
}
120+
let path = format!("/dev/{}", line);
121+
match std::fs::metadata(&path) {
122+
Ok(meta) => {
123+
let atime = meta.atime();
124+
let now = std::time::SystemTime::now()
125+
.duration_since(std::time::UNIX_EPOCH)
126+
.unwrap()
127+
.as_secs() as i64;
128+
let idle_secs = now - atime;
129+
if idle_secs < 60 {
130+
" .".to_string() // Active in last minute
131+
} else if idle_secs > 24 * 60 * 60 {
132+
" old".to_string()
133+
} else {
134+
let hours = idle_secs / 3600;
135+
let mins = (idle_secs % 3600) / 60;
136+
format!("{:02}:{:02}", hours, mins)
137+
}
138+
}
139+
Err(_) => " ?".to_string(),
140+
}
109141
}
110142

111-
fn current_terminal() -> String {
112-
let s = curuser::tty();
113-
if let Some(st) = s.strip_prefix("/dev/") {
114-
st.to_owned()
143+
fn print_fmt_short(args: &Args, entry: &Utmpx, line: &str) {
144+
if args.idle_time {
145+
println!(
146+
"{:<16} {:<12} {} {} {:>5}",
147+
entry.user,
148+
line,
149+
fmt_timestamp(entry.timestamp),
150+
get_idle_time(line),
151+
entry.pid
152+
);
115153
} else {
116-
s
154+
println!(
155+
"{:<16} {:<12} {}",
156+
entry.user,
157+
line,
158+
fmt_timestamp(entry.timestamp)
159+
);
117160
}
118161
}
119162

163+
fn print_fmt_term(args: &Args, entry: &Utmpx, line: &str) {
164+
let term_state = get_terminal_state(line);
165+
if args.idle_time {
166+
println!(
167+
"{:<16} {} {:<12} {} {} {:>5}",
168+
entry.user,
169+
term_state,
170+
line,
171+
fmt_timestamp(entry.timestamp),
172+
get_idle_time(line),
173+
entry.pid
174+
);
175+
} else {
176+
println!(
177+
"{:<16} {} {:<12} {}",
178+
entry.user,
179+
term_state,
180+
line,
181+
fmt_timestamp(entry.timestamp)
182+
);
183+
}
184+
}
185+
186+
fn current_terminal() -> Option<String> {
187+
curuser::tty().map(|s| {
188+
if let Some(st) = s.strip_prefix("/dev/") {
189+
st.to_owned()
190+
} else {
191+
s
192+
}
193+
})
194+
}
195+
120196
fn print_entry(args: &Args, entry: &Utmpx) {
121197
// Skip if current_terminal option is set and this entry is not for the current terminal
122198
if args.current_terminal {
123-
let current_tty = current_terminal();
124-
if entry.line != current_tty {
125-
return;
199+
match current_terminal() {
200+
Some(current_tty) => {
201+
if entry.line != current_tty {
202+
return;
203+
}
204+
}
205+
None => {
206+
// No tty available, skip all entries for -m
207+
return;
208+
}
126209
}
127210
}
128211

@@ -146,18 +229,18 @@ fn print_entry(args: &Args, entry: &Utmpx) {
146229
_ => entry.line.as_str(),
147230
};
148231

149-
if args.short_format {
150-
print_fmt_short(entry, line);
232+
if args.terminals {
233+
print_fmt_term(args, entry, line);
151234
} else {
152-
print_fmt_term(entry, line);
235+
print_fmt_short(args, entry, line);
153236
}
154237
}
155238

156239
fn show_utmpx_entries(args: &Args) {
157240
if args.heading {
158241
println!(
159242
"{:<16} {:<12} {}",
160-
gettext("USER"),
243+
gettext("NAME"),
161244
gettext("LINE"),
162245
gettext("TIME")
163246
);
@@ -170,16 +253,16 @@ fn show_utmpx_entries(args: &Args) {
170253
}
171254

172255
fn show_utmpx_summary() {
173-
let mut count = 0;
174256
let entries = utmpx::load();
175-
for entry in &entries {
176-
if !entry.user.is_empty() {
177-
println!("{}", entry.user);
178-
count += 1;
179-
}
180-
}
181-
182-
println!("# users = {}", count);
257+
let users: Vec<&str> = entries
258+
.iter()
259+
.filter(|e| !e.user.is_empty() && e.typ == platform::USER_PROCESS)
260+
.map(|e| e.user.as_str())
261+
.collect();
262+
263+
// Print users horizontally, space-separated (POSIX format)
264+
println!("{}", users.join(" "));
265+
println!("# users={}", users.len());
183266
}
184267

185268
fn main() -> Result<(), Box<dyn std::error::Error>> {

users/newgrp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ fn logger(name: &str, gid: u32) {
503503
let loginname = plib::curuser::login_name();
504504

505505
// Get the current tty device name
506-
let tty = plib::curuser::tty();
506+
let tty = plib::curuser::tty().unwrap_or_else(|| "???".to_string());
507507

508508
// Log the message
509509
eprintln!(

users/tty.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ fn main() {
1717
std::process::exit(1);
1818
}
1919

20-
let ttyname = tty();
21-
22-
println!("{}", ttyname);
20+
match tty() {
21+
Some(ttyname) => println!("{}", ttyname),
22+
None => {
23+
println!("not a tty");
24+
std::process::exit(1);
25+
}
26+
}
2327
}

users/write.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
227227
}
228228

229229
let sender_login_id = curuser::login_name();
230-
let sending_terminal = curuser::tty();
230+
let sending_terminal = curuser::tty().unwrap_or_else(|| "???".to_string());
231231
let date = get_current_date();
232232

233233
let message = format!(

0 commit comments

Comments
 (0)