use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
pub const MAX_SOCKET_PATH_LEN: usize = 104;
pub fn runtime_dir() -> Result<PathBuf> {
let base = dirs::data_dir().context("could not determine the user data directory")?;
Ok(base.join("omni-dev"))
}
pub fn socket_path() -> Result<PathBuf> {
Ok(runtime_dir()?.join("daemon.sock"))
}
pub fn token_path() -> Result<PathBuf> {
Ok(runtime_dir()?.join("bridge.token"))
}
pub fn token_path_for_socket(socket: &Path) -> PathBuf {
socket.parent().map_or_else(
|| PathBuf::from("bridge.token"),
|dir| dir.join("bridge.token"),
)
}
pub fn ensure_dir_0700(dir: &Path) -> Result<()> {
std::fs::create_dir_all(dir)
.with_context(|| format!("failed to create directory {}", dir.display()))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700))
.with_context(|| format!("failed to set 0700 on {}", dir.display()))?;
}
Ok(())
}
pub fn set_file_0600(path: &Path) -> Result<()> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
.with_context(|| format!("failed to set 0600 on {}", path.display()))?;
}
#[cfg(not(unix))]
{
let _ = path;
}
Ok(())
}
pub fn check_socket_path_len(path: &Path) -> Result<()> {
let len = path.as_os_str().len();
if len >= MAX_SOCKET_PATH_LEN {
bail!(
"socket path is {len} bytes, exceeding the {MAX_SOCKET_PATH_LEN}-byte limit: {} — \
pass a shorter --socket path",
path.display()
);
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn default_paths_sit_under_the_runtime_dir() {
let rt = runtime_dir().unwrap();
assert!(socket_path().unwrap().starts_with(&rt));
assert!(token_path().unwrap().starts_with(&rt));
}
#[test]
fn token_sits_beside_its_socket() {
assert_eq!(
token_path_for_socket(Path::new("/tmp/x/daemon.sock")),
Path::new("/tmp/x/bridge.token")
);
assert_eq!(
token_path_for_socket(Path::new("daemon.sock")),
Path::new("bridge.token")
);
}
#[test]
fn socket_path_length_guard() {
check_socket_path_len(Path::new("/tmp/short.sock")).unwrap();
let too_long = PathBuf::from(format!("/{}", "a".repeat(MAX_SOCKET_PATH_LEN)));
assert!(check_socket_path_len(&too_long).is_err());
}
#[test]
fn ensure_dir_and_file_perms() {
let dir = tempfile::tempdir().unwrap();
let sub = dir.path().join("run");
ensure_dir_0700(&sub).unwrap();
assert!(sub.is_dir());
let file = sub.join("k");
std::fs::write(&file, "x").unwrap();
set_file_0600(&file).unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
assert_eq!(
std::fs::metadata(&sub).unwrap().permissions().mode() & 0o777,
0o700
);
assert_eq!(
std::fs::metadata(&file).unwrap().permissions().mode() & 0o777,
0o600
);
}
}
}