@@ -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 > ,
@@ -112,7 +142,11 @@ impl SysusersEntry {
112142 . or_else ( || id. map ( |id| ( id, id) ) )
113143 . map ( |( uid, gid) | ( Some ( uid) , Some ( gid) ) )
114144 . 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 ( ) ) ?;
116150 let pgid = pgid. map ( |id| id. parse ( ) ) . transpose ( ) . map_err ( |_| err ( ) ) ?;
117151 let ( gecos, s) = Self :: next_token_owned ( s) . ok_or_else ( err. clone ( ) ) ?;
118152 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>> {
181215 found_users. insert ( name. clone ( ) ) ;
182216 found_groups. insert ( name. clone ( ) ) ;
183217 // 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+ } ) ;
184222 result. push ( SysusersEntry :: Group {
185223 name : name. clone ( ) ,
186- id : pgid. clone ( ) ,
224+ id : pgid,
187225 } ) ;
188226 result. push ( e) ;
189227 }
@@ -222,7 +260,7 @@ pub fn analyze(rootfs: &Dir) -> Result<SysusersAnalysis> {
222260 #[ allow( dead_code) ]
223261 uid : Option < u32 > ,
224262 #[ allow( dead_code) ]
225- pgid : Option < u32 > ,
263+ pgid : Option < GroupReference > ,
226264 }
227265
228266 struct SysgroupData {
@@ -351,18 +389,27 @@ mod tests {
351389 g nobody 65534
352390 "## } ;
353391
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+
354404 #[ test]
355405 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 ) ;
360407 assert_eq ! (
361408 entries. next( ) . unwrap( ) ,
362409 SysusersEntry :: User {
363410 name: "root" . into( ) ,
364411 uid: Some ( 0 ) ,
365- pgid: Some ( 0 ) ,
412+ pgid: Some ( 0 . into ( ) ) ,
366413 gecos: "Super User" . into( ) ,
367414 home: Some ( "/root" . into( ) ) ,
368415 shell: Some ( "/bin/bash" . into( ) )
@@ -373,7 +420,7 @@ mod tests {
373420 SysusersEntry :: User {
374421 name: "root" . into( ) ,
375422 uid: Some ( 0 ) ,
376- pgid: Some ( 0 ) ,
423+ pgid: Some ( 0 . into ( ) ) ,
377424 gecos: "Super User" . into( ) ,
378425 home: Some ( "/root" . into( ) ) ,
379426 shell: None
@@ -384,7 +431,7 @@ mod tests {
384431 SysusersEntry :: User {
385432 name: "bin" . into( ) ,
386433 uid: Some ( 1 ) ,
387- pgid: Some ( 1 ) ,
434+ pgid: Some ( 1 . into ( ) ) ,
388435 gecos: "bin" . into( ) ,
389436 home: Some ( "/bin" . into( ) ) ,
390437 shell: None
@@ -396,13 +443,39 @@ mod tests {
396443 SysusersEntry :: User {
397444 name: "adm" . into( ) ,
398445 uid: Some ( 3 ) ,
399- pgid: Some ( 4 ) ,
446+ pgid: Some ( 4 . into ( ) ) ,
400447 gecos: "adm" . into( ) ,
401448 home: Some ( "/var/adm" . into( ) ) ,
402449 shell: None
403450 }
404451 ) ;
405452 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+
406479 Ok ( ( ) )
407480 }
408481
0 commit comments