diff --git a/crates/fs/src/ensure_file.rs b/crates/fs/src/ensure_file.rs new file mode 100644 index 00000000..58bca3bf --- /dev/null +++ b/crates/fs/src/ensure_file.rs @@ -0,0 +1,66 @@ +use derive_more::{Display, Error}; +use miette::Diagnostic; +use std::{ + fs::{self, OpenOptions}, + io::{self, Write}, + path::{Path, PathBuf}, +}; + +/// Error type of [`ensure_file`]. +#[derive(Debug, Display, Error, Diagnostic)] +pub enum EnsureFileError { + #[display("Failed to create the parent directory at {parent_dir:?}: {error}")] + CreateDir { + parent_dir: PathBuf, + #[error(source)] + error: io::Error, + }, + #[display("Failed to create file at {file_path:?}: {error}")] + CreateFile { + file_path: PathBuf, + #[error(source)] + error: io::Error, + }, + #[display("Failed to write to file at {file_path:?}: {error}")] + WriteFile { + file_path: PathBuf, + #[error(source)] + error: io::Error, + }, +} + +/// Write `content` to `file_path` unless it already exists. +/// +/// Ancestor directories will be created if they don't already exist. +pub fn ensure_file( + file_path: &Path, + content: &[u8], + #[cfg_attr(windows, allow(unused))] mode: Option, +) -> Result<(), EnsureFileError> { + if file_path.exists() { + return Ok(()); + } + + let parent_dir = file_path.parent().unwrap(); + fs::create_dir_all(parent_dir).map_err(|error| EnsureFileError::CreateDir { + parent_dir: parent_dir.to_path_buf(), + error, + })?; + + let mut options = OpenOptions::new(); + options.write(true).create(true); + + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + if let Some(mode) = mode { + options.mode(mode); + } + } + + options + .open(file_path) + .map_err(|error| EnsureFileError::CreateFile { file_path: file_path.to_path_buf(), error })? + .write_all(content) + .map_err(|error| EnsureFileError::WriteFile { file_path: file_path.to_path_buf(), error }) +} diff --git a/crates/fs/src/file_mode.rs b/crates/fs/src/file_mode.rs new file mode 100644 index 00000000..04ffed14 --- /dev/null +++ b/crates/fs/src/file_mode.rs @@ -0,0 +1,32 @@ +use std::io; + +/// Bit mask to filter executable bits (`--x--x--x`). +pub const EXEC_MASK: u32 = 0b001_001_001; + +/// All can read and execute, but only owner can write (`rwxr-xr-x`). +pub const EXEC_MODE: u32 = 0b111_101_101; + +/// Whether a file mode has all executable bits. +pub fn is_all_exec(mode: u32) -> bool { + mode & EXEC_MASK == EXEC_MASK +} + +/// Set file mode to 777 on POSIX platforms such as Linux or macOS, +/// or do nothing on Windows. +#[cfg_attr(windows, allow(unused))] +pub fn make_file_executable(file: &std::fs::File) -> io::Result<()> { + #[cfg(unix)] + return { + use std::{ + fs::Permissions, + os::unix::fs::{MetadataExt, PermissionsExt}, + }; + let mode = file.metadata()?.mode(); + let mode = mode | EXEC_MASK; + let permissions = Permissions::from_mode(mode); + file.set_permissions(permissions) + }; + + #[cfg(windows)] + return Ok(()); +} diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index cd1d039b..0e489da8 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -1,109 +1,7 @@ -use derive_more::{Display, Error}; -use miette::Diagnostic; -use std::{ - fs::{self, OpenOptions}, - io::{self, Write}, - path::{Path, PathBuf}, -}; +mod ensure_file; +mod symlink_dir; -pub mod file_mode { - /// Bit mask to filter executable bits (`--x--x--x`). - pub const EXEC_MASK: u32 = 0b001_001_001; +pub use ensure_file::*; +pub use symlink_dir::*; - /// All can read and execute, but only owner can write (`rwxr-xr-x`). - pub const EXEC_MODE: u32 = 0b111_101_101; - - /// Whether a file mode has all executable bits. - pub fn is_all_exec(mode: u32) -> bool { - mode & EXEC_MASK == EXEC_MASK - } -} - -/// Create a symlink to a directory. -/// -/// The `link` path will be a symbolic link pointing to `original`. -pub fn symlink_dir(original: &Path, link: &Path) -> io::Result<()> { - #[cfg(unix)] - return std::os::unix::fs::symlink(original, link); - #[cfg(windows)] - return junction::create(original, link); // junctions instead of symlinks because symlinks may require elevated privileges. -} - -/// Error type of [`ensure_file`]. -#[derive(Debug, Display, Error, Diagnostic)] -pub enum EnsureFileError { - #[display("Failed to create the parent directory at {parent_dir:?}: {error}")] - CreateDir { - parent_dir: PathBuf, - #[error(source)] - error: io::Error, - }, - #[display("Failed to create file at {file_path:?}: {error}")] - CreateFile { - file_path: PathBuf, - #[error(source)] - error: io::Error, - }, - #[display("Failed to write to file at {file_path:?}: {error}")] - WriteFile { - file_path: PathBuf, - #[error(source)] - error: io::Error, - }, -} - -/// Write `content` to `file_path` unless it already exists. -/// -/// Ancestor directories will be created if they don't already exist. -pub fn ensure_file( - file_path: &Path, - content: &[u8], - #[cfg_attr(windows, allow(unused))] mode: Option, -) -> Result<(), EnsureFileError> { - if file_path.exists() { - return Ok(()); - } - - let parent_dir = file_path.parent().unwrap(); - fs::create_dir_all(parent_dir).map_err(|error| EnsureFileError::CreateDir { - parent_dir: parent_dir.to_path_buf(), - error, - })?; - - let mut options = OpenOptions::new(); - options.write(true).create(true); - - #[cfg(unix)] - { - use std::os::unix::fs::OpenOptionsExt; - if let Some(mode) = mode { - options.mode(mode); - } - } - - options - .open(file_path) - .map_err(|error| EnsureFileError::CreateFile { file_path: file_path.to_path_buf(), error })? - .write_all(content) - .map_err(|error| EnsureFileError::WriteFile { file_path: file_path.to_path_buf(), error }) -} - -/// Set file mode to 777 on POSIX platforms such as Linux or macOS, -/// or do nothing on Windows. -#[cfg_attr(windows, allow(unused))] -pub fn make_file_executable(file: &std::fs::File) -> io::Result<()> { - #[cfg(unix)] - return { - use std::{ - fs::Permissions, - os::unix::fs::{MetadataExt, PermissionsExt}, - }; - let mode = file.metadata()?.mode(); - let mode = mode | file_mode::EXEC_MASK; - let permissions = Permissions::from_mode(mode); - file.set_permissions(permissions) - }; - - #[cfg(windows)] - return Ok(()); -} +pub mod file_mode; diff --git a/crates/fs/src/symlink_dir.rs b/crates/fs/src/symlink_dir.rs new file mode 100644 index 00000000..08c68721 --- /dev/null +++ b/crates/fs/src/symlink_dir.rs @@ -0,0 +1,11 @@ +use std::{io, path::Path}; + +/// Create a symlink to a directory. +/// +/// The `link` path will be a symbolic link pointing to `original`. +pub fn symlink_dir(original: &Path, link: &Path) -> io::Result<()> { + #[cfg(unix)] + return std::os::unix::fs::symlink(original, link); + #[cfg(windows)] + return junction::create(original, link); // junctions instead of symlinks because symlinks may require elevated privileges. +} diff --git a/tasks/integrated-benchmark/src/work_env.rs b/tasks/integrated-benchmark/src/work_env.rs index f192ad86..74cbd377 100644 --- a/tasks/integrated-benchmark/src/work_env.rs +++ b/tasks/integrated-benchmark/src/work_env.rs @@ -5,7 +5,7 @@ use crate::{ }; use itertools::Itertools; use os_display::Quotable; -use pacquet_fs::make_file_executable; +use pacquet_fs::file_mode::make_file_executable; use pipe_trait::Pipe; use std::{ borrow::Cow,