Skip to content

Commit 552da45

Browse files
committed
sysusers: Handle named group references
The libvirt package has this entry with a numeric user but an explicit named group reference: `u qemu 107:qemu "qemu user" - -` Fix the parser to handle this. Signed-off-by: Colin Walters <[email protected]>
1 parent 5309d5b commit 552da45

File tree

1 file changed

+68
-11
lines changed

1 file changed

+68
-11
lines changed

sysusers/src/lib.rs

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ mod nameservice;
66

77
use std::collections::{BTreeMap, BTreeSet};
88
use std::io::{BufRead, BufReader};
9+
use std::num::ParseIntError;
910
use std::path::PathBuf;
11+
use std::str::FromStr;
1012

1113
use camino::Utf8Path;
1214
use cap_std_ext::dirext::{CapStdExtDirExt, CapStdExtDirExtUtf8};
@@ -36,6 +38,34 @@ pub enum Error {
3638
/// The type of Result.
3739
pub 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

Comments
 (0)