use std::path::{Path, PathBuf};
use libc::geteuid;
use log::{debug, trace};
const LOAD_PATHS_SYSTEM_MODE: &[(&str, bool, bool)] = &[
("/etc/voa/", false, true),
("/run/voa/", true, true),
("/usr/local/share/voa/", false, false),
("/usr/share/voa/", false, false),
];
#[derive(Clone, Debug)]
pub struct LoadPathFilter {
pub ephemeral: bool,
pub writable: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct LoadPath {
pub path: PathBuf,
ephemeral: bool,
writable: bool,
}
impl LoadPath {
pub(crate) fn new(path: impl Into<PathBuf>, ephemeral: bool, writable: bool) -> Self {
Self {
path: path.into(),
ephemeral,
writable,
}
}
pub fn ephemeral(&self) -> bool {
self.ephemeral
}
pub fn writable(&self) -> bool {
self.writable
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn matches_filter(&self, filter: &LoadPathFilter) -> bool {
self.ephemeral == filter.ephemeral && self.writable == filter.writable
}
}
impl From<&(&str, bool, bool)> for LoadPath {
fn from(value: &(&str, bool, bool)) -> Self {
Self {
path: value.0.into(),
ephemeral: value.1,
writable: value.2,
}
}
}
#[derive(Debug)]
pub struct LoadPathList(Vec<LoadPath>);
impl LoadPathList {
pub(crate) fn load_path_list_system() -> LoadPathList {
let paths = LOAD_PATHS_SYSTEM_MODE.iter().map(Into::into).collect();
LoadPathList(paths)
}
pub(crate) fn load_path_list_user() -> LoadPathList {
let mut paths = vec![];
if let Some(proj_dirs) = directories::ProjectDirs::from("voa", "VOA", "VOA") {
paths.push(LoadPath::new(
proj_dirs.config_dir().to_path_buf(),
false,
true,
));
let xdg = xdg::BaseDirectories::with_prefix("voa");
xdg.get_config_dirs()
.into_iter()
.for_each(|dir| paths.push(LoadPath::new(dir, false, false)));
if let Some(runtime_dir) = proj_dirs.runtime_dir() {
paths.push(LoadPath::new(runtime_dir, true, true));
}
paths.push(LoadPath::new(proj_dirs.data_dir(), false, false));
let mut data_dirs = xdg.get_data_dirs();
if data_dirs.is_empty() {
data_dirs.push("/usr/local/share/voa/".into());
data_dirs.push("/usr/share/voa/".into());
}
data_dirs
.into_iter()
.for_each(|dir| paths.push(LoadPath::new(dir, false, false)));
}
LoadPathList(paths)
}
pub fn from_effective_user() -> Self {
let euid = unsafe { geteuid() };
trace!("LoadPathList::from_effective_user called with process user id {euid}");
if euid < 1000 {
debug!("⤷ Using system mode load paths");
Self::load_path_list_system()
} else {
debug!("⤷ Using user mode load paths");
Self::load_path_list_user()
}
}
pub(crate) fn legal_symlink_load_paths(&self, current: &LoadPath) -> Vec<&LoadPath> {
let mut legal = vec![];
let mut searching = true;
for path in &self.0 {
if searching {
if path.path == current.path {
searching = false;
if !path.ephemeral {
legal.push(path);
}
}
} else if !path.ephemeral {
legal.push(path);
}
}
legal
}
pub fn paths(&self) -> &[LoadPath] {
&self.0
}
pub fn filter(&self, filter: &LoadPathFilter) -> Vec<&LoadPath> {
self.0
.iter()
.filter(|load_path| load_path.matches_filter(filter))
.collect()
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(
("/etc/voa/", false, true),
&[("/etc/voa/", false, true),
("/usr/local/share/voa/", false, false),
("/usr/share/voa/", false, false)
]
)]
#[case(
("/run/voa/", true, true),
&[("/usr/local/share/voa/", false, false),
("/usr/share/voa/", false, false)
]
)]
#[case(
("/usr/local/share/voa/", false, false),
&[("/usr/local/share/voa/", false, false),
("/usr/share/voa/", false, false)
]
)]
#[case(
("/usr/share/voa/", false, false),
&[("/usr/share/voa/", false, false)]
)]
#[case(
("/foo/bar/", false, false),
&[]
)]
fn test_legal_symlink_load_paths(
#[case] current: (&str, bool, bool),
#[case] expected: &[(&str, bool, bool)],
) -> testresult::TestResult {
let load_path_list = LoadPathList(LOAD_PATHS_SYSTEM_MODE.iter().map(Into::into).collect());
let expected_paths: Vec<_> = expected.iter().map(Into::into).collect();
let legal = load_path_list.legal_symlink_load_paths(&(¤t).into());
assert_eq!(legal, expected_paths.iter().collect::<Vec<_>>());
Ok(())
}
}