@@ -2,27 +2,30 @@ use clap::{crate_version, Arg, ArgAction, Command};
22use 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
711const ABOUT : & str = help_about ! ( "wall.md" ) ;
812const USAGE : & str = help_usage ! ( "wall.md" ) ;
913
1014#[ cfg( unix) ]
1115mod 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,30 @@ 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) {
60+ if !is_gr_member ( & record . user ( ) , gid) {
6761 continue ;
6862 }
6963 }
7064
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-
8365 // output message to device
8466 if !seen_ttys. contains ( & tty) {
8567 if let Err ( e) = ttymsg ( & tty, msg. clone ( ) , timeout) {
@@ -88,7 +70,6 @@ mod unix {
8870 seen_ttys. push ( tty) ;
8971 }
9072 }
91- unsafe { libc:: endutxent ( ) } ;
9273 }
9374
9475 // Create the banner and sanitise input
@@ -105,16 +86,7 @@ mod unix {
10586 }
10687 } ;
10788
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- } ;
89+ let user = uid2usr ( process:: getuid ( ) ) . unwrap_or ( "<someone>" . to_string ( ) ) ;
11890
11991 let tty = unsafe {
12092 let tty_ptr = libc:: ttyname ( libc:: STDOUT_FILENO ) ;
@@ -126,14 +98,14 @@ mod unix {
12698 }
12799 } ;
128100
129- let date = DateTime :: < Local > :: from ( SystemTime :: now ( ) ) . format ( "%a %b %e %T %Y" ) ;
101+ let date = chrono :: Local :: now ( ) . format ( "%a %b %e %T %Y" ) ;
130102 let banner = format ! ( "Broadcast message from {user}@{hostname} ({tty}) ({date}):" ) ;
131103
132104 blank ( & mut buf) ;
133105 buf += & banner;
134106 buf. extend ( std:: iter:: repeat_n (
135107 ' ' ,
136- TERM_WIDTH . saturating_sub ( banner. len ( ) ) ,
108+ TERM_WIDTH . saturating_sub ( banner. width ( ) ) ,
137109 ) ) ;
138110 buf += "\x07 \x07 \r \n " ;
139111 }
@@ -153,6 +125,8 @@ mod unix {
153125 // - wraps lines by TERM_WIDTH
154126 // - escapes control characters
155127 fn sanitise_line ( line : & str ) -> String {
128+ use std:: fmt:: Write ;
129+
156130 let mut buf = String :: with_capacity ( line. len ( ) ) ;
157131 let mut col = 0 ;
158132
@@ -179,7 +153,7 @@ mod unix {
179153 }
180154 _ => {
181155 buf. push ( ch) ;
182- col += ch. width_cjk ( ) . unwrap_or_default ( ) ;
156+ col += ch. width ( ) . unwrap_or_default ( ) ;
183157 }
184158 }
185159
@@ -196,60 +170,12 @@ mod unix {
196170 }
197171
198172 // 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 ( ) {
173+ fn is_gr_member ( user : & str , gid : libc:: gid_t ) -> bool {
174+ let Ok ( pw) = Passwd :: locate ( user) else {
203175 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" ) ) ;
238176 } ;
239177
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)
178+ gid == pw. gid || pw. belongs_to ( ) . contains ( & gid)
253179 }
254180
255181 // Write to the tty device
@@ -337,7 +263,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
337263
338264 // get nobanner flag and check if user is root
339265 let flag = args. get_flag ( "nobanner" ) ;
340- let print_banner = if flag && unsafe { libc :: geteuid ( ) } != 0 {
266+ let print_banner = if flag && process :: geteuid ( ) != 0 {
341267 eprintln ! ( "wall: --nobanner is available only for root" ) ;
342268 true
343269 } else {
@@ -347,7 +273,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
347273 // if group exists, map to corresponding gid
348274 let group = args
349275 . get_one :: < String > ( "group" )
350- . map ( unix:: get_group_gid)
276+ . map ( |g| {
277+ Group :: locate ( g. as_str ( ) )
278+ . map ( |g| g. gid )
279+ . map_err ( |_| USimpleError :: new ( 1 , format ! ( "{g}: unknown group" ) ) )
280+ } )
351281 . transpose ( ) ?;
352282
353283 // If we have a single input arg and it exists on disk, treat as a file.
@@ -365,14 +295,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
365295 // When we are not root, but suid or sgid, refuse to read files
366296 // (e.g. device files) that the user may not have access to.
367297 // 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- }
298+ let uid = process:: getuid ( ) ;
299+ if uid > 0 && ( uid != process:: geteuid ( ) || process:: getgid ( ) != process:: getegid ( ) ) {
300+ return Err ( USimpleError :: new (
301+ 1 ,
302+ format ! ( "will not read {fname} - use stdin" ) ,
303+ ) ) ;
376304 }
377305
378306 let Ok ( f) = File :: open ( p) else {
0 commit comments