1use std::env::{join_paths, split_paths, var_os};
2use std::ffi::OsString;
3use std::io;
4use std::path::PathBuf;
5
6use tracing::debug;
7use which::which_in;
8
9pub type SnixCliResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
10
11pub const DEFAULT_LIBEXEC_PATH_VAR: &str = "SNIX_LIBEXEC_PATH";
12
13pub fn make_search_path(default_libexec_path: Option<&str>) -> Option<OsString> {
22 let libexec_path = var_os(DEFAULT_LIBEXEC_PATH_VAR);
23 let libexec_paths = libexec_path.iter().flat_map(split_paths);
24
25 let default_libexec_paths = default_libexec_path.iter().flat_map(split_paths);
26
27 let path = var_os("PATH");
28 let paths = path.iter().flat_map(split_paths);
29
30 let current_exe = std::env::current_exe()
31 .ok()
32 .and_then(|p| p.parent().map(ToOwned::to_owned));
33 let paths = libexec_paths
34 .chain(default_libexec_paths)
35 .chain(paths)
36 .chain(current_exe);
37 join_paths(paths).ok()
38}
39
40pub fn find_command(sub_cmd: &str, default_libexec_path: Option<&str>) -> SnixCliResult<PathBuf> {
47 let cwd = std::env::current_exe()
48 .ok()
49 .and_then(|c| c.parent().map(ToOwned::to_owned))
50 .or_else(|| std::env::current_dir().ok())
51 .ok_or_else(|| io::Error::other("Could not resolve current directory"))?;
52 let binary_name = format!("snix-{sub_cmd}");
53 let search_path: Option<OsString> = make_search_path(default_libexec_path);
54 debug!(?search_path, binary_name, "Searching for {binary_name}");
55 Ok(which_in(binary_name, search_path, cwd)?)
56}
57
58pub async fn shutdown_signal() {
60 let ctrl_c = async {
61 tokio::signal::ctrl_c()
62 .await
63 .expect("failed to install Ctrl+C handler");
64 };
65
66 #[cfg(unix)]
67 let terminate = async {
68 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
69 .expect("failed to install SIGTERM handler")
70 .recv()
71 .await;
72 };
73
74 #[cfg(not(unix))]
75 let terminate = std::future::pending::<()>();
76
77 tokio::select! {
78 _ = ctrl_c => {},
79 _ = terminate => {},
80 }
81
82 debug!("signal received, shutting down…");
83}
84
85pub async fn reader_for_path(
87 path: impl AsRef<std::path::Path>,
88) -> std::io::Result<Box<dyn tokio::io::AsyncBufRead + Unpin + Send>> {
89 use std::os::unix::fs::FileTypeExt;
90 use tokio::io::BufReader;
91
92 let path = path.as_ref();
93 if path == "-" {
94 Ok(Box::new(BufReader::new(tokio::io::stdin())) as Box<_>)
95 } else {
96 let metadata = tokio::fs::metadata(path).await?;
97
98 if metadata.file_type().is_socket() {
99 let stream = tokio::net::UnixStream::connect(path).await?;
100 Ok(Box::new(BufReader::new(stream)))
101 } else {
102 let file = tokio::fs::File::open(path).await?;
103 Ok(Box::new(BufReader::new(file)))
104 }
105 }
106}