Skip to content

Commit

Permalink
sort users and groups by their ID
Browse files Browse the repository at this point in the history
  • Loading branch information
nikstur committed Sep 10, 2024
1 parent 62fad67 commit 7fe2b21
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 129 deletions.
23 changes: 0 additions & 23 deletions rust/userborn/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion rust/userborn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ anyhow = "1.0.86"
log = "0.4.22"
serde = { version = "1.0.204", features = [ "derive" ] }
serde_json = "1.0.121"
indexmap = "2.3.0"
env_logger = { version = "0.11.5", default-features = false }

[dev-dependencies]
Expand Down
67 changes: 34 additions & 33 deletions rust/userborn/src/group.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::{collections::BTreeSet, fs, path::Path};
use std::{
collections::{BTreeMap, BTreeSet},
fs,
path::Path,
};

use anyhow::{bail, Context, Result};
use indexmap::IndexMap;

use crate::{fs::atomic_write, id};

Expand Down Expand Up @@ -85,11 +88,12 @@ fn join_group_members(v: &[String]) -> String {
v.join(",")
}

#[derive(Default)]
pub struct Group {
/// Entries of /etc/group keyed by group name.
entries: IndexMap<String, Entry>,
/// Already allocated GIDs.
gids: BTreeSet<u32>,
entries: BTreeMap<u32, Entry>,
/// A mapping from names to GIDs.
uids: BTreeMap<String, u32>,
}

impl Group {
Expand All @@ -101,17 +105,17 @@ impl Group {
}

fn from_buffer(s: &str) -> Self {
let mut entries = IndexMap::new();
let mut gids = BTreeSet::new();
let mut entries = BTreeMap::new();
let mut uids = BTreeMap::new();
for line in s.lines() {
if let Some(e) = Entry::from_line(line) {
gids.insert(e.gid);
entries.insert(e.name.clone(), e.clone());
entries.insert(e.gid, e.clone());
uids.insert(e.name.clone(), e.gid);
} else {
log::warn!("Skipping group line because it cannot be parsed: {line}.");
}
}
Self { entries, gids }
Self { entries, uids }
}

pub fn to_file(&self, path: impl AsRef<Path>) -> Result<()> {
Expand All @@ -128,48 +132,40 @@ impl Group {
}

pub fn get(&self, name: &str) -> Option<&Entry> {
self.entries.get(name)
let gid = self.uids.get(name);
gid.and_then(|gid| self.entries.get(gid))
}

pub fn get_mut(&mut self, name: &str) -> Option<&mut Entry> {
self.entries.get_mut(name)
let gid = self.uids.get(name);
gid.and_then(|gid| self.entries.get_mut(gid))
}

pub fn insert(&mut self, entry: &Entry) -> Result<()> {
if self.gids.contains(&entry.gid) {
if self.entries.contains_key(&entry.gid) {
bail!("Group with GID {} already exists", entry.gid);
}

if self.entries.contains_key(&entry.name) {
if self.uids.contains_key(&entry.name) {
bail!("Group {} already exists", entry.name);
}

self.entries
.entry(entry.name.clone())
.or_insert(entry.clone());
self.gids.insert(entry.gid);
self.entries.entry(entry.gid).or_insert(entry.clone());
self.uids.insert(entry.name.clone(), entry.gid);

Ok(())
}

/// Allocate a new (i.e. unused) GID.
///
/// Returns `Err` if it cannot allocate a new GID because all in the range are already used.
pub fn allocate_gid(&self, is_normal_group: bool) -> Result<u32> {
id::allocate(&self.gids, is_normal_group)
pub fn allocate_gid(&self, is_normal: bool) -> Result<u32> {
let allocated_gids = self.entries.keys().copied().collect::<BTreeSet<u32>>();
id::allocate(&allocated_gids, is_normal)
}

pub fn contains_gid(&self, gid: u32) -> bool {
self.gids.contains(&gid)
}
}

impl Default for Group {
fn default() -> Self {
Self {
entries: IndexMap::new(),
gids: BTreeSet::new(),
}
self.entries.contains_key(&gid)
}
}

Expand All @@ -181,16 +177,21 @@ mod tests {
use indoc::indoc;

#[test]
fn read_and_write_back() {
fn sort() {
let buffer = indoc! {"
nixbld:x:30000:nixbld1,nixbld10,nixbld11,nixbld12,nixbld13,nixbld14,nixbld15,nixbld16,nixbld17,nixbld18,nixbld19,nixbld2,nixbld20,nixbld21,nixbld22,nixbld23,nixbld24,nixbld25,nixbld26,nixbld27,nixbld28,nixbld29,nixbld3,nixbld30,nixbld31,nixbld32,nixbld4,nixbld5,nixbld6,nixbld7,nixbld8,nixbld9
messagebus:x:4:
wheel:x:1:peter
"};

let group = Group::from_buffer(buffer);
let recreated_buffer = group.to_buffer();
assert_eq!(buffer, recreated_buffer);

let expected = expect![[r#"
wheel:x:1:peter
messagebus:x:4:
nixbld:x:30000:nixbld1,nixbld10,nixbld11,nixbld12,nixbld13,nixbld14,nixbld15,nixbld16,nixbld17,nixbld18,nixbld19,nixbld2,nixbld20,nixbld21,nixbld22,nixbld23,nixbld24,nixbld25,nixbld26,nixbld27,nixbld28,nixbld29,nixbld3,nixbld30,nixbld31,nixbld32,nixbld4,nixbld5,nixbld6,nixbld7,nixbld8,nixbld9
"#]];
expected.assert_eq(&recreated_buffer);
}

#[test]
Expand Down
38 changes: 19 additions & 19 deletions rust/userborn/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ fn run() -> Result<()> {
// We should create backup files with an `-` appended to the file name.
group_db.to_file(group_path)?;
passwd_db.to_file(passwd_path)?;
shadow_db.to_file(shadow_path)?;
shadow_db.to_file_sorted(&passwd_db, shadow_path)?;

Ok(())
}
Expand Down Expand Up @@ -388,10 +388,10 @@ mod tests {

update_users_and_groups(&gen0()?, &mut group_db, &mut passwd_db, &mut shadow_db);

let expected_group = expect![[r"
wheel:x:999:normalo
let expected_group = expect![[r#"
root:x:0:root
"]];
wheel:x:999:normalo
"#]];
expected_group.assert_eq(&group_db.to_buffer());

let expected_passwd = expect![[r"
Expand All @@ -402,59 +402,59 @@ mod tests {
let expected_shadow = expect![[r"
root:!*:1::::::
"]];
expected_shadow.assert_eq(&shadow_db.to_buffer());
expected_shadow.assert_eq(&shadow_db.to_buffer_sorted(&passwd_db));

// GEN 1

update_users_and_groups(&gen1()?, &mut group_db, &mut passwd_db, &mut shadow_db);

let expected_group = expect![[r#"
wheel:x:999:normalo,initial
root:x:0:root
normalo:x:1000:normalo
initial:x:998:initial
wheel:x:999:normalo,initial
normalo:x:1000:normalo
"#]];
expected_group.assert_eq(&group_db.to_buffer());

let expected_passwd = expect![[r#"
root:x:0:0:::/run/current-system/sw/bin/nologin
normalo:x:1000:1000::/home/normalo:/bin/bash
initial:x:999:999:::/run/current-system/sw/bin/nologin
normalo:x:1000:1000::/home/normalo:/bin/bash
"#]];
expected_passwd.assert_eq(&passwd_db.to_buffer());

let expected_shadow = expect![[r"
let expected_shadow = expect![[r#"
root:!*:1::::::
normalo:$y$j9T$kX/HY3hhcOSAlNLIhIhcL0$6TUZ0NNT18KBynYbuezPnk79TqyzRjH0BTE5h/m6Go7:1::::::
initial:$y$j9T$2e5ARUyMfmJ0nW9ZMPFg50$EGgRGQBqq0r/fxRlIRXL86K61o/ESEsIdVZYkyQvyN2:1::::::
"]];
expected_shadow.assert_eq(&shadow_db.to_buffer());
normalo:$y$j9T$kX/HY3hhcOSAlNLIhIhcL0$6TUZ0NNT18KBynYbuezPnk79TqyzRjH0BTE5h/m6Go7:1::::::
"#]];
expected_shadow.assert_eq(&shadow_db.to_buffer_sorted(&passwd_db));

// GEN 2

update_users_and_groups(&gen2()?, &mut group_db, &mut passwd_db, &mut shadow_db);

let expected_group = expect![[r#"
wheel:x:999:normalo,initial
root:x:0:root
normalo:x:1000:normalo
initial:x:998:initial
wheel:x:999:normalo,initial
normalo:x:1000:normalo
"#]];
expected_group.assert_eq(&group_db.to_buffer());

let expected_passwd = expect![[r#"
root:x:0:0::/root:/run/current-system/sw/bin/nologin
normalo:x:1000:1000:I'm normal I swear:/home/normalo:/bin/bash
initial:x:999:999:::/run/current-system/sw/bin/nologin
normalo:x:1000:1000:I'm normal I swear:/home/normalo:/bin/bash
"#]];
expected_passwd.assert_eq(&passwd_db.to_buffer());

let expected_shadow = expect![[r"
let expected_shadow = expect![[r#"
root:!*:1::::::
normalo:$y$j9T$CZSAJTLCfrBvcCgvOTY4W1$G7uzyX3O6K.DR8KJLL/oL.8EREPSRTIjBn76SpvcH4A:1::::::
initial:!*:1::::::
"]];
expected_shadow.assert_eq(&shadow_db.to_buffer());
normalo:$y$j9T$CZSAJTLCfrBvcCgvOTY4W1$G7uzyX3O6K.DR8KJLL/oL.8EREPSRTIjBn76SpvcH4A:1::::::
"#]];
expected_shadow.assert_eq(&shadow_db.to_buffer_sorted(&passwd_db));

Ok(())
}
Expand Down
Loading

0 comments on commit 7fe2b21

Please sign in to comment.