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;
    }
}