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