diff --git a/.vscode/settings.json b/.vscode/settings.json index aa47801..423b113 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,7 @@ "rsjudge-judger", "rsjudge-rest", "rsjudge-runner", + "rsjudge-utils", "xtask" ], "editor.defaultFormatter": "dprint.dprint", diff --git a/Cargo.lock b/Cargo.lock index 8650950..1bbaa37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1170,6 +1170,8 @@ name = "rsjudge-utils" version = "0.1.0" dependencies = [ "anyhow", + "log", + "shell-words", ] [[package]] @@ -1301,6 +1303,12 @@ dependencies = [ "quote", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "slab" version = "0.4.9" diff --git a/crates/rsjudge-runner/src/lib.rs b/crates/rsjudge-runner/src/lib.rs index 8bc4f8f..00ad559 100644 --- a/crates/rsjudge-runner/src/lib.rs +++ b/crates/rsjudge-runner/src/lib.rs @@ -4,6 +4,7 @@ use std::{os::unix::process::CommandExt as _, process::Command}; use caps::Capability; use nix::unistd::{setgroups, Gid}; +use rsjudge_utils::log_if_error; use uzers::User; pub use crate::{ @@ -26,11 +27,11 @@ pub trait RunAs { impl RunAs for Command { type Error = Error; fn run_as(&mut self, user: &User) -> Result<&mut Self> { - require_caps([ + log_if_error!(require_caps([ Capability::CAP_SETUID, Capability::CAP_SETGID, Capability::CAP_DAC_READ_SEARCH, - ])?; + ]))?; let uid = user.uid(); let gid = user.primary_group_id(); @@ -49,12 +50,11 @@ impl RunAs for Command { .into_iter() .map(|g| Gid::from_raw(g.gid())) .collect(); - unsafe { - self.pre_exec(move || { - setgroups(&groups)?; - Ok(()) - }) + let set_groups = move || { + log_if_error!(setgroups(&groups))?; + Ok(()) }; + unsafe { self.pre_exec(set_groups) }; } #[cfg(setgroups)] diff --git a/crates/rsjudge-utils/Cargo.toml b/crates/rsjudge-utils/Cargo.toml index 9e55852..39bc475 100644 --- a/crates/rsjudge-utils/Cargo.toml +++ b/crates/rsjudge-utils/Cargo.toml @@ -10,3 +10,5 @@ rust-version.workspace = true [dependencies] anyhow = "1.0.81" +log.workspace = true +shell-words = "1.1.0" diff --git a/crates/rsjudge-utils/src/command.rs b/crates/rsjudge-utils/src/command.rs index d06a2c3..e957b36 100644 --- a/crates/rsjudge-utils/src/command.rs +++ b/crates/rsjudge-utils/src/command.rs @@ -1,21 +1,44 @@ use std::{ io::ErrorKind, + iter, process::{Command, Output}, }; use anyhow::{bail, ensure}; /// Display a command in a human-readable format, suitable for error messages. +/// +/// # Examples +/// +/// ``` +/// use std::process::Command; +/// use rsjudge_utils::command::display_cmd; +/// +/// let mut cmd = Command::new("echo"); +/// cmd.arg("Hello, world!"); +/// assert_eq!(display_cmd(&cmd), "\"echo\" \"Hello, world!\""); +/// ``` pub fn display_cmd(cmd: &Command) -> String { - let mut s = format!("{:?}", cmd.get_program().to_string_lossy()); - s.extend( - cmd.get_args() - .map(|arg| format!(" {:?}", arg.to_string_lossy())), - ); - s + let args = iter::once(cmd.get_program()) + .chain(cmd.get_args()) + .map(|arg| arg.to_string_lossy()); + + shell_words::join(args) } /// Run a command, returning the output if succeeded, with some error handling. +/// +/// # Examples +/// +/// ```no_run +/// use std::process::Command; +/// use rsjudge_utils::command::check_output; +/// +/// let mut cmd = Command::new("echo"); +/// cmd.arg("Hello, world!"); +/// let output = check_output(&mut cmd).unwrap(); +/// assert_eq!(output.stdout, b"Hello, world!\n"); +/// ``` pub fn check_output(cmd: &mut Command) -> anyhow::Result { let output = match cmd.output() { Ok(o) => o, diff --git a/crates/rsjudge-utils/src/error_macros.rs b/crates/rsjudge-utils/src/error_macros.rs new file mode 100644 index 0000000..0220dfc --- /dev/null +++ b/crates/rsjudge-utils/src/error_macros.rs @@ -0,0 +1,25 @@ +//! Macro for logging when error occurs. + +/// Log an error message and return an `Err` variant of `Result`. +/// +/// This macro is transparent and does not affect the return value. +/// You may still want to use `?` to propagate the error to the caller. +/// +/// # Examples +/// +/// ``` +/// use anyhow::{anyhow, Result}; +/// use rsjudge_utils::log_if_error; +/// +/// let result: Result<()> = Err(anyhow!("An error")); +/// log_if_error!(result); +/// ``` +#[macro_export] +macro_rules! log_if_error { + ($expr: expr) => { + $expr.map_err(|err| { + ::log::error!("{}", err); + err + }) + }; +} diff --git a/crates/rsjudge-utils/src/lib.rs b/crates/rsjudge-utils/src/lib.rs index 94351b8..aeed86d 100644 --- a/crates/rsjudge-utils/src/lib.rs +++ b/crates/rsjudge-utils/src/lib.rs @@ -4,7 +4,9 @@ #![warn(missing_docs)] -/// Functions for trimming ASCII whitespace from `[u8]` and `str`. +#[macro_use] +mod error_macros; + pub mod trim; /// Functions for working with `std::process::Command`. diff --git a/crates/rsjudge-utils/src/trim.rs b/crates/rsjudge-utils/src/trim.rs index 3468ec4..f603403 100644 --- a/crates/rsjudge-utils/src/trim.rs +++ b/crates/rsjudge-utils/src/trim.rs @@ -1,3 +1,5 @@ +//! Functions for trimming ASCII whitespace from `[u8]` and `str`. + /// Returns a byte slice with leading ASCII whitespace bytes removed. /// /// 'Whitespace' refers to the definition used by