saluki_env/features/
mod.rs1use std::path::{Path, PathBuf};
7#[cfg(unix)]
8use std::{io::ErrorKind, net::Shutdown, os::unix::fs::FileTypeExt as _, time::Duration};
9
10use bitmask_enum::bitmask;
11#[cfg(unix)]
12use socket2::{Domain, SockAddr, Socket, Type};
13use tracing::debug;
14
15mod containerd;
16pub use self::containerd::ContainerdDetector;
17
18mod detector;
19pub use self::detector::FeatureDetector;
20
21const CONTAINER_HOST_MOUNT_PATH: &str = "/host";
22#[cfg(unix)]
23const SOCKET_CHECK_CONNECT_TIMEOUT: Duration = Duration::from_millis(500);
24
25#[bitmask(u16)]
30#[bitmask_config(vec_debug)]
31pub enum Feature {
32 HostMappedProcfs,
37
38 HostMappedCgroupfs,
43
44 Containerd,
46}
47
48fn get_env_var_or_empty(name: &str) -> String {
49 std::env::var(name).unwrap_or_else(|_| String::new())
50}
51
52fn is_env_var_present(name: &str) -> bool {
53 !get_env_var_or_empty(name).is_empty()
54}
55
56fn file_exists<P>(path: P) -> bool
57where
58 P: AsRef<Path>,
59{
60 std::fs::metadata(path).is_ok()
61}
62
63#[cfg(unix)]
64fn path_empty<P>(path: P) -> bool
65where
66 P: AsRef<Path>,
67{
68 path.as_ref().as_os_str().is_empty()
69}
70
71#[cfg(unix)]
72fn path_contains<P>(path: P, fragment: &str) -> bool
73where
74 P: AsRef<Path>,
75{
76 path.as_ref().to_string_lossy().contains(fragment)
77}
78
79#[cfg(unix)]
87fn check_unix_socket(path: &Path) -> Option<bool> {
88 let metadata = match std::fs::metadata(path) {
90 Ok(metadata) => metadata,
91 Err(e) => {
92 debug!(socket_path = %path.to_string_lossy(), error = %e, "Failed to get metadata for socket path.");
93 return None;
94 }
95 };
96 if !metadata.file_type().is_socket() {
97 return None;
98 }
99
100 let socket = match Socket::new_raw(Domain::UNIX, Type::STREAM, None) {
105 Ok(socket) => socket,
106 Err(e) => {
107 debug!(socket_path = %path.to_string_lossy(), error = %e, "Failed to create socket.");
108 return Some(false);
109 }
110 };
111
112 let socket_addr = match SockAddr::unix(path) {
113 Ok(socket_addr) => socket_addr,
114 Err(e) => {
115 debug!(socket_path = %path.to_string_lossy(), error = %e, "Failed to create socket address.");
116 return Some(false);
117 }
118 };
119
120 Some(
121 match socket.connect_timeout(&socket_addr, SOCKET_CHECK_CONNECT_TIMEOUT) {
122 Ok(_) => {
123 let _ = socket.shutdown(Shutdown::Both);
125
126 true
127 }
128 Err(e) => e.kind() != ErrorKind::PermissionDenied,
129 },
130 )
131}
132
133#[cfg(unix)]
134fn find_first_available_unix_socket<I, P>(socket_paths: I) -> Option<PathBuf>
135where
136 I: IntoIterator<Item = P>,
137 P: AsRef<Path>,
138{
139 for socket_path in socket_paths {
140 let socket_path = socket_path.as_ref();
141 let socket_path_str = socket_path.to_string_lossy();
142
143 match check_unix_socket(socket_path) {
144 None => {
145 debug!(socket_path = %socket_path_str, "No file at socket path or not a socket.");
146 continue;
147 }
148 Some(false) => {
149 debug!(socket_path = %socket_path_str, "Found file at socket path but unreachable. (permissions?)");
150 continue;
151 }
152 Some(true) => {
153 debug!(socket_path = %socket_path_str, "Found reachable socket.");
154 return Some(socket_path.to_path_buf());
155 }
156 }
157 }
158
159 None
160}
161
162fn is_running_inside_container() -> bool {
163 let is_containerized = is_env_var_present("DOCKER_DD_AGENT");
165 if is_containerized {
166 debug!("Found non-empty DOCKER_DD_AGENT environment variable. Likely running in a container.");
167 } else {
168 debug!("Did not find DOCKER_DD_AGENT environment variable. Likely not running in a container.");
169 }
170 is_containerized
171}
172
173#[cfg(unix)]
174fn is_running_inside_docker() -> bool {
175 file_exists("/.dockerenv")
179}
180
181#[cfg(unix)]
182fn with_host_mount_prefixes<I, P>(paths: I) -> Vec<PathBuf>
183where
184 I: IntoIterator<Item = P>,
185 P: AsRef<str>,
186{
187 let mut prefixes = vec![];
188
189 let root = PathBuf::from("/");
195 let host_root = PathBuf::from(CONTAINER_HOST_MOUNT_PATH);
196 let is_containerized = is_running_inside_container();
197
198 for path in paths {
199 let path = path.as_ref().trim_start_matches('/');
200
201 prefixes.push(root.join(path));
202
203 if is_containerized {
204 prefixes.push(host_root.join(path));
205 }
206 }
207
208 prefixes
209}
210
211fn has_host_mapped_procfs() -> bool {
212 let path = PathBuf::from(CONTAINER_HOST_MOUNT_PATH).join("proc");
213 is_running_inside_container() && file_exists(&path)
214}
215
216fn has_host_mapped_cgroupfs() -> bool {
217 let path = PathBuf::from(CONTAINER_HOST_MOUNT_PATH).join("sys/fs/cgroup");
218 is_running_inside_container() && file_exists(&path)
219}