use anyhow::{Context, Result, ensure};
use std::{
ffi::OsString,
fs,
os::windows::{ffi::OsStringExt, process::CommandExt},
path::{Path, PathBuf},
process::{Command, Stdio},
};
const CLEANUP_SCRIPT: &str = r#"
$ErrorActionPreference = 'Stop'
$path = $env:MAGI_UPDATE_RELOCATED_IMAGE
$deadline = [DateTime]::UtcNow.AddHours(2)
do {
try {
[IO.File]::Delete($path)
[IO.Directory]::Delete([IO.Path]::GetDirectoryName($path))
exit 0
} catch {
[Threading.Thread]::Sleep(1000)
}
} while ([DateTime]::UtcNow -lt $deadline)
exit 1
"#;
pub(super) fn system_directory() -> Result<PathBuf> {
let mut buffer = vec![0u16; 32768];
let length = unsafe {
windows_sys::Win32::System::SystemInformation::GetSystemDirectoryW(
buffer.as_mut_ptr(),
buffer.len() as u32,
)
} as usize;
ensure!(
length > 0 && length < buffer.len(),
"cannot resolve Windows system directory"
);
let directory = PathBuf::from(OsString::from_wide(&buffer[..length]));
ensure!(
directory.is_absolute(),
"Windows system directory is not absolute"
);
Ok(directory)
}
fn cleanup_command(system: &Path, relocated: &Path) -> Command {
let mut command = Command::new(system.join("WindowsPowerShell/v1.0/powershell.exe"));
command
.args([
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-Command",
CLEANUP_SCRIPT,
])
.env("MAGI_UPDATE_RELOCATED_IMAGE", relocated)
.current_dir(system)
.creation_flags(0x08000000) .stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
command
}
pub(super) fn release_running_executable(executable: &Path, copy: &Path) -> Result<()> {
let system = system_directory()?;
ensure!(
system
.join("WindowsPowerShell/v1.0/powershell.exe")
.is_file(),
"Windows PowerShell is unavailable; update this installation manually"
);
let directory = tempfile::Builder::new()
.prefix(".magi-code-relocated-")
.tempdir_in(executable.parent().context("executable has no parent")?)?;
let replacement = directory.path().join("replacement.exe");
fs::copy(copy, &replacement).context("cannot stage Windows replacement")?;
let directory = directory.keep();
let relocated = directory.join("previous.exe");
fs::rename(executable, &relocated).context("cannot relocate running Windows image")?;
if let Err(error) = fs::rename(&replacement, executable) {
fs::rename(&relocated, executable).with_context(|| {
format!(
"cannot restore Windows image; recovery files: {}",
directory.display()
)
})?;
return Err(error).context("cannot restore the Windows install path");
}
cleanup_command(&system, &relocated)
.spawn()
.with_context(|| {
format!(
"cannot start Windows cleanup; recovery files: {}",
directory.display()
)
})?;
Ok(())
}