mirror of
https://github.com/uutils/shadow.git
synced 2026-06-10 16:14:57 -07:00
shadow-rs: replace println!/eprintln! with non-panicking writes (#141)
println!() and eprintln!() panic when stdout/stderr is full or closed (e.g., `command >/dev/full`). Replace all production-code occurrences with non-panicking alternatives: - show_error!/show_warning! macros rewritten to use `let _ = writeln!(stderr())` instead of eprintln! - Normal output uses `let _ = writeln!(stdout)` with locked handles - PAM display_message uses `let _ = writeln!(stderr())` Also fixes a pre-existing clippy::map_unwrap_or lint in atomic.rs. Test code is intentionally left unchanged — panicking on write failure in tests is acceptable and desirable for fast failure. Fixes #141.
This commit is contained in:
@@ -19,6 +19,7 @@ use clap::{Arg, ArgAction, Command};
|
||||
use clap_complete::Shell;
|
||||
use clap_complete::generate;
|
||||
use std::io;
|
||||
use std::io::Write as _;
|
||||
|
||||
fn get_tool_app(name: &str) -> Option<Command> {
|
||||
match name {
|
||||
@@ -144,7 +145,7 @@ fn main() -> std::process::ExitCode {
|
||||
match run() {
|
||||
Ok(()) => std::process::ExitCode::SUCCESS,
|
||||
Err(msg) => {
|
||||
eprintln!("{msg}");
|
||||
let _ = writeln!(std::io::stderr(), "{msg}");
|
||||
std::process::ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
@@ -175,7 +176,11 @@ fn run() -> Result<(), String> {
|
||||
generate(shell, &mut cmd, *name, &mut file);
|
||||
}
|
||||
}
|
||||
eprintln!("generated {} completions in {dir}/", tools.len());
|
||||
let _ = writeln!(
|
||||
std::io::stderr(),
|
||||
"generated {} completions in {dir}/",
|
||||
tools.len()
|
||||
);
|
||||
} else {
|
||||
let stdout = io::stdout();
|
||||
let mut out = stdout.lock();
|
||||
|
||||
+33
-19
@@ -8,6 +8,7 @@
|
||||
//! Dispatches to the appropriate utility based on `argv[0]`.
|
||||
//! When invoked as `shadow-rs <util>`, uses the first argument instead.
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
|
||||
@@ -47,13 +48,25 @@ fn main() -> ExitCode {
|
||||
return to_exit_code(code);
|
||||
}
|
||||
|
||||
eprintln!("shadow-rs: unknown utility '{util_name}'");
|
||||
eprintln!("Run 'shadow-rs --list' for available utilities.");
|
||||
let _ = writeln!(
|
||||
std::io::stderr(),
|
||||
"shadow-rs: unknown utility '{util_name}'"
|
||||
);
|
||||
let _ = writeln!(
|
||||
std::io::stderr(),
|
||||
"Run 'shadow-rs --list' for available utilities."
|
||||
);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
eprintln!("Usage: shadow-rs <utility> [arguments...]");
|
||||
eprintln!("Run 'shadow-rs --list' for available utilities.");
|
||||
let _ = writeln!(
|
||||
std::io::stderr(),
|
||||
"Usage: shadow-rs <utility> [arguments...]"
|
||||
);
|
||||
let _ = writeln!(
|
||||
std::io::stderr(),
|
||||
"Run 'shadow-rs --list' for available utilities."
|
||||
);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
|
||||
@@ -92,34 +105,35 @@ fn dispatch(name: &str, args: &[std::ffi::OsString]) -> Option<i32> {
|
||||
}
|
||||
|
||||
fn print_available_utils() {
|
||||
println!("Available utilities:");
|
||||
let mut out = std::io::stdout().lock();
|
||||
let _ = writeln!(out, "Available utilities:");
|
||||
|
||||
#[cfg(feature = "chage")]
|
||||
println!(" chage");
|
||||
let _ = writeln!(out, " chage");
|
||||
#[cfg(feature = "chfn")]
|
||||
println!(" chfn");
|
||||
let _ = writeln!(out, " chfn");
|
||||
#[cfg(feature = "chpasswd")]
|
||||
println!(" chpasswd");
|
||||
let _ = writeln!(out, " chpasswd");
|
||||
#[cfg(feature = "chsh")]
|
||||
println!(" chsh");
|
||||
let _ = writeln!(out, " chsh");
|
||||
#[cfg(feature = "groupadd")]
|
||||
println!(" groupadd");
|
||||
let _ = writeln!(out, " groupadd");
|
||||
#[cfg(feature = "groupdel")]
|
||||
println!(" groupdel");
|
||||
let _ = writeln!(out, " groupdel");
|
||||
#[cfg(feature = "groupmod")]
|
||||
println!(" groupmod");
|
||||
let _ = writeln!(out, " groupmod");
|
||||
#[cfg(feature = "grpck")]
|
||||
println!(" grpck");
|
||||
let _ = writeln!(out, " grpck");
|
||||
#[cfg(feature = "newgrp")]
|
||||
println!(" newgrp");
|
||||
let _ = writeln!(out, " newgrp");
|
||||
#[cfg(feature = "passwd")]
|
||||
println!(" passwd");
|
||||
let _ = writeln!(out, " passwd");
|
||||
#[cfg(feature = "pwck")]
|
||||
println!(" pwck");
|
||||
let _ = writeln!(out, " pwck");
|
||||
#[cfg(feature = "useradd")]
|
||||
println!(" useradd");
|
||||
let _ = writeln!(out, " useradd");
|
||||
#[cfg(feature = "userdel")]
|
||||
println!(" userdel");
|
||||
let _ = writeln!(out, " userdel");
|
||||
#[cfg(feature = "usermod")]
|
||||
println!(" usermod");
|
||||
let _ = writeln!(out, " usermod");
|
||||
}
|
||||
|
||||
@@ -101,9 +101,9 @@ where
|
||||
|
||||
// Determine permissions: preserve original if target exists, otherwise 0600.
|
||||
// Set mode at creation time to avoid any window where the file is world-readable.
|
||||
let mode = fs::metadata(target)
|
||||
.map(|m| std::os::unix::fs::PermissionsExt::mode(&m.permissions()))
|
||||
.unwrap_or(0o600);
|
||||
let mode = fs::metadata(target).map_or(0o600, |m| {
|
||||
std::os::unix::fs::PermissionsExt::mode(&m.permissions())
|
||||
});
|
||||
|
||||
let mut guard = TmpGuard::new(tmp_path.clone());
|
||||
|
||||
|
||||
@@ -47,7 +47,12 @@ pub enum ShadowError {
|
||||
#[macro_export]
|
||||
macro_rules! show_error {
|
||||
($util:expr, $($arg:tt)*) => {
|
||||
eprintln!("{}: {}", $util, format_args!($($arg)*));
|
||||
{
|
||||
use std::io::Write as _;
|
||||
let mut err = std::io::stderr().lock();
|
||||
let _ = write!(err, "{}: ", $util);
|
||||
let _ = writeln!(err, $($arg)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,6 +60,11 @@ macro_rules! show_error {
|
||||
#[macro_export]
|
||||
macro_rules! show_warning {
|
||||
($util:expr, $($arg:tt)*) => {
|
||||
eprintln!("{}: warning: {}", $util, format_args!($($arg)*));
|
||||
{
|
||||
use std::io::Write as _;
|
||||
let mut err = std::io::stderr().lock();
|
||||
let _ = write!(err, "{}: warning: ", $util);
|
||||
let _ = writeln!(err, $($arg)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -338,7 +338,10 @@ fn display_message(message: &PamMessage, _is_error: bool) {
|
||||
let text = unsafe { CStr::from_ptr(message.msg) };
|
||||
let text = text.to_string_lossy();
|
||||
|
||||
eprintln!("{text}");
|
||||
{
|
||||
use std::io::Write as _;
|
||||
let _ = writeln!(std::io::stderr(), "{text}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Prompt for user input (with or without echo) and return a `malloc`-allocated
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
//! Drop-in replacement for GNU shadow-utils `chage(1)`.
|
||||
|
||||
use std::fmt;
|
||||
use std::io::Write as _;
|
||||
use std::path::Path;
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
@@ -460,13 +461,23 @@ fn print_aging_info(entry: &ShadowEntry) {
|
||||
.warn_days
|
||||
.map_or_else(|| "-1".to_string(), |v| v.to_string());
|
||||
|
||||
println!("Last password change\t\t\t\t\t: {last_change}");
|
||||
println!("Password expires\t\t\t\t\t: {password_expires}");
|
||||
println!("Password inactive\t\t\t\t\t: {password_inactive}");
|
||||
println!("Account expires\t\t\t\t\t\t: {account_expires}");
|
||||
println!("Minimum number of days between password change\t\t: {min_days}");
|
||||
println!("Maximum number of days between password change\t\t: {max_days}");
|
||||
println!("Number of days of warning before password expires\t: {warn_days}");
|
||||
let mut out = std::io::stdout().lock();
|
||||
let _ = writeln!(out, "Last password change\t\t\t\t\t: {last_change}");
|
||||
let _ = writeln!(out, "Password expires\t\t\t\t\t: {password_expires}");
|
||||
let _ = writeln!(out, "Password inactive\t\t\t\t\t: {password_inactive}");
|
||||
let _ = writeln!(out, "Account expires\t\t\t\t\t\t: {account_expires}");
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"Minimum number of days between password change\t\t: {min_days}"
|
||||
);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"Maximum number of days between password change\t\t: {max_days}"
|
||||
);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"Number of days of warning before password expires\t: {warn_days}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Compute the password expiry display string.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
//! Changes the login shell field in `/etc/passwd`.
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{self, BufRead};
|
||||
use std::io::{self, BufRead, Write as _};
|
||||
use std::path::Path;
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
@@ -264,8 +264,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
if shells.is_empty() {
|
||||
uucore::show_error!("no shells found in {}", root.shells_path().display());
|
||||
} else {
|
||||
let mut out = io::stdout().lock();
|
||||
for shell in &shells {
|
||||
println!("{shell}");
|
||||
let _ = writeln!(out, "{shell}");
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
//! Drop-in replacement for GNU shadow-utils `passwd(1)`.
|
||||
|
||||
use std::fmt;
|
||||
use std::io::Write as _;
|
||||
use std::path::Path;
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
@@ -444,6 +445,7 @@ fn cmd_status(root: &SysRoot, target_user: Option<&str>) -> UResult<()> {
|
||||
}
|
||||
};
|
||||
|
||||
let mut out = std::io::stdout().lock();
|
||||
match target_user {
|
||||
Some(user) => {
|
||||
let Some(entry) = entries.iter().find(|e| e.name == user) else {
|
||||
@@ -453,12 +455,12 @@ fn cmd_status(root: &SysRoot, target_user: Option<&str>) -> UResult<()> {
|
||||
))
|
||||
.into());
|
||||
};
|
||||
println!("{}", format_status(entry));
|
||||
let _ = writeln!(out, "{}", format_status(entry));
|
||||
}
|
||||
None => {
|
||||
// --all: show all users.
|
||||
for entry in &entries {
|
||||
println!("{}", format_status(entry));
|
||||
let _ = writeln!(out, "{}", format_status(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,7 +501,8 @@ impl Drop for PrivDrop {
|
||||
if let Err(e) = nix::unistd::seteuid(self.original_euid) {
|
||||
// Failing to restore privileges is a critical error — log it loudly.
|
||||
// We can't return an error from Drop, so at least make it visible.
|
||||
eprintln!(
|
||||
let _ = writeln!(
|
||||
std::io::stderr(),
|
||||
"passwd: CRITICAL: failed to restore euid to {}: {e}",
|
||||
self.original_euid
|
||||
);
|
||||
|
||||
+10
-6
@@ -10,7 +10,7 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::io::BufRead;
|
||||
use std::io::{BufRead, Write as _};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -203,7 +203,7 @@ fn run_checks(opts: &PwckOptions) -> UResult<()> {
|
||||
opts.read_only,
|
||||
)?;
|
||||
} else {
|
||||
eprintln!("pwck: no changes");
|
||||
uucore::show_error!("no changes");
|
||||
}
|
||||
|
||||
if errors > 0 {
|
||||
@@ -426,9 +426,11 @@ fn check_passwd_entries(
|
||||
PathBuf::from(&entry.home)
|
||||
};
|
||||
if !home_path.exists() {
|
||||
eprintln!(
|
||||
let _ = writeln!(
|
||||
std::io::stderr(),
|
||||
"user '{}': directory '{}' does not exist",
|
||||
entry.name, entry.home
|
||||
entry.name,
|
||||
entry.home
|
||||
);
|
||||
errors += 1;
|
||||
}
|
||||
@@ -450,9 +452,11 @@ fn check_passwd_entries(
|
||||
&& !valid_shells.contains(Path::new(&entry.shell))
|
||||
&& !shell_path.exists()
|
||||
{
|
||||
eprintln!(
|
||||
let _ = writeln!(
|
||||
std::io::stderr(),
|
||||
"user '{}': program '{}' does not exist",
|
||||
entry.name, entry.shell
|
||||
entry.name,
|
||||
entry.shell
|
||||
);
|
||||
errors += 1;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
//! directory and populate it from `/etc/skel`.
|
||||
|
||||
use std::fmt;
|
||||
use std::io::Write as _;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -320,13 +321,14 @@ fn cmd_defaults(_matches: &clap::ArgMatches) -> UResult<()> {
|
||||
let default_skel = defs.get("SKEL").unwrap_or("/etc/skel");
|
||||
let default_create_mail = defs.get("CREATE_MAIL_SPOOL").unwrap_or("no");
|
||||
|
||||
println!("GROUP=100");
|
||||
println!("HOME={default_home}");
|
||||
println!("INACTIVE={default_inactive}");
|
||||
println!("EXPIRE={default_expire}");
|
||||
println!("SHELL={default_shell}");
|
||||
println!("SKEL={default_skel}");
|
||||
println!("CREATE_MAIL_SPOOL={default_create_mail}");
|
||||
let mut out = std::io::stdout().lock();
|
||||
let _ = writeln!(out, "GROUP=100");
|
||||
let _ = writeln!(out, "HOME={default_home}");
|
||||
let _ = writeln!(out, "INACTIVE={default_inactive}");
|
||||
let _ = writeln!(out, "EXPIRE={default_expire}");
|
||||
let _ = writeln!(out, "SHELL={default_shell}");
|
||||
let _ = writeln!(out, "SKEL={default_skel}");
|
||||
let _ = writeln!(out, "CREATE_MAIL_SPOOL={default_create_mail}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user