use std::path::PathBuf;
pub fn is_rootless() -> bool {
unsafe { libc::getuid() != 0 }
}
pub fn data_dir() -> PathBuf {
let system_dir = PathBuf::from("/var/lib/pelagos");
if system_dir.exists() || !is_rootless() {
return system_dir;
}
if let Ok(xdg) = std::env::var("XDG_DATA_HOME") {
if !xdg.is_empty() {
return PathBuf::from(xdg).join("pelagos");
}
}
if let Ok(home) = std::env::var("HOME") {
return PathBuf::from(home).join(".local/share/pelagos");
}
PathBuf::from(format!("/tmp/pelagos-data-{}", unsafe { libc::getuid() }))
}
pub fn runtime_dir() -> PathBuf {
if is_rootless() {
if let Ok(xdg) = std::env::var("XDG_RUNTIME_DIR") {
if !xdg.is_empty() {
return PathBuf::from(xdg).join("pelagos");
}
}
let uid = unsafe { libc::getuid() };
let fallback = PathBuf::from(format!("/tmp/pelagos-{}", uid));
if !fallback.exists() {
let _ = std::fs::create_dir_all(&fallback);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&fallback, std::fs::Permissions::from_mode(0o700));
}
}
fallback
} else {
PathBuf::from("/run/pelagos")
}
}
pub fn images_dir() -> PathBuf {
data_dir().join("images")
}
pub fn layers_dir() -> PathBuf {
data_dir().join("layers")
}
pub fn volumes_dir() -> PathBuf {
data_dir().join("volumes")
}
pub fn rootfs_store_dir() -> PathBuf {
data_dir().join("rootfs")
}
pub fn counter_file() -> PathBuf {
data_dir().join("container_counter")
}
pub fn build_cache_dir() -> PathBuf {
data_dir().join("build-cache")
}
pub fn blobs_dir() -> PathBuf {
data_dir().join("blobs")
}
pub fn blob_path(digest: &str) -> PathBuf {
let hex = digest.strip_prefix("sha256:").unwrap_or(digest);
blobs_dir().join(format!("{}.tar.gz", hex))
}
pub fn blob_diffid_path(digest: &str) -> PathBuf {
let hex = digest.strip_prefix("sha256:").unwrap_or(digest);
blobs_dir().join(format!("{}.diffid", hex))
}
pub fn containers_dir() -> PathBuf {
runtime_dir().join("containers")
}
pub fn oci_state_dir(id: &str) -> PathBuf {
runtime_dir().join(id)
}
pub fn overlay_base(pid: i32, n: u32) -> PathBuf {
runtime_dir().join(format!("overlay-{}-{}", pid, n))
}
pub fn dns_dir(pid: i32, n: u32) -> PathBuf {
runtime_dir().join(format!("dns-{}-{}", pid, n))
}
pub fn hosts_dir(pid: i32, n: u32) -> PathBuf {
runtime_dir().join(format!("hosts-{}-{}", pid, n))
}
pub fn ipam_file() -> PathBuf {
runtime_dir().join("next_ip")
}
pub fn nat_refcount_file() -> PathBuf {
runtime_dir().join("nat_refcount")
}
pub fn port_forwards_file() -> PathBuf {
runtime_dir().join("port_forwards")
}
pub fn dns_config_dir() -> PathBuf {
runtime_dir().join("dns")
}
pub fn dns_pid_file() -> PathBuf {
dns_config_dir().join("pid")
}
pub fn dns_network_file(name: &str) -> PathBuf {
dns_config_dir().join(name)
}
pub fn dns_backend_file() -> PathBuf {
dns_config_dir().join("backend")
}
pub fn dns_dnsmasq_conf() -> PathBuf {
dns_config_dir().join("dnsmasq.conf")
}
pub fn dns_hosts_file(network_name: &str) -> PathBuf {
dns_config_dir().join(format!("hosts.{}", network_name))
}
pub fn compose_dir() -> PathBuf {
runtime_dir().join("compose")
}
pub fn compose_project_dir(project: &str) -> PathBuf {
compose_dir().join(project)
}
pub fn compose_state_file(project: &str) -> PathBuf {
compose_project_dir(project).join("state.json")
}
pub fn networks_config_dir() -> PathBuf {
data_dir().join("networks")
}
pub fn network_config_dir(name: &str) -> PathBuf {
networks_config_dir().join(name)
}
pub fn network_runtime_dir(name: &str) -> PathBuf {
runtime_dir().join("networks").join(name)
}
pub fn network_ipam_file(name: &str) -> PathBuf {
network_runtime_dir(name).join("next_ip")
}
pub fn network_nat_refcount_file(name: &str) -> PathBuf {
network_runtime_dir(name).join("nat_refcount")
}
pub fn network_port_forwards_file(name: &str) -> PathBuf {
network_runtime_dir(name).join("port_forwards")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_rootless_returns_bool() {
let _ = is_rootless();
}
#[test]
fn test_data_dir_is_absolute() {
assert!(data_dir().is_absolute());
}
#[test]
fn test_runtime_dir_is_absolute() {
assert!(runtime_dir().is_absolute());
}
#[test]
fn test_derived_paths_under_data_dir() {
let data = data_dir();
assert!(images_dir().starts_with(&data));
assert!(layers_dir().starts_with(&data));
assert!(volumes_dir().starts_with(&data));
assert!(rootfs_store_dir().starts_with(&data));
assert!(counter_file().starts_with(&data));
assert!(blobs_dir().starts_with(&data));
}
#[test]
fn test_blob_path() {
let p = blob_path("sha256:abc123");
assert_eq!(p, blobs_dir().join("abc123.tar.gz"));
let p2 = blob_path("abc123");
assert_eq!(p2, blobs_dir().join("abc123.tar.gz"));
}
#[test]
fn test_derived_paths_under_runtime_dir() {
let rt = runtime_dir();
assert!(containers_dir().starts_with(&rt));
assert!(oci_state_dir("test").starts_with(&rt));
assert!(overlay_base(1, 0).starts_with(&rt));
assert!(dns_dir(1, 0).starts_with(&rt));
assert!(hosts_dir(1, 0).starts_with(&rt));
assert!(ipam_file().starts_with(&rt));
assert!(nat_refcount_file().starts_with(&rt));
assert!(port_forwards_file().starts_with(&rt));
}
#[test]
fn test_network_config_paths_under_data_dir() {
let data = data_dir();
assert!(networks_config_dir().starts_with(&data));
assert!(network_config_dir("frontend").starts_with(&data));
assert_eq!(
network_config_dir("frontend"),
networks_config_dir().join("frontend")
);
}
#[test]
fn test_network_runtime_paths_under_runtime_dir() {
let rt = runtime_dir();
assert!(network_runtime_dir("frontend").starts_with(&rt));
assert!(network_ipam_file("frontend").starts_with(&rt));
assert!(network_nat_refcount_file("frontend").starts_with(&rt));
assert!(network_port_forwards_file("frontend").starts_with(&rt));
}
#[test]
fn test_compose_paths_under_runtime_dir() {
let rt = runtime_dir();
assert!(compose_dir().starts_with(&rt));
assert!(compose_project_dir("myapp").starts_with(&rt));
assert!(compose_state_file("myapp").starts_with(&rt));
assert_eq!(compose_project_dir("myapp"), compose_dir().join("myapp"));
assert_eq!(
compose_state_file("myapp"),
compose_project_dir("myapp").join("state.json")
);
}
#[test]
fn test_dns_paths_under_runtime_dir() {
let rt = runtime_dir();
assert!(dns_config_dir().starts_with(&rt));
assert!(dns_pid_file().starts_with(&rt));
assert!(dns_network_file("pelagos0").starts_with(&rt));
assert_eq!(
dns_network_file("frontend"),
dns_config_dir().join("frontend")
);
}
#[test]
fn test_dns_dnsmasq_paths_under_runtime_dir() {
let rt = runtime_dir();
assert!(dns_backend_file().starts_with(&rt));
assert!(dns_dnsmasq_conf().starts_with(&rt));
assert!(dns_hosts_file("pelagos0").starts_with(&rt));
assert_eq!(
dns_hosts_file("frontend"),
dns_config_dir().join("hosts.frontend")
);
}
}