priv: WriteNspawnFlags takes Vec<BindMount> instead of raw flag strings

This commit is contained in:
damocles 2026-06-01 17:22:09 +02:00 committed by mara
commit c9eb520e7c
3 changed files with 38 additions and 24 deletions

View file

@ -20,7 +20,7 @@
use std::path::{Path, PathBuf};
use anyhow::{Context as _, Result, bail};
use hive_sh4re::priv_proto::{AGENT_PREFIX, MANAGER_NAME, META_DIR, PRIV_SOCK, SIBLING_CONTAINERS, PrivRequest, PrivResponse};
use hive_sh4re::priv_proto::{AGENT_PREFIX, MANAGER_NAME, META_DIR, PRIV_SOCK, SIBLING_CONTAINERS, BindMount, PrivRequest, PrivResponse};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::{UnixListener, UnixStream};
use tokio::process::Command;
@ -173,12 +173,13 @@ async fn exec(req: PrivRequest) -> Result<(String, String)> {
PrivRequest::ListContainers => container_run(&["list"]).await,
PrivRequest::WriteNspawnFlags { ref container, ref extra_nspawn_flags } => {
PrivRequest::WriteNspawnFlags { ref container, ref binds } => {
validate_container_system_name(container)?;
for flag in extra_nspawn_flags {
validate_nspawn_flag(flag)?;
for bind in binds {
validate_bind_path(&bind.host_path)?;
validate_bind_path(&bind.container_path)?;
}
write_nspawn_flags(container, extra_nspawn_flags)?;
write_nspawn_flags(container, binds)?;
Ok((String::new(), String::new()))
}
@ -352,17 +353,15 @@ fn agent_flake_ref(name: &str) -> String {
format!("{META_DIR}#{name}")
}
/// Validate one nspawn flag entry. Must be non-empty and contain no
/// ASCII whitespace (would split the entry when the start script
/// expands `$EXTRA_NSPAWN_FLAGS` unquoted), double-quotes (would
/// break the `EXTRA_NSPAWN_FLAGS="..."` conf line), or null bytes.
fn validate_nspawn_flag(flag: &str) -> Result<()> {
if flag.is_empty()
|| flag.bytes().any(|b| b == 0 || b == b'"' || b.is_ascii_whitespace())
/// Validate a bind-mount path: must be absolute, non-empty, and contain
/// no newlines, null bytes, or double-quotes (which would break the
/// `EXTRA_NSPAWN_FLAGS="..."` conf line format).
fn validate_bind_path(path: &str) -> Result<()> {
if path.is_empty()
|| !path.starts_with('/')
|| path.bytes().any(|b| b == 0 || b == b'\n' || b == b'"')
{
bail!(
"invalid nspawn flag {flag:?}: must be non-empty and contain no whitespace, double-quotes, or null bytes"
);
bail!("invalid bind path {path:?}: must be an absolute path with no newlines, null bytes, or double-quotes");
}
Ok(())
}
@ -371,7 +370,7 @@ fn validate_nspawn_flag(flag: &str) -> Result<()> {
/// vars (`PRIVATE_NETWORK`, `HOST_ADDRESS*`, `LOCAL_ADDRESS*`, `HOST_BRIDGE`,
/// `EXTRA_NSPAWN_FLAGS`), forces `PRIVATE_NETWORK=0` and blank network vars,
/// then appends `EXTRA_NSPAWN_FLAGS="<flags>"`.
fn write_nspawn_flags(container: &str, extra_nspawn_flags: &[String]) -> Result<()> {
fn write_nspawn_flags(container: &str, binds: &[BindMount]) -> Result<()> {
let path = format!("/etc/nixos-containers/{container}.conf");
let original = std::fs::read_to_string(&path)
.with_context(|| format!("read {path}"))?;
@ -398,7 +397,11 @@ fn write_nspawn_flags(container: &str, extra_nspawn_flags: &[String]) -> Result<
out.push_str("HOST_ADDRESS6=\n");
out.push_str("LOCAL_ADDRESS6=\n");
out.push_str("HOST_BRIDGE=\n");
let flags_joined = extra_nspawn_flags.join(" ");
let flags: Vec<String> = binds.iter().map(|b| {
let flag = if b.read_only { "--bind-ro" } else { "--bind" };
format!("{flag}={}:{}", b.host_path, b.container_path)
}).collect();
let flags_joined = flags.join(" ");
out.push_str(&format!("EXTRA_NSPAWN_FLAGS=\"{flags_joined}\"\n"));
std::fs::write(&path, out).with_context(|| format!("write {path}"))
}