Support create_directory paths being an existing mount (#547)

* Support create_directory paths being an existing mount

* Tidy up after install even on mounts

* Repair steam deck test

* Fixup some nits
This commit is contained in:
Ana Hobden 2023-07-05 09:11:57 -07:00 committed by GitHub
parent 9c915b3f6a
commit 675d93c644
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 185 additions and 25 deletions

View file

@ -292,7 +292,6 @@ jobs:
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
planner: steam-deck
extra-args: --persistence /home/runner/.ci-test-nix-home
- name: Initial uninstall (without a `nix run` first)
run: sudo -E /nix/nix-installer uninstall
env:
@ -327,7 +326,6 @@ jobs:
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
planner: steam-deck
extra-args: --persistence /home/runner/.ci-test-nix-home
- name: echo $PATH
run: echo $PATH
- name: Test `nix` with `$GITHUB_PATH`

View file

@ -96,8 +96,12 @@ let
exit 1
fi
if [ -d /nix ]; then
echo "/nix exists after uninstall"
if [ -d /nix/store ]; then
echo "/nix/store exists after uninstall"
exit 1
fi
if [ -d /nix/var ]; then
echo "/nix/var exists after uninstall"
exit 1
fi
@ -173,6 +177,17 @@ let
uninstall = installCases.install-default.uninstall;
uninstallCheck = installCases.install-default.uninstallCheck;
};
install-bind-mounted-nix = {
preinstall = ''
sudo mkdir -p /nix
sudo mkdir -p /bind-mount-for-nix
sudo mount --bind /bind-mount-for-nix /nix
'';
install = installCases.install-default.install;
check = installCases.install-default.check;
uninstall = installCases.install-default.uninstall;
uninstallCheck = installCases.install-default.uninstallCheck;
};
};
cureSelfCases = {
cure-self-linux-working = {

View file

@ -3,11 +3,13 @@ use std::path::{Path, PathBuf};
use nix::unistd::{chown, Group, User};
use tokio::fs::{create_dir, remove_dir_all};
use tokio::fs::{create_dir, remove_dir_all, remove_file};
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::{Action, ActionDescription, ActionErrorKind, ActionState};
use crate::action::{ActionError, StatefulAction};
use crate::execute_command;
/** Create a directory at the given location, optionally with an owning user, group, and mode.
@ -20,6 +22,7 @@ pub struct CreateDirectory {
user: Option<String>,
group: Option<String>,
mode: Option<u32>,
is_mountpoint: bool,
force_prune_on_revert: bool,
}
@ -36,6 +39,7 @@ impl CreateDirectory {
let user = user.into();
let group = group.into();
let mode = mode.into();
let mut is_mountpoint = false;
let action_state = if path.exists() {
let metadata = tokio::fs::metadata(&path)
@ -84,7 +88,13 @@ impl CreateDirectory {
}
}
tracing::debug!("Creating directory `{}` already complete", path.display(),);
// Is it a mountpoint?
is_mountpoint = path_is_mountpoint(&path).await.map_err(Self::error)?;
tracing::debug!(
is_mountpoint,
"Creating directory `{}` already complete",
path.display(),
);
ActionState::Completed
} else {
ActionState::Uncompleted
@ -96,6 +106,7 @@ impl CreateDirectory {
user,
group,
mode,
is_mountpoint,
force_prune_on_revert,
},
state: action_state,
@ -137,9 +148,15 @@ impl Action for CreateDirectory {
user,
group,
mode,
is_mountpoint, // If `is_mountpoint = true` the `ActionState` should be completed.
force_prune_on_revert: _,
} = self;
if *is_mountpoint {
// A `/nix` mount exists, we don't need to do anything.
return Ok(());
}
let gid = if let Some(group) = group {
Some(
Group::from_name(group.as_str())
@ -189,9 +206,16 @@ impl Action for CreateDirectory {
user: _,
group: _,
mode: _,
is_mountpoint,
force_prune_on_revert,
} = &self;
vec![ActionDescription::new(
match (is_mountpoint, force_prune_on_revert) {
(true, true) => vec![ActionDescription::new(
format!("Clean contents of mountpoint `{}`", path.display(),),
vec![],
)],
(true, false) => vec![],
(false, _) => vec![ActionDescription::new(
format!(
"Remove the directory `{}`{}",
path.display(),
@ -202,7 +226,8 @@ impl Action for CreateDirectory {
}
),
vec![],
)]
)],
}
}
#[tracing::instrument(level = "debug", skip_all)]
@ -212,22 +237,50 @@ impl Action for CreateDirectory {
user: _,
group: _,
mode: _,
is_mountpoint,
force_prune_on_revert,
} = self;
let is_empty = path
let contents = path
.read_dir()
.map_err(|e| ActionErrorKind::Read(path.clone(), e))
.map_err(Self::error)?
.next()
.is_none();
.collect::<Vec<_>>();
let is_empty = contents.is_empty();
match (is_empty, force_prune_on_revert) {
(true, _) | (false, true) => remove_dir_all(path.clone())
match (is_mountpoint, is_empty, force_prune_on_revert) {
(true, _, true) => {
tracing::debug!("Cleaning mountpoint `{}`", path.display());
for child_path in contents {
let child_path = child_path
.map_err(|e| ActionErrorKind::ReadDir(path.clone(), e))
.map_err(Self::error)?;
let child_path_path = child_path.path();
let child_path_type = child_path
.file_type()
.map_err(|e| ActionErrorKind::GettingMetadata(child_path_path.clone(), e))
.map_err(Self::error)?;
if child_path_type.is_dir() {
remove_dir_all(child_path_path.clone())
.await
.map_err(|e| ActionErrorKind::Remove(path.clone(), e))
.map_err(Self::error)?
} else {
remove_file(child_path_path)
.await
.map_err(|e| ActionErrorKind::Remove(path.clone(), e))
.map_err(Self::error)?
}
}
},
(true, _, false) => {
tracing::debug!("Not cleaning mountpoint `{}`", path.display());
},
(false, true, _) | (false, false, true) => remove_dir_all(path.clone())
.await
.map_err(|e| ActionErrorKind::Remove(path.clone(), e))
.map_err(Self::error)?,
(false, false) => {
(false, false, false) => {
tracing::debug!("Not removing `{}`, the folder is not empty", path.display());
},
};
@ -236,6 +289,53 @@ impl Action for CreateDirectory {
}
}
// There are cleaner ways of doing this (eg `systemctl status $PATH`) however we need a widely supported way.
async fn path_is_mountpoint(path: &Path) -> Result<bool, ActionErrorKind> {
let path_str = match path.to_str() {
Some(path_str) => path_str,
None => return Err(ActionErrorKind::PathNoneString(path.to_path_buf())),
};
let mut mount_command = Command::new("mount");
mount_command.process_group(0);
#[cfg(target_os = "macos")]
mount_command.arg("-d"); // `-d` means `--dry-run`
#[cfg(target_os = "linux")]
mount_command.arg("-f"); // `-f` means `--fake` not `--force`
let output = execute_command(&mut mount_command).await?;
let output_string = String::from_utf8(output.stdout).map_err(ActionErrorKind::FromUtf8)?;
for line in output_string.lines() {
let mut line_splitter = line.split(" on ");
match line_splitter.next() {
Some(_device) => (),
None => continue,
}
let destination_and_options = match line_splitter.next() {
Some(destination_and_options) => destination_and_options,
None => continue,
};
// Each line on Linux looks like `portal on /run/user/1000/doc type fuse.portal (rw,nosuid,nodev,relatime,user_id=1000,group_id=100)`
#[cfg(target_os = "linux")]
let split_token = "type";
// Each line on MacOS looks like `/dev/disk3s6 on /System/Volumes/VM (apfs, local, noexec, journaled, noatime, nobrowse)`
#[cfg(target_os = "macos")]
let split_token = "(";
if let Some(mount_path) = destination_and_options.rsplit(split_token).last() {
let trimmed = mount_path.trim();
if trimmed == path_str {
tracing::trace!("Found mountpoint for `{mount_path}`");
return Ok(true);
}
}
}
Ok(false)
}
#[cfg(test)]
mod test {
use super::*;

View file

@ -36,7 +36,7 @@ impl CreateNixTree {
for path in PATHS {
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right
create_directories.push(
CreateDirectory::plan(path, String::from("root"), None, 0o0755, false)
CreateDirectory::plan(path, String::from("root"), None, 0o0755, true)
.await
.map_err(Self::error)?,
)

View file

@ -525,6 +525,8 @@ pub enum ActionErrorKind {
#[from]
std::string::FromUtf8Error,
),
#[error("Path `{}` could not be converted to valid UTF-8 string", .0.display())]
PathNoneString(std::path::PathBuf),
/// A MacOS (Darwin) plist related error
#[error(transparent)]
Plist(#[from] plist::Error),

View file

@ -8,6 +8,7 @@
"user": null,
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": true
},
"state": "Uncompleted"
@ -41,6 +42,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -51,6 +53,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -61,6 +64,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -71,6 +75,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -81,6 +86,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -91,6 +97,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -101,6 +108,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -111,6 +119,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -121,6 +130,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -131,6 +141,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -141,6 +152,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -151,6 +163,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -161,6 +174,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -221,6 +235,7 @@
"user": null,
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Completed"
@ -294,6 +309,7 @@
"user": null,
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"

View file

@ -8,6 +8,7 @@
"user": null,
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": true
},
"state": "Uncompleted"
@ -85,6 +86,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -95,6 +97,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -105,6 +108,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -115,6 +119,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -125,6 +130,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -135,6 +141,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -145,6 +152,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -155,6 +163,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -165,6 +174,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -175,6 +185,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -185,6 +196,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -195,6 +207,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -205,6 +218,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -315,6 +329,7 @@
"user": null,
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"

View file

@ -110,6 +110,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -120,6 +121,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -130,6 +132,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -140,6 +143,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -150,6 +154,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -160,6 +165,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -170,6 +176,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -180,6 +187,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -190,6 +198,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -200,6 +209,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -210,6 +220,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -220,6 +231,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -230,6 +242,7 @@
"user": "root",
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"
@ -350,6 +363,7 @@
"user": null,
"group": null,
"mode": 493,
"is_mountpoint": true,
"force_prune_on_revert": false
},
"state": "Uncompleted"