@@ -6,7 +6,9 @@ mod nameservice;
66
77use std:: collections:: { BTreeMap , BTreeSet } ;
88use std:: io:: { BufRead , BufReader } ;
9+ use std:: num:: ParseIntError ;
910use std:: path:: PathBuf ;
11+ use std:: str:: FromStr ;
1012
1113use camino:: Utf8Path ;
1214use cap_std_ext:: dirext:: { CapStdExtDirExt , CapStdExtDirExtUtf8 } ;
@@ -36,6 +38,34 @@ pub enum Error {
3638/// The type of Result.
3739pub type Result < T > = std:: result:: Result < T , Error > ;
3840
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+
3969/// A parsed sysusers.d entry
4070#[ derive( Debug , PartialEq , Eq ) ]
4171#[ allow( missing_docs) ]
@@ -44,7 +74,7 @@ pub enum SysusersEntry {
4474 User {
4575 name : String ,
4676 uid : Option < u32 > ,
47- pgid : Option < u32 > ,
77+ pgid : Option < GroupReference > ,
4878 gecos : String ,
4979 home : Option < String > ,
5080 shell : Option < String > ,
@@ -181,9 +211,13 @@ pub fn read_sysusers(rootfs: &Dir) -> Result<Vec<SysusersEntry>> {
181211 found_users. insert ( name. clone ( ) ) ;
182212 found_groups. insert ( name. clone ( ) ) ;
183213 // 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+ } ) ;
184218 result. push ( SysusersEntry :: Group {
185219 name : name. clone ( ) ,
186- id : pgid. clone ( ) ,
220+ id : pgid,
187221 } ) ;
188222 result. push ( e) ;
189223 }
@@ -222,7 +256,7 @@ pub fn analyze(rootfs: &Dir) -> Result<SysusersAnalysis> {
222256 #[ allow( dead_code) ]
223257 uid : Option < u32 > ,
224258 #[ allow( dead_code) ]
225- pgid : Option < u32 > ,
259+ pgid : Option < GroupReference > ,
226260 }
227261
228262 struct SysgroupData {
@@ -351,18 +385,26 @@ mod tests {
351385 g nobody 65534
352386 "## } ;
353387
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+
354399 #[ test]
355400 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 ) ;
360402 assert_eq ! (
361403 entries. next( ) . unwrap( ) ,
362404 SysusersEntry :: User {
363405 name: "root" . into( ) ,
364406 uid: Some ( 0 ) ,
365- pgid: Some ( 0 ) ,
407+ pgid: Some ( 0 . into ( ) ) ,
366408 gecos: "Super User" . into( ) ,
367409 home: Some ( "/root" . into( ) ) ,
368410 shell: Some ( "/bin/bash" . into( ) )
@@ -373,7 +415,7 @@ mod tests {
373415 SysusersEntry :: User {
374416 name: "root" . into( ) ,
375417 uid: Some ( 0 ) ,
376- pgid: Some ( 0 ) ,
418+ pgid: Some ( 0 . into ( ) ) ,
377419 gecos: "Super User" . into( ) ,
378420 home: Some ( "/root" . into( ) ) ,
379421 shell: None
@@ -384,7 +426,7 @@ mod tests {
384426 SysusersEntry :: User {
385427 name: "bin" . into( ) ,
386428 uid: Some ( 1 ) ,
387- pgid: Some ( 1 ) ,
429+ pgid: Some ( 1 . into ( ) ) ,
388430 gecos: "bin" . into( ) ,
389431 home: Some ( "/bin" . into( ) ) ,
390432 shell: None
@@ -396,13 +438,28 @@ mod tests {
396438 SysusersEntry :: User {
397439 name: "adm" . into( ) ,
398440 uid: Some ( 3 ) ,
399- pgid: Some ( 4 ) ,
441+ pgid: Some ( 4 . into ( ) ) ,
400442 gecos: "adm" . into( ) ,
401443 home: Some ( "/var/adm" . into( ) ) ,
402444 shell: None
403445 }
404446 ) ;
405447 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+
406463 Ok ( ( ) )
407464 }
408465
0 commit comments