#![allow(unsafe_code)]
use crate::error::Result;
use crate::libpod::Client;
#[cfg(any(not(windows), test))]
use std::path::Path;
#[cfg(any(not(windows), test))]
const ROOT_SOCKET: &str = "/run/podman/podman.sock";
#[cfg(windows)]
const DEFAULT_PIPE: &str = "//./pipe/podman-machine-default";
pub fn connect(socket_path: Option<&str>) -> Result<Client> {
let default_path = default_socket_path();
let raw = socket_path.unwrap_or(&default_path);
let path = raw
.strip_prefix("unix://")
.or_else(|| raw.strip_prefix("npipe://"))
.unwrap_or(raw);
Ok(Client::new(path))
}
pub fn connect_from_env() -> Result<Client> {
let socket = std::env::var("PODMAN_SOCKET")
.or_else(|_| std::env::var("DOCKER_HOST"))
.ok();
let path = socket.as_deref().and_then(|s| {
s.strip_prefix("unix://")
.or_else(|| s.strip_prefix("npipe://"))
});
connect(path)
}
#[cfg(not(windows))]
pub(crate) fn default_socket_path() -> String {
let candidates = candidate_socket_paths();
first_existing(&candidates)
.or_else(machine_socket_path)
.or_else(|| candidates.into_iter().next())
.unwrap_or_else(|| ROOT_SOCKET.to_string())
}
#[cfg(windows)]
pub(crate) fn default_socket_path() -> String {
machine_socket_path().unwrap_or_else(|| DEFAULT_PIPE.to_string())
}
#[cfg(all(unix, not(target_os = "macos")))]
fn candidate_socket_paths() -> Vec<String> {
let uid = unsafe { libc::getuid() };
runtime_candidates(uid, std::env::var("XDG_RUNTIME_DIR").ok().as_deref())
}
#[cfg(target_os = "macos")]
fn candidate_socket_paths() -> Vec<String> {
match std::env::var("HOME") {
Ok(home) => machine_candidates(&home),
Err(_) => vec![ROOT_SOCKET.to_string()],
}
}
#[cfg(any(all(unix, not(target_os = "macos")), test))]
fn runtime_candidates(uid: u32, xdg_runtime_dir: Option<&str>) -> Vec<String> {
if uid == 0 {
return vec![ROOT_SOCKET.to_string()];
}
let mut candidates = Vec::new();
if let Some(dir) = xdg_runtime_dir {
if !dir.is_empty() {
candidates.push(format!("{dir}/podman/podman.sock"));
}
}
let run_user = format!("/run/user/{uid}/podman/podman.sock");
if !candidates.contains(&run_user) {
candidates.push(run_user);
}
candidates
}
#[cfg(any(target_os = "macos", test))]
fn machine_candidates(home: &str) -> Vec<String> {
let machine_dir = format!("{home}/.local/share/containers/podman/machine");
vec![
format!("{machine_dir}/podman.sock"),
format!("{machine_dir}/applehv/podman.sock"),
format!("{machine_dir}/vz/podman.sock"),
format!("{machine_dir}/qemu/podman.sock"),
format!("{machine_dir}/podman-machine-default/podman.sock"),
]
}
#[cfg(target_os = "macos")]
fn machine_socket_path() -> Option<String> {
let output = std::process::Command::new("podman")
.args([
"machine",
"inspect",
"--format",
"{{ .ConnectionInfo.PodmanSocket.Path }}",
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let path = String::from_utf8(output.stdout).ok()?.trim().to_string();
(!path.is_empty() && Path::new(&path).exists()).then_some(path)
}
#[cfg(windows)]
fn machine_socket_path() -> Option<String> {
let output = std::process::Command::new("podman")
.args([
"machine",
"inspect",
"--format",
"{{ .ConnectionInfo.PodmanPipe.Path }}",
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let path = String::from_utf8(output.stdout).ok()?.trim().to_string();
(!path.is_empty() && path != "<nil>").then_some(path)
}
#[cfg(all(unix, not(target_os = "macos")))]
fn machine_socket_path() -> Option<String> {
None
}
#[cfg(any(not(windows), test))]
fn first_existing(candidates: &[String]) -> Option<String> {
candidates.iter().find(|p| Path::new(p).exists()).cloned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_existing_picks_first_match() {
let dir = tempfile::tempdir().unwrap();
let hit = dir.path().join("podman.sock");
std::fs::write(&hit, b"").unwrap();
let candidates = vec![
dir.path().join("missing.sock").display().to_string(),
hit.display().to_string(),
dir.path().join("later.sock").display().to_string(),
];
assert_eq!(first_existing(&candidates), Some(hit.display().to_string()));
}
#[test]
fn first_existing_none_when_no_candidate_exists() {
let candidates = vec!["/nonexistent/podup-test/podman.sock".to_string()];
assert_eq!(first_existing(&candidates), None);
}
#[test]
fn runtime_candidates_root_uses_system_socket() {
let candidates = runtime_candidates(0, Some("/run/user/0"));
assert_eq!(candidates, vec![ROOT_SOCKET.to_string()]);
}
#[test]
fn runtime_candidates_prefers_xdg_runtime_dir() {
let candidates = runtime_candidates(1000, Some("/custom/runtime"));
assert_eq!(
candidates,
vec![
"/custom/runtime/podman/podman.sock".to_string(),
"/run/user/1000/podman/podman.sock".to_string(),
]
);
}
#[test]
fn runtime_candidates_dedupes_default_runtime_dir() {
let candidates = runtime_candidates(1000, Some("/run/user/1000"));
assert_eq!(
candidates,
vec!["/run/user/1000/podman/podman.sock".to_string()]
);
}
#[test]
fn runtime_candidates_ignores_empty_runtime_dir() {
let candidates = runtime_candidates(1000, Some(""));
assert_eq!(
candidates,
vec!["/run/user/1000/podman/podman.sock".to_string()]
);
}
#[test]
fn machine_candidates_cover_known_layouts() {
let machine_dir = "/Users/dev/.local/share/containers/podman/machine";
assert_eq!(
machine_candidates("/Users/dev"),
vec![
format!("{machine_dir}/podman.sock"),
format!("{machine_dir}/applehv/podman.sock"),
format!("{machine_dir}/vz/podman.sock"),
format!("{machine_dir}/qemu/podman.sock"),
format!("{machine_dir}/podman-machine-default/podman.sock"),
]
);
}
#[test]
fn connect_strips_unix_scheme() {
let c = connect(Some("unix:///run/user/1000/podman/podman.sock")).unwrap();
drop(c);
}
#[test]
fn connect_strips_npipe_scheme() {
let c = connect(Some("npipe:////./pipe/podman")).unwrap();
drop(c);
}
#[test]
fn connect_passes_plain_path_unchanged() {
let c = connect(Some("/run/user/1000/podman/podman.sock")).unwrap();
drop(c);
}
}