mod process;
use std::{
ffi::{CString, OsStr, OsString},
fs::{self, DirBuilder, File, OpenOptions, Permissions},
future::Future,
io,
os::{
fd::{AsRawFd, FromRawFd},
unix::{
ffi::OsStrExt,
fs::{DirBuilderExt, MetadataExt, OpenOptionsExt, PermissionsExt},
process::ExitStatusExt,
},
},
path::{Path, PathBuf},
process::ExitStatus,
};
use tempfile::TempDir;
use tokio::signal::unix::{SignalKind, signal};
use super::FileKind;
pub(crate) use process::{Child, process_start_time, spawn_detached, spawn_gated};
pub(crate) fn create_private_dir_all(path: &Path) -> io::Result<()> {
DirBuilder::new().recursive(true).mode(0o700).create(path)
}
pub(crate) fn create_private_dir(path: &Path) -> io::Result<()> {
DirBuilder::new().mode(0o700).create(path)?;
fs::set_permissions(path, Permissions::from_mode(0o700))
}
pub(crate) fn private_tempdir_in(prefix: &str, parent: &Path) -> io::Result<TempDir> {
let directory = tempfile::Builder::new()
.prefix(prefix)
.permissions(Permissions::from_mode(0o700))
.tempdir_in(parent)?;
fs::set_permissions(directory.path(), Permissions::from_mode(0o700))?;
Ok(directory)
}
pub(crate) fn is_private_dir(path: &Path) -> io::Result<bool> {
Ok(fs::metadata(path)?.mode() & 0o077 == 0)
}
pub(crate) fn is_private_file(file: &File) -> io::Result<bool> {
let metadata = file.metadata()?;
Ok(metadata.is_file() && metadata.mode() & 0o777 == 0o600 && metadata.nlink() == 1)
}
pub(crate) fn private_options() -> OpenOptions {
let mut options = OpenOptions::new();
options.mode(0o600);
options
}
pub(crate) fn restrict_file(file: &File) -> io::Result<()> {
file.set_permissions(Permissions::from_mode(0o600))
}
pub(crate) fn canonicalize(path: &Path) -> io::Result<PathBuf> {
fs::canonicalize(path)
}
pub(crate) fn sync_dir(path: &Path) -> io::Result<()> {
File::open(path).and_then(|directory| directory.sync_all())
}
pub(crate) fn open_no_follow(options: &mut OpenOptions, path: &Path) -> io::Result<File> {
options
.custom_flags(libc::O_NOFOLLOW | libc::O_NONBLOCK)
.open(path)
}
pub(crate) fn open_nonblocking(path: &Path) -> io::Result<File> {
OpenOptions::new()
.read(true)
.custom_flags(libc::O_NONBLOCK)
.open(path)
}
pub(crate) fn open_directory(path: &Path) -> io::Result<File> {
File::open(path)
}
pub(crate) struct EntryName(CString);
impl EntryName {
pub fn new(name: &OsStr) -> Option<Self> {
CString::new(name.as_bytes()).ok().map(Self)
}
}
pub(crate) fn open_entry(directory: &File, name: &EntryName) -> io::Result<File> {
let fd = unsafe {
libc::openat(
directory.as_raw_fd(),
name.0.as_ptr(),
libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_NONBLOCK | libc::O_CLOEXEC,
)
};
if fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { File::from_raw_fd(fd) })
}
pub(crate) fn read_dir(
directory: &File,
) -> io::Result<impl Iterator<Item = io::Result<DirEntry>> + '_> {
Ok(
fs::read_dir(format!("/proc/self/fd/{}", directory.as_raw_fd()))?
.map(|entry| entry.map(DirEntry)),
)
}
pub(crate) struct DirEntry(fs::DirEntry);
impl DirEntry {
pub fn file_name(&self) -> OsString {
self.0.file_name()
}
pub fn file_type(&self) -> io::Result<FileKind> {
self.0.file_type().map(kind)
}
}
pub(crate) fn entry_kind(directory: &File, name: &OsStr) -> io::Result<FileKind> {
let path = Path::new(&format!("/proc/self/fd/{}", directory.as_raw_fd())).join(name);
fs::symlink_metadata(path).map(|metadata| kind(metadata.file_type()))
}
fn kind(file_type: fs::FileType) -> FileKind {
if file_type.is_file() {
FileKind::File
} else if file_type.is_dir() {
FileKind::Directory
} else if file_type.is_symlink() {
FileKind::Symlink
} else {
FileKind::Other
}
}
pub(crate) fn is_executable(path: &Path) -> bool {
path.metadata()
.is_ok_and(|m| m.is_file() && m.permissions().mode() & 0o111 != 0)
}
pub(crate) const DEFAULT_EDITOR: &str = "vi";
pub(crate) fn editor(editor: &str, file: &Path) -> tokio::process::Command {
let mut command = tokio::process::Command::new("sh");
command
.arg("-c")
.arg(format!("{editor} \"$1\""))
.arg("sh")
.arg(file);
command
}
pub(crate) fn exit_signal(status: &ExitStatus) -> Option<i32> {
status.signal()
}
pub(crate) fn host_name() -> Option<String> {
fs::read_to_string("/proc/sys/kernel/hostname").ok()
}
pub(crate) fn stop_requested() -> io::Result<impl Future<Output = ()> + Send + 'static> {
let mut interrupt = signal(SignalKind::interrupt())?;
let mut terminate = signal(SignalKind::terminate())?;
Ok(async move {
tokio::select! { _ = interrupt.recv() => {}, _ = terminate.recv() => {} }
})
}
pub(crate) struct HiddenInput(libc::termios);
impl HiddenInput {
pub fn new() -> io::Result<Self> {
let mut termios = std::mem::MaybeUninit::uninit();
let saved = unsafe {
if libc::tcgetattr(libc::STDIN_FILENO, termios.as_mut_ptr()) != 0 {
return Err(io::Error::last_os_error());
}
termios.assume_init()
};
let mut hidden = saved;
hidden.c_lflag &= !libc::ECHO;
if unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &hidden) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(Self(saved))
}
}
impl Drop for HiddenInput {
fn drop(&mut self) {
unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &self.0) };
}
}