use std::io::{Seek, Write};
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
pub(crate) fn enumerate_files(
root: &Path,
parser: &cqs::Parser,
no_ignore: bool,
) -> Result<Vec<PathBuf>> {
let exts = parser.supported_extensions();
cqs::enumerate_files(root, &exts, no_ignore)
}
#[cfg(unix)]
fn process_exists(pid: u32) -> bool {
i32::try_from(pid).is_ok_and(|p| unsafe { libc::kill(p, 0) == 0 })
}
#[cfg(windows)]
fn process_exists(pid: u32) -> bool {
use std::process::Command;
Command::new("tasklist")
.args(["/FI", &format!("PID eq {}", pid), "/NH"])
.output()
.map(|o| {
let output = String::from_utf8_lossy(&o.stdout);
!output.contains("INFO:")
})
.unwrap_or(false)
}
fn open_lock_file(lock_path: &Path) -> Result<std::fs::File> {
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.read(true)
.write(true)
.mode(0o600)
.open(lock_path)
.context("Failed to create lock file")
}
#[cfg(not(unix))]
{
std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.read(true)
.write(true)
.open(lock_path)
.context("Failed to create lock file")
}
}
fn write_pid(file: &mut std::fs::File) -> Result<()> {
file.set_len(0)?;
file.seek(std::io::SeekFrom::Start(0))?;
writeln!(file, "{}", std::process::id())?;
file.sync_all()?;
Ok(())
}
pub(crate) fn try_acquire_index_lock(cqs_dir: &Path) -> Result<Option<std::fs::File>> {
let lock_path = cqs_dir.join("index.lock");
let lock_file = open_lock_file(&lock_path)?;
match lock_file.try_lock() {
Ok(()) => {
let mut file = lock_file;
write_pid(&mut file)?;
Ok(Some(file))
}
Err(std::fs::TryLockError::WouldBlock) => Ok(None),
Err(e) => {
tracing::warn!(error = %e, path = %lock_path.display(), "Lock I/O error (not contention)");
Err(anyhow::anyhow!("Lock error: {}", e))
}
}
}
pub(crate) fn acquire_index_lock(cqs_dir: &Path) -> Result<std::fs::File> {
let lock_path = cqs_dir.join("index.lock");
let mut retried = false;
loop {
let lock_file = open_lock_file(&lock_path)?;
match lock_file.try_lock() {
Ok(()) => {
let mut file = lock_file;
write_pid(&mut file)?;
return Ok(file);
}
Err(_) => {
if !retried {
if let Ok(content) = std::fs::read_to_string(&lock_path) {
if let Ok(pid) = content.trim().parse::<u32>() {
if !process_exists(pid) {
tracing::warn!(
"Removing stale lock (PID {} no longer exists)",
pid
);
drop(lock_file);
std::fs::remove_file(&lock_path)?;
retried = true;
continue;
}
}
}
}
bail!(
"Another cqs process is indexing (see .cqs/index.lock). \
Hint: Wait for it to finish, or delete .cqs/index.lock if the process crashed."
)
}
}
}
}