@@ -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 > ,
@@ -181,9 +211,13 @@ pub fn read_sysusers(rootfs: &Dir) -> Result<Vec<SysusersEntry>> {
181
211
found_users. insert ( name. clone ( ) ) ;
182
212
found_groups. insert ( name. clone ( ) ) ;
183
213
// Users implicitly create a group with the same name
214
+ let pgid = pgid. as_ref ( ) . and_then ( |g| match g {
215
+ GroupReference :: Numeric ( n) => Some ( * n) ,
216
+ GroupReference :: Name ( _) => None ,
217
+ } ) ;
184
218
result. push ( SysusersEntry :: Group {
185
219
name : name. clone ( ) ,
186
- id : pgid. clone ( ) ,
220
+ id : pgid,
187
221
} ) ;
188
222
result. push ( e) ;
189
223
}
@@ -222,7 +256,7 @@ pub fn analyze(rootfs: &Dir) -> Result<SysusersAnalysis> {
222
256
#[ allow( dead_code) ]
223
257
uid : Option < u32 > ,
224
258
#[ allow( dead_code) ]
225
- pgid : Option < u32 > ,
259
+ pgid : Option < GroupReference > ,
226
260
}
227
261
228
262
struct SysgroupData {
@@ -351,18 +385,26 @@ mod tests {
351
385
g nobody 65534
352
386
"## } ;
353
387
388
+ /// Non-default sysusers found in the wild
389
+ const OTHER_SYSUSERS_REF : & str = indoc ! { r#"
390
+ u qemu 107:qemu "qemu user" - -
391
+ "# } ;
392
+
393
+ fn parse_all ( s : & str ) -> impl Iterator < Item = SysusersEntry > + use < ' _ > {
394
+ s. lines ( )
395
+ . filter ( |line| !( line. is_empty ( ) || line. starts_with ( '#' ) ) )
396
+ . map ( |line| SysusersEntry :: parse ( line) . unwrap ( ) . unwrap ( ) )
397
+ }
398
+
354
399
#[ test]
355
400
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 ( ) ) ;
401
+ let mut entries = parse_all ( SYSUSERS_REF ) ;
360
402
assert_eq ! (
361
403
entries. next( ) . unwrap( ) ,
362
404
SysusersEntry :: User {
363
405
name: "root" . into( ) ,
364
406
uid: Some ( 0 ) ,
365
- pgid: Some ( 0 ) ,
407
+ pgid: Some ( 0 . into ( ) ) ,
366
408
gecos: "Super User" . into( ) ,
367
409
home: Some ( "/root" . into( ) ) ,
368
410
shell: Some ( "/bin/bash" . into( ) )
@@ -373,7 +415,7 @@ mod tests {
373
415
SysusersEntry :: User {
374
416
name: "root" . into( ) ,
375
417
uid: Some ( 0 ) ,
376
- pgid: Some ( 0 ) ,
418
+ pgid: Some ( 0 . into ( ) ) ,
377
419
gecos: "Super User" . into( ) ,
378
420
home: Some ( "/root" . into( ) ) ,
379
421
shell: None
@@ -384,7 +426,7 @@ mod tests {
384
426
SysusersEntry :: User {
385
427
name: "bin" . into( ) ,
386
428
uid: Some ( 1 ) ,
387
- pgid: Some ( 1 ) ,
429
+ pgid: Some ( 1 . into ( ) ) ,
388
430
gecos: "bin" . into( ) ,
389
431
home: Some ( "/bin" . into( ) ) ,
390
432
shell: None
@@ -396,13 +438,28 @@ mod tests {
396
438
SysusersEntry :: User {
397
439
name: "adm" . into( ) ,
398
440
uid: Some ( 3 ) ,
399
- pgid: Some ( 4 ) ,
441
+ pgid: Some ( 4 . into ( ) ) ,
400
442
gecos: "adm" . into( ) ,
401
443
home: Some ( "/var/adm" . into( ) ) ,
402
444
shell: None
403
445
}
404
446
) ;
405
447
assert_eq ! ( entries. count( ) , 9 ) ;
448
+
449
+ let mut entries = parse_all ( OTHER_SYSUSERS_REF ) ;
450
+ assert_eq ! (
451
+ entries. next( ) . unwrap( ) ,
452
+ SysusersEntry :: User {
453
+ name: "qemu" . into( ) ,
454
+ uid: Some ( 107 ) ,
455
+ pgid: Some ( GroupReference :: Name ( "qemu" . into( ) ) ) ,
456
+ gecos: "qemu user" . into( ) ,
457
+ home: None ,
458
+ shell: None
459
+ }
460
+ ) ;
461
+ assert_eq ! ( entries. count( ) , 0 ) ;
462
+
406
463
Ok ( ( ) )
407
464
}
408
465
0 commit comments