Skip to content

Commit 3393a60

Browse files
author
Paul C
committed
Merge beta: v25.2.82 LXC config trailing-newline fix
2 parents e367d04 + 98e5b0c commit 3393a60

3 files changed

Lines changed: 55 additions & 9 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "wolfstack"
3-
version = "25.2.81"
3+
version = "25.2.82"
44
edition = "2024"
55
authors = ["Wolf Software Systems Ltd"]
66
description = "Server management platform for the Wolf software suite"

src/containers/mod.rs

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8305,6 +8305,53 @@ pub fn lxc_update_settings(container: &str, settings: &LxcSettingsUpdate) -> Res
83058305
})
83068306
}
83078307

8308+
/// Join LXC config lines into a file body, guaranteeing exactly one trailing
8309+
/// newline. `Vec<_>::join("\n")` drops the terminating newline, so a config
8310+
/// written straight from joined lines is POSIX-malformed — and, worse, any
8311+
/// later `>> config` append would land on the same line as the final
8312+
/// directive. Every config rewrite goes through this so no writer can
8313+
/// reintroduce that (the autostart toggle did, 2026-07: it wrote
8314+
/// `lxc.start.auto = 1` with no trailing newline).
8315+
fn lxc_config_body<S: AsRef<str>>(lines: &[S]) -> String {
8316+
let mut body = lines.iter().map(|s| s.as_ref()).collect::<Vec<&str>>().join("\n");
8317+
if !body.ends_with('\n') {
8318+
body.push('\n');
8319+
}
8320+
body
8321+
}
8322+
8323+
/// Write an LXC container config from its line vector with a guaranteed
8324+
/// trailing newline. See `lxc_config_body`.
8325+
fn write_lxc_config<S: AsRef<str>>(path: &str, lines: &[S]) -> std::io::Result<()> {
8326+
std::fs::write(path, lxc_config_body(lines))
8327+
}
8328+
8329+
#[cfg(test)]
8330+
mod lxc_config_body_tests {
8331+
use super::lxc_config_body;
8332+
8333+
#[test]
8334+
fn always_ends_with_exactly_one_newline() {
8335+
// The autostart-toggle regression: joined lines dropped the trailing
8336+
// newline. The body must always end with exactly one.
8337+
let owned = vec![
8338+
"lxc.start.auto = 1".to_string(),
8339+
"lxc.start.delay = 5".to_string(),
8340+
];
8341+
assert_eq!(lxc_config_body(&owned), "lxc.start.auto = 1\nlxc.start.delay = 5\n");
8342+
8343+
// Works with &str slices too (the mount-remove path uses Vec<&str>).
8344+
assert_eq!(lxc_config_body(&["a", "b"]), "a\nb\n");
8345+
8346+
// Idempotent: an already-terminated final line is not doubled.
8347+
assert_eq!(lxc_config_body(&["x\n"]), "x\n");
8348+
8349+
// Empty input still yields a newline-terminated (empty) body.
8350+
let empty: [&str; 0] = [];
8351+
assert_eq!(lxc_config_body(&empty), "\n");
8352+
}
8353+
}
8354+
83088355
/// Update LXC container autostart specifically
83098356
pub fn lxc_set_autostart(container: &str, enabled: bool) -> Result<String, String> {
83108357
if is_proxmox() {
@@ -8332,7 +8379,7 @@ pub fn lxc_set_autostart(container: &str, enabled: bool) -> Result<String, Strin
83328379
new_lines.push("lxc.start.delay = 5".to_string());
83338380
}
83348381

8335-
std::fs::write(&path, new_lines.join("\n")).map_err(|e| e.to_string())?;
8382+
write_lxc_config(&path, &new_lines).map_err(|e| e.to_string())?;
83368383
}
83378384
Ok(format!("Autostart set to {}", enabled))
83388385
}
@@ -8359,7 +8406,7 @@ pub fn lxc_set_network_link(container: &str, link: &str) -> Result<String, Strin
83598406
new_lines.push(format!("lxc.net.0.link = {}", link));
83608407
}
83618408

8362-
std::fs::write(&path, new_lines.join("\n")).map_err(|e| e.to_string())?;
8409+
write_lxc_config(&path, &new_lines).map_err(|e| e.to_string())?;
83638410
Ok(format!("Network link set to {}", link))
83648411
}
83658412

@@ -11740,7 +11787,7 @@ pub fn lxc_migrate_fixup(new_name: &str) {
1174011787
}
1174111788
line.to_string()
1174211789
}).collect();
11743-
let _ = std::fs::write(&config_path, updated.join("\n"));
11790+
let _ = write_lxc_config(&config_path, &updated);
1174411791
}
1174511792
// Mark setup done so lxc_post_start_setup doesn't reassign a bridge IP
1174611793
// at first boot — the carried rootfs already holds the original network
@@ -11818,7 +11865,7 @@ pub fn lxc_clone_fixup_ip(new_name: &str) {
1181811865

1181911866
}
1182011867

11821-
let _ = std::fs::write(&config_path, updated.join("\n"));
11868+
let _ = write_lxc_config(&config_path, &updated);
1182211869
}
1182311870

1182411871
// Write the setup_done marker so lxc_post_start_setup doesn't
@@ -11872,7 +11919,7 @@ pub fn lxc_ensure_network_config(name: &str) {
1187211919
for (i, line) in additions.iter().enumerate() {
1187311920
lines.insert(insert_pos + i, line.clone());
1187411921
}
11875-
let _ = std::fs::write(&config_path, lines.join("\n"));
11922+
let _ = write_lxc_config(&config_path, &lines);
1187611923

1187711924
}
1187811925

@@ -12522,8 +12569,7 @@ pub fn lxc_remove_mount(container: &str, host_path: &str) -> Result<String, Stri
1252212569
})
1252312570
.collect();
1252412571

12525-
let new_config = filtered.join("\n");
12526-
std::fs::write(&config_path, &new_config)
12572+
write_lxc_config(&config_path, &filtered)
1252712573
.map_err(|e| format!("Failed to write config: {}", e))?;
1252812574

1252912575

0 commit comments

Comments
 (0)