1use std::path::{Path, PathBuf};
2
3pub struct ZLayerDirs {
7 data_dir: PathBuf,
8}
9
10impl ZLayerDirs {
11 pub fn new(data_dir: impl Into<PathBuf>) -> Self {
13 Self {
14 data_dir: data_dir.into(),
15 }
16 }
17
18 pub fn system_default() -> Self {
20 Self::new(Self::default_data_dir())
21 }
22
23 pub fn default_data_dir() -> PathBuf {
35 if let Some(env_dir) = std::env::var_os("ZLAYER_DATA_DIR") {
36 if !env_dir.is_empty() {
37 return PathBuf::from(env_dir);
38 }
39 }
40 platform_default_data_dir()
41 }
42
43 pub fn detect_data_dir() -> PathBuf {
52 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
53 {
54 if !is_root() {
55 let system_data = PathBuf::from("/var/lib/zlayer");
56 if system_data.join("daemon.json").exists() {
57 return system_data;
58 }
59 }
60 }
61 #[cfg(target_os = "windows")]
62 {
63 let system_data = windows_program_data_root();
64 if system_data.join("daemon.json").exists() {
65 return system_data;
66 }
67 }
68 Self::default_data_dir()
69 }
70
71 pub fn default_run_dir() -> PathBuf {
77 Self::default_run_dir_for(&Self::default_data_dir())
78 }
79
80 pub fn default_run_dir_for(data_dir: &Path) -> PathBuf {
87 let system_default = platform_default_data_dir();
88 if data_dir == system_default.as_path() {
89 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
90 {
91 return PathBuf::from("/var/run/zlayer");
92 }
93 #[cfg(any(target_os = "macos", target_os = "windows"))]
94 {
95 return system_default.join("run");
96 }
97 }
98 data_dir.join("run")
99 }
100
101 pub fn default_log_dir() -> PathBuf {
107 Self::default_log_dir_for(&Self::default_data_dir())
108 }
109
110 pub fn default_log_dir_for(data_dir: &Path) -> PathBuf {
117 let system_default = platform_default_data_dir();
118 if data_dir == system_default.as_path() {
119 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
120 {
121 return PathBuf::from("/var/log/zlayer");
122 }
123 #[cfg(any(target_os = "macos", target_os = "windows"))]
124 {
125 return system_default.join("logs");
126 }
127 }
128 data_dir.join("logs")
129 }
130
131 pub fn default_socket_path() -> String {
137 Self::default_socket_path_for(&Self::default_data_dir())
138 }
139
140 pub fn default_socket_path_for(data_dir: &Path) -> String {
149 #[cfg(target_os = "windows")]
150 {
151 let _ = data_dir;
152 "tcp://127.0.0.1:3669".to_string()
153 }
154 #[cfg(not(target_os = "windows"))]
155 {
156 let system_default = platform_default_data_dir();
157 if data_dir == system_default.as_path() {
158 #[cfg(target_os = "macos")]
159 {
160 return system_default
161 .join("run")
162 .join("zlayer.sock")
163 .to_string_lossy()
164 .into_owned();
165 }
166 #[cfg(not(target_os = "macos"))]
167 {
168 return "/var/run/zlayer.sock".to_string();
169 }
170 }
171 let natural = data_dir
172 .join("run")
173 .join("zlayer.sock")
174 .to_string_lossy()
175 .into_owned();
176 if natural.len() <= SUN_PATH_MAX {
177 natural
178 } else {
179 socket_safe_fallback(data_dir, "daemon")
180 }
181 }
182 }
183
184 pub fn default_overlayd_socket_path_for(data_dir: &Path) -> String {
194 #[cfg(target_os = "windows")]
195 {
196 let _ = data_dir;
197 r"\\.\pipe\zlayer-overlayd".to_string()
198 }
199 #[cfg(not(target_os = "windows"))]
200 {
201 let system_default = platform_default_data_dir();
202 if data_dir == system_default.as_path() {
203 #[cfg(target_os = "macos")]
204 {
205 return system_default
206 .join("run")
207 .join("zlayer-overlayd.sock")
208 .to_string_lossy()
209 .into_owned();
210 }
211 #[cfg(not(target_os = "macos"))]
212 {
213 return "/var/run/zlayer-overlayd.sock".to_string();
214 }
215 }
216 let natural = data_dir
217 .join("run")
218 .join("zlayer-overlayd.sock")
219 .to_string_lossy()
220 .into_owned();
221 if natural.len() <= SUN_PATH_MAX {
222 natural
223 } else {
224 socket_safe_fallback(data_dir, "overlayd")
225 }
226 }
227 }
228
229 pub fn default_docker_socket_path() -> String {
237 #[cfg(target_os = "windows")]
238 {
239 r"\\.\pipe\zlayer-docker".to_string()
240 }
241 #[cfg(not(target_os = "windows"))]
242 {
243 #[cfg(target_os = "macos")]
244 {
245 let path = Self::default_data_dir()
246 .join("run")
247 .join("docker.sock")
248 .to_string_lossy()
249 .into_owned();
250 if path.len() <= SUN_PATH_MAX {
251 path
252 } else {
253 socket_safe_fallback(&Self::default_data_dir(), "docker")
254 }
255 }
256 #[cfg(not(target_os = "macos"))]
257 {
258 if is_root() {
259 "/var/run/zlayer/docker.sock".to_string()
260 } else if let Some(xdg) = std::env::var_os("XDG_RUNTIME_DIR") {
261 let xdg_path = PathBuf::from(&xdg);
262 let mut p = xdg_path.clone();
263 p.push("zlayer");
264 p.push("docker.sock");
265 let path = p.to_string_lossy().into_owned();
266 if path.len() <= SUN_PATH_MAX {
267 path
268 } else {
269 socket_safe_fallback(&xdg_path, "docker")
270 }
271 } else {
272 let path = Self::default_data_dir()
273 .join("run")
274 .join("docker.sock")
275 .to_string_lossy()
276 .into_owned();
277 if path.len() <= SUN_PATH_MAX {
278 path
279 } else {
280 socket_safe_fallback(&Self::default_data_dir(), "docker")
281 }
282 }
283 }
284 }
285 }
286
287 pub fn default_binary_dir() -> PathBuf {
296 #[cfg(unix)]
298 {
299 let probe = PathBuf::from("/usr/local/bin/.zlayer_write_probe");
300 if std::fs::write(&probe, b"").is_ok() {
301 let _ = std::fs::remove_file(&probe);
302 return PathBuf::from("/usr/local/bin");
303 }
304 }
305 let dirs = Self::system_default();
307 let bin_dir = dirs.bin();
308 let _ = std::fs::create_dir_all(&bin_dir);
309 bin_dir
310 }
311
312 pub fn data_dir(&self) -> &Path {
316 &self.data_dir
317 }
318
319 pub fn containers(&self) -> PathBuf {
321 self.data_dir.join("containers")
322 }
323
324 pub fn rootfs(&self) -> PathBuf {
326 self.data_dir.join("rootfs")
327 }
328
329 pub fn bundles(&self) -> PathBuf {
331 self.data_dir.join("bundles")
332 }
333
334 pub fn cache(&self) -> PathBuf {
336 self.data_dir.join("cache")
337 }
338
339 pub fn volumes(&self) -> PathBuf {
341 self.data_dir.join("volumes")
342 }
343
344 pub fn projects(&self) -> PathBuf {
347 self.data_dir.join("projects")
348 }
349
350 pub fn wasm(&self) -> PathBuf {
352 self.data_dir.join("wasm")
353 }
354
355 pub fn wasm_compiled(&self) -> PathBuf {
357 self.data_dir.join("wasm").join("compiled")
358 }
359
360 pub fn secrets(&self) -> PathBuf {
362 self.data_dir.join("secrets")
363 }
364
365 pub fn certs(&self) -> PathBuf {
367 self.data_dir.join("certs")
368 }
369
370 pub fn raft(&self) -> PathBuf {
372 self.data_dir.join("raft")
373 }
374
375 pub fn admin_password(&self) -> PathBuf {
377 self.data_dir.join("admin_password")
378 }
379
380 #[must_use]
394 pub fn admin_bearer_path(&self) -> PathBuf {
395 self.data_dir.join("admin_bearer.token")
396 }
397
398 pub fn daemon_json(&self) -> PathBuf {
400 self.data_dir.join("daemon.json")
401 }
402
403 pub fn agent_ipam_state(&self) -> PathBuf {
405 self.data_dir.join("agent_ipam.json")
406 }
407
408 pub fn agent_network_state(&self) -> PathBuf {
416 self.data_dir.join("agent_network.json")
417 }
418
419 pub fn logs(&self) -> PathBuf {
422 self.data_dir.join("logs")
423 }
424
425 pub fn vms(&self) -> PathBuf {
429 self.data_dir.join("vms")
430 }
431
432 pub fn images(&self) -> PathBuf {
434 self.data_dir.join("images")
435 }
436
437 pub fn bin(&self) -> PathBuf {
439 self.data_dir.join("bin")
440 }
441
442 #[must_use]
447 pub fn buildd_bin(&self) -> PathBuf {
448 self.bin().join("zlayer-buildd")
449 }
450
451 #[must_use]
455 pub fn buildd(&self) -> PathBuf {
456 self.data_dir.join("buildd")
457 }
458
459 pub fn toolchain_cache(&self) -> PathBuf {
461 self.data_dir.join("toolchain-cache")
462 }
463
464 pub fn tmp(&self) -> PathBuf {
466 self.data_dir.join("tmp")
467 }
468
469 pub fn scratch_dir(&self, prefix: &str) -> std::io::Result<zlayer_types::Scratch> {
483 std::fs::create_dir_all(self.tmp())?;
484 let td = tempfile::Builder::new()
485 .prefix(prefix)
486 .tempdir_in(self.tmp())?;
487 Ok(zlayer_types::Scratch::from_tempdir(td))
488 }
489
490 pub fn scratch_file(&self, prefix: &str) -> std::io::Result<zlayer_types::ScratchFile> {
500 std::fs::create_dir_all(self.tmp())?;
501 let nf = tempfile::Builder::new()
502 .prefix(prefix)
503 .tempfile_in(self.tmp())?;
504 Ok(zlayer_types::ScratchFile::from_named(nf))
505 }
506
507 pub fn wireguard(&self) -> PathBuf {
518 #[cfg(any(target_os = "macos", target_os = "windows"))]
519 let natural = self.data_dir.join("run").join("wireguard");
520 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
521 let natural = if self.data_dir == Self::default_data_dir() {
522 PathBuf::from("/var/run/wireguard")
523 } else {
524 self.data_dir.join("run").join("wireguard")
525 };
526 const WG_DIR_MAX: usize = SUN_PATH_MAX - 21;
528 if natural.to_string_lossy().len() <= WG_DIR_MAX {
529 natural
530 } else {
531 PathBuf::from(format!(
532 "/tmp/zlayer-wg-{:016x}",
533 hash_for_socket(&self.data_dir, "wg")
534 ))
535 }
536 }
537}
538
539#[must_use]
541pub fn default_admin_bearer_path() -> PathBuf {
542 ZLayerDirs::system_default().admin_bearer_path()
543}
544
545pub(crate) fn platform_default_data_dir() -> PathBuf {
556 #[cfg(target_os = "macos")]
557 {
558 home_dir_or_fallback().join(".zlayer")
559 }
560 #[cfg(target_os = "windows")]
561 {
562 windows_program_data_root()
563 }
564 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
565 {
566 if is_root() {
567 PathBuf::from("/var/lib/zlayer")
568 } else {
569 home_dir_or_fallback().join(".zlayer")
570 }
571 }
572}
573
574const SUN_PATH_MAX: usize = 103;
579
580fn hash_for_socket(data_dir: &Path, label: &str) -> u64 {
585 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
586 for b in data_dir.to_string_lossy().as_bytes() {
587 h ^= u64::from(*b);
588 h = h.wrapping_mul(0x0000_0100_0000_01b3);
589 }
590 for b in label.as_bytes() {
591 h ^= u64::from(*b);
592 h = h.wrapping_mul(0x0000_0100_0000_01b3);
593 }
594 h
595}
596
597#[cfg(not(target_os = "windows"))]
609fn socket_safe_fallback(data_dir: &Path, label: &str) -> String {
610 format!(
611 "/tmp/zlayer-{label}-{:016x}.sock",
612 hash_for_socket(data_dir, label)
613 )
614}
615
616#[cfg(not(target_os = "windows"))]
617fn home_dir_or_fallback() -> PathBuf {
618 std::env::var_os("HOME")
622 .map(PathBuf::from)
623 .unwrap_or_else(|| PathBuf::from("/var/lib/zlayer"))
624}
625
626#[cfg(target_os = "windows")]
632fn windows_program_data_root() -> PathBuf {
633 if let Some(program_data) = std::env::var_os("PROGRAMDATA") {
634 let mut p = PathBuf::from(program_data);
635 p.push("ZLayer");
636 p
637 } else {
638 PathBuf::from(r"C:\ProgramData\ZLayer")
639 }
640}
641
642#[cfg(unix)]
650#[must_use]
651pub fn is_root() -> bool {
652 unsafe { libc::geteuid() == 0 }
654}
655
656#[cfg(windows)]
672#[must_use]
673pub fn is_root() -> bool {
674 use windows::Win32::UI::Shell::IsUserAnAdmin;
675 unsafe { IsUserAnAdmin().as_bool() }
677}
678
679#[cfg(not(any(unix, windows)))]
681#[must_use]
682pub fn is_root() -> bool {
683 false
684}
685
686#[cfg(test)]
687mod tests {
688 use super::*;
689
690 static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
697
698 #[test]
699 fn subdirectories_are_relative_to_data_dir() {
700 let dirs = ZLayerDirs::new("/test/data");
701 assert_eq!(dirs.containers(), PathBuf::from("/test/data/containers"));
702 assert_eq!(dirs.rootfs(), PathBuf::from("/test/data/rootfs"));
703 assert_eq!(dirs.bundles(), PathBuf::from("/test/data/bundles"));
704 assert_eq!(dirs.cache(), PathBuf::from("/test/data/cache"));
705 assert_eq!(dirs.volumes(), PathBuf::from("/test/data/volumes"));
706 assert_eq!(dirs.wasm(), PathBuf::from("/test/data/wasm"));
707 assert_eq!(
708 dirs.wasm_compiled(),
709 PathBuf::from("/test/data/wasm/compiled")
710 );
711 assert_eq!(dirs.secrets(), PathBuf::from("/test/data/secrets"));
712 assert_eq!(dirs.certs(), PathBuf::from("/test/data/certs"));
713 assert_eq!(dirs.raft(), PathBuf::from("/test/data/raft"));
714 assert_eq!(
715 dirs.admin_password(),
716 PathBuf::from("/test/data/admin_password")
717 );
718 assert_eq!(dirs.daemon_json(), PathBuf::from("/test/data/daemon.json"));
719 assert_eq!(dirs.logs(), PathBuf::from("/test/data/logs"));
720 assert_eq!(dirs.vms(), PathBuf::from("/test/data/vms"));
721 assert_eq!(dirs.images(), PathBuf::from("/test/data/images"));
722 assert_eq!(dirs.bin(), PathBuf::from("/test/data/bin"));
723 assert_eq!(
724 dirs.buildd_bin(),
725 PathBuf::from("/test/data/bin/zlayer-buildd")
726 );
727 assert_eq!(dirs.buildd(), PathBuf::from("/test/data/buildd"));
728 assert_eq!(
729 dirs.toolchain_cache(),
730 PathBuf::from("/test/data/toolchain-cache")
731 );
732 assert_eq!(dirs.tmp(), PathBuf::from("/test/data/tmp"));
733 }
734
735 #[test]
736 fn system_default_uses_default_data_dir() {
737 let _env_guard = ENV_LOCK.lock().unwrap();
738 let dirs = ZLayerDirs::system_default();
739 assert_eq!(dirs.data_dir(), ZLayerDirs::default_data_dir().as_path());
740 }
741
742 #[test]
743 fn admin_bearer_path_is_under_data_dir() {
744 let dirs = ZLayerDirs::new(PathBuf::from("/var/lib/zlayer-test"));
745 assert_eq!(
746 dirs.admin_bearer_path(),
747 PathBuf::from("/var/lib/zlayer-test/admin_bearer.token")
748 );
749 }
750
751 #[test]
752 fn default_admin_bearer_path_matches_system_default() {
753 let _env_guard = ENV_LOCK.lock().unwrap();
754 assert_eq!(
755 default_admin_bearer_path(),
756 ZLayerDirs::system_default().admin_bearer_path()
757 );
758 }
759
760 #[cfg(target_os = "windows")]
761 #[test]
762 fn windows_default_data_dir_uses_program_data() {
763 let _env_guard = ENV_LOCK.lock().unwrap();
764 let prev = std::env::var_os("PROGRAMDATA");
765 std::env::set_var("PROGRAMDATA", r"C:\TestProgramData");
766
767 let data = ZLayerDirs::default_data_dir();
768 assert_eq!(data, PathBuf::from(r"C:\TestProgramData\ZLayer"));
769
770 let dirs = ZLayerDirs::system_default();
772 assert_eq!(dirs.certs(), data.join("certs"));
773 assert_eq!(dirs.secrets(), data.join("secrets"));
774 assert_eq!(dirs.logs(), data.join("logs"));
775
776 assert_eq!(ZLayerDirs::default_run_dir(), data.join("run"));
778 assert_eq!(ZLayerDirs::default_log_dir(), data.join("logs"));
779
780 assert_eq!(ZLayerDirs::default_socket_path(), "tcp://127.0.0.1:3669");
783
784 match prev {
785 Some(v) => std::env::set_var("PROGRAMDATA", v),
786 None => std::env::remove_var("PROGRAMDATA"),
787 }
788 }
789
790 #[test]
791 fn default_log_dir_for_returns_system_path_when_data_dir_is_default() {
792 let _env_guard = ENV_LOCK.lock().unwrap();
793 let system_default = ZLayerDirs::default_data_dir();
794 let result = ZLayerDirs::default_log_dir_for(&system_default);
795 assert_eq!(result, ZLayerDirs::default_log_dir());
796 }
797
798 #[test]
799 fn default_log_dir_for_returns_data_subdir_when_data_dir_overridden() {
800 let _env_guard = ENV_LOCK.lock().unwrap();
801 let tmp = tempfile::tempdir().expect("create tempdir");
802 let custom = tmp.path().to_path_buf();
803 let result = ZLayerDirs::default_log_dir_for(&custom);
804 assert_eq!(result, custom.join("logs"));
805 assert_ne!(result, ZLayerDirs::default_log_dir());
807 }
808
809 #[test]
810 fn default_run_dir_for_returns_system_path_when_data_dir_is_default() {
811 let _env_guard = ENV_LOCK.lock().unwrap();
812 let system_default = ZLayerDirs::default_data_dir();
813 let result = ZLayerDirs::default_run_dir_for(&system_default);
814 assert_eq!(result, ZLayerDirs::default_run_dir());
815 }
816
817 #[test]
818 fn default_run_dir_for_returns_data_subdir_when_data_dir_overridden() {
819 let _env_guard = ENV_LOCK.lock().unwrap();
820 let tmp = tempfile::tempdir().expect("create tempdir");
821 let custom = tmp.path().to_path_buf();
822 let result = ZLayerDirs::default_run_dir_for(&custom);
823 assert_eq!(result, custom.join("run"));
824 assert_ne!(result, ZLayerDirs::default_run_dir());
825 }
826
827 #[test]
828 fn default_socket_path_for_returns_system_path_when_data_dir_is_default() {
829 let _env_guard = ENV_LOCK.lock().unwrap();
830 let system_default = ZLayerDirs::default_data_dir();
831 let result = ZLayerDirs::default_socket_path_for(&system_default);
832 assert_eq!(result, ZLayerDirs::default_socket_path());
833 }
834
835 #[cfg(not(target_os = "windows"))]
836 #[test]
837 fn default_socket_path_for_returns_data_subdir_when_data_dir_overridden() {
838 let _env_guard = ENV_LOCK.lock().unwrap();
839 let tmp = tempfile::tempdir().expect("create tempdir");
840 let custom = tmp.path().to_path_buf();
841 let result = ZLayerDirs::default_socket_path_for(&custom);
842 let expected = custom
843 .join("run")
844 .join("zlayer.sock")
845 .to_string_lossy()
846 .into_owned();
847 assert_eq!(result, expected);
848 assert_ne!(result, ZLayerDirs::default_socket_path());
849 }
850
851 #[cfg(target_os = "windows")]
852 #[test]
853 fn default_socket_path_for_always_tcp_on_windows() {
854 let _env_guard = ENV_LOCK.lock().unwrap();
855 let tmp = tempfile::tempdir().expect("create tempdir");
856 let custom = tmp.path().to_path_buf();
857 assert_eq!(
859 ZLayerDirs::default_socket_path_for(&custom),
860 "tcp://127.0.0.1:3669"
861 );
862 }
863
864 #[cfg(not(target_os = "windows"))]
865 #[test]
866 fn default_socket_path_uses_data_subdir_when_env_var_overrides() {
867 let _env_guard = ENV_LOCK.lock().unwrap();
868 let prev = std::env::var_os("ZLAYER_DATA_DIR");
869 let tmp = tempfile::tempdir().expect("create tempdir");
870 let custom = tmp.path().to_path_buf();
871 std::env::set_var("ZLAYER_DATA_DIR", &custom);
872
873 let result = ZLayerDirs::default_socket_path();
874 let expected = custom
875 .join("run")
876 .join("zlayer.sock")
877 .to_string_lossy()
878 .into_owned();
879 assert_eq!(result, expected);
880
881 match prev {
882 Some(v) => std::env::set_var("ZLAYER_DATA_DIR", v),
883 None => std::env::remove_var("ZLAYER_DATA_DIR"),
884 }
885 }
886
887 #[cfg(target_os = "windows")]
888 #[test]
889 fn default_socket_path_uses_data_subdir_when_env_var_overrides() {
890 let _env_guard = ENV_LOCK.lock().unwrap();
891 let prev = std::env::var_os("ZLAYER_DATA_DIR");
894 let tmp = tempfile::tempdir().expect("create tempdir");
895 let custom = tmp.path().to_path_buf();
896 std::env::set_var("ZLAYER_DATA_DIR", &custom);
897
898 assert_eq!(ZLayerDirs::default_socket_path(), "tcp://127.0.0.1:3669");
899
900 match prev {
901 Some(v) => std::env::set_var("ZLAYER_DATA_DIR", v),
902 None => std::env::remove_var("ZLAYER_DATA_DIR"),
903 }
904 }
905
906 #[cfg(target_os = "windows")]
907 #[test]
908 fn windows_default_data_dir_fallback_when_env_missing() {
909 let _env_guard = ENV_LOCK.lock().unwrap();
910 let prev = std::env::var_os("PROGRAMDATA");
911 std::env::remove_var("PROGRAMDATA");
912
913 let data = ZLayerDirs::default_data_dir();
914 assert_eq!(data, PathBuf::from(r"C:\ProgramData\ZLayer"));
915
916 if let Some(v) = prev {
917 std::env::set_var("PROGRAMDATA", v);
918 }
919 }
920
921 #[test]
922 fn default_docker_socket_path_not_empty() {
923 let result = ZLayerDirs::default_docker_socket_path();
924 assert!(!result.is_empty());
925 }
926
927 #[cfg(target_os = "windows")]
928 #[test]
929 fn default_docker_socket_path_platform_shape() {
930 let result = ZLayerDirs::default_docker_socket_path();
931 assert!(result.starts_with(r"\\.\pipe"));
932 }
933
934 #[cfg(target_os = "macos")]
935 #[test]
936 fn default_docker_socket_path_platform_shape() {
937 let result = ZLayerDirs::default_docker_socket_path();
938 assert!(result.ends_with("/docker.sock"));
939 }
940
941 #[cfg(all(not(target_os = "windows"), not(target_os = "macos")))]
942 #[test]
943 fn default_docker_socket_path_platform_shape() {
944 let result = ZLayerDirs::default_docker_socket_path();
945 assert!(result.ends_with("/docker.sock"));
946 }
947
948 #[cfg(all(not(target_os = "windows"), not(target_os = "macos")))]
949 #[test]
950 fn wireguard_returns_fhs_path_on_default_data_dir() {
951 let dirs = ZLayerDirs::system_default();
952 assert_eq!(dirs.wireguard(), PathBuf::from("/var/run/wireguard"));
953 }
954
955 #[test]
956 fn wireguard_returns_data_subdir_when_overridden() {
957 let tmp = tempfile::tempdir().expect("create tempdir");
958 let custom = tmp.path().to_path_buf();
959 let dirs = ZLayerDirs::new(&custom);
960 let result = dirs.wireguard();
961 assert_eq!(result, custom.join("run").join("wireguard"));
962 #[cfg(all(not(target_os = "windows"), not(target_os = "macos")))]
964 assert_ne!(result, PathBuf::from("/var/run/wireguard"));
965 }
966
967 #[cfg(target_os = "macos")]
968 #[test]
969 fn wireguard_always_returns_data_subdir_on_macos() {
970 let dirs = ZLayerDirs::system_default();
973 let expected = ZLayerDirs::default_data_dir().join("run").join("wireguard");
974 assert_eq!(dirs.wireguard(), expected);
975 }
976
977 #[test]
978 fn scratch_dir_under_data_tmp() {
979 let parent = tempfile::tempdir().expect("parent");
980 let dirs = ZLayerDirs::new(parent.path());
981 let s = dirs.scratch_dir("zlayer-test-").expect("scratch_dir");
982 assert!(s.path().starts_with(dirs.tmp()));
983 assert!(s.path().is_dir());
984 let kept = s.path().to_path_buf();
985 drop(s);
986 assert!(!kept.exists());
987 }
988
989 #[test]
990 fn scratch_file_under_data_tmp() {
991 let parent = tempfile::tempdir().expect("parent");
992 let dirs = ZLayerDirs::new(parent.path());
993 let f = dirs.scratch_file("zlayer-test-").expect("scratch_file");
994 assert!(f.path().starts_with(dirs.tmp()));
995 assert!(f.path().is_file());
996 let kept = f.path().to_path_buf();
997 drop(f);
998 assert!(!kept.exists());
999 }
1000
1001 #[cfg(not(target_os = "windows"))]
1002 #[test]
1003 fn default_socket_path_for_falls_back_when_path_too_long() {
1004 let deep = PathBuf::from(
1005 "/var/lib/forgejo-runner/workdir/9dbc274201705d7d/hostexecutor/target/zlayer-e2e/cluster_3node/node1/data",
1006 );
1007 let result = ZLayerDirs::default_socket_path_for(&deep);
1008 assert!(
1009 result.len() <= SUN_PATH_MAX,
1010 "fallback path overflows sun_path: len={} path={}",
1011 result.len(),
1012 result,
1013 );
1014 assert_eq!(result, ZLayerDirs::default_socket_path_for(&deep));
1016 let other = deep.parent().unwrap().to_path_buf();
1018 assert_ne!(result, ZLayerDirs::default_socket_path_for(&other));
1019 }
1020
1021 #[cfg(not(target_os = "windows"))]
1022 #[test]
1023 fn default_socket_path_for_keeps_natural_path_when_short() {
1024 let tmp = tempfile::tempdir().expect("create tempdir");
1025 let short = tmp.path().to_path_buf();
1026 assert!(short.to_string_lossy().len() < 80);
1027 let result = ZLayerDirs::default_socket_path_for(&short);
1028 assert!(result.ends_with("/run/zlayer.sock"));
1029 assert!(result.starts_with(&*short.to_string_lossy()));
1030 }
1031
1032 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
1033 #[test]
1034 fn wireguard_dir_falls_back_when_path_too_long() {
1035 let deep = PathBuf::from(
1036 "/var/lib/forgejo-runner/workdir/9dbc274201705d7d/hostexecutor/target/zlayer-e2e/cluster_3node/node1/data",
1037 );
1038 let dirs = ZLayerDirs::new(&deep);
1039 let wg = dirs.wireguard();
1040 assert!(
1042 wg.to_string_lossy().len() + 21 <= SUN_PATH_MAX,
1043 "wireguard dir + ifname overflows: dir={}",
1044 wg.display(),
1045 );
1046 }
1047}