1
Fork 0
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:
Jan Scheer 2021-10-02 12:48:54 +02:00
commit 22b59289e8
No known key found for this signature in database
GPG key ID: C62AD4C29E2B9828
28 changed files with 1092 additions and 391 deletions

View file

@ -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
View file

@ -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",

View file

@ -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())
}

View file

@ -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"

View file

@ -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(())
}
}

View file

@ -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())

View file

@ -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 == '.');

View file

@ -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
View 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);
}
}
}

View file

@ -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);
}
}

View file

@ -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<_>>();

View file

@ -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()

View file

@ -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"

View file

@ -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)
}

View file

@ -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]))
}

View file

@ -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)
}

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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.

View file

@ -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();
}
}
}

View file

@ -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

View file

@ -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)
}));
}

View file

@ -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());
}
}
)*)
}

View file

@ -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));
}

View file

@ -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.

View file

@ -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" {

View file

@ -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() {