use crate::repo_read_layout::read_layout_in;
use crate::service_instance::{SOCKET_DIRECTORY, instance_of, socket_for};
use crate::service_provision::{ProvisionHost, RETIREMENT_WAIT};
use crate::service_server::Bind;
use crate::systemd_dbus::session_bus_path;
use crate::transaction_git::{GitContext, GitRequest, run_git};
use std::path::{Path, PathBuf};
static OWN_EXECUTABLE: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
pub fn remember_own_executable(path: &Path) {
let _first = OWN_EXECUTABLE.set(path.to_path_buf());
}
pub fn remembered_own_executable() -> Option<&'static Path> {
return OWN_EXECUTABLE.get().map(PathBuf::as_path);
}
#[derive(Clone, Copy, Debug)]
pub struct RouteInputs<'a> {
pub runtime_dir: Option<&'a Path>,
pub bus_address: Option<&'a str>,
pub config_home: Option<&'a Path>,
pub home: Option<&'a Path>,
pub own_executable: Option<&'a Path>,
pub real_git: &'a Path,
pub git_dir: &'a Path,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Route {
pub address: Bind,
pub provision: Option<ProvisionHost>,
}
pub fn user_unit_dir(config_home: Option<&Path>, home: Option<&Path>) -> Option<PathBuf> {
if let Some(directory) = config_home {
return Some(directory.join("systemd").join("user"));
}
let directory: &Path = home?;
return Some(directory.join(".config").join("systemd").join("user"));
}
pub fn choose_route(inputs: &RouteInputs<'_>) -> Option<Route> {
let runtime_dir = inputs.runtime_dir?;
let socket: PathBuf = socket_for(runtime_dir, inputs.git_dir);
let socket_exists: bool = socket.exists();
let bus: PathBuf = match session_bus_path(inputs.bus_address, Some(runtime_dir)) {
Some(path) if path.exists() => path,
_ => return if socket_exists { Some(Route { address: Bind::Path(socket), provision: None }) } else { None },
};
let service_binary: PathBuf = match inputs.own_executable {
Some(executable) if executable.is_file() => executable.to_path_buf(),
_ => return if socket_exists { Some(Route { address: Bind::Path(socket), provision: None }) } else { None },
};
let host: ProvisionHost = ProvisionHost {
runtime_dir: runtime_dir.to_path_buf(),
user_unit_dir: user_unit_dir(inputs.config_home, inputs.home),
bus,
service_binary,
git: inputs.real_git.to_path_buf(),
instance: instance_of(inputs.git_dir),
retirement_wait: RETIREMENT_WAIT,
};
return Some(Route { address: Bind::Path(socket), provision: Some(host) });
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StatusReport {
pub code: i32,
pub stdout: String,
pub stderr: String,
}
pub fn worktree_git_dir(context: &GitContext, cwd: &Path) -> Option<PathBuf> {
if let Some(layout) = read_layout_in(cwd, context.overlay.as_slice()) {
return Some(layout.git_dir);
}
let output = run_git(context, &GitRequest::new(cwd, &["rev-parse", "--path-format=absolute", "--absolute-git-dir"])).ok()?;
if !output.succeeded() {
return None;
}
let text: String = String::from_utf8_lossy(output.stdout.as_slice()).trim().to_string();
return if text.is_empty() { None } else { Some(PathBuf::from(text)) };
}
pub(crate) fn non_empty_path_variable(name: &str) -> Option<PathBuf> {
return match std::env::var_os(name) {
Some(value) if !value.is_empty() => Some(PathBuf::from(value)),
_ => None,
};
}
pub fn route_from_environment(real_git: &Path, git_dir: &Path) -> Option<Route> {
let runtime: Option<PathBuf> = non_empty_path_variable("XDG_RUNTIME_DIR");
let config_home: Option<PathBuf> = non_empty_path_variable("XDG_CONFIG_HOME");
let home: Option<PathBuf> = non_empty_path_variable("HOME");
let bus_address: Option<String> = std::env::var("DBUS_SESSION_BUS_ADDRESS").ok();
return choose_route(&RouteInputs {
runtime_dir: runtime.as_deref(),
bus_address: bus_address.as_deref(),
config_home: config_home.as_deref(),
home: home.as_deref(),
own_executable: remembered_own_executable(),
real_git,
git_dir,
});
}
fn service_answers(socket: &Path) -> bool {
return std::os::unix::net::UnixStream::connect(socket).is_ok();
}
fn any_socket_answers(directory: &Path) -> bool {
let Ok(entries) = std::fs::read_dir(directory) else {
return false;
};
for entry in entries.flatten() {
let path: PathBuf = entry.path();
let is_socket_file: bool = match path.extension() {
Some(extension) => extension == "sock",
None => false,
};
if is_socket_file && service_answers(path.as_path()) {
return true;
}
}
return false;
}
pub fn service_status(runtime_dir: Option<&Path>, git_dir: Option<&Path>) -> StatusReport {
let (answered, what): (bool, &str) = match (runtime_dir, git_dir) {
(Some(runtime), Some(git)) => (service_answers(socket_for(runtime, git).as_path()), "The commit service of this worktree"),
(Some(runtime), None) => {
let any: bool = any_socket_answers(runtime.join(SOCKET_DIRECTORY).as_path());
(any, "A commit service")
}
(None, _) => (false, "The commit service"),
};
return if answered {
StatusReport { code: 0, stdout: format!("{what} is running and answers.\n"), stderr: String::new() }
} else {
StatusReport {
code: 1,
stdout: String::new(),
stderr: format!(
"{what} does not answer here. It sets itself up the first time a commit needs it, so there is nothing to \
install; if a commit fails, its message says what to do.\n"
),
}
};
}
#[cfg(test)]
#[path = "service_route_tests.rs"]
mod tests;