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:
commit
59d34ce667
47 changed files with 1068 additions and 649 deletions
|
|
@ -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
23
src/uu/cksum/cksum.md
Normal 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)
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
8
src/uu/fold/fold.md
Normal 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
|
||||
|
|
@ -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
7
src/uu/mkfifo/mkfifo.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# mkfifo
|
||||
|
||||
```
|
||||
mkfifo [OPTION]... NAME...
|
||||
```
|
||||
|
||||
Create a FIFO with the given name.
|
||||
|
|
@ -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
25
src/uu/mknod/mknod.md
Normal 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.
|
||||
|
|
@ -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
7
src/uu/mktemp/mktemp.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# mktemp
|
||||
|
||||
```
|
||||
mktemp [OPTION]... [TEMPLATE]
|
||||
```
|
||||
|
||||
Create a temporary file or directory.
|
||||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
8
src/uu/relpath/relpath.md
Normal file
8
src/uu/relpath/relpath.md
Normal 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.
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ path = "src/sleep.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { workspace=true }
|
||||
fundu = { workspace=true }
|
||||
uucore = { workspace=true }
|
||||
|
||||
[[bin]]
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
9
src/uu/uptime/uptime.md
Normal 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.
|
||||
|
|
@ -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
8
src/uu/who/who.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# who
|
||||
|
||||
```
|
||||
who [OPTION]... [ FILE | ARG1 ARG2 ]
|
||||
```
|
||||
|
||||
Print information about users who are currently logged in.
|
||||
|
||||
|
|
@ -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
7
src/uu/whoami/whoami.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# whoami
|
||||
|
||||
```
|
||||
whoami
|
||||
```
|
||||
|
||||
Print the current username.
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue