use std::process::Command;
use crate as cindy;
use crate::Context;
#[derive(Clone, PartialEq, Eq)]
#[crate::wire]
pub enum Presence {
Absent,
Present {
gid: Option<u32>,
system: bool,
},
}
impl Default for Presence {
fn default() -> Self {
Self::Present {
gid: None,
system: false,
}
}
}
#[derive(Clone, Default, PartialEq, Eq)]
#[crate::wire]
pub struct State {
pub name: String,
pub presence: Presence,
}
impl crate::Diff for State {}
fn sys_gid_max() -> u32 {
const DEFAULT_SYS_GID_MAX: u32 = 999;
let Ok(contents) = std::fs::read_to_string("/etc/login.defs") else {
return DEFAULT_SYS_GID_MAX;
};
contents
.lines()
.find_map(|line| {
let rest = line.trim().strip_prefix("SYS_GID_MAX")?;
rest.trim().parse::<u32>().ok()
})
.unwrap_or(DEFAULT_SYS_GID_MAX)
}
fn observe(name: &str) -> crate::Result<Presence> {
match nix::unistd::Group::from_name(name).context("Group lookup failed")? {
Some(g) => {
let gid = g.gid.as_raw();
Ok(Presence::Present {
gid: Some(gid),
system: gid <= sys_gid_max(),
})
}
None => Ok(Presence::Absent),
}
}
fn show_diff(name: &str, old: Presence, new: Presence) {
let old_view = State {
name: name.to_owned(),
presence: old,
};
let new_view = State {
name: name.to_owned(),
presence: new,
};
if old_view != new_view {
let _ = <State as crate::Diff>::diff(&old_view, &new_view, &mut std::io::stderr().lock());
}
}
#[crate::remote]
pub fn group(state: State) -> crate::Result<super::Return> {
let observed = observe(&state.name)?;
let changed = match (&state.presence, &observed) {
(Presence::Absent, Presence::Absent) => false,
(Presence::Absent, present @ Presence::Present { .. }) => {
show_diff(&state.name, present.clone(), Presence::Absent);
super::run_check(Command::new("groupdel").args(["--", &state.name]))?;
true
}
(want @ Presence::Present { gid, system }, Presence::Absent) => {
show_diff(&state.name, Presence::Absent, want.clone());
let mut cmd = Command::new("groupadd");
if *system {
cmd.arg("--system");
}
if let Some(gid) = gid {
cmd.args(["--gid", &gid.to_string()]);
}
super::run_check(cmd.args(["--", &state.name]))?;
true
}
(
Presence::Present {
gid: Some(want), ..
},
Presence::Present {
gid: Some(have), ..
},
) if want != have => {
show_diff(
&state.name,
observed.clone(),
Presence::Present {
gid: Some(*want),
system: *want <= sys_gid_max(),
},
);
super::run_check(Command::new("groupmod").args([
"--gid",
&want.to_string(),
"--",
&state.name,
]))?;
true
}
(Presence::Present { .. }, Presence::Present { .. }) => false,
};
Ok(super::Return::from_changed(changed))
}