use super::{Action, HostFacts, OwnerFixup, Props};
use crate::error::{Error, Result};
use crate::owner::{numeric_ids, parse_mode};
use crate::spec::{SandboxSpec, VolumeSpec};
pub(super) fn for_mount(
name: &str,
spec: &SandboxSpec,
v: &VolumeSpec,
def: Option<&crate::spec::NamedVolumeSpec>,
host: &HostFacts,
(path, device, pool, volume): (&str, &str, &str, &str),
) -> Result<(Props, Vec<OwnerFixup>)> {
let mode = v
.mode
.as_deref()
.map(parse_mode)
.transpose()
.map_err(|e| Error::invalid(format!("{name}: {}: {e}", v.target)))?;
let fixup = |owner: Option<&String>, mode: Option<u32>, new_volume: bool| OwnerFixup {
device: device.into(),
path: path.into(),
owner: owner.cloned(),
mode: mode.map(|m| format!("{m:04o}")),
new_volume: new_volume.then(|| (pool.to_string(), volume.to_string())),
};
let mut initial = Props::new();
let mut fixups = Vec::new();
let implicit = v.owner.is_none().then_some(spec.user.as_ref()).flatten();
if let Some((uid, gid)) = v.owner.as_ref().or(implicit).and_then(|o| numeric_ids(o)) {
initial.insert("initial.uid".into(), uid.to_string());
initial.insert("initial.gid".into(), gid.to_string());
}
if let Some(m) = mode {
initial.insert("initial.mode".into(), format!("{m:04o}"));
}
if !host.initial_owner {
initial.clear();
}
if let Some(o) = &v.owner {
fixups.push(fixup(Some(o), mode, false));
} else {
if let Some(u) = implicit.filter(|_| !initial.contains_key("initial.uid")) {
fixups.push(fixup(Some(u), None, true));
}
if mode.is_some() {
fixups.push(fixup(None, mode, false));
}
}
let mut config = def.map(|d| d.config.clone()).unwrap_or_default();
for (k, val) in initial {
config.entry(k).or_insert(val);
}
Ok((config, fixups))
}
pub(super) fn describe(f: &mut std::fmt::Formatter<'_>, a: &Action) -> std::fmt::Result {
let Action::FixOwner {
path,
owner,
mode,
fresh_only,
} = a
else {
return Ok(());
};
match (owner, mode) {
(Some(o), Some(m)) => write!(f, "~ chown {o} {path}, chmod {m}")?,
(Some(o), None) => write!(f, "~ chown {o} {path}")?,
(None, m) => write!(f, "~ chmod {} {path}", m.as_deref().unwrap_or("-"))?,
}
if *fresh_only {
write!(f, " (new volume the image did not seed)")?;
}
Ok(())
}
pub(super) fn actions(
owners: &[OwnerFixup],
new_device: &dyn Fn(&str) -> bool,
volumes_missing: &[(String, String)],
) -> Vec<Action> {
owners
.iter()
.filter(|o| new_device(&o.device))
.filter(|o| {
o.new_volume
.as_ref()
.is_none_or(|v| volumes_missing.contains(v))
})
.map(|o| Action::FixOwner {
path: o.path.clone(),
owner: o.owner.clone(),
mode: o.mode.clone(),
fresh_only: o.new_volume.is_some(),
})
.collect()
}