use crate::service_journal::{DONE_RETAINED, prune_done};
use crate::wrapper_names_old_and_new::COMMAND_WORD;
use crate::service_land::{LandContext, recover_instance};
use crate::service_protocol::PROTOCOL_VERSION;
use crate::service_timing::{log, started_line};
use crate::service_server::{Bind, IDLE_EXIT, ServeSettings, bind, serve};
use crate::transaction_git::GitContext;
use std::ffi::OsString;
use std::path::PathBuf;
use std::time::Duration;
pub const SERVICE_WORDS: [&str; 3] = [COMMAND_WORD.current, "service", "run"];
pub const EARLIER_SERVICE_WORDS: [&str; 3] = [COMMAND_WORD.earlier, "service", "run"];
pub const EXIT_REFUSED: i32 = 2;
pub const EXIT_NOT_LISTENING: i32 = 3;
pub fn is_service_start(arguments: &[OsString]) -> bool {
return arguments.len() >= SERVICE_WORDS.len()
&& (starts_with_service_words(arguments, &SERVICE_WORDS) || starts_with_service_words(arguments, &EARLIER_SERVICE_WORDS));
}
pub fn service_options(arguments: &[OsString]) -> Result<Vec<String>, OsString> {
let mut options: Vec<String> = Vec::new();
for argument in arguments.iter().skip(SERVICE_WORDS.len()) {
match argument.to_str() {
Some(text) => options.push(text.to_string()),
None => return Err(argument.clone()),
}
}
return Ok(options);
}
fn starts_with_service_words(arguments: &[OsString], words: &[&str; 3]) -> bool {
for (argument, word) in arguments.iter().zip(words) {
if argument != *word {
return false;
}
}
return true;
}
fn number_option(arguments: &[String], name: &str) -> Option<u64> {
let text: String = option(arguments, name)?;
return text.parse::<u64>().ok();
}
fn option(arguments: &[String], name: &str) -> Option<String> {
for (at, argument) in arguments.iter().enumerate() {
if argument == name {
return arguments.get(at + 1).cloned();
}
}
return None;
}
fn git_is_wrapper(git: &std::path::Path) -> bool {
let environment: Vec<(OsString, OsString)> = std::env::vars_os().collect();
return match crate::real_git::process_resolution_inputs(environment.as_slice()) {
Ok(inputs) => matches!(crate::real_git_candidate::classify_candidate(git, inputs.own_executable.as_path()), crate::real_git_candidate::CandidateKind::Wrapper),
Err(_) => crate::real_git_candidate::is_wrapper_launcher(git, None),
};
}
pub fn run(arguments: &[String]) -> i32 {
crate::hooks_off::activate();
crate::service_signal::install();
crate::push_deadline_advice::mark_service_process();
let address: Bind = match (option(arguments, "--abstract"), option(arguments, "--path")) {
(Some(name), _) => Bind::Abstract(name),
(None, Some(path)) => Bind::Path(PathBuf::from(path)),
(None, None) => Bind::Inherited,
};
if matches!(address, Bind::Inherited) && !crate::service_server::socket_passed_in() {
crate::service_error!(
"monochromatic-git-policy-cli service run is started by the service manager, which passes it the listening socket of one worktree's commit service; no socket was passed in and no address was named, so nothing was started. Nothing needs starting by hand: the first commit in a worktree sets the service up. For a development run name an address with --path <socket> or --abstract <name>"
);
return EXIT_NOT_LISTENING;
}
let Some(journal) = option(arguments, "--journal").map(PathBuf::from) else {
crate::service_error!("--journal <directory> is required");
return EXIT_REFUSED;
};
let git: PathBuf = match option(arguments, "--git") {
Some(named) => PathBuf::from(named),
None => PathBuf::from("/usr/bin/git"),
};
if git_is_wrapper(git.as_path()) {
crate::service_error!(
"--git {} is the monochromatic-git-policy-cli executable itself, a copy of it or a toolchain launcher of it, so every Git the service started would start monochromatic-git-policy-cli again; pass the real Git executable (for example /usr/bin/git), or remove the unit that names this path and let the next monochromatic-git-policy-cli command write it again",
git.display()
);
return EXIT_REFUSED;
}
if let Err(error) = std::fs::create_dir_all(&journal) {
crate::service_error!("cannot create the journal directory {}: {error}", journal.display());
return EXIT_REFUSED;
}
let listener = match bind(&address) {
Ok(listener) => listener,
Err(error) => {
crate::service_error!("cannot listen: {error}");
return EXIT_NOT_LISTENING;
}
};
let context: LandContext = LandContext {
git: GitContext { real_git: git, overlay: crate::child_environment::service_child_overlay(std::env::vars_os().collect::<Vec<_>>().as_slice()), global_prefix: Vec::new(), common_dir: None },
journal,
on_objects_written: None,
lock_ceiling: number_option(arguments, "--lock-ceiling-millis").map_or(crate::wait_ceilings::LOCK_CEILING, Duration::from_millis),
};
let idle: Duration = number_option(arguments, "--idle-seconds").map_or(IDLE_EXIT, Duration::from_secs);
let mut settings: ServeSettings = ServeSettings::new(idle);
if let Some(release) = option(arguments, "--release") {
settings.release = release;
}
settings.instance = option(arguments, "--instance");
crate::service_timing::set_context(settings.release.as_str(), settings.instance.as_deref());
log(started_line(settings.release.as_str(), PROTOCOL_VERSION, settings.instance.as_deref()).as_str());
if let (Some(instance), Some(shared)) = (settings.instance.as_deref(), context.journal.parent()) {
let earlier: LandContext = LandContext { git: context.git.clone(), journal: shared.to_path_buf(), on_objects_written: None, lock_ceiling: context.lock_ceiling };
let _reports = recover_instance(&earlier, instance);
let _pruned = prune_done(shared, DONE_RETAINED);
}
crate::service_log!("ready");
serve(listener, &context, &settings);
return 0;
}
#[cfg(test)]
#[path = "service_main_tests.rs"]
mod tests;