File size: 6,029 Bytes
854994d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | //! Coordinates package selection with the shell and PowerShell installers.
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use std::fs::OpenOptions;
use std::path::Path;
use std::time::Duration;
use tokio::time::sleep;
pub(crate) struct InstallLock {
_file: Option<tokio::fs::File>,
directory: Option<std::path::PathBuf>,
_directory_owner: Option<tempfile::TempDir>,
}
impl Drop for InstallLock {
fn drop(&mut self) {
if let Some(directory) = &self.directory {
let _ = std::fs::remove_file(directory);
}
}
}
pub(crate) async fn acquire_install_lock(root: &Path) -> Result<InstallLock> {
let lock = root.join("install.lock");
let deadline = tokio::time::Instant::now() + Duration::from_secs(30);
#[cfg(unix)]
let use_lockf = {
// Match the installer so both processes coordinate on the same lock.
let choice = tokio::process::Command::new("/bin/sh")
.args(["-c", if cfg!(target_os = "macos") {
"if command -v lockf >/dev/null 2>&1; then exit 0; elif command -v flock >/dev/null 2>&1; then exit 1; else exit 2; fi"
} else {
"if command -v flock >/dev/null 2>&1; then exit 1; else exit 2; fi"
}])
.status().await?;
match choice.code() {
Some(0) => true,
Some(1) => false,
Some(2) => {
let directory = root.join("install.lock.d");
// Publish complete metadata atomically. Older shell installers
// immediately remove an empty lock directory as stale.
let owner = tempfile::Builder::new()
.prefix("install-lock-")
.tempdir_in(root)?;
std::fs::write(owner.path().join("pid"), std::process::id().to_string())?;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs();
std::fs::write(owner.path().join("started_at"), now.to_string())?;
loop {
match std::os::unix::fs::symlink(owner.path(), &directory) {
Ok(()) => {
return Ok(InstallLock {
_file: None,
directory: Some(directory),
_directory_owner: Some(owner),
});
}
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(error) => {
return Err(error)
.context("failed to acquire daemon installer directory lock");
}
}
let pid = std::fs::read_to_string(directory.join("pid"))
.ok()
.and_then(|pid| pid.trim().parse::<i32>().ok())
.filter(|pid| *pid > 0);
let dead = pid.is_none_or(|pid| unsafe { libc::kill(pid, 0) } != 0
&& std::io::Error::last_os_error().raw_os_error() == Some(libc::ESRCH));
let age = std::fs::metadata(&directory)
.and_then(|metadata| metadata.modified())
.ok()
.and_then(|modified| modified.elapsed().ok())
.unwrap_or_default();
// Allow the creator time to publish its PID; match the installer's
// ten-minute stale-lock grace instead of stealing an active lock.
if dead && age >= Duration::from_secs(600) {
std::fs::remove_dir_all(&directory)?;
continue;
}
anyhow::ensure!(
tokio::time::Instant::now() < deadline,
"timed out waiting for daemon installer lock {}",
directory.display()
);
sleep(Duration::from_millis(50)).await;
}
}
_ => return Err(anyhow!("failed to detect daemon installer lock tools")),
}
};
let mut options = OpenOptions::new();
options.create(true).truncate(false).write(true);
#[cfg(windows)]
{
use std::os::windows::fs::OpenOptionsExt;
options.share_mode(0);
}
loop {
let acquired = options.open(&lock);
#[cfg(unix)]
let acquired = acquired.and_then(|file| {
{
use std::os::fd::AsRawFd;
let result = if use_lockf {
unsafe { libc::lockf(file.as_raw_fd(), libc::F_TLOCK, 0) }
} else {
unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) }
};
if result != 0 {
return Err(std::io::Error::last_os_error());
}
}
Ok(file)
});
match acquired {
Ok(file) => {
return Ok(InstallLock {
_file: Some(tokio::fs::File::from_std(file)),
directory: None,
_directory_owner: None,
});
}
Err(error)
if error.kind() == std::io::ErrorKind::WouldBlock
|| cfg!(windows)
&& (error.raw_os_error() == Some(32)
|| error.kind() == std::io::ErrorKind::PermissionDenied) => {}
Err(error) => return Err(error).context("failed to acquire daemon installer lock"),
}
if tokio::time::Instant::now() >= deadline {
return Err(anyhow!(
"timed out waiting for daemon installer lock {}",
lock.display()
));
}
sleep(Duration::from_millis(50)).await;
}
}
|