1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2026-01-17 18:51:05 +00:00

Merge branch 'main' into dd-seconds-precision-3

This commit is contained in:
jfinkels 2023-03-19 13:29:14 -04:00 committed by GitHub
commit 59d34ce667
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 1068 additions and 649 deletions

View file

@ -8,6 +8,7 @@
// spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's
use clap::{crate_version, Arg, ArgAction, Command};
use std::ffi::OsString;
use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
@ -35,14 +36,64 @@ mod options {
pub const FILE: &str = "FILE";
}
/// Extract negative modes (starting with '-') from the rest of the arguments.
///
/// This is mainly required for GNU compatibility, where "non-positional negative" modes are used
/// as the actual positional MODE. Some examples of these cases are:
/// * "chmod -w -r file", which is the same as "chmod -w,-r file"
/// * "chmod -w file -r", which is the same as "chmod -w,-r file"
///
/// These can currently not be handled by clap.
/// Therefore it might be possible that a pseudo MODE is inserted to pass clap parsing.
/// The pseudo MODE is later replaced by the extracted (and joined) negative modes.
fn extract_negative_modes(mut args: impl uucore::Args) -> (Option<String>, Vec<OsString>) {
// we look up the args until "--" is found
// "-mode" will be extracted into parsed_cmode_vec
let (parsed_cmode_vec, pre_double_hyphen_args): (Vec<OsString>, Vec<OsString>) =
args.by_ref().take_while(|a| a != "--").partition(|arg| {
let arg = if let Some(arg) = arg.to_str() {
arg.to_string()
} else {
return false;
};
arg.len() >= 2
&& arg.starts_with('-')
&& matches!(
arg.chars().nth(1).unwrap(),
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7'
)
});
let mut clean_args = Vec::new();
if !parsed_cmode_vec.is_empty() {
// we need a pseudo cmode for clap, which won't be used later.
// this is required because clap needs the default "chmod MODE FILE" scheme.
clean_args.push("w".into());
}
clean_args.extend(pre_double_hyphen_args);
if let Some(arg) = args.next() {
// as there is still something left in the iterator, we previously consumed the "--"
// -> add it to the args again
clean_args.push("--".into());
clean_args.push(arg);
}
clean_args.extend(args);
let parsed_cmode = Some(
parsed_cmode_vec
.iter()
.map(|s| s.to_str().unwrap())
.collect::<Vec<&str>>()
.join(","),
)
.filter(|s| !s.is_empty());
(parsed_cmode, clean_args)
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut args = args.collect_lossy();
// Before we can parse 'args' with clap (and previously getopts),
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
let mode_had_minus_prefix = mode::strip_minus_from_mode(&mut args);
let (parsed_cmode, args) = extract_negative_modes(args.skip(1)); // skip binary name
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
let changes = matches.get_flag(options::CHANGES);
@ -62,13 +113,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
},
None => None,
};
let modes = matches.get_one::<String>(options::MODE).unwrap(); // should always be Some because required
let cmode = if mode_had_minus_prefix {
// clap parsing is finished, now put prefix back
format!("-{modes}")
let modes = matches.get_one::<String>(options::MODE);
let cmode = if let Some(parsed_cmode) = parsed_cmode {
parsed_cmode
} else {
modes.to_string()
modes.unwrap().to_string() // modes is required
};
// FIXME: enable non-utf8 paths
let mut files: Vec<String> = matches
.get_many::<String>(options::FILE)
.map(|v| v.map(ToString::to_string).collect())
@ -107,6 +159,7 @@ pub fn uu_app() -> Command {
.override_usage(format_usage(USAGE))
.args_override_self(true)
.infer_long_args(true)
.no_binary_name(true)
.arg(
Arg::new(options::CHANGES)
.long(options::CHANGES)
@ -376,3 +429,34 @@ impl Chmoder {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_negative_modes() {
// "chmod -w -r file" becomes "chmod -w,-r file". clap does not accept "-w,-r" as MODE.
// Therefore, "w" is added as pseudo mode to pass clap.
let (c, a) = extract_negative_modes(vec!["-w", "-r", "file"].iter().map(OsString::from));
assert_eq!(c, Some("-w,-r".to_string()));
assert_eq!(a, vec!["w", "file"]);
// "chmod -w file -r" becomes "chmod -w,-r file". clap does not accept "-w,-r" as MODE.
// Therefore, "w" is added as pseudo mode to pass clap.
let (c, a) = extract_negative_modes(vec!["-w", "file", "-r"].iter().map(OsString::from));
assert_eq!(c, Some("-w,-r".to_string()));
assert_eq!(a, vec!["w", "file"]);
// "chmod -w -- -r file" becomes "chmod -w -r file", where "-r" is interpreted as file.
// Again, "w" is needed as pseudo mode.
let (c, a) = extract_negative_modes(vec!["-w", "--", "-r", "f"].iter().map(OsString::from));
assert_eq!(c, Some("-w".to_string()));
assert_eq!(a, vec!["w", "--", "-r", "f"]);
// "chmod -- -r file" becomes "chmod -r file".
let (c, a) = extract_negative_modes(vec!["--", "-r", "file"].iter().map(OsString::from));
assert_eq!(c, None);
assert_eq!(a, vec!["--", "-r", "file"]);
}
}

23
src/uu/cksum/cksum.md Normal file
View file

@ -0,0 +1,23 @@
# cksum
```
cksum [OPTIONS] [FILE]...
```
Print CRC and size for each file
## After Help
DIGEST determines the digest algorithm and default output format:
- `-a=sysv`: (equivalent to sum -s)
- `-a=bsd`: (equivalent to sum -r)
- `-a=crc`: (equivalent to cksum)
- `-a=md5`: (equivalent to md5sum)
- `-a=sha1`: (equivalent to sha1sum)
- `-a=sha224`: (equivalent to sha224sum)
- `-a=sha256`: (equivalent to sha256sum)
- `-a=sha384`: (equivalent to sha384sum)
- `-a=sha512`: (equivalent to sha512sum)
- `-a=blake2b`: (equivalent to b2sum)
- `-a=sm3`: (only available through cksum)

View file

@ -15,15 +15,16 @@ use std::iter;
use std::path::Path;
use uucore::{
error::{FromIo, UResult},
format_usage,
format_usage, help_about, help_section, help_usage,
sum::{
div_ceil, Blake2b, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha512, Sm3,
BSD, CRC, SYSV,
},
};
const USAGE: &str = "{} [OPTIONS] [FILE]...";
const ABOUT: &str = "Print CRC and size for each file";
const USAGE: &str = help_usage!("cksum.md");
const ABOUT: &str = help_about!("cksum.md");
const AFTER_HELP: &str = help_section!("after help", "cksum.md");
const ALGORITHM_OPTIONS_SYSV: &str = "sysv";
const ALGORITHM_OPTIONS_BSD: &str = "bsd";
@ -205,21 +206,6 @@ mod options {
pub static ALGORITHM: &str = "algorithm";
}
const ALGORITHM_HELP_DESC: &str =
"DIGEST determines the digest algorithm and default output format:\n\
\n\
-a=sysv: (equivalent to sum -s)\n\
-a=bsd: (equivalent to sum -r)\n\
-a=crc: (equivalent to cksum)\n\
-a=md5: (equivalent to md5sum)\n\
-a=sha1: (equivalent to sha1sum)\n\
-a=sha224: (equivalent to sha224sum)\n\
-a=sha256: (equivalent to sha256sum)\n\
-a=sha384: (equivalent to sha384sum)\n\
-a=sha512: (equivalent to sha512sum)\n\
-a=blake2b: (equivalent to b2sum)\n\
-a=sm3: (only available through cksum)\n";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args.collect_ignore();
@ -278,5 +264,5 @@ pub fn uu_app() -> Command {
ALGORITHM_OPTIONS_SM3,
]),
)
.after_help(ALGORITHM_HELP_DESC)
.after_help(AFTER_HELP)
}

View file

@ -22,7 +22,7 @@ use uucore::display::Quotable;
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
use uucore::error::FromIo;
use uucore::error::{UResult, USimpleError};
use uucore::{format_usage, help_about, help_usage, show_error};
use uucore::{format_usage, help_about, help_usage, show};
#[cfg(windows)]
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
@ -114,8 +114,8 @@ impl<'a> From<&'a str> for Iso8601Format {
SECONDS | SECOND => Self::Seconds,
NS => Self::Ns,
DATE => Self::Date,
// Should be caught by clap
_ => panic!("Invalid format: {s}"),
// Note: This is caught by clap via `possible_values`
_ => unreachable!(),
}
}
}
@ -203,9 +203,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
return set_system_datetime(date);
} else {
// Declare a file here because it needs to outlive the `dates` iterator.
let file: File;
// Get the current time, either in the local time zone or UTC.
let now: DateTime<FixedOffset> = if settings.utc {
let now = Utc::now();
@ -222,12 +219,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let iter = std::iter::once(date);
Box::new(iter)
}
DateSource::File(ref path) => {
file = File::open(path).unwrap();
let lines = BufReader::new(file).lines();
let iter = lines.filter_map(Result::ok).map(parse_date);
Box::new(iter)
}
DateSource::File(ref path) => match File::open(path) {
Ok(file) => {
let lines = BufReader::new(file).lines();
let iter = lines.filter_map(Result::ok).map(parse_date);
Box::new(iter)
}
Err(_err) => {
return Err(USimpleError::new(
2,
format!("{}: No such file or directory", path.display()),
));
}
},
DateSource::Now => {
let iter = std::iter::once(Ok(now));
Box::new(iter)
@ -257,7 +261,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.replace("%f", "%N");
println!("{formatted}");
}
Err((input, _err)) => show_error!("invalid date {}", input.quote()),
Err((input, _err)) => show!(USimpleError::new(
1,
format!("invalid date {}", input.quote())
)),
}
}
}
@ -291,6 +298,9 @@ pub fn uu_app() -> Command {
.short('I')
.long(OPT_ISO_8601)
.value_name("FMT")
.value_parser([DATE, HOUR, HOURS, MINUTE, MINUTES, SECOND, SECONDS, NS])
.num_args(0..=1)
.default_missing_value(OPT_DATE)
.help(ISO_8601_HELP_STRING),
)
.arg(
@ -304,6 +314,7 @@ pub fn uu_app() -> Command {
Arg::new(OPT_RFC_3339)
.long(OPT_RFC_3339)
.value_name("FMT")
.value_parser([DATE, SECOND, SECONDS, NS])
.help(RFC_3339_HELP_STRING),
)
.arg(

View file

@ -27,11 +27,14 @@ use std::cmp;
use std::env;
use std::ffi::OsString;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Stdin, Stdout, Write};
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
use std::io::{self, Read, Seek, SeekFrom, Stdout, Write};
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::fs::OpenOptionsExt;
#[cfg(unix)]
use std::os::unix::{
fs::FileTypeExt,
io::{AsRawFd, FromRawFd},
};
use std::path::Path;
use std::sync::mpsc;
use std::thread;
@ -93,21 +96,44 @@ impl Num {
}
/// Data sources.
///
/// Use [`Source::stdin_as_file`] if available to enable more
/// fine-grained access to reading from stdin.
enum Source {
/// Input from stdin.
Stdin(Stdin),
#[cfg(not(unix))]
Stdin(io::Stdin),
/// Input from a file.
File(File),
/// Input from stdin, opened from its file descriptor.
#[cfg(unix)]
StdinFile(File),
/// Input from a named pipe, also known as a FIFO.
#[cfg(unix)]
Fifo(File),
}
impl Source {
/// Create a source from stdin using its raw file descriptor.
///
/// This returns an instance of the `Source::StdinFile` variant,
/// using the raw file descriptor of [`std::io::Stdin`] to create
/// the [`std::fs::File`] parameter. You can use this instead of
/// `Source::Stdin` to allow reading from stdin without consuming
/// the entire contents of stdin when this process terminates.
#[cfg(unix)]
fn stdin_as_file() -> Self {
let fd = io::stdin().as_raw_fd();
let f = unsafe { File::from_raw_fd(fd) };
Self::StdinFile(f)
}
fn skip(&mut self, n: u64) -> io::Result<u64> {
match self {
#[cfg(not(unix))]
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
Ok(m) if m < n => {
show_error!("'standard input': cannot skip to specified offset");
@ -116,6 +142,15 @@ impl Source {
Ok(m) => Ok(m),
Err(e) => Err(e),
},
#[cfg(unix)]
Self::StdinFile(f) => match io::copy(&mut f.take(n), &mut io::sink()) {
Ok(m) if m < n => {
show_error!("'standard input': cannot skip to specified offset");
Ok(m)
}
Ok(m) => Ok(m),
Err(e) => Err(e),
},
Self::File(f) => f.seek(io::SeekFrom::Start(n)),
#[cfg(unix)]
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
@ -126,9 +161,12 @@ impl Source {
impl Read for Source {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
#[cfg(not(unix))]
Self::Stdin(stdin) => stdin.read(buf),
Self::File(f) => f.read(buf),
#[cfg(unix)]
Self::StdinFile(f) => f.read(buf),
#[cfg(unix)]
Self::Fifo(f) => f.read(buf),
}
}
@ -151,7 +189,10 @@ struct Input<'a> {
impl<'a> Input<'a> {
/// Instantiate this struct with stdin as a source.
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
#[cfg(not(unix))]
let mut src = Source::Stdin(io::stdin());
#[cfg(unix)]
let mut src = Source::stdin_as_file();
if settings.skip > 0 {
src.skip(settings.skip)?;
}

8
src/uu/fold/fold.md Normal file
View file

@ -0,0 +1,8 @@
# fold
```
fold [OPTION]... [FILE]...
```
Writes each file (or standard input if no files are given)
to standard output whilst breaking long lines

View file

@ -13,13 +13,12 @@ use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError};
use uucore::format_usage;
use uucore::{format_usage, help_about, help_usage};
const TAB_WIDTH: usize = 8;
static USAGE: &str = "{} [OPTION]... [FILE]...";
static ABOUT: &str = "Writes each file (or standard input if no files are given)
to standard output whilst breaking long lines";
const USAGE: &str = help_usage!("fold.md");
const ABOUT: &str = help_about!("fold.md");
mod options {
pub const BYTES: &str = "bytes";

7
src/uu/mkfifo/mkfifo.md Normal file
View file

@ -0,0 +1,7 @@
# mkfifo
```
mkfifo [OPTION]... NAME...
```
Create a FIFO with the given name.

View file

@ -10,10 +10,10 @@ use libc::mkfifo;
use std::ffi::CString;
use uucore::display::Quotable;
use uucore::error::{UResult, USimpleError};
use uucore::{format_usage, show};
use uucore::{format_usage, help_about, help_usage, show};
static USAGE: &str = "{} [OPTION]... NAME...";
static ABOUT: &str = "Create a FIFO with the given name.";
static USAGE: &str = help_usage!("mkfifo.md");
static ABOUT: &str = help_about!("mkfifo.md");
mod options {
pub static MODE: &str = "mode";

25
src/uu/mknod/mknod.md Normal file
View file

@ -0,0 +1,25 @@
# mknod
```
mknod [OPTION]... NAME TYPE [MAJOR MINOR]
```
Create the special file NAME of the given TYPE.
## After Help
Mandatory arguments to long options are mandatory for short options too.
`-m`, `--mode=MODE` set file permission bits to `MODE`, not `a=rw - umask`
Both `MAJOR` and `MINOR` must be specified when `TYPE` is `b`, `c`, or `u`, and they
must be omitted when `TYPE` is `p`. If `MAJOR` or `MINOR` begins with `0x` or `0X`,
it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal;
otherwise, as decimal. `TYPE` may be:
* `b` create a block (buffered) special file
* `c`, `u` create a character (unbuffered) special file
* `p` create a FIFO
NOTE: your shell may have its own version of mknod, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.

View file

@ -14,28 +14,11 @@ use std::ffi::CString;
use uucore::display::Quotable;
use uucore::error::{set_exit_code, UResult, USimpleError, UUsageError};
use uucore::format_usage;
use uucore::{format_usage, help_about, help_section, help_usage};
static ABOUT: &str = "Create the special file NAME of the given TYPE.";
static USAGE: &str = "{} [OPTION]... NAME TYPE [MAJOR MINOR]";
static LONG_HELP: &str = "Mandatory arguments to long options are mandatory for short options too.
-m, --mode=MODE set file permission bits to MODE, not a=rw - umask
--help display this help and exit
--version output version information and exit
Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they
must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X,
it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal;
otherwise, as decimal. TYPE may be:
b create a block (buffered) special file
c, u create a character (unbuffered) special file
p create a FIFO
NOTE: your shell may have its own version of mknod, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.
";
const ABOUT: &str = help_about!("mknod.md");
const USAGE: &str = help_usage!("mknod.md");
const AFTER_HELP: &str = help_section!("after help", "mknod.md");
const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
@ -142,7 +125,7 @@ pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.override_usage(format_usage(USAGE))
.after_help(LONG_HELP)
.after_help(AFTER_HELP)
.about(ABOUT)
.infer_long_args(true)
.arg(

7
src/uu/mktemp/mktemp.md Normal file
View file

@ -0,0 +1,7 @@
# mktemp
```
mktemp [OPTION]... [TEMPLATE]
```
Create a temporary file or directory.

View file

@ -11,7 +11,7 @@
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
use uucore::display::{println_verbatim, Quotable};
use uucore::error::{FromIo, UError, UResult, UUsageError};
use uucore::format_usage;
use uucore::{format_usage, help_about, help_usage};
use std::env;
use std::error::Error;
@ -28,8 +28,8 @@ use std::os::unix::prelude::PermissionsExt;
use rand::Rng;
use tempfile::Builder;
static ABOUT: &str = "Create a temporary file or directory.";
const USAGE: &str = "{} [OPTION]... [TEMPLATE]";
const ABOUT: &str = help_about!("mktemp.md");
const USAGE: &str = help_usage!("mktemp.md");
static DEFAULT_TEMPLATE: &str = "tmp.XXXXXXXXXX";

View file

@ -15,10 +15,10 @@ use uucore::{format_usage, help_about, help_usage};
use uucore::display::println_verbatim;
use uucore::error::{FromIo, UResult};
static ABOUT: &str = help_about!("pwd.md");
const ABOUT: &str = help_about!("pwd.md");
const USAGE: &str = help_usage!("pwd.md");
static OPT_LOGICAL: &str = "logical";
static OPT_PHYSICAL: &str = "physical";
const OPT_LOGICAL: &str = "logical";
const OPT_PHYSICAL: &str = "physical";
fn physical_path() -> io::Result<PathBuf> {
// std::env::current_dir() is a thin wrapper around libc::getcwd().
@ -84,36 +84,22 @@ fn logical_path() -> io::Result<PathBuf> {
{
use std::fs::metadata;
use std::os::unix::fs::MetadataExt;
let path_info = match metadata(path) {
Ok(info) => info,
Err(_) => return false,
};
let real_info = match metadata(".") {
Ok(info) => info,
Err(_) => return false,
};
if path_info.dev() != real_info.dev() || path_info.ino() != real_info.ino() {
return false;
match (metadata(path), metadata(".")) {
(Ok(info1), Ok(info2)) => {
info1.dev() == info2.dev() && info1.ino() == info2.ino()
}
_ => false,
}
}
#[cfg(not(unix))]
{
use std::fs::canonicalize;
let canon_path = match canonicalize(path) {
Ok(path) => path,
Err(_) => return false,
};
let real_path = match canonicalize(".") {
Ok(path) => path,
Err(_) => return false,
};
if canon_path != real_path {
return false;
match (canonicalize(path), canonicalize(".")) {
(Ok(path1), Ok(path2)) => path1 == path2,
_ => false,
}
}
true
}
match env::var_os("PWD").map(PathBuf::from) {

View file

@ -0,0 +1,8 @@
# relpath
```
relpath [-d DIR] TO [FROM]
```
Convert TO destination to the relative path from the FROM dir.
If FROM path is omitted, current working dir will be used.

View file

@ -12,12 +12,11 @@ use std::env;
use std::path::{Path, PathBuf};
use uucore::display::println_verbatim;
use uucore::error::{FromIo, UResult};
use uucore::format_usage;
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use uucore::{format_usage, help_about, help_usage};
static ABOUT: &str = "Convert TO destination to the relative path from the FROM dir.
If FROM path is omitted, current working dir will be used.";
const USAGE: &str = "{} [-d DIR] TO [FROM]";
const USAGE: &str = help_usage!("relpath.md");
const ABOUT: &str = help_about!("relpath.md");
mod options {
pub const DIR: &str = "DIR";

View file

@ -16,6 +16,7 @@ path = "src/sleep.rs"
[dependencies]
clap = { workspace=true }
fundu = { workspace=true }
uucore = { workspace=true }
[[bin]]

View file

@ -14,6 +14,7 @@ use uucore::{
};
use clap::{crate_version, Arg, ArgAction, Command};
use fundu::{self, DurationParser, ParseError};
static ABOUT: &str = help_about!("sleep.md");
const USAGE: &str = help_usage!("sleep.md");
@ -61,14 +62,34 @@ pub fn uu_app() -> Command {
fn sleep(args: &[&str]) -> UResult<()> {
let mut arg_error = false;
use fundu::TimeUnit::*;
let parser = DurationParser::with_time_units(&[Second, Minute, Hour, Day]);
let sleep_dur = args
.iter()
.filter_map(|input| {
uucore::parse_time::from_str(input.trim()).ok().or_else(|| {
.filter_map(|input| match parser.parse(input.trim()) {
Ok(duration) => Some(duration),
Err(error) => {
arg_error = true;
show_error!("invalid time interval '{input}'");
let reason = match error {
ParseError::Empty if input.is_empty() => "Input was empty".to_string(),
ParseError::Empty => "Found only whitespace in input".to_string(),
ParseError::Syntax(pos, description)
| ParseError::TimeUnit(pos, description) => {
format!("{description} at position {}", pos.saturating_add(1))
}
ParseError::NegativeExponentOverflow | ParseError::PositiveExponentOverflow => {
"Exponent was out of bounds".to_string()
}
ParseError::NegativeNumber => "Number was negative".to_string(),
error => error.to_string(),
};
show_error!("invalid time interval '{input}': {reason}");
None
})
}
})
.fold(Duration::ZERO, |acc, n| acc.saturating_add(n));

View file

@ -196,6 +196,22 @@ fn report_if_verbose(signal: usize, cmd: &str, verbose: bool) {
}
}
fn send_signal(process: &mut Child, signal: usize, foreground: bool) {
// NOTE: GNU timeout doesn't check for errors of signal.
// The subprocess might have exited just after the timeout.
// Sending a signal now would return "No such process", but we should still try to kill the children.
_ = process.send_signal(signal);
if !foreground {
_ = process.send_signal_group(signal);
let kill_signal = signal_by_name_or_value("KILL").unwrap();
let continued_signal = signal_by_name_or_value("CONT").unwrap();
if signal != kill_signal && signal != continued_signal {
_ = process.send_signal(continued_signal);
_ = process.send_signal_group(continued_signal);
}
}
}
/// Wait for a child process and send a kill signal if it does not terminate.
///
/// This function waits for the child `process` for the time period
@ -217,10 +233,11 @@ fn report_if_verbose(signal: usize, cmd: &str, verbose: bool) {
/// If there is a problem sending the `SIGKILL` signal or waiting for
/// the process after that signal is sent.
fn wait_or_kill_process(
mut process: Child,
process: &mut Child,
cmd: &str,
duration: Duration,
preserve_status: bool,
foreground: bool,
verbose: bool,
) -> std::io::Result<i32> {
match process.wait_or_timeout(duration) {
@ -234,7 +251,7 @@ fn wait_or_kill_process(
Ok(None) => {
let signal = signal_by_name_or_value("KILL").unwrap();
report_if_verbose(signal, cmd, verbose);
process.send_signal(signal)?;
send_signal(process, signal, foreground);
process.wait()?;
Ok(ExitStatus::SignalSent(signal).into())
}
@ -300,7 +317,7 @@ fn timeout(
enable_pipe_errors()?;
let mut process = process::Command::new(&cmd[0])
let process = &mut process::Command::new(&cmd[0])
.args(&cmd[1..])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
@ -335,7 +352,7 @@ fn timeout(
.into()),
Ok(None) => {
report_if_verbose(signal, &cmd[0], verbose);
process.send_signal(signal)?;
send_signal(process, signal, foreground);
match kill_after {
None => {
if preserve_status {
@ -350,6 +367,7 @@ fn timeout(
&cmd[0],
kill_after,
preserve_status,
foreground,
verbose,
) {
Ok(status) => Err(status.into()),
@ -363,11 +381,8 @@ fn timeout(
}
Err(_) => {
// We're going to return ERR_EXIT_STATUS regardless of
// whether `send_signal()` succeeds or fails, so just
// ignore the return value.
process
.send_signal(signal)
.map_err(|e| USimpleError::new(ExitStatus::TimeoutFailed.into(), format!("{e}")))?;
// whether `send_signal()` succeeds or fails
send_signal(process, signal, foreground);
Err(ExitStatus::TimeoutFailed.into())
}
}

View file

@ -11,17 +11,13 @@
use chrono::{Local, TimeZone, Utc};
use clap::{crate_version, Arg, ArgAction, Command};
use uucore::format_usage;
// import crate time from utmpx
pub use uucore::libc;
use uucore::libc::time_t;
use uucore::{format_usage, help_about, help_usage};
use uucore::error::{UResult, USimpleError};
static ABOUT: &str = "Display the current time, the length of time the system has been up,\n\
the number of users on the system, and the average number of jobs\n\
in the run queue over the last 1, 5 and 15 minutes.";
const USAGE: &str = "{} [OPTION]...";
const ABOUT: &str = help_about!("uptime.md");
const USAGE: &str = help_usage!("uptime.md");
pub mod options {
pub static SINCE: &str = "since";
}

9
src/uu/uptime/uptime.md Normal file
View file

@ -0,0 +1,9 @@
# uptime
```
uptime [OPTION]...
```
Display the current time, the length of time the system has been up,
the number of users on the system, and the average number of jobs
in the run queue over the last 1, 5 and 15 minutes.

View file

@ -18,7 +18,7 @@ use std::ffi::CStr;
use std::fmt::Write;
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use uucore::format_usage;
use uucore::{format_usage, help_about, help_usage};
mod options {
pub const ALL: &str = "all";
@ -38,8 +38,8 @@ mod options {
pub const FILE: &str = "FILE"; // if length=1: FILE, if length=2: ARG1 ARG2
}
static ABOUT: &str = "Print information about users who are currently logged in.";
const USAGE: &str = "{} [OPTION]... [ FILE | ARG1 ARG2 ]";
const ABOUT: &str = help_about!("who.md");
const USAGE: &str = help_usage!("who.md");
#[cfg(target_os = "linux")]
static RUNLEVEL_HELP: &str = "print current runlevel";

8
src/uu/who/who.md Normal file
View file

@ -0,0 +1,8 @@
# who
```
who [OPTION]... [ FILE | ARG1 ARG2 ]
```
Print information about users who are currently logged in.

View file

@ -11,10 +11,12 @@ use clap::{crate_version, Command};
use uucore::display::println_verbatim;
use uucore::error::{FromIo, UResult};
use uucore::{format_usage, help_about, help_usage};
mod platform;
static ABOUT: &str = "Print the current username.";
const ABOUT: &str = help_about!("whoami.md");
const USAGE: &str = help_usage!("whoami.md");
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
@ -28,5 +30,6 @@ pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
}

7
src/uu/whoami/whoami.md Normal file
View file

@ -0,0 +1,7 @@
# whoami
```
whoami
```
Print the current username.

View file

@ -122,6 +122,15 @@ fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) {
'o' => srwx = ((fperm << 6) & 0o700) | ((fperm << 3) & 0o070) | (fperm & 0o007),
_ => break,
};
if ch == 'u' || ch == 'g' || ch == 'o' {
// symbolic modes only allows perms to be a single letter of 'ugo'
// therefore this must either be the first char or it is unexpected
if pos != 0 {
break;
}
pos = 1;
break;
}
pos += 1;
}
if pos == 0 {

View file

@ -48,6 +48,9 @@ pub trait ChildExt {
/// send the signal to an unrelated process that recycled the PID.
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
/// Send a signal to a process group.
fn send_signal_group(&mut self, signal: usize) -> io::Result<()>;
/// Wait for a process to finish or return after the specified duration.
/// A `timeout` of zero disables the timeout.
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>>;
@ -62,6 +65,18 @@ impl ChildExt for Child {
}
}
fn send_signal_group(&mut self, signal: usize) -> io::Result<()> {
// Ignore the signal, so we don't go into a signal loop.
if unsafe { libc::signal(signal as i32, libc::SIG_IGN) } != 0 {
return Err(io::Error::last_os_error());
}
if unsafe { libc::kill(0, signal as i32) } != 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>> {
if timeout == Duration::from_micros(0) {
return self.wait().map(Some);