Incomplete uninstallations leave the system in a tricky state #34

Open
opened 2024-11-12 14:59:30 +00:00 by lheckemann · 1 comment
Member

To reproduce (on macOS):

  • cd /nix
  • ./nix-installer uninstall

The uninstallation will fail because /nix is still in use (as the working directory of the shell from which the uninstaller was invoked) and can't be unmounted/deleted.

Attempting to uninstall again will fail more spectacularly because some of the steps will have been reverted already.

To reproduce (on macOS): - cd `/nix` - `./nix-installer uninstall` The uninstallation will fail because `/nix` is still in use (as the working directory of the shell from which the uninstaller was invoked) and can't be unmounted/deleted. Attempting to uninstall again will fail more spectacularly because some of the steps will have been reverted already.
ktemkin self-assigned this 2025-09-21 16:42:21 +00:00
Member

I'm puzzled by why this happens, as there's a very clear attempt in the uninstall command to mitigate this:

Preview has been truncated
if let Ok(current_dir) = std::env::current_dir() {
let mut components = current_dir.components();
let should_be_root = components.next();
let maybe_nix = components.next();
if should_be_root == Some(std::path::Component::RootDir)
&& maybe_nix == Some(std::path::Component::Normal(std::ffi::OsStr::new("nix")))
{
tracing::debug!("Changing current directory to be outside of `/nix`");
std::env::set_current_dir("/").wrap_err("Uninstall process was run from `/nix` folder, but could not change directory away from `/nix`, please change the current directory and try again.")?;
}
}
// During install, `nix-installer` will store a copy of itself in `/nix/nix-installer`
// If the user opted to run that particular copy of `nix-installer` to do this uninstall,
// well, we have a problem, since the binary would delete itself.
// Instead, detect if we're in that location, if so, move the binary and `execv` it.
if let Ok(current_exe) = std::env::current_exe() {
if current_exe.as_path() == Path::new("/nix/nix-installer") {
tracing::debug!(
"Detected uninstall from `/nix/nix-installer`, moving executable and re-executing"
);
let temp = std::env::temp_dir();
let random_trailer: String = {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789";
const PASSWORD_LEN: usize = 16;
let mut rng = rand::thread_rng();
(0..PASSWORD_LEN)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect()
};
let temp_exe = temp.join(&format!("nix-installer-{random_trailer}"));
tokio::fs::copy(&current_exe, &temp_exe)
.await
.wrap_err("Copying nix-installer to tempdir")?;
let args = std::env::args();
let mut arg_vec_cstring = vec![];
for arg in args {
arg_vec_cstring.push(CString::new(arg).wrap_err("Making arg into C string")?);
}
let temp_exe_cstring = CString::new(temp_exe.to_string_lossy().into_owned())
.wrap_err("Making C string of executable path")?;
tracing::trace!("Execv'ing `{temp_exe_cstring:?} {arg_vec_cstring:?}`");
nix::unistd::execv(&temp_exe_cstring, &arg_vec_cstring)

Does it fail despite that? Is it some kind of issue with std::env::set_current_dir?

I'm puzzled by why this happens, as there's a very clear attempt in the `uninstall` command to mitigate this: https://git.lix.systems/lix-project/lix-installer/src/commit/81a5d136aaf6b81adc044171f1eb118220d99b9e/src/cli/subcommand/uninstall.rs#L57-L109 Does it fail despite that? Is it some kind of issue with `std::env::set_current_dir`?
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lix-project/lix-installer#34
No description provided.