#![allow(
clippy::wildcard_imports,
clippy::too_many_arguments,
clippy::needless_pass_by_value,
dead_code,
reason = "split-out module preserves existing code while keeping files under the linecheck limit"
)]
use super::*;
pub(crate) fn wait_until(mut check: impl FnMut() -> bool, timeout: Duration) -> bool {
let deadline = std::time::Instant::now() + timeout;
loop {
if check() {
return true;
}
if std::time::Instant::now() >= deadline {
return false;
}
std::thread::sleep(WAIT_POLL_INTERVAL);
}
}
pub(crate) fn http_request(method: &str, path: &str) -> std::io::Result<u16> {
http_request_with_body(method, path).map(|(status, _)| status)
}
pub(crate) const DATA_OP_TIMEOUT: Duration = Duration::from_secs(10);
pub(crate) fn http_request_with_body(method: &str, path: &str) -> std::io::Result<(u16, String)> {
http_request_core(method, path, None, PROBE_TIMEOUT)
}
pub(crate) fn http_request_json(
method: &str,
path: &str,
body: Option<&str>,
) -> std::io::Result<(u16, String)> {
http_request_core(method, path, body, DATA_OP_TIMEOUT)
}
pub(crate) fn http_request_core(
method: &str,
path: &str,
body: Option<&str>,
timeout: Duration,
) -> std::io::Result<(u16, String)> {
let addr_str = crate::cli::bind_addr();
let addr: SocketAddr = addr_str.parse().map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("invalid bind address {addr_str:?}: {err}"),
)
})?;
let mut stream = std::net::TcpStream::connect_timeout(&addr, timeout)?;
stream.set_read_timeout(Some(timeout))?;
stream.set_write_timeout(Some(timeout))?;
let payload = body.unwrap_or_default();
let auth_header = crate::cli::api_token()
.map(|token| format!("Authorization: Bearer {token}\r\n"))
.unwrap_or_default();
let req = format!(
"{method} {path} HTTP/1.1\r\nHost: {addr_str}\r\n{auth_header}Content-Type: application/json\r\n\
Content-Length: {}\r\nConnection: close\r\n\r\n{payload}",
payload.len()
);
stream.write_all(req.as_bytes())?;
let mut resp = String::new();
let _ = stream.read_to_string(&mut resp);
let status = parse_status_code(&resp).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"no HTTP status line in response",
)
})?;
Ok((status, parse_body(&resp)))
}
pub(crate) fn parse_status_code(resp: &str) -> Option<u16> {
resp.lines().next()?.split_whitespace().nth(1)?.parse().ok()
}
pub(crate) fn parse_body(resp: &str) -> String {
resp.split_once("\r\n\r\n")
.map(|(_, body)| body.to_string())
.unwrap_or_default()
}
pub(crate) fn parse_removed_count(body: &str) -> Option<usize> {
let value: serde_json::Value = serde_json::from_str(body).ok()?;
value.get("removed")?.as_u64().map(|n| n as usize)
}
pub(crate) fn parse_freed_bytes(body: &str) -> Option<u64> {
let value: serde_json::Value = serde_json::from_str(body).ok()?;
value.get("freed_bytes")?.as_u64()
}
pub(crate) fn spawn_detached() -> anyhow::Result<u32> {
spawn_detached_with(|cmd| {
cmd.arg("--interactive")
.env(crate::cli::DAEMONIZED_ENV, "1");
})
}
pub fn spawn_restart() -> anyhow::Result<u32> {
spawn_detached_with(|cmd| {
cmd.arg("--background");
})
}
pub(crate) fn spawn_detached_with(
configure: impl FnOnce(&mut std::process::Command),
) -> anyhow::Result<u32> {
use std::process::{Command as Proc, Stdio};
let exe = crate::utils::process::current_exe()
.map_err(|err| anyhow::anyhow!("resolve current executable path: {err}"))?;
let log_path = crate::paths::daemon_log_file();
let log_parent = crate::utils::fs_perms::parent_or_err(&log_path, "daemon log")?;
crate::utils::fs_perms::create_private_dir_all(log_parent)?;
rotate_daemon_log_if_due(&log_path);
let out = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&log_path)?;
let err = out.try_clone()?;
let mut cmd = Proc::new(exe);
cmd.stdin(Stdio::null())
.stdout(Stdio::from(out))
.stderr(Stdio::from(err));
configure(&mut cmd);
detach(&mut cmd);
#[allow(
clippy::zombie_processes,
reason = "intentionally detached: the child outlives this process and is reaped by the OS/service manager, not waited on here"
)]
let child = cmd.spawn()?;
Ok(child.id())
}
#[cfg(unix)]
pub(crate) fn detach(cmd: &mut std::process::Command) {
use std::os::unix::process::CommandExt as _;
cmd.process_group(0);
}
#[cfg(not(unix))]
pub(crate) fn detach(_cmd: &mut std::process::Command) {}