use std::io::Write as _;
use std::path::PathBuf;
fn record_start(path: &std::path::Path) {
let line = format!(
"{}\t{}\n",
chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
std::process::id()
);
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
{
let _ = file.write_all(line.as_bytes());
let _ = file.flush();
}
}
fn path_arguments() -> impl Iterator<Item = PathBuf> {
std::env::args_os()
.skip(1)
.filter(|argument| !argument.to_string_lossy().starts_with("--"))
.map(PathBuf::from)
}
fn heartbeat_path() -> Option<PathBuf> {
path_arguments().next()
}
fn runner_root() -> Option<PathBuf> {
path_arguments().nth(1)
}
fn workspace_outcome_path(heartbeat: &std::path::Path) -> PathBuf {
let mut name = heartbeat.as_os_str().to_owned();
name.push(".workspace");
PathBuf::from(name)
}
fn exercise_workspace(root: &std::path::Path) -> std::io::Result<()> {
let child = root.join(format!("selftest-{}", std::process::id()));
std::fs::create_dir(&child)?;
std::fs::write(child.join("marker"), b"a job would put its checkout here")?;
std::fs::remove_dir_all(&child)
}
fn record_workspace(heartbeat: &std::path::Path, root: &std::path::Path) {
let outcome = match exercise_workspace(root) {
Ok(()) => "ok".to_owned(),
Err(error) => format!("error: {error}"),
};
let _ = std::fs::write(workspace_outcome_path(heartbeat), outcome);
}
#[cfg(windows)]
fn launched_by_the_service_control_manager() -> bool {
std::env::args_os()
.any(|argument| argument == runner_manager_platform::service::WINDOWS_SCM_HOST_ARGUMENT)
}
fn run_as_an_ordinary_process() -> ! {
let Some(path) = heartbeat_path() else {
std::process::exit(2);
};
record_start(&path);
if let Some(root) = runner_root() {
record_workspace(&path, &root);
}
loop {
std::thread::sleep(std::time::Duration::from_secs(3600));
}
}
#[cfg(windows)]
mod host {
use std::ffi::OsString;
use std::sync::mpsc;
use std::time::Duration;
use windows_service::service::{
ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,
ServiceType,
};
use windows_service::service_control_handler::{self, ServiceControlHandlerResult};
windows_service::define_windows_service!(ffi_service_main, service_main);
pub fn run() {
if !super::launched_by_the_service_control_manager() {
super::run_as_an_ordinary_process();
}
if let Err(error) = windows_service::service_dispatcher::start("", ffi_service_main) {
eprintln!("this fixture only runs under the Windows Service Control Manager: {error}");
std::process::exit(2);
}
}
fn service_main(_arguments: Vec<OsString>) {
let Some(path) = super::heartbeat_path() else {
return;
};
let (stop_tx, stop_rx) = mpsc::channel();
let handler = move |control| match control {
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
ServiceControl::Stop | ServiceControl::Shutdown => {
let _ = stop_tx.send(());
ServiceControlHandlerResult::NoError
}
_ => ServiceControlHandlerResult::NotImplemented,
};
let Ok(status_handle) = service_control_handler::register("", handler) else {
return;
};
let running = ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
};
if status_handle.set_service_status(running.clone()).is_err() {
return;
}
super::record_start(&path);
if let Some(root) = super::runner_root() {
super::record_workspace(&path, &root);
}
let _ = stop_rx.recv();
let _ = status_handle.set_service_status(ServiceStatus {
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
..running
});
}
}
#[cfg(unix)]
mod host {
pub fn run() {
super::run_as_an_ordinary_process();
}
}
fn main() {
host::run();
}