#![warn(clippy::pedantic)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::struct_field_names)]
use clap::Parser;
use std::os::fd::AsRawFd;
use std::path::PathBuf;
#[derive(Parser)]
#[command(
name = "ixd",
version = env!("CARGO_PKG_VERSION"),
about = "Background daemon that watches directories for changes and rebuilds the index.",
after_help = "EXAMPLES:\n \
ixd /path/to/repo\n \
ixd --daemon /path/to/repo # detach and run in the background\n \
ixd /project-a /project-b /project-c\n\n\
DOCS:\n \
https://github.com/moeshawky/ix/blob/main/docs/DAEMON-RUNBOOK.md\n \
https://github.com/moeshawky/ix/blob/main/docs/.ixd.toml.md"
)]
struct Cli {
#[arg(default_value = ".", value_name = "PATH")]
paths: Vec<PathBuf>,
#[arg(long)]
daemon: bool,
}
#[cfg(unix)]
fn main() -> ix::error::Result<()> {
let cli = Cli::parse();
let roots = resolve_roots(&cli.paths)?;
if cli.daemon {
daemonize()?;
}
ix::daemon::run_many(&roots)
}
#[cfg(not(unix))]
fn main() {
eprintln!("ixd: the daemon is not supported on this platform");
std::process::exit(1);
}
#[cfg(unix)]
fn resolve_roots(paths: &[PathBuf]) -> ix::error::Result<Vec<PathBuf>> {
if paths.is_empty() {
return Ok(Vec::new());
}
let cwd = std::env::current_dir().map_err(|e| {
ix::error::Error::Config(format!("ixd: cannot determine current directory: {e}"))
})?;
paths
.iter()
.map(|p| {
if p.is_absolute() {
Ok(p.clone())
} else {
Ok(cwd.join(p))
}
})
.collect()
}
#[cfg(unix)]
fn daemonize() -> ix::error::Result<()> {
use nix::unistd::{ForkResult, fork, setsid};
use std::fs::OpenOptions;
let cfg = |ctx: &str, e: nix::errno::Errno| {
ix::error::Error::Config(format!("daemonize: {ctx}: {e}"))
};
match unsafe { fork() }.map_err(|e| cfg("fork", e))? {
ForkResult::Parent { .. } => std::process::exit(0),
ForkResult::Child => {}
}
setsid().map_err(|e| cfg("setsid", e))?;
match unsafe { fork() }.map_err(|e| cfg("fork", e))? {
ForkResult::Parent { .. } => std::process::exit(0),
ForkResult::Child => {}
}
let _ = std::env::set_current_dir("/");
let devnull = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/null")
.map_err(|e| ix::error::Error::Config(format!("daemonize: cannot open /dev/null: {e}")))?;
let raw = devnull.as_raw_fd();
for fd in [0_i32, 1, 2] {
let rc = unsafe { libc::dup2(raw, fd) };
if rc < 0 {
return Err(ix::error::Error::Config(format!(
"daemonize: cannot redirect fd {fd} to /dev/null"
)));
}
}
std::mem::forget(devnull);
Ok(())
}