1use std::fs;
2use std::io::{Read, Seek, SeekFrom, Write};
3use std::path::PathBuf;
4
5use anyhow::{Result, anyhow};
6use colored::Colorize;
7use fs2::FileExt;
8
9thread_local! {
10 static LOCK_COUNT: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
13}
14
15fn get_lock_path() -> Result<PathBuf> {
17 Ok(crate::utils::get_user_state_dir()?
18 .join("pkgs")
19 .join("lock"))
20}
21
22pub fn acquire_lock() -> Result<LockGuard> {
40 if std::env::var("ZOI_SKIP_LOCK").is_ok_and(|v| v == "1") {
41 return Ok(LockGuard::noop());
42 }
43
44 let lock_path = get_lock_path()?;
45
46 let count = LOCK_COUNT.with(|c| {
48 let val = c.get();
49 c.set(val + 1);
50 val
51 });
52
53 if count > 0 {
54 return Ok(LockGuard::recursive());
55 }
56
57 if let Some(parent) = lock_path.parent()
58 && let Err(e) = fs::create_dir_all(parent)
59 {
60 eprintln!(
61 "Warning: could not create lock directory {}: {}",
62 parent.display(),
63 e
64 );
65 }
66
67 let file_res = fs::OpenOptions::new()
68 .read(true)
69 .write(true)
70 .create(true)
71 .truncate(false)
72 .open(&lock_path);
73
74 let file = match file_res {
75 Ok(f) => f,
76 Err(e) => {
77 LOCK_COUNT.with(|c| c.set(c.get() - 1));
78 return Err(e.into());
79 }
80 };
81
82 if file.try_lock_exclusive().is_err() {
83 LOCK_COUNT.with(|c| c.set(c.get() - 1));
84 let mut content = String::new();
85 if let Ok(mut f) = fs::File::open(&lock_path) {
86 let _ = f.read_to_string(&mut content);
87 }
88
89 let pid_info = if content.trim().is_empty() {
90 String::new()
91 } else {
92 format!(" (held by PID {})", content.trim())
93 };
94
95 eprintln!(
96 "{}: Another Zoi process{} may be running.",
97 "Error".red().bold(),
98 pid_info
99 );
100 eprintln!(
101 "If you are absolutely sure no other Zoi process is running, you \
102 can manually remove the lock file:"
103 );
104 eprintln!(" {}", lock_path.display());
105 return Err(anyhow!("Could not acquire lock."));
106 }
107
108 let mut file = file;
109 let _ = file.set_len(0);
110 let _ = file.seek(SeekFrom::Start(0));
111 let _ = write!(file, "{}", std::process::id());
112 let _ = file.flush();
113
114 Ok(LockGuard {
115 file: Some(file),
116 is_recursive: false
117 })
118}
119
120pub fn release_lock() -> Result<()> {
129 Err(anyhow!("drop the LockGuard to release the system lock"))
130}
131
132pub struct LockGuard {
134 file: Option<fs::File>,
136 is_recursive: bool
138}
139
140impl LockGuard {
141 pub fn noop() -> Self {
143 Self {
144 file: None,
145 is_recursive: false
146 }
147 }
148
149 pub fn recursive() -> Self {
151 Self {
152 file: None,
153 is_recursive: true
154 }
155 }
156}
157
158impl Drop for LockGuard {
159 fn drop(&mut self) {
160 if self.is_recursive {
161 LOCK_COUNT.with(|c| c.set(c.get() - 1));
162 return;
163 }
164
165 if self.file.take().is_some() {
166 LOCK_COUNT.with(|c| c.set(0));
167 }
168 }
169}