use anyhow::{bail, Result};
use libcontainer::utils::create_dir_all_with_mode;
use libcontainer::utils::rootless_required;
use nix::libc;
use nix::sys::stat::Mode;
use nix::unistd::getuid;
use std::fs;
use std::path::{Path, PathBuf};
pub fn determine(root_path: Option<PathBuf>) -> Result<PathBuf> {
let uid = getuid().as_raw();
if let Some(path) = root_path {
if !path.exists() {
create_dir_all_with_mode(&path, uid, Mode::S_IRWXU)?;
}
let path = path.canonicalize()?;
return Ok(path);
}
if !rootless_required() {
let path = get_default_not_rootless_path();
create_dir_all_with_mode(&path, uid, Mode::S_IRWXU)?;
return Ok(path);
}
if let Ok(path) = std::env::var("XDG_RUNTIME_DIR") {
let path = Path::new(&path).join("youki");
if create_dir_all_with_mode(&path, uid, Mode::S_IRWXU).is_ok() {
return Ok(path);
}
}
let path = get_default_rootless_path(uid);
if create_dir_all_with_mode(&path, uid, Mode::S_IRWXU).is_ok() {
return Ok(path);
}
if let Ok(path) = std::env::var("HOME") {
if let Ok(resolved) = fs::canonicalize(path) {
let run_dir = resolved.join(".youki/run");
if create_dir_all_with_mode(&run_dir, uid, Mode::S_IRWXU).is_ok() {
return Ok(run_dir);
}
}
}
let tmp_dir = PathBuf::from(format!("/tmp/youki-{uid}"));
if create_dir_all_with_mode(&tmp_dir, uid, Mode::S_IRWXU).is_ok() {
return Ok(tmp_dir);
}
bail!("could not find a storage location with suitable permissions for the current user");
}
#[cfg(not(test))]
fn get_default_not_rootless_path() -> PathBuf {
PathBuf::from("/run/youki")
}
#[cfg(test)]
fn get_default_not_rootless_path() -> PathBuf {
std::env::temp_dir().join("default_youki_path")
}
#[cfg(not(test))]
fn get_default_rootless_path(uid: libc::uid_t) -> PathBuf {
PathBuf::from(format!("/run/user/{uid}/youki"))
}
#[cfg(test)]
fn get_default_rootless_path(uid: libc::uid_t) -> PathBuf {
std::env::temp_dir().join(format!("default_rootless_youki_path_{uid}").as_str())
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::{Context, Result};
use nix::sys::stat::Mode;
use nix::unistd::getuid;
use std::fs;
use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
#[test]
fn test_user_specified() -> Result<()> {
let tmp = tempfile::tempdir()?;
let specified_path = tmp.path().join("provided_path");
let non_abs_path = specified_path.join("../provided_path");
let path = determine(Some(non_abs_path)).context("failed with specified path")?;
assert_eq!(path, specified_path);
Ok(())
}
#[test]
fn test_user_specified_exists() -> Result<()> {
let tmp = tempfile::tempdir()?;
let specified_path = tmp.path().join("provided_path");
std::fs::create_dir(&specified_path).context("failed to create dir")?;
let non_abs_path = specified_path.join("../provided_path");
let path = determine(Some(non_abs_path)).context("failed with specified path")?;
assert_eq!(path, specified_path);
Ok(())
}
#[test]
fn test_determine_root_path_non_rootless() -> Result<()> {
if !getuid().is_root() {
return Ok(());
}
{
let expected_path = super::get_default_not_rootless_path();
let path = determine(None).context("failed with default non rootless path")?;
assert_eq!(path, expected_path);
assert!(path.exists());
fs::remove_dir_all(&expected_path).context("failed to remove dir")?;
}
{
let expected_path = get_default_not_rootless_path();
fs::create_dir(&expected_path).context("failed to create dir")?;
fs::set_permissions(&expected_path, Permissions::from_mode(Mode::S_IRUSR.bits()))
.context("failed to set invalid permissions")?;
assert!(determine(None).is_err());
fs::remove_dir_all(&expected_path).context("failed to remove dir")?;
}
Ok(())
}
#[test]
fn test_determine_root_path_rootless() -> Result<()> {
std::env::set_var("YOUKI_USE_ROOTLESS", "true");
let tmp = tempfile::tempdir()?;
let xdg_dir = tmp.path().join("xdg_runtime");
std::env::set_var("XDG_RUNTIME_DIR", &xdg_dir);
let path = determine(None).context("failed with $XDG_RUNTIME_DIR path")?;
assert_eq!(path, xdg_dir.join("youki"));
assert!(path.exists());
std::env::remove_var("XDG_RUNTIME_DIR");
let uid = getuid().as_raw();
let default_rootless_path = get_default_rootless_path(uid);
scopeguard::defer!({
let _ = fs::remove_dir_all(&default_rootless_path);
});
let path = determine(None).context("failed with default rootless path")?;
assert_eq!(path, default_rootless_path);
assert!(path.exists());
fs::set_permissions(
&default_rootless_path,
Permissions::from_mode(Mode::S_IRUSR.bits()),
)
.context("failed to set invalid permissions")?;
let tmp = tempfile::tempdir()?;
let home_path = tmp.path().join("youki_home");
fs::create_dir_all(&home_path).context("failed to create fake home path")?;
std::env::set_var("HOME", &home_path);
let path = determine(None).context("failed with $HOME path")?;
assert_eq!(path, home_path.join(".youki/run"));
assert!(path.exists());
std::env::remove_var("HOME");
let uid = getuid().as_raw();
let expected_temp_path = PathBuf::from(format!("/tmp/youki-{uid}"));
let path = determine(None).context("failed with temp path")?;
assert_eq!(path, expected_temp_path);
fs::set_permissions(
&expected_temp_path,
Permissions::from_mode(Mode::S_IRUSR.bits()),
)
.context("failed to set invalid permissions")?;
assert!(determine(None).is_err());
fs::remove_dir_all(&expected_temp_path).context("failed to remove dir")?;
Ok(())
}
}