use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
pub fn user_runtime_dir(product: &str) -> PathBuf {
if let Some(dir) = std::env::var_os("XDG_RUNTIME_DIR") {
return PathBuf::from(dir).join(product);
}
let uid = unsafe { libc::getuid() };
PathBuf::from(format!("/tmp/{product}-{uid}"))
}
pub fn user_state_dir(product: &str) -> PathBuf {
if let Some(dir) = std::env::var_os("XDG_STATE_HOME") {
PathBuf::from(dir).join(product)
} else if let Some(home) = dirs::home_dir() {
home.join(".local/state").join(product)
} else {
PathBuf::from(format!("/tmp/{product}-state"))
}
}
pub fn user_run_data_root(product: &str) -> PathBuf {
user_runtime_dir(product)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FileIdentity {
pub device: u64,
pub file: u64,
}
pub fn file_identity(file: &File) -> io::Result<Option<FileIdentity>> {
use std::os::unix::fs::MetadataExt as _;
let metadata = file.metadata()?;
Ok(Some(FileIdentity {
device: metadata.dev(),
file: metadata.ino(),
}))
}
pub fn path_identity(path: &Path) -> io::Result<Option<FileIdentity>> {
use std::os::unix::fs::MetadataExt as _;
let metadata = path.metadata()?;
Ok(Some(FileIdentity {
device: metadata.dev(),
file: metadata.ino(),
}))
}
pub fn open_lock_file(path: &Path) -> io::Result<File> {
use std::os::unix::fs::OpenOptionsExt as _;
std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.mode(0o600)
.open(path)
}
pub fn try_lock_exclusive(file: &File) -> io::Result<()> {
use std::os::unix::io::AsRawFd as _;
let result = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
if result == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn unlock(file: &File) -> io::Result<()> {
use std::os::unix::io::AsRawFd as _;
let result = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
if result == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn is_lock_conflict(error: &io::Error) -> bool {
error.raw_os_error() == Some(libc::EWOULDBLOCK) || error.raw_os_error() == Some(libc::EAGAIN)
}
pub fn encode_path_bytes(path: &Path) -> Vec<u8> {
use std::os::unix::ffi::OsStrExt as _;
path.as_os_str().as_bytes().to_vec()
}
pub fn decode_path_bytes(bytes: &[u8]) -> io::Result<PathBuf> {
use std::os::unix::ffi::OsStringExt as _;
Ok(PathBuf::from(std::ffi::OsString::from_vec(bytes.to_vec())))
}
pub fn user_data_dir(product: &str) -> PathBuf {
if let Some(data_home) = std::env::var_os("XDG_DATA_HOME") {
return PathBuf::from(data_home).join(product);
}
dirs::home_dir()
.unwrap_or_else(std::env::temp_dir)
.join(".local")
.join("share")
.join(product)
}
pub fn user_config_dir(product: &str) -> PathBuf {
if let Some(config_home) = std::env::var_os("XDG_CONFIG_HOME") {
return PathBuf::from(config_home).join(product);
}
dirs::home_dir()
.unwrap_or_else(std::env::temp_dir)
.join(".config")
.join(product)
}
pub fn replace_file(tmp: &Path, target: &Path) -> io::Result<()> {
std::fs::rename(tmp, target)
}
pub fn sync_directory(directory: &Path) -> io::Result<()> {
File::open(directory)?.sync_all()
}
pub fn create_private_file(path: &Path) -> io::Result<File> {
use std::os::unix::fs::OpenOptionsExt as _;
std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o600)
.open(path)
}