Skip to main content

zlayer_paths/
lib.rs

1use std::path::{Path, PathBuf};
2
3/// Centralized filesystem path resolution for ZLayer.
4///
5/// All ZLayer crates should use this instead of hardcoding paths.
6pub struct ZLayerDirs {
7    data_dir: PathBuf,
8}
9
10impl ZLayerDirs {
11    /// Create from an explicit data directory.
12    pub fn new(data_dir: impl Into<PathBuf>) -> Self {
13        Self {
14            data_dir: data_dir.into(),
15        }
16    }
17
18    /// Create using the platform default data directory.
19    pub fn system_default() -> Self {
20        Self::new(Self::default_data_dir())
21    }
22
23    // -- Platform defaults (associated functions) ----------------------------
24
25    /// Platform-aware default data directory.
26    ///
27    /// - macOS: `~/.zlayer`
28    /// - Linux (root): `/var/lib/zlayer`
29    /// - Linux (user): `~/.zlayer`
30    /// - Windows: `%LOCALAPPDATA%\ZLayer` or `C:\ProgramData\ZLayer`
31    pub fn default_data_dir() -> PathBuf {
32        #[cfg(target_os = "macos")]
33        {
34            home_dir_or_tmp().join(".zlayer")
35        }
36        #[cfg(target_os = "windows")]
37        {
38            if let Some(local_app_data) = std::env::var_os("LOCALAPPDATA") {
39                PathBuf::from(local_app_data).join("ZLayer")
40            } else {
41                PathBuf::from(r"C:\ProgramData\ZLayer")
42            }
43        }
44        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
45        {
46            if is_root() {
47                PathBuf::from("/var/lib/zlayer")
48            } else {
49                home_dir_or_tmp().join(".zlayer")
50            }
51        }
52    }
53
54    /// Default runtime directory.
55    ///
56    /// - Linux: `/var/run/zlayer`
57    /// - macOS: `{default_data_dir}/run`
58    /// - Windows: `{default_data_dir}\run`
59    pub fn default_run_dir() -> PathBuf {
60        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
61        {
62            PathBuf::from("/var/run/zlayer")
63        }
64        #[cfg(any(target_os = "macos", target_os = "windows"))]
65        {
66            Self::default_data_dir().join("run")
67        }
68    }
69
70    /// Default log directory.
71    ///
72    /// - Linux: `/var/log/zlayer`
73    /// - macOS: `{default_data_dir}/logs`
74    /// - Windows: `{default_data_dir}\logs`
75    pub fn default_log_dir() -> PathBuf {
76        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
77        {
78            PathBuf::from("/var/log/zlayer")
79        }
80        #[cfg(any(target_os = "macos", target_os = "windows"))]
81        {
82            Self::default_data_dir().join("logs")
83        }
84    }
85
86    /// Default Unix socket path.
87    ///
88    /// - Linux: `/var/run/zlayer.sock`
89    /// - macOS: `{default_data_dir}/run/zlayer.sock`
90    /// - Windows: `tcp://127.0.0.1:3669`
91    pub fn default_socket_path() -> String {
92        #[cfg(target_os = "windows")]
93        {
94            "tcp://127.0.0.1:3669".to_string()
95        }
96        #[cfg(not(target_os = "windows"))]
97        {
98            #[cfg(target_os = "macos")]
99            {
100                Self::default_data_dir()
101                    .join("run")
102                    .join("zlayer.sock")
103                    .to_string_lossy()
104                    .into_owned()
105            }
106            #[cfg(not(target_os = "macos"))]
107            {
108                "/var/run/zlayer.sock".to_string()
109            }
110        }
111    }
112
113    // -- Core subdirectories -------------------------------------------------
114
115    /// Root data directory.
116    pub fn data_dir(&self) -> &Path {
117        &self.data_dir
118    }
119
120    /// Container state directory (`{data}/containers`).
121    pub fn containers(&self) -> PathBuf {
122        self.data_dir.join("containers")
123    }
124
125    /// Unpacked image rootfs directory (`{data}/rootfs`).
126    pub fn rootfs(&self) -> PathBuf {
127        self.data_dir.join("rootfs")
128    }
129
130    /// OCI bundle directory (`{data}/bundles`).
131    pub fn bundles(&self) -> PathBuf {
132        self.data_dir.join("bundles")
133    }
134
135    /// Image/blob cache directory (`{data}/cache`).
136    pub fn cache(&self) -> PathBuf {
137        self.data_dir.join("cache")
138    }
139
140    /// Named volumes directory (`{data}/volumes`).
141    pub fn volumes(&self) -> PathBuf {
142        self.data_dir.join("volumes")
143    }
144
145    /// WASM module cache directory (`{data}/wasm`).
146    pub fn wasm(&self) -> PathBuf {
147        self.data_dir.join("wasm")
148    }
149
150    /// AOT-compiled WASM cache directory (`{data}/wasm/compiled`).
151    pub fn wasm_compiled(&self) -> PathBuf {
152        self.data_dir.join("wasm").join("compiled")
153    }
154
155    /// Encrypted secrets store directory (`{data}/secrets`).
156    pub fn secrets(&self) -> PathBuf {
157        self.data_dir.join("secrets")
158    }
159
160    /// TLS certificate storage directory (`{data}/certs`).
161    pub fn certs(&self) -> PathBuf {
162        self.data_dir.join("certs")
163    }
164
165    /// Raft consensus data directory (`{data}/raft`).
166    pub fn raft(&self) -> PathBuf {
167        self.data_dir.join("raft")
168    }
169
170    /// Admin password file path (`{data}/admin_password`).
171    pub fn admin_password(&self) -> PathBuf {
172        self.data_dir.join("admin_password")
173    }
174
175    /// Daemon metadata file path (`{data}/daemon.json`).
176    pub fn daemon_json(&self) -> PathBuf {
177        self.data_dir.join("daemon.json")
178    }
179
180    /// Logs subdirectory under data_dir (`{data}/logs`).
181    /// Used on macOS where logs live under the user data dir.
182    pub fn logs(&self) -> PathBuf {
183        self.data_dir.join("logs")
184    }
185
186    // -- macOS sandbox / builder paths ---------------------------------------
187
188    /// macOS VM state directory (`{data}/vms`).
189    pub fn vms(&self) -> PathBuf {
190        self.data_dir.join("vms")
191    }
192
193    /// OCI image storage directory (`{data}/images`).
194    pub fn images(&self) -> PathBuf {
195        self.data_dir.join("images")
196    }
197
198    /// Local binary directory (`{data}/bin`).
199    pub fn bin(&self) -> PathBuf {
200        self.data_dir.join("bin")
201    }
202
203    /// Toolchain download cache directory (`{data}/toolchain-cache`).
204    pub fn toolchain_cache(&self) -> PathBuf {
205        self.data_dir.join("toolchain-cache")
206    }
207
208    /// Temporary build directory (`{data}/tmp`).
209    pub fn tmp(&self) -> PathBuf {
210        self.data_dir.join("tmp")
211    }
212}
213
214// -- Internal helpers --------------------------------------------------------
215
216fn home_dir_or_tmp() -> PathBuf {
217    std::env::var_os("HOME")
218        .map(PathBuf::from)
219        .unwrap_or_else(|| PathBuf::from("/tmp"))
220}
221
222#[cfg(not(any(target_os = "macos", target_os = "windows")))]
223fn is_root() -> bool {
224    #[cfg(unix)]
225    {
226        nix::unistd::geteuid().is_root()
227    }
228    #[cfg(not(unix))]
229    {
230        false
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    #[test]
239    fn subdirectories_are_relative_to_data_dir() {
240        let dirs = ZLayerDirs::new("/test/data");
241        assert_eq!(dirs.containers(), PathBuf::from("/test/data/containers"));
242        assert_eq!(dirs.rootfs(), PathBuf::from("/test/data/rootfs"));
243        assert_eq!(dirs.bundles(), PathBuf::from("/test/data/bundles"));
244        assert_eq!(dirs.cache(), PathBuf::from("/test/data/cache"));
245        assert_eq!(dirs.volumes(), PathBuf::from("/test/data/volumes"));
246        assert_eq!(dirs.wasm(), PathBuf::from("/test/data/wasm"));
247        assert_eq!(
248            dirs.wasm_compiled(),
249            PathBuf::from("/test/data/wasm/compiled")
250        );
251        assert_eq!(dirs.secrets(), PathBuf::from("/test/data/secrets"));
252        assert_eq!(dirs.certs(), PathBuf::from("/test/data/certs"));
253        assert_eq!(dirs.raft(), PathBuf::from("/test/data/raft"));
254        assert_eq!(
255            dirs.admin_password(),
256            PathBuf::from("/test/data/admin_password")
257        );
258        assert_eq!(dirs.daemon_json(), PathBuf::from("/test/data/daemon.json"));
259        assert_eq!(dirs.logs(), PathBuf::from("/test/data/logs"));
260        assert_eq!(dirs.vms(), PathBuf::from("/test/data/vms"));
261        assert_eq!(dirs.images(), PathBuf::from("/test/data/images"));
262        assert_eq!(dirs.bin(), PathBuf::from("/test/data/bin"));
263        assert_eq!(
264            dirs.toolchain_cache(),
265            PathBuf::from("/test/data/toolchain-cache")
266        );
267        assert_eq!(dirs.tmp(), PathBuf::from("/test/data/tmp"));
268    }
269
270    #[test]
271    fn system_default_uses_default_data_dir() {
272        let dirs = ZLayerDirs::system_default();
273        assert_eq!(dirs.data_dir(), ZLayerDirs::default_data_dir().as_path());
274    }
275}