mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge branch 'master' into tail_notify
This commit is contained in:
commit
22b59289e8
28 changed files with 1092 additions and 391 deletions
|
@ -68,6 +68,7 @@ structs
|
|||
substr
|
||||
splitn
|
||||
trunc
|
||||
uninit
|
||||
|
||||
# * uutils
|
||||
basenc
|
||||
|
@ -277,6 +278,7 @@ ULONG
|
|||
ULONGLONG
|
||||
UNLEN
|
||||
WCHAR
|
||||
WSADATA
|
||||
errhandlingapi
|
||||
fileapi
|
||||
handleapi
|
||||
|
|
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -669,9 +669,9 @@ checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
|||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.6.2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a"
|
||||
checksum = "ecae1c064e29fcabb6c2e9939e53dc7da72ed90234ae36ebfe03a478742efbd1"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
@ -3290,6 +3290,7 @@ name = "uu_whoami"
|
|||
version = "0.0.7"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"libc",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
"winapi 0.3.9",
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
mod syntax_tree;
|
||||
|
@ -23,7 +24,8 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.arg(Arg::with_name(HELP).long(HELP))
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
|
@ -32,13 +34,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
// The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)`
|
||||
|
||||
if maybe_handle_help_or_version(&args) {
|
||||
0
|
||||
Ok(())
|
||||
} else {
|
||||
let token_strings = args[1..].to_vec();
|
||||
|
||||
match process_expr(&token_strings) {
|
||||
Ok(expr_result) => print_expr_ok(&expr_result),
|
||||
Err(expr_error) => print_expr_error(&expr_error),
|
||||
Err(expr_error) => Err(USimpleError::new(2, &expr_error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,19 +51,15 @@ fn process_expr(token_strings: &[String]) -> Result<String, String> {
|
|||
evaluate_ast(maybe_ast)
|
||||
}
|
||||
|
||||
fn print_expr_ok(expr_result: &str) -> i32 {
|
||||
fn print_expr_ok(expr_result: &str) -> UResult<()> {
|
||||
println!("{}", expr_result);
|
||||
if expr_result == "0" || expr_result.is_empty() {
|
||||
1
|
||||
Err(1.into())
|
||||
} else {
|
||||
0
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn print_expr_error(expr_error: &str) -> ! {
|
||||
crash!(2, "{}", expr_error)
|
||||
}
|
||||
|
||||
fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
|
||||
maybe_ast.and_then(|ast| ast.evaluate())
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ edition = "2018"
|
|||
path = "src/hashsum.rs"
|
||||
|
||||
[dependencies]
|
||||
digest = "0.6.2"
|
||||
digest = "0.6.1"
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
hex = "0.2.0"
|
||||
libc = "0.2.42"
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
// spell-checker:ignore memmem
|
||||
//! Implementations of digest functions, like md5 and sha1.
|
||||
//!
|
||||
//! The [`Digest`] trait represents the interface for providing inputs
|
||||
//! to these digest functions and accessing the resulting hash. The
|
||||
//! [`DigestWriter`] struct provides a wrapper around [`Digest`] that
|
||||
//! implements the [`Write`] trait, for use in situations where calling
|
||||
//! [`write`] would be useful.
|
||||
extern crate digest;
|
||||
extern crate md5;
|
||||
extern crate sha1;
|
||||
extern crate sha2;
|
||||
extern crate sha3;
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use hex::ToHex;
|
||||
#[cfg(windows)]
|
||||
use memchr::memmem;
|
||||
|
||||
use crate::digest::digest::{ExtendableOutput, Input, XofReader};
|
||||
|
||||
|
@ -158,3 +170,76 @@ impl_digest_sha!(sha3::Sha3_384, 384);
|
|||
impl_digest_sha!(sha3::Sha3_512, 512);
|
||||
impl_digest_shake!(sha3::Shake128);
|
||||
impl_digest_shake!(sha3::Shake256);
|
||||
|
||||
/// A struct that writes to a digest.
|
||||
///
|
||||
/// This struct wraps a [`Digest`] and provides a [`Write`]
|
||||
/// implementation that passes input bytes directly to the
|
||||
/// [`Digest::input`].
|
||||
///
|
||||
/// On Windows, if `binary` is `false`, then the [`write`]
|
||||
/// implementation replaces instances of "\r\n" with "\n" before passing
|
||||
/// the input bytes to the [`digest`].
|
||||
pub struct DigestWriter<'a> {
|
||||
digest: &'a mut Box<dyn Digest>,
|
||||
|
||||
/// Whether to write to the digest in binary mode or text mode on Windows.
|
||||
///
|
||||
/// If this is `false`, then instances of "\r\n" are replaced with
|
||||
/// "\n" before passing input bytes to the [`digest`].
|
||||
#[allow(dead_code)]
|
||||
binary: bool,
|
||||
// TODO This is dead code only on non-Windows operating systems. It
|
||||
// might be better to use a `#[cfg(windows)]` guard here.
|
||||
}
|
||||
|
||||
impl<'a> DigestWriter<'a> {
|
||||
pub fn new(digest: &'a mut Box<dyn Digest>, binary: bool) -> DigestWriter {
|
||||
DigestWriter { digest, binary }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Write for DigestWriter<'a> {
|
||||
#[cfg(not(windows))]
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.digest.input(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
if self.binary {
|
||||
self.digest.input(buf);
|
||||
return Ok(buf.len());
|
||||
}
|
||||
|
||||
// In Windows text mode, replace each occurrence of "\r\n"
|
||||
// with "\n".
|
||||
//
|
||||
// Find all occurrences of "\r\n", inputting the slice just
|
||||
// before the "\n" in the previous instance of "\r\n" and
|
||||
// the beginning of this "\r\n".
|
||||
//
|
||||
// FIXME This fails if one call to `write()` ends with the
|
||||
// "\r" and the next call to `write()` begins with the "\n".
|
||||
let n = buf.len();
|
||||
let mut i_prev = 0;
|
||||
for i in memmem::find_iter(buf, b"\r\n") {
|
||||
self.digest.input(&buf[i_prev..i]);
|
||||
i_prev = i + 1;
|
||||
}
|
||||
self.digest.input(&buf[i_prev..n]);
|
||||
|
||||
// Even though we dropped a "\r" for each "\r\n" we found, we
|
||||
// still report the number of bytes written as `n`. This is
|
||||
// because the meaning of the returned number is supposed to be
|
||||
// the number of bytes consumed by the writer, so that if the
|
||||
// calling code were calling `write()` in a loop, it would know
|
||||
// where the next contiguous slice of the buffer starts.
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) algo, algoname, regexes, nread memmem
|
||||
// spell-checker:ignore (ToDO) algo, algoname, regexes, nread
|
||||
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
@ -18,11 +18,11 @@ extern crate uucore;
|
|||
mod digest;
|
||||
|
||||
use self::digest::Digest;
|
||||
use self::digest::DigestWriter;
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use hex::ToHex;
|
||||
use md5::Context as Md5;
|
||||
use memchr::memmem;
|
||||
use regex::Regex;
|
||||
use sha1::Sha1;
|
||||
use sha2::{Sha224, Sha256, Sha384, Sha512};
|
||||
|
@ -540,7 +540,7 @@ where
|
|||
let real_sum = crash_if_err!(
|
||||
1,
|
||||
digest_reader(
|
||||
&mut *options.digest,
|
||||
&mut options.digest,
|
||||
&mut ckf,
|
||||
binary_check,
|
||||
options.output_bits
|
||||
|
@ -571,7 +571,7 @@ where
|
|||
let sum = crash_if_err!(
|
||||
1,
|
||||
digest_reader(
|
||||
&mut *options.digest,
|
||||
&mut options.digest,
|
||||
&mut file,
|
||||
options.binary,
|
||||
options.output_bits
|
||||
|
@ -598,48 +598,21 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn digest_reader<'a, T: Read>(
|
||||
digest: &mut (dyn Digest + 'a),
|
||||
fn digest_reader<T: Read>(
|
||||
digest: &mut Box<dyn Digest>,
|
||||
reader: &mut BufReader<T>,
|
||||
binary: bool,
|
||||
output_bits: usize,
|
||||
) -> io::Result<String> {
|
||||
digest.reset();
|
||||
|
||||
// Digest file, do not hold too much in memory at any given moment
|
||||
let windows = cfg!(windows);
|
||||
let mut buffer = Vec::with_capacity(524_288);
|
||||
loop {
|
||||
match reader.read_to_end(&mut buffer) {
|
||||
Ok(0) => {
|
||||
break;
|
||||
}
|
||||
Ok(nread) => {
|
||||
if windows && !binary {
|
||||
// In Windows text mode, replace each occurrence of
|
||||
// "\r\n" with "\n".
|
||||
//
|
||||
// Find all occurrences of "\r\n", inputting the
|
||||
// slice just before the "\n" in the previous
|
||||
// instance of "\r\n" and the beginning of this
|
||||
// "\r\n".
|
||||
//
|
||||
// FIXME This fails if one call to `read()` ends
|
||||
// with the "\r" and the next call to `read()`
|
||||
// begins with the "\n".
|
||||
let mut i_prev = 0;
|
||||
for i in memmem::find_iter(&buffer[0..nread], b"\r\n") {
|
||||
digest.input(&buffer[i_prev..i]);
|
||||
i_prev = i + 1;
|
||||
}
|
||||
digest.input(&buffer[i_prev..nread]);
|
||||
} else {
|
||||
digest.input(&buffer[..nread]);
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
// Read bytes from `reader` and write those bytes to `digest`.
|
||||
//
|
||||
// If `binary` is `false` and the operating system is Windows, then
|
||||
// `DigestWriter` replaces "\r\n" with "\n" before it writes the
|
||||
// bytes into `digest`. Otherwise, it just inserts the bytes as-is.
|
||||
let mut digest_writer = DigestWriter::new(digest, binary);
|
||||
std::io::copy(reader, &mut digest_writer)?;
|
||||
|
||||
if digest.output_bits() > 0 {
|
||||
Ok(digest.result_str())
|
||||
|
|
|
@ -10,18 +10,13 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg, ArgMatches};
|
||||
use std::collections::hash_set::HashSet;
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::str;
|
||||
#[cfg(windows)]
|
||||
use uucore::error::UUsageError;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
|
||||
#[cfg(windows)]
|
||||
use winapi::shared::minwindef::MAKEWORD;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::winsock2::{WSACleanup, WSAStartup};
|
||||
use clap::{crate_version, App, Arg, ArgMatches};
|
||||
|
||||
use uucore::error::{FromIo, UResult};
|
||||
|
||||
static ABOUT: &str = "Display or set the system's host name.";
|
||||
|
||||
|
@ -31,45 +26,52 @@ static OPT_FQDN: &str = "fqdn";
|
|||
static OPT_SHORT: &str = "short";
|
||||
static OPT_HOST: &str = "host";
|
||||
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
#![allow(clippy::let_and_return)]
|
||||
#[cfg(windows)]
|
||||
unsafe {
|
||||
#[allow(deprecated)]
|
||||
let mut data = std::mem::uninitialized();
|
||||
if WSAStartup(MAKEWORD(2, 2), &mut data as *mut _) != 0 {
|
||||
return Err(UUsageError::new(
|
||||
1,
|
||||
"Failed to start Winsock 2.2".to_string(),
|
||||
));
|
||||
#[cfg(windows)]
|
||||
mod wsa {
|
||||
use std::io;
|
||||
|
||||
use winapi::shared::minwindef::MAKEWORD;
|
||||
use winapi::um::winsock2::{WSACleanup, WSAStartup, WSADATA};
|
||||
|
||||
pub(super) struct WsaHandle(());
|
||||
|
||||
pub(super) fn start() -> io::Result<WsaHandle> {
|
||||
let err = unsafe {
|
||||
let mut data = std::mem::MaybeUninit::<WSADATA>::uninit();
|
||||
WSAStartup(MAKEWORD(2, 2), data.as_mut_ptr())
|
||||
};
|
||||
if err != 0 {
|
||||
Err(io::Error::from_raw_os_error(err))
|
||||
} else {
|
||||
Ok(WsaHandle(()))
|
||||
}
|
||||
}
|
||||
let result = execute(args);
|
||||
#[cfg(windows)]
|
||||
unsafe {
|
||||
WSACleanup();
|
||||
|
||||
impl Drop for WsaHandle {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// This possibly returns an error but we can't handle it
|
||||
let _err = WSACleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn usage() -> String {
|
||||
format!("{0} [OPTION]... [HOSTNAME]", uucore::execution_phrase())
|
||||
}
|
||||
|
||||
fn execute(args: impl uucore::Args) -> UResult<()> {
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let usage = usage();
|
||||
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
||||
|
||||
match matches.value_of(OPT_HOST) {
|
||||
#[cfg(windows)]
|
||||
let _handle = wsa::start().map_err_context(|| "failed to start Winsock".to_owned())?;
|
||||
|
||||
match matches.value_of_os(OPT_HOST) {
|
||||
None => display_hostname(&matches),
|
||||
Some(host) => {
|
||||
if let Err(err) = hostname::set(host) {
|
||||
return Err(USimpleError::new(1, format!("{}", err)));
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Some(host) => hostname::set(host).map_err_context(|| "failed to set hostname".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,64 +83,68 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
Arg::with_name(OPT_DOMAIN)
|
||||
.short("d")
|
||||
.long("domain")
|
||||
.overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT])
|
||||
.help("Display the name of the DNS domain if possible"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_IP_ADDRESS)
|
||||
.short("i")
|
||||
.long("ip-address")
|
||||
.overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT])
|
||||
.help("Display the network address(es) of the host"),
|
||||
)
|
||||
// TODO: support --long
|
||||
.arg(
|
||||
Arg::with_name(OPT_FQDN)
|
||||
.short("f")
|
||||
.long("fqdn")
|
||||
.overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT])
|
||||
.help("Display the FQDN (Fully Qualified Domain Name) (default)"),
|
||||
)
|
||||
.arg(Arg::with_name(OPT_SHORT).short("s").long("short").help(
|
||||
"Display the short hostname (the portion before the first dot) if \
|
||||
possible",
|
||||
))
|
||||
.arg(
|
||||
Arg::with_name(OPT_SHORT)
|
||||
.short("s")
|
||||
.long("short")
|
||||
.overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT])
|
||||
.help("Display the short hostname (the portion before the first dot) if possible"),
|
||||
)
|
||||
.arg(Arg::with_name(OPT_HOST))
|
||||
}
|
||||
|
||||
fn display_hostname(matches: &ArgMatches) -> UResult<()> {
|
||||
let hostname = hostname::get().unwrap().into_string().unwrap();
|
||||
let hostname = hostname::get()
|
||||
.map_err_context(|| "failed to get hostname".to_owned())?
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
if matches.is_present(OPT_IP_ADDRESS) {
|
||||
// XXX: to_socket_addrs needs hostname:port so append a dummy port and remove it later.
|
||||
// This was originally supposed to use std::net::lookup_host, but that seems to be
|
||||
// deprecated. Perhaps we should use the dns-lookup crate?
|
||||
let hostname = hostname + ":1";
|
||||
match hostname.to_socket_addrs() {
|
||||
Ok(addresses) => {
|
||||
let mut hashset = HashSet::new();
|
||||
let mut output = String::new();
|
||||
for addr in addresses {
|
||||
// XXX: not sure why this is necessary...
|
||||
if !hashset.contains(&addr) {
|
||||
let mut ip = format!("{}", addr);
|
||||
if ip.ends_with(":1") {
|
||||
let len = ip.len();
|
||||
ip.truncate(len - 2);
|
||||
}
|
||||
output.push_str(&ip);
|
||||
output.push(' ');
|
||||
hashset.insert(addr);
|
||||
}
|
||||
let addresses = hostname
|
||||
.to_socket_addrs()
|
||||
.map_err_context(|| "failed to resolve socket addresses".to_owned())?;
|
||||
let mut hashset = HashSet::new();
|
||||
let mut output = String::new();
|
||||
for addr in addresses {
|
||||
// XXX: not sure why this is necessary...
|
||||
if !hashset.contains(&addr) {
|
||||
let mut ip = addr.to_string();
|
||||
if ip.ends_with(":1") {
|
||||
let len = ip.len();
|
||||
ip.truncate(len - 2);
|
||||
}
|
||||
let len = output.len();
|
||||
if len > 0 {
|
||||
println!("{}", &output[0..len - 1]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(f) => {
|
||||
return Err(USimpleError::new(1, format!("{}", f)));
|
||||
output.push_str(&ip);
|
||||
output.push(' ');
|
||||
hashset.insert(addr);
|
||||
}
|
||||
}
|
||||
let len = output.len();
|
||||
if len > 0 {
|
||||
println!("{}", &output[0..len - 1]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
if matches.is_present(OPT_SHORT) || matches.is_present(OPT_DOMAIN) {
|
||||
let mut it = hostname.char_indices().filter(|&ci| ci.1 == '.');
|
||||
|
|
|
@ -15,7 +15,7 @@ use libc::{c_int, pid_t};
|
|||
use std::io::Error;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::signals::ALL_SIGNALS;
|
||||
use uucore::signals::{signal_by_name_or_value, ALL_SIGNALS};
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
static ABOUT: &str = "Send signal to processes or list information about signals.";
|
||||
|
@ -37,10 +37,10 @@ pub enum Mode {
|
|||
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args
|
||||
let mut args = args
|
||||
.collect_str(InvalidEncodingHandling::Ignore)
|
||||
.accept_any();
|
||||
let (args, obs_signal) = handle_obsolete(args);
|
||||
let obs_signal = handle_obsolete(&mut args);
|
||||
|
||||
let usage = format!("{} [OPTIONS]... PID...", uucore::execution_phrase());
|
||||
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
||||
|
@ -60,13 +60,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
match mode {
|
||||
Mode::Kill => {
|
||||
let sig = match (obs_signal, matches.value_of(options::SIGNAL)) {
|
||||
(Some(s), Some(_)) => s, // -s takes precedence
|
||||
(Some(s), None) => s,
|
||||
(None, Some(s)) => s.to_owned(),
|
||||
(None, None) => "TERM".to_owned(),
|
||||
let sig = if let Some(signal) = obs_signal {
|
||||
signal
|
||||
} else if let Some(signal) = matches.value_of(options::SIGNAL) {
|
||||
parse_signal_value(signal)?
|
||||
} else {
|
||||
15_usize //SIGTERM
|
||||
};
|
||||
kill(&sig, &pids_or_signals)
|
||||
let pids = parse_pids(&pids_or_signals)?;
|
||||
kill(sig, &pids)
|
||||
}
|
||||
Mode::Table => {
|
||||
table();
|
||||
|
@ -109,26 +111,22 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
)
|
||||
}
|
||||
|
||||
fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) {
|
||||
let mut i = 0;
|
||||
while i < args.len() {
|
||||
// this is safe because slice is valid when it is referenced
|
||||
let slice = &args[i].clone();
|
||||
if slice.starts_with('-') && slice.chars().nth(1).map_or(false, |c| c.is_digit(10)) {
|
||||
let val = &slice[1..];
|
||||
match val.parse() {
|
||||
Ok(num) => {
|
||||
if uucore::signals::is_signal(num) {
|
||||
args.remove(i);
|
||||
return (args, Some(val.to_owned()));
|
||||
}
|
||||
}
|
||||
Err(_) => break, /* getopts will error out for us */
|
||||
fn handle_obsolete(args: &mut Vec<String>) -> Option<usize> {
|
||||
// Sanity check
|
||||
if args.len() > 2 {
|
||||
// Old signal can only be in the first argument position
|
||||
let slice = args[1].as_str();
|
||||
if let Some(signal) = slice.strip_prefix('-') {
|
||||
// Check if it is a valid signal
|
||||
let opt_signal = signal_by_name_or_value(signal);
|
||||
if opt_signal.is_some() {
|
||||
// remove the signal before return
|
||||
args.remove(1);
|
||||
return opt_signal;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
(args, None)
|
||||
None
|
||||
}
|
||||
|
||||
fn table() {
|
||||
|
@ -160,18 +158,13 @@ fn print_signal(signal_name_or_value: &str) -> UResult<()> {
|
|||
}
|
||||
|
||||
fn print_signals() {
|
||||
let mut pos = 0;
|
||||
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
|
||||
pos += signal.len();
|
||||
print!("{}", signal);
|
||||
if idx > 0 && pos > 73 {
|
||||
println!();
|
||||
pos = 0;
|
||||
} else {
|
||||
pos += 1;
|
||||
if idx > 0 {
|
||||
print!(" ");
|
||||
}
|
||||
print!("{}", signal);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
fn list(arg: Option<String>) -> UResult<()> {
|
||||
|
@ -184,31 +177,32 @@ fn list(arg: Option<String>) -> UResult<()> {
|
|||
}
|
||||
}
|
||||
|
||||
fn kill(signalname: &str, pids: &[String]) -> UResult<()> {
|
||||
let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname);
|
||||
let signal_value = match optional_signal_value {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
format!("unknown signal name {}", signalname.quote()),
|
||||
));
|
||||
fn parse_signal_value(signal_name: &str) -> UResult<usize> {
|
||||
let optional_signal_value = signal_by_name_or_value(signal_name);
|
||||
match optional_signal_value {
|
||||
Some(x) => Ok(x),
|
||||
None => Err(USimpleError::new(
|
||||
1,
|
||||
format!("unknown signal name {}", signal_name.quote()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_pids(pids: &[String]) -> UResult<Vec<usize>> {
|
||||
pids.iter()
|
||||
.map(|x| {
|
||||
x.parse::<usize>().map_err(|e| {
|
||||
USimpleError::new(1, format!("failed to parse argument {}: {}", x.quote(), e))
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn kill(signal_value: usize, pids: &[usize]) -> UResult<()> {
|
||||
for &pid in pids {
|
||||
if unsafe { libc::kill(pid as pid_t, signal_value as c_int) } != 0 {
|
||||
show!(USimpleError::new(1, format!("{}", Error::last_os_error())));
|
||||
}
|
||||
};
|
||||
for pid in pids {
|
||||
match pid.parse::<usize>() {
|
||||
Ok(x) => {
|
||||
if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 {
|
||||
show!(USimpleError::new(1, format!("{}", Error::last_os_error())));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
format!("failed to parse argument {}: {}", pid.quote(), e),
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
190
src/uu/seq/src/digits.rs
Normal file
190
src/uu/seq/src/digits.rs
Normal file
|
@ -0,0 +1,190 @@
|
|||
//! Counting number of digits needed to represent a number.
|
||||
//!
|
||||
//! The [`num_integral_digits`] and [`num_fractional_digits`] functions
|
||||
//! count the number of digits needed to represent a number in decimal
|
||||
//! notation (like "123.456").
|
||||
use std::convert::TryInto;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
|
||||
/// The number of digits after the decimal point in a given number.
|
||||
///
|
||||
/// The input `s` is a string representing a number, either an integer
|
||||
/// or a floating point number in either decimal notation or scientific
|
||||
/// notation. This function returns the number of digits after the
|
||||
/// decimal point needed to print the number in decimal notation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3);
|
||||
/// ```
|
||||
pub fn num_fractional_digits(s: &str) -> Result<usize, ParseIntError> {
|
||||
match (s.find('.'), s.find('e')) {
|
||||
// For example, "123456".
|
||||
(None, None) => Ok(0),
|
||||
|
||||
// For example, "123e456".
|
||||
(None, Some(j)) => {
|
||||
let exponent: i64 = s[j + 1..].parse()?;
|
||||
if exponent < 0 {
|
||||
Ok(-exponent as usize)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
// For example, "123.456".
|
||||
(Some(i), None) => Ok(s.len() - (i + 1)),
|
||||
|
||||
// For example, "123.456e789".
|
||||
(Some(i), Some(j)) if i < j => {
|
||||
// Because of the match guard, this subtraction will not underflow.
|
||||
let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64;
|
||||
let exponent: i64 = s[j + 1..].parse()?;
|
||||
if num_digits_between_decimal_point_and_e < exponent {
|
||||
Ok(0)
|
||||
} else {
|
||||
Ok((num_digits_between_decimal_point_and_e - exponent)
|
||||
.try_into()
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
_ => crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
s.quote(),
|
||||
uucore::execution_phrase()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of digits before the decimal point in a given number.
|
||||
///
|
||||
/// The input `s` is a string representing a number, either an integer
|
||||
/// or a floating point number in either decimal notation or scientific
|
||||
/// notation. This function returns the number of digits before the
|
||||
/// decimal point needed to print the number in decimal notation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 2);
|
||||
/// ```
|
||||
pub fn num_integral_digits(s: &str) -> Result<usize, ParseIntError> {
|
||||
match (s.find('.'), s.find('e')) {
|
||||
// For example, "123456".
|
||||
(None, None) => Ok(s.len()),
|
||||
|
||||
// For example, "123e456".
|
||||
(None, Some(j)) => {
|
||||
let exponent: i64 = s[j + 1..].parse()?;
|
||||
let total = j as i64 + exponent;
|
||||
if total < 1 {
|
||||
Ok(1)
|
||||
} else {
|
||||
Ok(total.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
// For example, "123.456".
|
||||
(Some(i), None) => Ok(i),
|
||||
|
||||
// For example, "123.456e789".
|
||||
(Some(i), Some(j)) => {
|
||||
let exponent: i64 = s[j + 1..].parse()?;
|
||||
let minimum: usize = {
|
||||
let integral_part: f64 = crash_if_err!(1, s[..j].parse());
|
||||
if integral_part == -0.0 && integral_part.is_sign_negative() {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
let total = i as i64 + exponent;
|
||||
if total < minimum as i64 {
|
||||
Ok(minimum)
|
||||
} else {
|
||||
Ok(total.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
mod test_num_integral_digits {
|
||||
use crate::num_integral_digits;
|
||||
|
||||
#[test]
|
||||
fn test_integer() {
|
||||
assert_eq!(num_integral_digits("123").unwrap(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal() {
|
||||
assert_eq!(num_integral_digits("123.45").unwrap(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_no_decimal_positive_exponent() {
|
||||
assert_eq!(num_integral_digits("123e4").unwrap(), 3 + 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_with_decimal_positive_exponent() {
|
||||
assert_eq!(num_integral_digits("123.45e6").unwrap(), 3 + 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_no_decimal_negative_exponent() {
|
||||
assert_eq!(num_integral_digits("123e-4").unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_with_decimal_negative_exponent() {
|
||||
assert_eq!(num_integral_digits("123.45e-6").unwrap(), 1);
|
||||
assert_eq!(num_integral_digits("123.45e-1").unwrap(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_num_fractional_digits {
|
||||
use crate::num_fractional_digits;
|
||||
|
||||
#[test]
|
||||
fn test_integer() {
|
||||
assert_eq!(num_fractional_digits("123").unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal() {
|
||||
assert_eq!(num_fractional_digits("123.45").unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_no_decimal_positive_exponent() {
|
||||
assert_eq!(num_fractional_digits("123e4").unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_with_decimal_positive_exponent() {
|
||||
assert_eq!(num_fractional_digits("123.45e6").unwrap(), 0);
|
||||
assert_eq!(num_fractional_digits("123.45e1").unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_no_decimal_negative_exponent() {
|
||||
assert_eq!(num_fractional_digits("123e-4").unwrap(), 4);
|
||||
assert_eq!(num_fractional_digits("123e-1").unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_with_decimal_negative_exponent() {
|
||||
assert_eq!(num_fractional_digits("123.45e-6").unwrap(), 8);
|
||||
assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,11 @@ use num_traits::{Num, ToPrimitive};
|
|||
use std::cmp;
|
||||
use std::io::{stdout, ErrorKind, Write};
|
||||
use std::str::FromStr;
|
||||
|
||||
mod digits;
|
||||
use crate::digits::num_fractional_digits;
|
||||
use crate::digits::num_integral_digits;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
|
||||
static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT.";
|
||||
|
@ -63,35 +68,15 @@ impl Number {
|
|||
}
|
||||
}
|
||||
|
||||
/// Number of characters needed to print the integral part of the number.
|
||||
/// Convert this number into a bigint, consuming it.
|
||||
///
|
||||
/// The number of characters includes one character to represent the
|
||||
/// minus sign ("-") if this number is negative.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use num_bigint::{BigInt, Sign};
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// Number::BigInt(BigInt::new(Sign::Plus, vec![123])).num_digits(),
|
||||
/// 3
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// Number::BigInt(BigInt::new(Sign::Minus, vec![123])).num_digits(),
|
||||
/// 4
|
||||
/// );
|
||||
/// assert_eq!(Number::F64(123.45).num_digits(), 3);
|
||||
/// assert_eq!(Number::MinusZero.num_digits(), 2);
|
||||
/// ```
|
||||
fn num_digits(&self) -> usize {
|
||||
/// For floats, this returns the [`BigInt`] corresponding to the
|
||||
/// floor of the number.
|
||||
fn into_bigint(self) -> BigInt {
|
||||
match self {
|
||||
Number::MinusZero => 2,
|
||||
Number::BigInt(n) => n.to_string().len(),
|
||||
Number::F64(n) => {
|
||||
let s = n.to_string();
|
||||
s.find('.').unwrap_or_else(|| s.len())
|
||||
}
|
||||
Number::MinusZero => BigInt::zero(),
|
||||
Number::F64(x) => BigInt::from(x.floor() as i64),
|
||||
Number::BigInt(n) => n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,20 +140,49 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
};
|
||||
|
||||
let mut largest_dec = 0;
|
||||
let mut padding = 0;
|
||||
let first = if numbers.len() > 1 {
|
||||
let slice = numbers[0];
|
||||
let len = slice.len();
|
||||
let dec = slice.find('.').unwrap_or(len);
|
||||
largest_dec = len - dec;
|
||||
largest_dec = num_fractional_digits(slice).unwrap_or_else(|_| {
|
||||
crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
slice.quote(),
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
});
|
||||
padding = num_integral_digits(slice).unwrap_or_else(|_| {
|
||||
crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
slice.quote(),
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
});
|
||||
crash_if_err!(1, slice.parse())
|
||||
} else {
|
||||
Number::BigInt(BigInt::one())
|
||||
};
|
||||
let increment = if numbers.len() > 2 {
|
||||
let slice = numbers[1];
|
||||
let len = slice.len();
|
||||
let dec = slice.find('.').unwrap_or(len);
|
||||
largest_dec = cmp::max(largest_dec, len - dec);
|
||||
let dec = num_fractional_digits(slice).unwrap_or_else(|_| {
|
||||
crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
slice.quote(),
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
});
|
||||
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
|
||||
crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
slice.quote(),
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
});
|
||||
largest_dec = cmp::max(largest_dec, dec);
|
||||
padding = cmp::max(padding, int_digits);
|
||||
crash_if_err!(1, slice.parse())
|
||||
} else {
|
||||
Number::BigInt(BigInt::one())
|
||||
|
@ -183,35 +197,50 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
let last: Number = {
|
||||
let slice = numbers[numbers.len() - 1];
|
||||
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
|
||||
crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
slice.quote(),
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
});
|
||||
padding = cmp::max(padding, int_digits);
|
||||
crash_if_err!(1, slice.parse())
|
||||
};
|
||||
if largest_dec > 0 {
|
||||
largest_dec -= 1;
|
||||
}
|
||||
|
||||
let padding = first
|
||||
.num_digits()
|
||||
.max(increment.num_digits())
|
||||
.max(last.num_digits());
|
||||
let is_negative_zero_f64 = |x: f64| x == -0.0 && x.is_sign_negative() && largest_dec == 0;
|
||||
let result = match (first, last, increment) {
|
||||
(Number::MinusZero, Number::BigInt(last), Number::BigInt(increment)) => print_seq_integers(
|
||||
(BigInt::zero(), increment, last),
|
||||
// For example, `seq -0 1 2` or `seq -0 1 2.0`.
|
||||
(Number::MinusZero, last, Number::BigInt(increment)) => print_seq_integers(
|
||||
(BigInt::zero(), increment, last.into_bigint()),
|
||||
options.separator,
|
||||
options.terminator,
|
||||
options.widths,
|
||||
padding,
|
||||
true,
|
||||
),
|
||||
(Number::BigInt(first), Number::BigInt(last), Number::BigInt(increment)) => {
|
||||
// For example, `seq -0e0 1 2` or `seq -0e0 1 2.0`.
|
||||
(Number::F64(x), last, Number::BigInt(increment)) if is_negative_zero_f64(x) => {
|
||||
print_seq_integers(
|
||||
(first, increment, last),
|
||||
(BigInt::zero(), increment, last.into_bigint()),
|
||||
options.separator,
|
||||
options.terminator,
|
||||
options.widths,
|
||||
padding,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
}
|
||||
// For example, `seq 0 1 2` or `seq 0 1 2.0`.
|
||||
(Number::BigInt(first), last, Number::BigInt(increment)) => print_seq_integers(
|
||||
(first, increment, last.into_bigint()),
|
||||
options.separator,
|
||||
options.terminator,
|
||||
options.widths,
|
||||
padding,
|
||||
false,
|
||||
),
|
||||
// For example, `seq 0 0.5 1` or `seq 0.0 0.5 1` or `seq 0.0 0.5 1.0`.
|
||||
(first, last, increment) => print_seq(
|
||||
(first.into_f64(), increment.into_f64(), last.into_f64()),
|
||||
largest_dec,
|
||||
|
@ -286,24 +315,31 @@ fn print_seq(
|
|||
let mut stdout = stdout.lock();
|
||||
let (first, increment, last) = range;
|
||||
let mut i = 0isize;
|
||||
let is_first_minus_zero = first == -0.0 && first.is_sign_negative();
|
||||
let mut value = first + i as f64 * increment;
|
||||
let padding = if pad { padding + 1 + largest_dec } else { 0 };
|
||||
let mut is_first_iteration = true;
|
||||
while !done_printing(&value, &increment, &last) {
|
||||
let istr = format!("{:.*}", largest_dec, value);
|
||||
let ilen = istr.len();
|
||||
let before_dec = istr.find('.').unwrap_or(ilen);
|
||||
if pad && before_dec < padding {
|
||||
for _ in 0..(padding - before_dec) {
|
||||
write!(stdout, "0")?;
|
||||
}
|
||||
}
|
||||
write!(stdout, "{}", istr)?;
|
||||
i += 1;
|
||||
value = first + i as f64 * increment;
|
||||
if !done_printing(&value, &increment, &last) {
|
||||
if !is_first_iteration {
|
||||
write!(stdout, "{}", separator)?;
|
||||
}
|
||||
let mut width = padding;
|
||||
if is_first_iteration && is_first_minus_zero {
|
||||
write!(stdout, "-")?;
|
||||
width -= 1;
|
||||
}
|
||||
is_first_iteration = false;
|
||||
write!(
|
||||
stdout,
|
||||
"{value:>0width$.precision$}",
|
||||
value = value,
|
||||
width = width,
|
||||
precision = largest_dec,
|
||||
)?;
|
||||
i += 1;
|
||||
value = first + i as f64 * increment;
|
||||
}
|
||||
if (first >= last && increment < 0f64) || (first <= last && increment > 0f64) {
|
||||
if !is_first_iteration {
|
||||
write!(stdout, "{}", terminator)?;
|
||||
}
|
||||
stdout.flush()?;
|
||||
|
@ -360,23 +396,3 @@ fn print_seq_integers(
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Number;
|
||||
use num_bigint::{BigInt, Sign};
|
||||
|
||||
#[test]
|
||||
fn test_number_num_digits() {
|
||||
assert_eq!(
|
||||
Number::BigInt(BigInt::new(Sign::Plus, vec![123])).num_digits(),
|
||||
3
|
||||
);
|
||||
assert_eq!(
|
||||
Number::BigInt(BigInt::new(Sign::Minus, vec![123])).num_digits(),
|
||||
4
|
||||
);
|
||||
assert_eq!(Number::F64(123.45).num_digits(), 3);
|
||||
assert_eq!(Number::MinusZero.num_digits(), 2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
// spell-checker:ignore (paths) wtmp
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use uucore::utmpx::{self, Utmpx};
|
||||
|
||||
|
@ -36,19 +38,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.after_help(&after_help[..])
|
||||
.get_matches_from(args);
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.values_of(ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
let files: Vec<&Path> = matches
|
||||
.values_of_os(ARG_FILES)
|
||||
.map(|v| v.map(AsRef::as_ref).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let filename = if !files.is_empty() {
|
||||
files[0].as_ref()
|
||||
files[0]
|
||||
} else {
|
||||
utmpx::DEFAULT_FILE
|
||||
utmpx::DEFAULT_FILE.as_ref()
|
||||
};
|
||||
|
||||
let mut users = Utmpx::iter_all_records()
|
||||
.read_from(filename)
|
||||
let mut users = Utmpx::iter_all_records_from(filename)
|
||||
.filter(Utmpx::is_user_process)
|
||||
.map(|ut| ut.user())
|
||||
.collect::<Vec<_>>();
|
||||
|
|
|
@ -341,15 +341,14 @@ impl Who {
|
|||
utmpx::DEFAULT_FILE
|
||||
};
|
||||
if self.short_list {
|
||||
let users = Utmpx::iter_all_records()
|
||||
.read_from(f)
|
||||
let users = Utmpx::iter_all_records_from(f)
|
||||
.filter(Utmpx::is_user_process)
|
||||
.map(|ut| ut.user())
|
||||
.collect::<Vec<_>>();
|
||||
println!("{}", users.join(" "));
|
||||
println!("# users={}", users.len());
|
||||
} else {
|
||||
let records = Utmpx::iter_all_records().read_from(f).peekable();
|
||||
let records = Utmpx::iter_all_records_from(f).peekable();
|
||||
|
||||
if self.include_heading {
|
||||
self.print_heading()
|
||||
|
|
|
@ -16,12 +16,15 @@ path = "src/whoami.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "wide"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries"] }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["lmcons"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.42"
|
||||
|
||||
[[bin]]
|
||||
name = "whoami"
|
||||
path = "src/main.rs"
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
// spell-checker:ignore (ToDO) getusername
|
||||
use std::ffi::OsString;
|
||||
use std::io;
|
||||
|
||||
use std::io::Result;
|
||||
use uucore::entries::uid2usr;
|
||||
use uucore::libc::geteuid;
|
||||
|
||||
pub unsafe fn get_username() -> Result<String> {
|
||||
// Get effective user id
|
||||
let uid = geteuid();
|
||||
uid2usr(uid)
|
||||
pub fn get_username() -> io::Result<OsString> {
|
||||
// SAFETY: getuid() does nothing with memory and is always successful.
|
||||
let uid = unsafe { libc::geteuid() };
|
||||
// uid2usr should arguably return an OsString but currently doesn't
|
||||
uid2usr(uid).map(Into::into)
|
||||
}
|
||||
|
|
|
@ -7,22 +7,21 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
extern crate winapi;
|
||||
use std::ffi::OsString;
|
||||
use std::io;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
|
||||
use self::winapi::shared::lmcons;
|
||||
use self::winapi::shared::minwindef;
|
||||
use self::winapi::um::{winbase, winnt};
|
||||
use std::io::{Error, Result};
|
||||
use std::mem;
|
||||
use uucore::wide::FromWide;
|
||||
use winapi::shared::lmcons;
|
||||
use winapi::shared::minwindef::DWORD;
|
||||
use winapi::um::winbase;
|
||||
|
||||
pub unsafe fn get_username() -> Result<String> {
|
||||
#[allow(deprecated)]
|
||||
let mut buffer: [winnt::WCHAR; lmcons::UNLEN as usize + 1] = mem::uninitialized();
|
||||
let mut len = buffer.len() as minwindef::DWORD;
|
||||
if winbase::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 {
|
||||
return Err(Error::last_os_error());
|
||||
pub fn get_username() -> io::Result<OsString> {
|
||||
const BUF_LEN: DWORD = lmcons::UNLEN + 1;
|
||||
let mut buffer = [0_u16; BUF_LEN as usize];
|
||||
let mut len = BUF_LEN;
|
||||
// SAFETY: buffer.len() == len
|
||||
if unsafe { winbase::GetUserNameW(buffer.as_mut_ptr(), &mut len) } == 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
let username = String::from_wide(&buffer[..len as usize - 1]);
|
||||
Ok(username)
|
||||
Ok(OsString::from_wide(&buffer[..len as usize - 1]))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use clap::App;
|
||||
|
||||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * (c) Jordi Boggiano <j.boggiano@seld.be>
|
||||
|
@ -14,46 +12,25 @@ extern crate clap;
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use clap::App;
|
||||
|
||||
use uucore::display::println_verbatim;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
|
||||
mod platform;
|
||||
|
||||
static ABOUT: &str = "Print the current username.";
|
||||
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let app = uu_app();
|
||||
|
||||
if let Err(err) = app.get_matches_from_safe(args) {
|
||||
if err.kind == clap::ErrorKind::HelpDisplayed
|
||||
|| err.kind == clap::ErrorKind::VersionDisplayed
|
||||
{
|
||||
println!("{}", err);
|
||||
Ok(())
|
||||
} else {
|
||||
return Err(USimpleError::new(1, format!("{}", err)));
|
||||
}
|
||||
} else {
|
||||
exec()
|
||||
}
|
||||
uu_app().get_matches_from(args);
|
||||
let username = platform::get_username().map_err_context(|| "failed to get username".into())?;
|
||||
println_verbatim(&username).map_err_context(|| "failed to print username".into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
app_from_crate!()
|
||||
}
|
||||
|
||||
pub fn exec() -> UResult<()> {
|
||||
unsafe {
|
||||
match platform::get_username() {
|
||||
Ok(username) => {
|
||||
println!("{}", username);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => match err.raw_os_error() {
|
||||
Some(0) | None => Err(USimpleError::new(1, "failed to get username")),
|
||||
Some(_) => Err(USimpleError::new(
|
||||
1,
|
||||
format!("failed to get username: {}", err),
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ pub mod fsext;
|
|||
pub mod ringbuffer;
|
||||
|
||||
// * (platform-specific) feature-gated modules
|
||||
// ** non-windows
|
||||
// ** non-windows (i.e. Unix + Fuchsia)
|
||||
#[cfg(all(not(windows), feature = "mode"))]
|
||||
pub mod mode;
|
||||
|
||||
|
|
|
@ -26,16 +26,12 @@ const MAX_PATH: usize = 266;
|
|||
static EXIT_ERR: i32 = 1;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::ffi::OsString;
|
||||
use std::ffi::OsStr;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
#[cfg(windows)]
|
||||
use winapi::shared::minwindef::DWORD;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::fileapi::GetDiskFreeSpaceW;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::fileapi::{
|
||||
|
@ -47,11 +43,12 @@ use winapi::um::handleapi::INVALID_HANDLE_VALUE;
|
|||
#[cfg(windows)]
|
||||
use winapi::um::winbase::DRIVE_REMOTE;
|
||||
|
||||
// Warning: the pointer has to be used *immediately* or the Vec
|
||||
// it points to will be dropped!
|
||||
#[cfg(windows)]
|
||||
macro_rules! String2LPWSTR {
|
||||
($str: expr) => {
|
||||
OsString::from($str.clone())
|
||||
.as_os_str()
|
||||
OsStr::new(&$str)
|
||||
.encode_wide()
|
||||
.chain(Some(0))
|
||||
.collect::<Vec<u16>>()
|
||||
|
@ -62,10 +59,8 @@ macro_rules! String2LPWSTR {
|
|||
#[cfg(windows)]
|
||||
#[allow(non_snake_case)]
|
||||
fn LPWSTR2String(buf: &[u16]) -> String {
|
||||
let len = unsafe { libc::wcslen(buf.as_ptr()) };
|
||||
OsString::from_wide(&buf[..len as usize])
|
||||
.into_string()
|
||||
.unwrap()
|
||||
let len = buf.iter().position(|&n| n == 0).unwrap();
|
||||
String::from_utf16(&buf[..len]).unwrap()
|
||||
}
|
||||
|
||||
use self::time::Timespec;
|
||||
|
@ -77,7 +72,6 @@ use std::borrow::Cow;
|
|||
use std::convert::{AsRef, From};
|
||||
#[cfg(unix)]
|
||||
use std::ffi::CString;
|
||||
#[cfg(unix)]
|
||||
use std::io::Error as IOError;
|
||||
#[cfg(unix)]
|
||||
use std::mem;
|
||||
|
@ -157,16 +151,14 @@ impl MountInfo {
|
|||
fn set_missing_fields(&mut self) {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
// We want to keep the dev_id on Windows
|
||||
// but set dev_id
|
||||
let path = CString::new(self.mount_dir.clone()).unwrap();
|
||||
unsafe {
|
||||
let mut stat = mem::zeroed();
|
||||
if libc::stat(path.as_ptr(), &mut stat) == 0 {
|
||||
self.dev_id = (stat.st_dev as i32).to_string();
|
||||
} else {
|
||||
self.dev_id = "".to_string();
|
||||
}
|
||||
if let Ok(stat) = std::fs::metadata(&self.mount_dir) {
|
||||
// Why do we cast this to i32?
|
||||
self.dev_id = (stat.dev() as i32).to_string()
|
||||
} else {
|
||||
self.dev_id = "".to_string();
|
||||
}
|
||||
}
|
||||
// set MountInfo::dummy
|
||||
|
@ -247,8 +239,7 @@ impl MountInfo {
|
|||
volume_name.pop();
|
||||
unsafe {
|
||||
QueryDosDeviceW(
|
||||
OsString::from(volume_name.clone())
|
||||
.as_os_str()
|
||||
OsStr::new(&volume_name)
|
||||
.encode_wide()
|
||||
.chain(Some(0))
|
||||
.skip(4)
|
||||
|
@ -445,9 +436,11 @@ pub fn read_fs_list() -> Vec<MountInfo> {
|
|||
FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as DWORD)
|
||||
};
|
||||
if INVALID_HANDLE_VALUE == find_handle {
|
||||
crash!(EXIT_ERR, "FindFirstVolumeW failed: {}", unsafe {
|
||||
GetLastError()
|
||||
});
|
||||
crash!(
|
||||
EXIT_ERR,
|
||||
"FindFirstVolumeW failed: {}",
|
||||
IOError::last_os_error()
|
||||
);
|
||||
}
|
||||
let mut mounts = Vec::<MountInfo>::new();
|
||||
loop {
|
||||
|
@ -466,8 +459,9 @@ pub fn read_fs_list() -> Vec<MountInfo> {
|
|||
volume_name_buf.len() as DWORD,
|
||||
)
|
||||
} {
|
||||
let err = unsafe { GetLastError() };
|
||||
if err != winapi::shared::winerror::ERROR_NO_MORE_FILES {
|
||||
let err = IOError::last_os_error();
|
||||
if err.raw_os_error() != Some(winapi::shared::winerror::ERROR_NO_MORE_FILES as i32)
|
||||
{
|
||||
crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err);
|
||||
}
|
||||
break;
|
||||
|
@ -527,7 +521,7 @@ impl FsUsage {
|
|||
crash!(
|
||||
EXIT_ERR,
|
||||
"GetVolumePathNamesForVolumeNameW failed: {}",
|
||||
unsafe { GetLastError() }
|
||||
IOError::last_os_error()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -547,9 +541,11 @@ impl FsUsage {
|
|||
};
|
||||
if 0 == success {
|
||||
// Fails in case of CD for example
|
||||
//crash!(EXIT_ERR, "GetDiskFreeSpaceW failed: {}", unsafe {
|
||||
//GetLastError()
|
||||
//});
|
||||
// crash!(
|
||||
// EXIT_ERR,
|
||||
// "GetDiskFreeSpaceW failed: {}",
|
||||
// IOError::last_os_error()
|
||||
// );
|
||||
}
|
||||
|
||||
let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
|
||||
|
|
|
@ -143,6 +143,13 @@ pub fn parse_mode(mode: &str) -> Result<mode_t, String> {
|
|||
}
|
||||
|
||||
pub fn get_umask() -> u32 {
|
||||
// There's no portable way to read the umask without changing it.
|
||||
// We have to replace it and then quickly set it back, hopefully before
|
||||
// some other thread is affected.
|
||||
// On modern Linux kernels the current umask could instead be read
|
||||
// from /proc/self/status. But that's a lot of work.
|
||||
// SAFETY: umask always succeeds and doesn't operate on memory. Races are
|
||||
// possible but it can't violate Rust's guarantees.
|
||||
let mask = unsafe { umask(0) };
|
||||
unsafe { umask(mask) };
|
||||
mask as u32
|
||||
|
|
|
@ -19,6 +19,8 @@ use std::process::ExitStatus as StdExitStatus;
|
|||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// SAFETY: These functions always succeed and return simple integers.
|
||||
|
||||
/// `geteuid()` returns the effective user ID of the calling process.
|
||||
pub fn geteuid() -> uid_t {
|
||||
unsafe { libc::geteuid() }
|
||||
|
@ -96,6 +98,9 @@ impl fmt::Display for ExitStatus {
|
|||
/// Missing methods for Child objects
|
||||
pub trait ChildExt {
|
||||
/// Send a signal to a Child process.
|
||||
///
|
||||
/// Caller beware: if the process already exited then you may accidentally
|
||||
/// send the signal to an unrelated process that recycled the PID.
|
||||
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
|
||||
|
||||
/// Wait for a process to finish or return after the specified duration.
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
//!
|
||||
//! ```
|
||||
//! use uucore::utmpx::Utmpx;
|
||||
//! for ut in Utmpx::iter_all_records().read_from("/some/where/else") {
|
||||
//! for ut in Utmpx::iter_all_records_from("/some/where/else") {
|
||||
//! if ut.is_user_process() {
|
||||
//! println!("{}: {}", ut.host(), ut.user())
|
||||
//! }
|
||||
|
@ -35,9 +35,12 @@ pub extern crate time;
|
|||
use self::time::{Timespec, Tm};
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::io::Error as IOError;
|
||||
use std::io::Result as IOResult;
|
||||
use std::marker::PhantomData;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
pub use self::ut::*;
|
||||
use libc::utmpx;
|
||||
|
@ -54,7 +57,9 @@ pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int {
|
|||
0
|
||||
}
|
||||
|
||||
pub use crate::*; // import macros from `../../macros.rs`
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::*; // import macros from `../../macros.rs`
|
||||
|
||||
// In case the c_char array doesn't end with NULL
|
||||
macro_rules! chars2string {
|
||||
|
@ -248,30 +253,76 @@ impl Utmpx {
|
|||
Ok(host.to_string())
|
||||
}
|
||||
|
||||
/// Iterate through all the utmp records.
|
||||
///
|
||||
/// This will use the default location, or the path [`Utmpx::iter_all_records_from`]
|
||||
/// was most recently called with.
|
||||
///
|
||||
/// Only one instance of [`UtmpxIter`] may be active at a time. This
|
||||
/// function will block as long as one is still active. Beware!
|
||||
pub fn iter_all_records() -> UtmpxIter {
|
||||
UtmpxIter
|
||||
let iter = UtmpxIter::new();
|
||||
unsafe {
|
||||
// This can technically fail, and it would be nice to detect that,
|
||||
// but it doesn't return anything so we'd have to do nasty things
|
||||
// with errno.
|
||||
setutxent();
|
||||
}
|
||||
iter
|
||||
}
|
||||
|
||||
/// Iterate through all the utmp records from a specific file.
|
||||
///
|
||||
/// No failure is reported or detected.
|
||||
///
|
||||
/// This function affects subsequent calls to [`Utmpx::iter_all_records`].
|
||||
///
|
||||
/// The same caveats as for [`Utmpx::iter_all_records`] apply.
|
||||
pub fn iter_all_records_from<P: AsRef<Path>>(path: P) -> UtmpxIter {
|
||||
let iter = UtmpxIter::new();
|
||||
let path = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
|
||||
unsafe {
|
||||
// In glibc, utmpxname() only fails if there's not enough memory
|
||||
// to copy the string.
|
||||
// Solaris returns 1 on success instead of 0. Supposedly there also
|
||||
// exist systems where it returns void.
|
||||
// GNU who on Debian seems to output nothing if an invalid filename
|
||||
// is specified, no warning or anything.
|
||||
// So this function is pretty crazy and we don't try to detect errors.
|
||||
// Not much we can do besides pray.
|
||||
utmpxname(path.as_ptr());
|
||||
setutxent();
|
||||
}
|
||||
iter
|
||||
}
|
||||
}
|
||||
|
||||
// On some systems these functions are not thread-safe. On others they're
|
||||
// thread-local. Therefore we use a mutex to allow only one guard to exist at
|
||||
// a time, and make sure UtmpxIter cannot be sent across threads.
|
||||
//
|
||||
// I believe the only technical memory unsafety that could happen is a data
|
||||
// race while copying the data out of the pointer returned by getutxent(), but
|
||||
// ordinary race conditions are also very much possible.
|
||||
static LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
|
||||
|
||||
/// Iterator of login records
|
||||
pub struct UtmpxIter;
|
||||
pub struct UtmpxIter {
|
||||
#[allow(dead_code)]
|
||||
guard: MutexGuard<'static, ()>,
|
||||
/// Ensure UtmpxIter is !Send. Technically redundant because MutexGuard
|
||||
/// is also !Send.
|
||||
phantom: PhantomData<std::rc::Rc<()>>,
|
||||
}
|
||||
|
||||
impl UtmpxIter {
|
||||
/// Sets the name of the utmpx-format file for the other utmpx functions to access.
|
||||
///
|
||||
/// If not set, default record file will be used(file path depends on the target OS)
|
||||
pub fn read_from(self, f: &str) -> Self {
|
||||
let res = unsafe {
|
||||
let cstring = CString::new(f).unwrap();
|
||||
utmpxname(cstring.as_ptr())
|
||||
};
|
||||
if res != 0 {
|
||||
show_warning!("utmpxname: {}", IOError::last_os_error());
|
||||
fn new() -> Self {
|
||||
// PoisonErrors can safely be ignored
|
||||
let guard = LOCK.lock().unwrap_or_else(|err| err.into_inner());
|
||||
UtmpxIter {
|
||||
guard,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
unsafe {
|
||||
setutxent();
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,13 +332,24 @@ impl Iterator for UtmpxIter {
|
|||
unsafe {
|
||||
let res = getutxent();
|
||||
if !res.is_null() {
|
||||
// The data behind this pointer will be replaced by the next
|
||||
// call to getutxent(), so we have to read it now.
|
||||
// All the strings live inline in the struct as arrays, which
|
||||
// makes things easier.
|
||||
Some(Utmpx {
|
||||
inner: ptr::read(res as *const _),
|
||||
})
|
||||
} else {
|
||||
endutxent();
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for UtmpxIter {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
endutxent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ pub use crate::features::fsext;
|
|||
pub use crate::features::ringbuffer;
|
||||
|
||||
// * (platform-specific) feature-gated modules
|
||||
// ** non-windows
|
||||
// ** non-windows (i.e. Unix + Fuchsia)
|
||||
#[cfg(all(not(windows), feature = "mode"))]
|
||||
pub use crate::features::mode;
|
||||
// ** unix-only
|
||||
|
|
|
@ -1,17 +1,42 @@
|
|||
//! Custom panic hooks that allow silencing certain types of errors.
|
||||
//!
|
||||
//! Use the [`mute_sigpipe_panic`] function to silence panics caused by
|
||||
//! broken pipe errors. This can happen when a process is still
|
||||
//! producing data when the consuming process terminates and closes the
|
||||
//! pipe. For example,
|
||||
//!
|
||||
//! ```sh
|
||||
//! $ seq inf | head -n 1
|
||||
//! ```
|
||||
//!
|
||||
use std::panic;
|
||||
use std::panic::PanicInfo;
|
||||
|
||||
//## SIGPIPE handling background/discussions ...
|
||||
//* `uutils` ~ <https://github.com/uutils/coreutils/issues/374> , <https://github.com/uutils/coreutils/pull/1106>
|
||||
//* rust and `rg` ~ <https://github.com/rust-lang/rust/issues/62569> , <https://github.com/BurntSushi/ripgrep/issues/200> , <https://github.com/crev-dev/cargo-crev/issues/287>
|
||||
/// Decide whether a panic was caused by a broken pipe (SIGPIPE) error.
|
||||
fn is_broken_pipe(info: &PanicInfo) -> bool {
|
||||
if let Some(res) = info.payload().downcast_ref::<String>() {
|
||||
if res.contains("BrokenPipe") || res.contains("Broken pipe") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Terminate without error on panics that occur due to broken pipe errors.
|
||||
///
|
||||
/// For background discussions on `SIGPIPE` handling, see
|
||||
///
|
||||
/// * https://github.com/uutils/coreutils/issues/374
|
||||
/// * https://github.com/uutils/coreutils/pull/1106
|
||||
/// * https://github.com/rust-lang/rust/issues/62569
|
||||
/// * https://github.com/BurntSushi/ripgrep/issues/200
|
||||
/// * https://github.com/crev-dev/cargo-crev/issues/287
|
||||
///
|
||||
pub fn mute_sigpipe_panic() {
|
||||
let hook = panic::take_hook();
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
if let Some(res) = info.payload().downcast_ref::<String>() {
|
||||
if res.contains("BrokenPipe") {
|
||||
return;
|
||||
}
|
||||
if !is_broken_pipe(info) {
|
||||
hook(info)
|
||||
}
|
||||
hook(info)
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -38,6 +38,30 @@ macro_rules! test_digest {
|
|||
.no_stderr()
|
||||
.stdout_is("input.txt: OK\n");
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_text_mode() {
|
||||
// TODO Replace this with hard-coded files that store the
|
||||
// expected output of text mode on an input file that has
|
||||
// "\r\n" line endings.
|
||||
let result = new_ucmd!()
|
||||
.args(&[DIGEST_ARG, BITS_ARG, "-b"])
|
||||
.pipe_in("a\nb\nc\n")
|
||||
.succeeds();
|
||||
let expected = result.no_stderr().stdout();
|
||||
// Replace the "*-\n" at the end of the output with " -\n".
|
||||
// The asterisk indicates that the digest was computed in
|
||||
// binary mode.
|
||||
let n = expected.len();
|
||||
let expected = [&expected[..n - 3], &[b' ', b'-', b'\n']].concat();
|
||||
new_ucmd!()
|
||||
.args(&[DIGEST_ARG, BITS_ARG, "-t"])
|
||||
.pipe_in("a\r\nb\r\nc\r\n")
|
||||
.succeeds()
|
||||
.no_stderr()
|
||||
.stdout_is(std::str::from_utf8(&expected).unwrap());
|
||||
}
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
|
|
@ -56,6 +56,12 @@ fn test_kill_list_all_signals() {
|
|||
.stdout_contains("HUP");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kill_list_final_new_line() {
|
||||
let re = Regex::new("\\n$").unwrap();
|
||||
assert!(re.is_match(new_ucmd!().arg("-l").succeeds().stdout_str()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kill_list_all_signals_as_table() {
|
||||
// Check for a few signals. Do not try to be comprehensive.
|
||||
|
@ -104,6 +110,26 @@ fn test_kill_with_signal_number_old_form() {
|
|||
assert_eq!(target.wait_for_signal(), Some(9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kill_with_signal_name_old_form() {
|
||||
let mut target = Target::new();
|
||||
new_ucmd!()
|
||||
.arg("-KILL")
|
||||
.arg(format!("{}", target.pid()))
|
||||
.succeeds();
|
||||
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kill_with_signal_prefixed_name_old_form() {
|
||||
let mut target = Target::new();
|
||||
new_ucmd!()
|
||||
.arg("-SIGKILL")
|
||||
.arg(format!("{}", target.pid()))
|
||||
.succeeds();
|
||||
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kill_with_signal_number_new_form() {
|
||||
let mut target = Target::new();
|
||||
|
@ -125,3 +151,14 @@ fn test_kill_with_signal_name_new_form() {
|
|||
.succeeds();
|
||||
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kill_with_signal_prefixed_name_new_form() {
|
||||
let mut target = Target::new();
|
||||
new_ucmd!()
|
||||
.arg("-s")
|
||||
.arg("SIGKILL")
|
||||
.arg(format!("{}", target.pid()))
|
||||
.succeeds();
|
||||
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
|
||||
}
|
||||
|
|
|
@ -23,6 +23,56 @@ fn test_rejects_non_floats() {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_float() {
|
||||
new_ucmd!()
|
||||
.args(&["1e2.3"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid floating point argument: '1e2.3'")
|
||||
.stderr_contains("for more information.");
|
||||
new_ucmd!()
|
||||
.args(&["1e2.3", "2"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid floating point argument: '1e2.3'")
|
||||
.stderr_contains("for more information.");
|
||||
new_ucmd!()
|
||||
.args(&["1", "1e2.3"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid floating point argument: '1e2.3'")
|
||||
.stderr_contains("for more information.");
|
||||
new_ucmd!()
|
||||
.args(&["1e2.3", "2", "3"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid floating point argument: '1e2.3'")
|
||||
.stderr_contains("for more information.");
|
||||
new_ucmd!()
|
||||
.args(&["1", "1e2.3", "3"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid floating point argument: '1e2.3'")
|
||||
.stderr_contains("for more information.");
|
||||
new_ucmd!()
|
||||
.args(&["1", "2", "1e2.3"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid floating point argument: '1e2.3'")
|
||||
.stderr_contains("for more information.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_width_invalid_float() {
|
||||
new_ucmd!()
|
||||
.args(&["-w", "1e2.3"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid floating point argument: '1e2.3'")
|
||||
.stderr_contains("for more information.");
|
||||
}
|
||||
|
||||
// ---- Tests for the big integer based path ----
|
||||
|
||||
#[test]
|
||||
|
@ -149,6 +199,16 @@ fn test_preserve_negative_zero_start() {
|
|||
.succeeds()
|
||||
.stdout_is("-0\n1\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-0", "1", "2"])
|
||||
.succeeds()
|
||||
.stdout_is("-0\n1\n2\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-0", "1", "2.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-0\n1\n2\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -176,6 +236,203 @@ fn test_width_negative_zero() {
|
|||
.succeeds()
|
||||
.stdout_is("-0\n01\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0", "1", "2"])
|
||||
.succeeds()
|
||||
.stdout_is("-0\n01\n02\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0", "1", "2.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-0\n01\n02\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_width_negative_zero_decimal_notation() {
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.0", "1"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.0\n01.0\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.0", "1.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.0\n01.0\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.0", "1", "2"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.0\n01.0\n02.0\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.0", "1", "2.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.0\n01.0\n02.0\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.0", "1.0", "2"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.0\n01.0\n02.0\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.0", "1.0", "2.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.0\n01.0\n02.0\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_width_negative_zero_scientific_notation() {
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0e0", "1"])
|
||||
.succeeds()
|
||||
.stdout_is("-0\n01\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0e0", "1", "2"])
|
||||
.succeeds()
|
||||
.stdout_is("-0\n01\n02\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0e0", "1", "2.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-0\n01\n02\n")
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0e+1", "1"])
|
||||
.succeeds()
|
||||
.stdout_is("-00\n001\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0e+1", "1", "2"])
|
||||
.succeeds()
|
||||
.stdout_is("-00\n001\n002\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0e+1", "1", "2.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-00\n001\n002\n")
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e0", "1"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.000\n01.000\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e0", "1", "2"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.000\n01.000\n02.000\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e0", "1", "2.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.000\n01.000\n02.000\n")
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e-2", "1"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.00000\n01.00000\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e-2", "1", "2"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.00000\n01.00000\n02.00000\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e-2", "1", "2.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.00000\n01.00000\n02.00000\n")
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e5", "1"])
|
||||
.succeeds()
|
||||
.stdout_is("-000000\n0000001\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e5", "1", "2"])
|
||||
.succeeds()
|
||||
.stdout_is("-000000\n0000001\n0000002\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e5", "1", "2.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-000000\n0000001\n0000002\n")
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e5", "1"])
|
||||
.succeeds()
|
||||
.stdout_is("-000000\n0000001\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e5", "1", "2"])
|
||||
.succeeds()
|
||||
.stdout_is("-000000\n0000001\n0000002\n")
|
||||
.no_stderr();
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.000e5", "1", "2.0"])
|
||||
.succeeds()
|
||||
.stdout_is("-000000\n0000001\n0000002\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_width_decimal_scientific_notation_increment() {
|
||||
new_ucmd!()
|
||||
.args(&["-w", ".1", "1e-2", ".11"])
|
||||
.succeeds()
|
||||
.stdout_is("0.10\n0.11\n")
|
||||
.no_stderr();
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-w", ".0", "1.500e-1", ".2"])
|
||||
.succeeds()
|
||||
.stdout_is("0.0000\n0.1500\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
/// Test that trailing zeros in the start argument contribute to precision.
|
||||
#[test]
|
||||
fn test_width_decimal_scientific_notation_trailing_zeros_start() {
|
||||
new_ucmd!()
|
||||
.args(&["-w", ".1000", "1e-2", ".11"])
|
||||
.succeeds()
|
||||
.stdout_is("0.1000\n0.1100\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
/// Test that trailing zeros in the increment argument contribute to precision.
|
||||
#[test]
|
||||
fn test_width_decimal_scientific_notation_trailing_zeros_increment() {
|
||||
new_ucmd!()
|
||||
.args(&["-w", "1e-1", "0.0100", ".11"])
|
||||
.succeeds()
|
||||
.stdout_is("0.1000\n0.1100\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
/// Test that trailing zeros in the end argument do not contribute to width.
|
||||
#[test]
|
||||
fn test_width_decimal_scientific_notation_trailing_zeros_end() {
|
||||
new_ucmd!()
|
||||
.args(&["-w", "1e-1", "1e-2", ".1100"])
|
||||
.succeeds()
|
||||
.stdout_is("0.10\n0.11\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_width_floats() {
|
||||
new_ucmd!()
|
||||
.args(&["-w", "9.0", "10.0"])
|
||||
.succeeds()
|
||||
.stdout_is("09.0\n10.0\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
// TODO This is duplicated from `test_yes.rs`; refactor them.
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
#[cfg(unix)]
|
||||
use crate::common::util::*;
|
||||
|
||||
#[test]
|
||||
|
@ -34,7 +33,6 @@ fn test_normal_compare_id() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_normal_compare_env() {
|
||||
let whoami = whoami();
|
||||
if whoami == "nobody" {
|
||||
|
|
|
@ -1069,10 +1069,12 @@ pub fn whoami() -> String {
|
|||
|
||||
// Use environment variable to get current user instead of
|
||||
// invoking `whoami` and fall back to user "nobody" on error.
|
||||
std::env::var("USER").unwrap_or_else(|e| {
|
||||
println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e);
|
||||
"nobody".to_string()
|
||||
})
|
||||
std::env::var("USER")
|
||||
.or_else(|_| std::env::var("USERNAME"))
|
||||
.unwrap_or_else(|e| {
|
||||
println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e);
|
||||
"nobody".to_string()
|
||||
})
|
||||
}
|
||||
|
||||
/// Add prefix 'g' for `util_name` if not on linux
|
||||
|
@ -1167,7 +1169,7 @@ pub fn check_coreutil_version(
|
|||
if s.contains(&format!("(GNU coreutils) {}", version_expected)) {
|
||||
Ok(format!("{}: {}", UUTILS_INFO, s.to_string()))
|
||||
} else if s.contains("(GNU coreutils)") {
|
||||
let version_found = s.split_whitespace().last().unwrap()[..4].parse::<f32>().unwrap_or_default();
|
||||
let version_found = parse_coreutil_version(s);
|
||||
let version_expected = version_expected.parse::<f32>().unwrap_or_default();
|
||||
if version_found > version_expected {
|
||||
Ok(format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found))
|
||||
|
@ -1180,6 +1182,20 @@ pub fn check_coreutil_version(
|
|||
)
|
||||
}
|
||||
|
||||
// simple heuristic to parse the coreutils SemVer string, e.g. "id (GNU coreutils) 8.32.263-0475"
|
||||
fn parse_coreutil_version(version_string: &str) -> f32 {
|
||||
version_string
|
||||
.split_whitespace()
|
||||
.last()
|
||||
.unwrap()
|
||||
.split('.')
|
||||
.take(2)
|
||||
.collect::<Vec<_>>()
|
||||
.join(".")
|
||||
.parse::<f32>()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// This runs the GNU coreutils `util_name` binary in `$PATH` in order to
|
||||
/// dynamically gather reference values on the system.
|
||||
/// If the `util_name` in `$PATH` doesn't include a coreutils version string,
|
||||
|
@ -1472,6 +1488,36 @@ mod tests {
|
|||
res.normalized_newlines_stdout_is("A\r\nB\nC\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_parse_coreutil_version() {
|
||||
use std::assert_eq;
|
||||
assert_eq!(
|
||||
parse_coreutil_version("id (GNU coreutils) 9.0.123-0123").to_string(),
|
||||
"9"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_coreutil_version("id (GNU coreutils) 8.32.263-0475").to_string(),
|
||||
"8.32"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_coreutil_version("id (GNU coreutils) 8.25.123-0123").to_string(),
|
||||
"8.25"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_coreutil_version("id (GNU coreutils) 9.0").to_string(),
|
||||
"9"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_coreutil_version("id (GNU coreutils) 8.32").to_string(),
|
||||
"8.32"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_coreutil_version("id (GNU coreutils) 8.25").to_string(),
|
||||
"8.25"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_check_coreutil_version() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue