Skip to content

Commit

Permalink
chgrp existing store if GID is different (#1330)
Browse files Browse the repository at this point in the history
* After unpacking Nix, regrp the store if its group isn't the new build-user-group.

* Update src/action/common/provision_nix.rs

* Use filter_map instead of filter, update the note about /nix/store dirents due to BSD behavior

* Reformat.

* discrepAncy
  • Loading branch information
grahamc authored Dec 2, 2024
1 parent b3afc54 commit ac5e8c0
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 14 deletions.
83 changes: 69 additions & 14 deletions src/action/common/provision_nix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Place Nix and it's requirements onto the target
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(tag = "action_name", rename = "provision_nix")]
pub struct ProvisionNix {
nix_store_gid: u32,

pub(crate) fetch_nix: StatefulAction<FetchAndUnpackNix>,
pub(crate) create_nix_tree: StatefulAction<CreateNixTree>,
pub(crate) move_unpacked_nix: StatefulAction<MoveUnpackedNix>,
Expand All @@ -27,12 +29,6 @@ pub struct ProvisionNix {
impl ProvisionNix {
#[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
if std::path::Path::new(NIX_STORE_LOCATION).exists() {
check_existing_nix_store_gid_matches(settings.nix_build_group_id)
.await
.map_err(Self::error)?;
}

let fetch_nix = FetchAndUnpackNix::plan(
settings.nix_package_url.clone(),
PathBuf::from(SCRATCH_DIR),
Expand All @@ -46,6 +42,7 @@ impl ProvisionNix {
.await
.map_err(Self::error)?;
Ok(Self {
nix_store_gid: settings.nix_build_group_id,
fetch_nix,
create_nix_tree,
move_unpacked_nix,
Expand Down Expand Up @@ -73,6 +70,7 @@ impl Action for ProvisionNix {
fetch_nix,
create_nix_tree,
move_unpacked_nix,
nix_store_gid,
} = &self;

let mut buf = Vec::default();
Expand All @@ -81,6 +79,13 @@ impl Action for ProvisionNix {
buf.append(&mut create_nix_tree.describe_execute());
buf.append(&mut move_unpacked_nix.describe_execute());

buf.push(ActionDescription::new(
"Synchronize /nix/store ownership".to_string(),
vec![format!(
"Will update existing files in the Nix Store to use the Nix build group ID {nix_store_gid}"
)],
));

buf
}

Expand All @@ -107,6 +112,10 @@ impl Action for ProvisionNix {
.await
.map_err(Self::error)?;

ensure_nix_store_group(self.nix_store_gid)
.await
.map_err(Self::error)?;

Ok(())
}

Expand All @@ -115,6 +124,7 @@ impl Action for ProvisionNix {
fetch_nix,
create_nix_tree,
move_unpacked_nix,
nix_store_gid: _,
} = &self;

let mut buf = Vec::default();
Expand Down Expand Up @@ -157,19 +167,64 @@ impl Action for ProvisionNix {
/// If there is an existing /nix/store directory, ensure that the group ID we're going to use for
/// the nix build group matches the group that owns /nix/store to prevent weird mismatched-ownership
/// issues.
async fn check_existing_nix_store_gid_matches(
desired_nix_build_group_id: u32,
) -> Result<(), ActionErrorKind> {
async fn ensure_nix_store_group(desired_nix_build_group_id: u32) -> Result<(), ActionErrorKind> {
let previous_store_metadata = tokio::fs::metadata(NIX_STORE_LOCATION)
.await
.map_err(|e| ActionErrorKind::GettingMetadata(NIX_STORE_LOCATION.into(), e))?;
let previous_store_group_id = previous_store_metadata.gid();
if previous_store_group_id != desired_nix_build_group_id {
return Err(ActionErrorKind::PathGroupMismatch(
NIX_STORE_LOCATION.into(),
previous_store_group_id,
desired_nix_build_group_id,
));
let entryiter = walkdir::WalkDir::new(NIX_STORE_LOCATION)
.follow_links(false)
.same_file_system(true)
// chown all of the contents of the dir before NIX_STORE_LOCATION,
// this means our test of "does /nix/store have the right gid?"
// is useful until the entire store is examined
.contents_first(true)
.into_iter()
.filter_map(|entry| {
match entry {
Ok(entry) => Some(entry),
Err(e) => {
tracing::warn!(%e, "Enumerating the Nix store");
None
}
}
})
.filter_map(|entry| match entry.metadata() {
Ok(metadata) => Some((entry, metadata)),
Err(e) => {
tracing::warn!(
path = %entry.path().to_string_lossy(),
%e,
"Reading ownership and mode data"
);
None
}
})
.filter_map(|(entry, metadata)| {
// If the dirent's group ID is the *previous* GID, reassign.
// NOTE(@grahamc, 2024-11-15): Nix on macOS has store paths with a group of nixbld, and sometimes a group of `wheel` (0).
// On NixOS, all the store paths have their GID set to 0.
// The discrepancy is due to BSD's behavior around the /nix/store sticky bit.
// On BSD, it causes newly created files to inherit the group of the parent directory.
if metadata.gid() == previous_store_group_id {
return Some((entry, metadata));
}

None
});
for (entry, _metadata) in entryiter {
if let Err(e) =
std::os::unix::fs::lchown(entry.path(), Some(0), Some(desired_nix_build_group_id))
{
tracing::warn!(
path = %entry.path().to_string_lossy(),
%e,
"Failed to set the owner:group to 0:{}",
desired_nix_build_group_id
);
}
}
}

Ok(())
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/linux/linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
{
"action": {
"action_name": "provision_nix",
"nix_store_gid": 350,
"fetch_nix": {
"action": {
"action_name": "fetch_and_unpack_nix",
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/linux/steam-deck.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
{
"action": {
"action_name": "provision_nix",
"nix_store_gid": 350,
"fetch_nix": {
"action": {
"action_name": "fetch_and_unpack_nix",
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/macos/macos.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
{
"action": {
"action_name": "provision_nix",
"nix_store_gid": 350,
"fetch_nix": {
"action": {
"action_name": "fetch_and_unpack_nix",
Expand Down

0 comments on commit ac5e8c0

Please sign in to comment.