evalbox_sandbox/
sysinfo.rs1use std::path::{Path, PathBuf};
7use std::sync::LazyLock;
8
9pub static SYSTEM_PATHS: LazyLock<SystemPaths> = LazyLock::new(SystemPaths::detect);
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum SystemType {
14 NixOS,
16 Guix,
18 Fhs,
20}
21
22impl SystemType {
23 pub fn detect() -> Self {
25 if Path::new("/etc/NIXOS").exists() {
29 return SystemType::NixOS;
30 }
31 if Path::new("/nix/store").exists() {
32 if let Ok(target) = std::fs::read_link("/bin/sh") {
34 if target.to_string_lossy().contains("/nix/store") {
35 return SystemType::NixOS;
36 }
37 }
38 if !Path::new("/bin/sh").exists() {
40 return SystemType::NixOS;
41 }
42 }
43
44 if Path::new("/gnu/store").exists() {
46 if let Ok(target) = std::fs::read_link("/bin/sh") {
47 if target.to_string_lossy().contains("/gnu/store") {
48 return SystemType::Guix;
49 }
50 }
51 if !Path::new("/bin/sh").exists() {
52 return SystemType::Guix;
53 }
54 }
55
56 SystemType::Fhs
57 }
58
59 pub fn is_nix_like(self) -> bool {
60 matches!(self, SystemType::NixOS | SystemType::Guix)
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct SystemPaths {
67 pub system_type: SystemType,
69 pub readonly_mounts: Vec<PathBuf>,
71 pub default_path: String,
73}
74
75impl SystemPaths {
76 pub fn detect() -> Self {
78 let system_type = SystemType::detect();
79
80 match system_type {
81 SystemType::NixOS => Self::nixos_paths(),
82 SystemType::Guix => Self::guix_paths(),
83 SystemType::Fhs => Self::fhs_paths(),
84 }
85 }
86
87 fn nixos_paths() -> Self {
88 let mut readonly_mounts = Vec::new();
89
90 if Path::new("/nix/store").exists() {
92 readonly_mounts.push(PathBuf::from("/nix/store"));
93 }
94
95 if Path::new("/run/current-system/sw").exists() {
97 readonly_mounts.push(PathBuf::from("/run/current-system/sw"));
98 }
99
100 let path_dirs = [
105 "/run/current-system/sw/bin",
106 "/nix/var/nix/profiles/default/bin",
107 ];
108
109 let default_path = path_dirs
110 .iter()
111 .filter(|p| Path::new(p).exists())
112 .copied()
113 .collect::<Vec<_>>()
114 .join(":");
115
116 Self {
117 system_type: SystemType::NixOS,
118 readonly_mounts,
119 default_path: if default_path.is_empty() {
120 "/bin".to_string()
121 } else {
122 default_path
123 },
124 }
125 }
126
127 fn guix_paths() -> Self {
128 let mut readonly_mounts = Vec::new();
129
130 if Path::new("/gnu/store").exists() {
131 readonly_mounts.push(PathBuf::from("/gnu/store"));
132 }
133
134 Self {
138 system_type: SystemType::Guix,
139 readonly_mounts,
140 default_path: "/run/current-system/profile/bin".to_string(),
141 }
142 }
143
144 fn fhs_paths() -> Self {
145 const FHS_DIRS: &[&str] = &["/usr", "/bin", "/lib", "/lib64", "/sbin"];
148
149 let readonly_mounts = FHS_DIRS
150 .iter()
151 .map(Path::new)
152 .filter(|p| p.exists())
153 .map(Path::to_path_buf)
154 .collect();
155
156 Self {
157 system_type: SystemType::Fhs,
158 readonly_mounts,
159 default_path: "/usr/local/bin:/usr/bin:/bin".to_string(),
160 }
161 }
162
163 pub fn landlock_readonly_paths(&self) -> Vec<&Path> {
165 self.readonly_mounts.iter().map(|p| p.as_path()).collect()
166 }
167}
168
169pub fn is_nix_store_path(path: &Path) -> bool {
170 path.starts_with("/nix/store")
171}
172
173pub fn is_guix_store_path(path: &Path) -> bool {
174 path.starts_with("/gnu/store")
175}
176
177pub fn get_store_path(path: &Path) -> Option<PathBuf> {
179 let path_str = path.to_string_lossy();
180
181 for store in ["/nix/store", "/gnu/store"] {
182 if path_str.starts_with(store) {
183 return Some(PathBuf::from(store));
185 }
186 }
187
188 None
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn test_system_type_detect() {
197 let system_type = SystemType::detect();
198 assert!(matches!(
200 system_type,
201 SystemType::NixOS | SystemType::Guix | SystemType::Fhs
202 ));
203 }
204
205 #[test]
206 fn test_system_paths_detect() {
207 let paths = SystemPaths::detect();
208 assert!(!paths.default_path.is_empty());
210 }
211
212 #[test]
213 fn test_is_nix_store_path() {
214 assert!(is_nix_store_path(Path::new("/nix/store/abc123/bin/python")));
215 assert!(!is_nix_store_path(Path::new("/usr/bin/python")));
216 }
217
218 #[test]
219 fn test_get_store_path() {
220 let nix_path = Path::new("/nix/store/abc123/bin/python");
221 assert_eq!(get_store_path(nix_path), Some(PathBuf::from("/nix/store")));
222
223 let usr_path = Path::new("/usr/bin/python");
224 assert_eq!(get_store_path(usr_path), None);
225 }
226}