@@ -6,7 +6,9 @@ mod nameservice;
6
6
7
7
use std:: collections:: { BTreeMap , BTreeSet } ;
8
8
use std:: io:: { BufRead , BufReader } ;
9
+ use std:: num:: ParseIntError ;
9
10
use std:: path:: PathBuf ;
11
+ use std:: str:: FromStr ;
10
12
11
13
use camino:: Utf8Path ;
12
14
use cap_std_ext:: dirext:: { CapStdExtDirExt , CapStdExtDirExtUtf8 } ;
@@ -36,6 +38,34 @@ pub enum Error {
36
38
/// The type of Result.
37
39
pub type Result < T > = std:: result:: Result < T , Error > ;
38
40
41
+ /// In sysusers, a user can refer to a group via name or number
42
+ #[ derive( Debug , PartialEq , Eq ) ]
43
+ pub enum GroupReference {
44
+ /// A numeric reference
45
+ Numeric ( u32 ) ,
46
+ /// A named reference
47
+ Name ( String ) ,
48
+ }
49
+
50
+ impl From < u32 > for GroupReference {
51
+ fn from ( value : u32 ) -> Self {
52
+ Self :: Numeric ( value)
53
+ }
54
+ }
55
+
56
+ impl FromStr for GroupReference {
57
+ type Err = ParseIntError ;
58
+
59
+ fn from_str ( s : & str ) -> std:: result:: Result < Self , Self :: Err > {
60
+ let r = if s. chars ( ) . all ( |c| matches ! ( c, '0' ..='9' ) ) {
61
+ Self :: Numeric ( u32:: from_str ( s) ?)
62
+ } else {
63
+ Self :: Name ( s. to_owned ( ) )
64
+ } ;
65
+ Ok ( r)
66
+ }
67
+ }
68
+
39
69
/// A parsed sysusers.d entry
40
70
#[ derive( Debug , PartialEq , Eq ) ]
41
71
#[ allow( missing_docs) ]
@@ -44,7 +74,7 @@ pub enum SysusersEntry {
44
74
User {
45
75
name : String ,
46
76
uid : Option < u32 > ,
47
- pgid : Option < u32 > ,
77
+ pgid : Option < GroupReference > ,
48
78
gecos : String ,
49
79
home : Option < String > ,
50
80
shell : Option < String > ,
@@ -112,7 +142,11 @@ impl SysusersEntry {
112
142
. or_else ( || id. map ( |id| ( id, id) ) )
113
143
. map ( |( uid, gid) | ( Some ( uid) , Some ( gid) ) )
114
144
. unwrap_or ( ( None , None ) ) ;
115
- let uid = uid. map ( |id| id. parse ( ) ) . transpose ( ) . map_err ( |_| err ( ) ) ?;
145
+ let uid = uid
146
+ . filter ( |& v| v != "-" )
147
+ . map ( |id| id. parse ( ) )
148
+ . transpose ( )
149
+ . map_err ( |_| err ( ) ) ?;
116
150
let pgid = pgid. map ( |id| id. parse ( ) ) . transpose ( ) . map_err ( |_| err ( ) ) ?;
117
151
let ( gecos, s) = Self :: next_token_owned ( s) . ok_or_else ( err. clone ( ) ) ?;
118
152
let ( home, s) = Self :: next_optional_token_owned ( s) . unwrap_or_default ( ) ;
@@ -181,9 +215,13 @@ pub fn read_sysusers(rootfs: &Dir) -> Result<Vec<SysusersEntry>> {
181
215
found_users. insert ( name. clone ( ) ) ;
182
216
found_groups. insert ( name. clone ( ) ) ;
183
217
// Users implicitly create a group with the same name
218
+ let pgid = pgid. as_ref ( ) . and_then ( |g| match g {
219
+ GroupReference :: Numeric ( n) => Some ( * n) ,
220
+ GroupReference :: Name ( _) => None ,
221
+ } ) ;
184
222
result. push ( SysusersEntry :: Group {
185
223
name : name. clone ( ) ,
186
- id : pgid. clone ( ) ,
224
+ id : pgid,
187
225
} ) ;
188
226
result. push ( e) ;
189
227
}
@@ -222,7 +260,7 @@ pub fn analyze(rootfs: &Dir) -> Result<SysusersAnalysis> {
222
260
#[ allow( dead_code) ]
223
261
uid : Option < u32 > ,
224
262
#[ allow( dead_code) ]
225
- pgid : Option < u32 > ,
263
+ pgid : Option < GroupReference > ,
226
264
}
227
265
228
266
struct SysgroupData {
@@ -351,18 +389,27 @@ mod tests {
351
389
g nobody 65534
352
390
"## } ;
353
391
392
+ /// Non-default sysusers found in the wild
393
+ const OTHER_SYSUSERS_REF : & str = indoc ! { r#"
394
+ u qemu 107:qemu "qemu user" - -
395
+ u vboxadd -:1 - /var/run/vboxadd -
396
+ "# } ;
397
+
398
+ fn parse_all ( s : & str ) -> impl Iterator < Item = SysusersEntry > + use < ' _ > {
399
+ s. lines ( )
400
+ . filter ( |line| !( line. is_empty ( ) || line. starts_with ( '#' ) ) )
401
+ . map ( |line| SysusersEntry :: parse ( line) . unwrap ( ) . unwrap ( ) )
402
+ }
403
+
354
404
#[ test]
355
405
fn test_sysusers_parse ( ) -> Result < ( ) > {
356
- let mut entries = SYSUSERS_REF
357
- . lines ( )
358
- . filter ( |line| !( line. is_empty ( ) || line. starts_with ( '#' ) ) )
359
- . map ( |line| SysusersEntry :: parse ( line) . unwrap ( ) . unwrap ( ) ) ;
406
+ let mut entries = parse_all ( SYSUSERS_REF ) ;
360
407
assert_eq ! (
361
408
entries. next( ) . unwrap( ) ,
362
409
SysusersEntry :: User {
363
410
name: "root" . into( ) ,
364
411
uid: Some ( 0 ) ,
365
- pgid: Some ( 0 ) ,
412
+ pgid: Some ( 0 . into ( ) ) ,
366
413
gecos: "Super User" . into( ) ,
367
414
home: Some ( "/root" . into( ) ) ,
368
415
shell: Some ( "/bin/bash" . into( ) )
@@ -373,7 +420,7 @@ mod tests {
373
420
SysusersEntry :: User {
374
421
name: "root" . into( ) ,
375
422
uid: Some ( 0 ) ,
376
- pgid: Some ( 0 ) ,
423
+ pgid: Some ( 0 . into ( ) ) ,
377
424
gecos: "Super User" . into( ) ,
378
425
home: Some ( "/root" . into( ) ) ,
379
426
shell: None
@@ -384,7 +431,7 @@ mod tests {
384
431
SysusersEntry :: User {
385
432
name: "bin" . into( ) ,
386
433
uid: Some ( 1 ) ,
387
- pgid: Some ( 1 ) ,
434
+ pgid: Some ( 1 . into ( ) ) ,
388
435
gecos: "bin" . into( ) ,
389
436
home: Some ( "/bin" . into( ) ) ,
390
437
shell: None
@@ -396,13 +443,39 @@ mod tests {
396
443
SysusersEntry :: User {
397
444
name: "adm" . into( ) ,
398
445
uid: Some ( 3 ) ,
399
- pgid: Some ( 4 ) ,
446
+ pgid: Some ( 4 . into ( ) ) ,
400
447
gecos: "adm" . into( ) ,
401
448
home: Some ( "/var/adm" . into( ) ) ,
402
449
shell: None
403
450
}
404
451
) ;
405
452
assert_eq ! ( entries. count( ) , 9 ) ;
453
+
454
+ let mut entries = parse_all ( OTHER_SYSUSERS_REF ) ;
455
+ assert_eq ! (
456
+ entries. next( ) . unwrap( ) ,
457
+ SysusersEntry :: User {
458
+ name: "qemu" . into( ) ,
459
+ uid: Some ( 107 ) ,
460
+ pgid: Some ( GroupReference :: Name ( "qemu" . into( ) ) ) ,
461
+ gecos: "qemu user" . into( ) ,
462
+ home: None ,
463
+ shell: None
464
+ }
465
+ ) ;
466
+ assert_eq ! (
467
+ entries. next( ) . unwrap( ) ,
468
+ SysusersEntry :: User {
469
+ name: "vboxadd" . into( ) ,
470
+ uid: None ,
471
+ pgid: Some ( 1 . into( ) ) ,
472
+ gecos: "-" . into( ) ,
473
+ home: Some ( "/var/run/vboxadd" . into( ) ) ,
474
+ shell: None
475
+ }
476
+ ) ;
477
+ assert_eq ! ( entries. count( ) , 0 ) ;
478
+
406
479
Ok ( ( ) )
407
480
}
408
481
0 commit comments