metrale_bench/artifacts/
home.rs1use std::path::{Path, PathBuf};
12
13use anyhow::{Context, Result, bail};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum HomeSource {
20 Env,
22 HomeDefault,
24}
25
26impl HomeSource {
27 pub fn describe(self) -> &'static str {
29 match self {
30 Self::Env => "from METRALE_HOME",
31 Self::HomeDefault => "default, $HOME/.metrale",
32 }
33 }
34}
35
36#[derive(Clone, Debug)]
38pub struct MetraleHome {
39 pub root: PathBuf,
40 pub source: HomeSource,
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
45pub enum HomeFault {
46 NotWritable {
48 owner_uid: Option<u32>,
50 process_uid: Option<u32>,
52 },
53 NotADirectory,
55 Uncreatable(String),
57}
58
59impl std::fmt::Display for HomeFault {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 match self {
62 Self::NotWritable {
63 owner_uid: Some(o),
64 process_uid: Some(p),
65 } if o != p => write!(
66 f,
67 "not writable: owned by uid {o}, this process is uid {p}. A `sudo` \
68 or container run most likely created it. Fix with \
69 `sudo chown -R {p} <path>`, or point METRALE_HOME somewhere this \
70 user owns."
71 ),
72 Self::NotWritable { .. } => write!(
73 f,
74 "not writable by this process. Fix the permissions, or point \
75 METRALE_HOME somewhere this user owns."
76 ),
77 Self::NotADirectory => write!(f, "exists but is not a directory"),
78 Self::Uncreatable(e) => write!(f, "does not exist and could not be created: {e}"),
79 }
80 }
81}
82
83impl MetraleHome {
84 pub fn resolve() -> Result<Self> {
88 resolve_from(std::env::var_os("METRALE_HOME"), std::env::var_os("HOME"))
89 }
90
91 pub fn fault(&self) -> Option<HomeFault> {
97 check_usable(&self.root)
98 }
99
100 pub fn describe(&self) -> String {
102 format!("{} ({})", self.root.display(), self.source.describe())
103 }
104}
105
106pub(super) fn resolve_from(
110 metrale_home: Option<std::ffi::OsString>,
111 home: Option<std::ffi::OsString>,
112) -> Result<MetraleHome> {
113 if let Some(explicit) = metrale_home {
114 let root = PathBuf::from(explicit);
115 if root.as_os_str().is_empty() {
116 bail!("METRALE_HOME is set but empty");
117 }
118 return Ok(MetraleHome {
119 root,
120 source: HomeSource::Env,
121 });
122 }
123 let home = home
124 .map(PathBuf::from)
125 .filter(|h| !h.as_os_str().is_empty())
126 .context("neither METRALE_HOME nor HOME is set — cannot place ~/.metrale")?;
127 Ok(MetraleHome {
128 root: home.join(".metrale"),
129 source: HomeSource::HomeDefault,
130 })
131}
132
133pub(super) fn check_usable(root: &Path) -> Option<HomeFault> {
134 if root.exists() && !root.is_dir() {
135 return Some(HomeFault::NotADirectory);
136 }
137 if !root.exists()
138 && let Err(e) = std::fs::create_dir_all(root)
139 {
140 return Some(HomeFault::Uncreatable(e.to_string()));
141 }
142 let probe = root.join(".metrale-write-probe");
143 match std::fs::write(&probe, b"") {
144 Ok(()) => {
145 let _ = std::fs::remove_file(&probe);
146 None
147 }
148 Err(_) => Some(HomeFault::NotWritable {
149 owner_uid: owner_uid_of(root),
150 process_uid: current_uid(),
151 }),
152 }
153}
154
155#[cfg(unix)]
156fn owner_uid_of(p: &Path) -> Option<u32> {
157 use std::os::unix::fs::MetadataExt;
158 std::fs::metadata(p).ok().map(|m| m.uid())
159}
160
161#[cfg(not(unix))]
162fn owner_uid_of(_p: &Path) -> Option<u32> {
163 None
164}
165
166#[cfg(unix)]
170fn current_uid() -> Option<u32> {
171 use std::os::unix::fs::MetadataExt;
172 std::fs::metadata("/proc/self").ok().map(|m| m.uid())
173}
174
175#[cfg(not(unix))]
176fn current_uid() -> Option<u32> {
177 None
178}