Skip to content

Commit daf3584

Browse files
committed
Implement the user and group options for etc files
1 parent 744a170 commit daf3584

File tree

5 files changed

+80
-11
lines changed

5 files changed

+80
-11
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ im = { version = "15.1.0", features = ["serde"] }
2727
itertools = "0.14.0"
2828
log = "0.4.17"
2929
nix = { version = "0.29.0", features = ["hostname", "user"] }
30+
regex = "1.11.1"
3031
serde = { version = "1.0.152", features = ["derive"] }
3132
serde_json = "1.0.91"
3233
thiserror = "2.0.0"

examples/example.nix

+9-1
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,22 @@
5555
source = "/run/systemd/system/";
5656
};
5757

58-
test_perms = {
58+
with_ownership = {
5959
text = ''
6060
This is just a test!
6161
'';
6262
mode = "0755";
6363
uid = 5;
6464
gid = 6;
6565
};
66+
with_ownership2 = {
67+
text = ''
68+
This is just a test!
69+
'';
70+
mode = "0755";
71+
user = "nobody";
72+
group = "users";
73+
};
6674
};
6775
};
6876

src/activate/etc_files.rs

+59-7
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ mod etc_tree;
22

33
use im::HashMap;
44
use itertools::Itertools;
5+
use regex;
56
use serde::{Deserialize, Serialize};
67
use std::fs::{DirBuilder, Permissions};
78
use std::os::unix::fs as unixfs;
89
use std::os::unix::prelude::PermissionsExt;
910
use std::path;
1011
use std::path::{Path, PathBuf};
12+
use std::sync::OnceLock;
1113
use std::{fs, io};
1214

1315
use self::etc_tree::FileStatus;
@@ -22,6 +24,12 @@ pub use etc_tree::FileTree;
2224

2325
type EtcActivationResult = ActivationResult<FileTree>;
2426

27+
static UID_GID_REGEX: OnceLock<regex::Regex> = OnceLock::new();
28+
29+
fn get_uid_gid_regex() -> &'static regex::Regex {
30+
UID_GID_REGEX.get_or_init(|| regex::Regex::new(r"^\+[0-9]+$").expect("could not compile regex"))
31+
}
32+
2533
#[derive(Debug, Clone, Serialize, Deserialize)]
2634
#[serde(rename_all = "camelCase")]
2735
struct EtcFile {
@@ -352,9 +360,7 @@ fn create_etc_entry(
352360
match copy_file(
353361
&entry.source.store_path.join(&entry.target),
354362
&target_path,
355-
&entry.mode,
356-
entry.uid,
357-
entry.gid,
363+
entry,
358364
old_state,
359365
) {
360366
Ok(_) => Ok(new_state.register_managed_entry(&target_path)),
@@ -414,8 +420,54 @@ fn create_dir_recursively(dir: &Path, state: FileTree) -> EtcActivationResult {
414420
new_state
415421
}
416422

417-
fn copy_file(source: &Path, target: &Path, mode: &str,
418-
uid: u32, gid: u32, old_state: &FileTree) -> anyhow::Result<()> {
423+
fn find_uid(entry: &EtcFile) -> anyhow::Result<u32> {
424+
if !get_uid_gid_regex().is_match(&entry.user) {
425+
nix::unistd::User::from_name(&entry.user)
426+
.map(|maybe_user| {
427+
maybe_user.map_or_else(
428+
|| {
429+
log::warn!(
430+
"Specified user {} not found, defaulting to root",
431+
&entry.user
432+
);
433+
0
434+
},
435+
|user| user.uid.as_raw(),
436+
)
437+
})
438+
.map_err(|err| anyhow::anyhow!(err).context("Failed to determine user"))
439+
} else {
440+
Ok(entry.uid)
441+
}
442+
}
443+
444+
fn find_gid(entry: &EtcFile) -> anyhow::Result<u32> {
445+
if !get_uid_gid_regex().is_match(&entry.group) {
446+
nix::unistd::Group::from_name(&entry.group)
447+
.map(|maybe_group| {
448+
maybe_group.map_or_else(
449+
|| {
450+
log::warn!(
451+
"Specified group {} not found, defaulting to root",
452+
&entry.group
453+
);
454+
0
455+
},
456+
|group| group.gid.as_raw(),
457+
)
458+
})
459+
.map_err(|err| anyhow::anyhow!(err).context("Failed to determine group"))
460+
} else {
461+
Ok(entry.gid)
462+
}
463+
}
464+
465+
fn copy_file(
466+
source: &Path,
467+
target: &Path,
468+
entry: &EtcFile,
469+
old_state: &FileTree,
470+
) -> anyhow::Result<()> {
419471
let exists = target.try_exists()?;
420472
if !exists || old_state.is_managed(target) {
421473
log::debug!(
@@ -424,9 +476,9 @@ fn copy_file(source: &Path, target: &Path, mode: &str,
424476
target.display()
425477
);
426478
fs::copy(source, target)?;
427-
let mode_int = u32::from_str_radix(mode, 8)?;
479+
let mode_int = u32::from_str_radix(&entry.mode, 8)?;
428480
fs::set_permissions(target, Permissions::from_mode(mode_int))?;
429-
unixfs::chown(target, Some(uid), Some(gid))?;
481+
unixfs::chown(target, Some(find_uid(entry)?), Some(find_gid(entry)?))?;
430482
Ok(())
431483
} else {
432484
anyhow::bail!("File {} already exists, ignoring.", target.display());

test/nix/modules/default.nix

+10-3
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,17 @@ forEachUbuntuImage "example" {
175175
vm.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf")
176176
vm.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf")
177177
178-
uid = vm.succeed("stat -c %u /etc/test_perms").strip()
179-
gid = vm.succeed("stat -c %g /etc/test_perms").strip()
178+
uid = vm.succeed("stat -c %u /etc/with_ownership").strip()
179+
gid = vm.succeed("stat -c %g /etc/with_ownership").strip()
180180
assert uid == "5", f"uid was {uid}, expected 5"
181-
assert gid == "6", f"uid was {gid}, expected 6"
181+
assert gid == "6", f"gid was {gid}, expected 6"
182+
183+
print(vm.succeed("cat /etc/passwd"))
184+
185+
user = vm.succeed("stat -c %U /etc/with_ownership2").strip()
186+
group = vm.succeed("stat -c %G /etc/with_ownership2").strip()
187+
assert user == "nobody", f"user was {user}, expected nobody"
188+
assert group == "users", f"group was {group}, expected users"
182189
183190
vm.succeed("test -d /var/tmp/system-manager")
184191
vm.succeed("test -d /var/tmp/sample")

0 commit comments

Comments
 (0)