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
13#[cfg(feature = "listener")]
18pub async fn make_listener(
19 address: &tokio_listener::ListenerAddress,
20 user_options: &tokio_listener::UserOptions,
21) -> std::io::Result<tokio_listener::Listener> {
22 tokio_listener::Listener::bind(
23 address,
24 &{
25 let mut system_options = tokio_listener::SystemOptions::default();
26 system_options.sleep_on_errors = true;
27 system_options
28 },
29 user_options,
30 )
31 .await
32}
33
34pub fn make_search_path(default_libexec_path: Option<&str>) -> Option<OsString> {
43 let libexec_path = var_os(DEFAULT_LIBEXEC_PATH_VAR);
44 let libexec_paths = libexec_path.iter().flat_map(split_paths);
45
46 let default_libexec_paths = default_libexec_path.iter().flat_map(split_paths);
47
48 let path = var_os("PATH");
49 let paths = path.iter().flat_map(split_paths);
50
51 let current_exe = std::env::current_exe()
52 .ok()
53 .and_then(|p| p.parent().map(ToOwned::to_owned));
54 let paths = libexec_paths
55 .chain(default_libexec_paths)
56 .chain(paths)
57 .chain(current_exe);
58 join_paths(paths).ok()
59}
60
61pub fn find_command(sub_cmd: &str, default_libexec_path: Option<&str>) -> SnixCliResult<PathBuf> {
68 let cwd = std::env::current_exe()
69 .ok()
70 .and_then(|c| c.parent().map(ToOwned::to_owned))
71 .or_else(|| std::env::current_dir().ok())
72 .ok_or_else(|| io::Error::other("Could not resolve current directory"))?;
73 let binary_name = format!("snix-{sub_cmd}");
74 let search_path: Option<OsString> = make_search_path(default_libexec_path);
75 debug!(?search_path, binary_name, "Searching for {binary_name}");
76 Ok(which_in(binary_name, search_path, cwd)?)
77}
78
79pub async fn shutdown_signal() {
81 let ctrl_c = async {
82 tokio::signal::ctrl_c()
83 .await
84 .expect("failed to install Ctrl+C handler");
85 };
86
87 #[cfg(unix)]
88 let terminate = async {
89 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
90 .expect("failed to install SIGTERM handler")
91 .recv()
92 .await;
93 };
94
95 #[cfg(not(unix))]
96 let terminate = std::future::pending::<()>();
97
98 tokio::select! {
99 _ = ctrl_c => {},
100 _ = terminate => {},
101 }
102
103 debug!("signal received, shutting down…");
104}
105
106pub async fn reader_for_path(
108 path: impl AsRef<std::path::Path>,
109) -> std::io::Result<Box<dyn tokio::io::AsyncBufRead + Unpin + Send>> {
110 use std::os::unix::fs::FileTypeExt;
111 use tokio::io::BufReader;
112
113 let path = path.as_ref();
114 if path == "-" {
115 Ok(Box::new(BufReader::new(tokio::io::stdin())) as Box<_>)
116 } else {
117 let metadata = tokio::fs::metadata(path).await?;
118
119 if metadata.file_type().is_socket() {
120 let stream = tokio::net::UnixStream::connect(path).await?;
121 Ok(Box::new(BufReader::new(stream)))
122 } else {
123 let file = tokio::fs::File::open(path).await?;
124 Ok(Box::new(BufReader::new(file)))
125 }
126 }
127}