1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge branch 'main' into mktemp-set-dir-mode

This commit is contained in:
Sylvestre Ledru 2022-05-11 12:59:05 +02:00 committed by GitHub
commit 25ecb81c7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 1349 additions and 530 deletions

View file

@ -9,3 +9,12 @@ rustflags = [
"-Wclippy::single_char_pattern",
"-Wclippy::explicit_iter_loop",
]
[build]
# See https://github.com/time-rs/time/issues/293#issuecomment-1005002386. The
# unsoundness here is not in the `time` library, but in the Rust stdlib, and as
# such it needs to be fixed there.
rustflags = "--cfg unsound_local_offset"
[target.'cfg(target_os = "linux")']
rustflags = ["--cfg", "unsound_local_offset"]

View file

@ -5,3 +5,8 @@ updates:
schedule:
interval: "daily"
open-pull-requests-limit: 5
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 5

View file

@ -384,9 +384,9 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils
args: -v ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils
env:
RUSTFLAGS: "-Awarnings"
RUSTFLAGS: "-Awarnings --cfg unsound_local_offset"
deps:
name: Dependencies

View file

@ -8,6 +8,10 @@ on: [push, pull_request]
jobs:
gnu:
permissions:
actions: read # for dawidd6/action-download-artifact to query and download artifacts
contents: read # for actions/checkout to fetch code
pull-requests: read # for dawidd6/action-download-artifact to query commit hash
name: Run GNU tests
runs-on: ubuntu-latest
steps:
@ -170,6 +174,8 @@ jobs:
REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}'
if test -f "${REF_LOG_FILE}"; then
echo "Reference SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")"
REF_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort)
NEW_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | sort)
REF_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort)
NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | sort)
for LINE in ${REF_FAILING}
@ -186,6 +192,21 @@ jobs:
have_new_failures="true"
fi
done
for LINE in ${REF_ERROR}
do
if ! grep -Fxq ${LINE}<<<"${NEW_ERROR}"; then
echo "::warning ::Congrats! The gnu test ${LINE} is no longer ERROR!"
fi
done
for LINE in ${NEW_ERROR}
do
if ! grep -Fxq ${LINE}<<<"${REF_ERROR}"
then
echo "::error ::GNU test error: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
have_new_failures="true"
fi
done
else
echo "::warning ::Skipping test failure comparison; no prior reference test logs are available."
fi

319
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -391,7 +391,7 @@ rand = "0.8"
regex = "1.5"
sha1 = { version="0.10", features=["std"] }
tempfile = "3"
time = "0.1"
time = {version="0.3", features=["local-offset"]}
unindent = "0.1"
uucore = { version=">=0.0.11", package="uucore", path="src/uucore", features=["entries", "process"] }
walkdir = "2.2"

View file

@ -11,6 +11,8 @@ unmaintained = "warn"
yanked = "warn"
notice = "warn"
ignore = [
"RUSTSEC-2020-0159",
"RUSTSEC-2020-0071",
#"RUSTSEC-0000-0000",
]
@ -62,7 +64,7 @@ highlight = "all"
# spell-checker: disable
skip = [
# getrandom
{ name = "wasi", version="0.10.2+wasi-snapshot-preview1" },
{ name = "wasi", version="0.10.0+wasi-snapshot-preview1" },
# blake2d_simd
{ name = "arrayvec", version = "=0.7.2" },
# flimit/unix_socket
@ -84,6 +86,8 @@ skip = [
{ name = "memchr", version = "=1.0.2" },
{ name = "quote", version = "=0.3.15" },
{ name = "unicode-xid", version = "=0.0.4" },
# chrono
{ name = "time", version = "=0.1.44" },
]
# spell-checker: enable

View file

@ -5,7 +5,7 @@ utilities in [Rust](https://www.rust-lang.org). It is available for
Linux, Windows, Mac and other platforms.
The API reference for `uucore`, the library of functions shared between
various utils, is hosted at at
various utils, is hosted at
[docs.rs](https://docs.rs/uucore/latest/uucore/).
uutils is licensed under the [MIT License](https://github.com/uutils/coreutils/blob/main/LICENSE).
@ -17,4 +17,4 @@ uutils is licensed under the [MIT License](https://github.com/uutils/coreutils/b
* [Discord](https://discord.gg/wQVJbvJ)
> Note: This manual is automatically generated from the source code and is
> a work in progress.
> a work in progress.

View file

@ -90,7 +90,7 @@ pub fn parse_base_cmd_args(args: impl uucore::Args, about: &str, usage: &str) ->
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
Config::from(&command.get_matches_from(arg_list))
Config::from(&command.try_get_matches_from(arg_list)?)
}
pub fn base_app<'a>(about: &'a str, usage: &'a str) -> Command<'a> {

View file

@ -188,7 +188,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = uu_app().get_matches_from(args);
let matches = uu_app().try_get_matches_from(args)?;
let number_mode = if matches.is_present(options::NUMBER_NONBLANK) {
NumberingMode::NonEmpty

View file

@ -16,7 +16,7 @@ path = "src/chmod.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode"] }
[[bin]]

View file

@ -21,7 +21,7 @@ path = "src/cp.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
filetime = "0.2"
libc = "0.2.124"
libc = "0.2.125"
quick-error = "2.0.1"
selinux = { version="0.2", optional=true }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] }
@ -34,7 +34,7 @@ ioctl-sys = "0.8"
winapi = { version="0.3", features=["fileapi"] }
[target.'cfg(unix)'.dependencies]
xattr="0.2.1"
xattr="0.2.3"
exacl= { version = "0.8.0", optional=true }
[[bin]]

View file

@ -57,7 +57,7 @@ use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr;
use std::string::ToString;
use uucore::backup_control::{self, BackupMode};
use uucore::error::{set_exit_code, ExitCode, UError, UResult};
use uucore::error::{set_exit_code, ExitCode, UClapError, UError, UResult};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use walkdir::WalkDir;
@ -485,7 +485,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
app.print_help()?;
}
clap::ErrorKind::DisplayVersion => println!("{}", app.render_version()),
_ => return Err(Box::new(e)),
_ => return Err(Box::new(e.with_exit_code(1))),
};
} else if let Ok(matches) = matches {
let options = Options::from_matches(&matches)?;
@ -992,7 +992,9 @@ fn copy_directory(
}
// if no-dereference is enabled and this is a symlink, copy it as a file
if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink() {
if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink()
// replace by is_symlink in rust>=1.58
{
return copy_file(root, target, options, symlinked_files);
}
@ -1036,6 +1038,7 @@ fn copy_directory(
{
let p = or_continue!(path);
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
// replace by is_symlink in rust >=1.58
let path = current_dir.join(&p.path());
let local_to_root_parent = match root_parent {
@ -1288,7 +1291,7 @@ fn copy_file(
// Fail if dest is a dangling symlink or a symlink this program created previously
if fs::symlink_metadata(dest)
.map(|m| m.file_type().is_symlink())
.map(|m| m.file_type().is_symlink()) // replace by is_symlink in rust>=1.58
.unwrap_or(false)
{
if FileInformation::from_path(dest, false)
@ -1301,7 +1304,7 @@ fn copy_file(
dest.display()
)));
}
if !dest.exists() {
if options.dereference && !dest.exists() {
return Err(Error::Error(format!(
"not writing through dangling symlink '{}'",
dest.display()
@ -1535,7 +1538,7 @@ fn copy_link(
} else {
// we always need to remove the file to be able to create a symlink,
// even if it is writeable.
if dest.exists() {
if dest.is_file() {
fs::remove_file(dest)?;
}
dest.into()

View file

@ -3,9 +3,9 @@
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
//! Types for representing and displaying block sizes.
use crate::{OPT_BLOCKSIZE, OPT_HUMAN_READABLE_BINARY, OPT_HUMAN_READABLE_DECIMAL};
use crate::OPT_BLOCKSIZE;
use clap::ArgMatches;
use std::fmt;
use std::{env, fmt};
use uucore::parse_size::{parse_size, ParseSizeError};
@ -26,6 +26,22 @@ const IEC_BASES: [u128; 10] = [
/// Suffixes for the first nine multi-byte unit suffixes.
const SUFFIXES: [char; 9] = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
const SI_BASES: [u128; 10] = [
1,
1_000,
1_000_000,
1_000_000_000,
1_000_000_000_000,
1_000_000_000_000_000,
1_000_000_000_000_000_000,
1_000_000_000_000_000_000_000,
1_000_000_000_000_000_000_000_000,
1_000_000_000_000_000_000_000_000_000,
];
// we use "kB" instead of "KB" because of GNU df
const SI_SUFFIXES: [&str; 9] = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
/// Convert a multiple of 1024 into a string like "12K" or "34M".
///
/// # Examples
@ -57,63 +73,112 @@ fn to_magnitude_and_suffix_1024(n: u128) -> Result<String, ()> {
Err(())
}
/// Convert a number into a string like "12kB" or "34MB".
///
/// Powers of 1000 become "1kB", "1MB", "1GB", etc.
///
/// The returned string has a maximum length of 5 chars, for example: "1.1kB", "999kB", "1MB".
fn to_magnitude_and_suffix_not_powers_of_1024(n: u128) -> Result<String, ()> {
let mut i = 0;
while SI_BASES[i + 1] - SI_BASES[i] < n && i < SI_SUFFIXES.len() {
i += 1;
}
let quot = n / SI_BASES[i];
let rem = n % SI_BASES[i];
let suffix = SI_SUFFIXES[i];
if rem == 0 {
Ok(format!("{}{}", quot, suffix))
} else {
let tenths_place = rem / (SI_BASES[i] / 10);
if rem % (SI_BASES[i] / 10) == 0 {
Ok(format!("{}.{}{}", quot, tenths_place, suffix))
} else if tenths_place + 1 == 10 {
Ok(format!("{}{}", quot + 1, suffix))
} else {
Ok(format!("{}.{}{}", quot, tenths_place + 1, suffix))
}
}
}
/// Convert a number into a magnitude and a multi-byte unit suffix.
///
/// # Errors
///
/// If the number is too large to represent.
fn to_magnitude_and_suffix(n: u128) -> Result<String, ()> {
if n % 1024 == 0 {
if n % 1024 == 0 && n % 1000 != 0 {
to_magnitude_and_suffix_1024(n)
} else {
// TODO Implement this, probably using code from `numfmt`.
Ok("1kB".into())
to_magnitude_and_suffix_not_powers_of_1024(n)
}
}
/// A mode to use in condensing the display of a large number of bytes.
pub(crate) enum SizeFormat {
HumanReadable(HumanReadable),
StaticBlockSize,
}
impl Default for SizeFormat {
fn default() -> Self {
Self::StaticBlockSize
}
}
/// A mode to use in condensing the human readable display of a large number
/// of bytes.
///
/// The [`HumanReadable::Decimal`] and[`HumanReadable::Binary`] variants
/// represent dynamic block sizes: as the number of bytes increases, the
/// divisor increases as well (for example, from 1 to 1,000 to 1,000,000
/// and so on in the case of [`HumanReadable::Decimal`]).
#[derive(Clone, Copy)]
pub(crate) enum HumanReadable {
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,000. Contrast with
/// [`HumanReadable::Binary`], which represents powers of
/// 1,024.
Decimal,
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,024. Contrast with
/// [`HumanReadable::Decimal`], which represents powers
/// of 1,000.
Binary,
}
/// A block size to use in condensing the display of a large number of bytes.
///
/// The [`BlockSize::Bytes`] variant represents a static block
/// size. The [`BlockSize::HumanReadableDecimal`] and
/// [`BlockSize::HumanReadableBinary`] variants represent dynamic
/// block sizes: as the number of bytes increases, the divisor
/// increases as well (for example, from 1 to 1,000 to 1,000,000 and
/// so on in the case of [`BlockSize::HumanReadableDecimal`]).
/// size.
///
/// The default variant is `Bytes(1024)`.
#[derive(Debug, PartialEq)]
pub(crate) enum BlockSize {
/// A fixed number of bytes.
///
/// The number must be positive.
Bytes(u64),
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,000. Contrast with
/// [`BlockSize::HumanReadableBinary`], which represents powers of
/// 1,024.
HumanReadableDecimal,
/// Use the largest divisor corresponding to a unit, like B, K, M, G, etc.
///
/// This variant represents powers of 1,024. Contrast with
/// [`BlockSize::HumanReadableDecimal`], which represents powers
/// of 1,000.
HumanReadableBinary,
}
impl Default for BlockSize {
fn default() -> Self {
Self::Bytes(1024)
if env::var("POSIXLY_CORRECT").is_ok() {
Self::Bytes(512)
} else {
Self::Bytes(1024)
}
}
}
pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> {
if matches.is_present(OPT_HUMAN_READABLE_BINARY) {
Ok(BlockSize::HumanReadableBinary)
} else if matches.is_present(OPT_HUMAN_READABLE_DECIMAL) {
Ok(BlockSize::HumanReadableDecimal)
} else if matches.is_present(OPT_BLOCKSIZE) {
if matches.is_present(OPT_BLOCKSIZE) {
let s = matches.value_of(OPT_BLOCKSIZE).unwrap();
Ok(BlockSize::Bytes(parse_size(s)?))
} else {
@ -124,10 +189,8 @@ pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize,
impl fmt::Display for BlockSize {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::HumanReadableBinary => write!(f, "Size"),
Self::HumanReadableDecimal => write!(f, "Size"),
Self::Bytes(n) => match to_magnitude_and_suffix(*n as u128) {
Ok(s) => write!(f, "{}-blocks", s),
Ok(s) => write!(f, "{}", s),
Err(_) => Err(fmt::Error),
},
}
@ -137,6 +200,8 @@ impl fmt::Display for BlockSize {
#[cfg(test)]
mod tests {
use std::env;
use crate::blocks::{to_magnitude_and_suffix, BlockSize};
#[test]
@ -153,46 +218,53 @@ mod tests {
);
}
// TODO We have not yet implemented this behavior, but when we do,
// uncomment this test.
#[test]
fn test_to_magnitude_and_suffix_not_powers_of_1024() {
assert_eq!(to_magnitude_and_suffix(1).unwrap(), "1B");
assert_eq!(to_magnitude_and_suffix(999).unwrap(), "999B");
// #[test]
// fn test_to_magnitude_and_suffix_not_powers_of_1024() {
// assert_eq!(to_magnitude_and_suffix(1).unwrap(), "1B");
// assert_eq!(to_magnitude_and_suffix(999).unwrap(), "999B");
assert_eq!(to_magnitude_and_suffix(1000).unwrap(), "1kB");
assert_eq!(to_magnitude_and_suffix(1001).unwrap(), "1.1kB");
assert_eq!(to_magnitude_and_suffix(1023).unwrap(), "1.1kB");
assert_eq!(to_magnitude_and_suffix(1025).unwrap(), "1.1kB");
assert_eq!(to_magnitude_and_suffix(999_000).unwrap(), "999kB");
// assert_eq!(to_magnitude_and_suffix(1000).unwrap(), "1kB");
// assert_eq!(to_magnitude_and_suffix(1001).unwrap(), "1.1kB");
// assert_eq!(to_magnitude_and_suffix(1023).unwrap(), "1.1kB");
// assert_eq!(to_magnitude_and_suffix(1025).unwrap(), "1.1kB");
// assert_eq!(to_magnitude_and_suffix(999_000).unwrap(), "999kB");
assert_eq!(to_magnitude_and_suffix(999_001).unwrap(), "1MB");
assert_eq!(to_magnitude_and_suffix(999_999).unwrap(), "1MB");
assert_eq!(to_magnitude_and_suffix(1_000_000).unwrap(), "1MB");
assert_eq!(to_magnitude_and_suffix(1_000_001).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_100_000).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_100_001).unwrap(), "1.2MB");
assert_eq!(to_magnitude_and_suffix(1_900_000).unwrap(), "1.9MB");
assert_eq!(to_magnitude_and_suffix(1_900_001).unwrap(), "2MB");
assert_eq!(to_magnitude_and_suffix(9_900_000).unwrap(), "9.9MB");
assert_eq!(to_magnitude_and_suffix(9_900_001).unwrap(), "10MB");
assert_eq!(to_magnitude_and_suffix(999_000_000).unwrap(), "999MB");
// assert_eq!(to_magnitude_and_suffix(999_001).unwrap(), "1MB");
// assert_eq!(to_magnitude_and_suffix(999_999).unwrap(), "1MB");
// assert_eq!(to_magnitude_and_suffix(1_000_000).unwrap(), "1MB");
// assert_eq!(to_magnitude_and_suffix(1_000_001).unwrap(), "1.1MB");
// assert_eq!(to_magnitude_and_suffix(1_100_000).unwrap(), "1.1MB");
// assert_eq!(to_magnitude_and_suffix(1_100_001).unwrap(), "1.2MB");
// assert_eq!(to_magnitude_and_suffix(1_900_000).unwrap(), "1.9MB");
// assert_eq!(to_magnitude_and_suffix(1_900_001).unwrap(), "2MB");
// assert_eq!(to_magnitude_and_suffix(9_900_000).unwrap(), "9.9MB");
// assert_eq!(to_magnitude_and_suffix(9_900_001).unwrap(), "10MB");
// assert_eq!(to_magnitude_and_suffix(999_000_000).unwrap(), "999MB");
assert_eq!(to_magnitude_and_suffix(999_000_001).unwrap(), "1GB");
assert_eq!(to_magnitude_and_suffix(1_000_000_000).unwrap(), "1GB");
assert_eq!(to_magnitude_and_suffix(1_000_000_001).unwrap(), "1.1GB");
}
// assert_eq!(to_magnitude_and_suffix(999_000_001).unwrap(), "1GB");
// assert_eq!(to_magnitude_and_suffix(1_000_000_000).unwrap(), "1GB");
// // etc.
// }
#[test]
fn test_to_magnitude_and_suffix_multiples_of_1000_and_1024() {
assert_eq!(to_magnitude_and_suffix(128_000).unwrap(), "128kB");
assert_eq!(to_magnitude_and_suffix(1000 * 1024).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_000_000_000_000).unwrap(), "1TB");
}
#[test]
fn test_block_size_display() {
assert_eq!(format!("{}", BlockSize::HumanReadableBinary), "Size");
assert_eq!(format!("{}", BlockSize::HumanReadableDecimal), "Size");
assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K-blocks");
assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K-blocks");
assert_eq!(
format!("{}", BlockSize::Bytes(3 * 1024 * 1024)),
"3M-blocks"
);
assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K");
assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K");
assert_eq!(format!("{}", BlockSize::Bytes(3 * 1024 * 1024)), "3M");
}
#[test]
fn test_default_block_size() {
assert_eq!(BlockSize::Bytes(1024), BlockSize::default());
env::set_var("POSIXLY_CORRECT", "1");
assert_eq!(BlockSize::Bytes(512), BlockSize::default());
env::remove_var("POSIXLY_CORRECT");
}
}

View file

@ -197,6 +197,7 @@ impl Column {
match column {
// 14 = length of "Filesystem" plus 4 spaces
Self::Source => 14,
Self::Used => 5,
// the shortest headers have a length of 4 chars so we use that as the minimum width
_ => 4,
}

View file

@ -11,9 +11,11 @@ mod columns;
mod filesystem;
mod table;
use blocks::{HumanReadable, SizeFormat};
use uucore::display::Quotable;
use uucore::error::{UError, UResult, USimpleError};
use uucore::fsext::{read_fs_list, MountInfo};
use uucore::parse_size::ParseSizeError;
use uucore::{format_usage, show};
use clap::{crate_version, Arg, ArgMatches, Command};
@ -30,6 +32,13 @@ use crate::table::Table;
static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
or all file systems by default.";
const USAGE: &str = "{} [OPTION]... [FILE]...";
const LONG_HELP: &str = "Display values are in units of the first available SIZE from --block-size,
and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.
Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).
SIZE is an integer and optional unit (example: 10M is 10*1024*1024).
Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers
of 1000).";
static OPT_HELP: &str = "help";
static OPT_ALL: &str = "all";
@ -61,6 +70,7 @@ static OUTPUT_FIELD_LIST: [&str; 12] = [
struct Options {
show_local_fs: bool,
show_all_fs: bool,
size_format: SizeFormat,
block_size: BlockSize,
/// Optional list of filesystem types to include in the output table.
@ -88,6 +98,7 @@ impl Default for Options {
show_local_fs: Default::default(),
show_all_fs: Default::default(),
block_size: Default::default(),
size_format: Default::default(),
include: Default::default(),
exclude: Default::default(),
show_total: Default::default(),
@ -105,7 +116,8 @@ impl Default for Options {
#[derive(Debug)]
enum OptionsError {
InvalidBlockSize,
BlockSizeTooLarge(String),
InvalidBlockSize(String),
/// An error getting the columns to display in the output table.
ColumnError(ColumnError),
@ -116,11 +128,14 @@ enum OptionsError {
impl fmt::Display for OptionsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
// TODO This should include the raw string provided as the argument.
//
// TODO This needs to vary based on whether `--block-size`
// or `-B` were provided.
Self::InvalidBlockSize => write!(f, "invalid --block-size argument"),
Self::BlockSizeTooLarge(s) => {
write!(f, "--block-size argument {} too large", s.quote())
}
// TODO This needs to vary based on whether `--block-size`
// or `-B` were provided.
Self::InvalidBlockSize(s) => write!(f, "invalid --block-size argument {}", s),
Self::ColumnError(ColumnError::MultipleColumns(s)) => write!(
f,
"option --output: field {} used more than once",
@ -155,8 +170,21 @@ impl Options {
Ok(Self {
show_local_fs: matches.is_present(OPT_LOCAL),
show_all_fs: matches.is_present(OPT_ALL),
block_size: block_size_from_matches(matches)
.map_err(|_| OptionsError::InvalidBlockSize)?,
block_size: block_size_from_matches(matches).map_err(|e| match e {
ParseSizeError::SizeTooBig(_) => OptionsError::BlockSizeTooLarge(
matches.value_of(OPT_BLOCKSIZE).unwrap().to_string(),
),
ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s),
})?,
size_format: {
if matches.is_present(OPT_HUMAN_READABLE_BINARY) {
SizeFormat::HumanReadable(HumanReadable::Binary)
} else if matches.is_present(OPT_HUMAN_READABLE_DECIMAL) {
SizeFormat::HumanReadable(HumanReadable::Decimal)
} else {
SizeFormat::StaticBlockSize
}
},
include,
exclude,
show_total: matches.is_present(OPT_TOTAL),
@ -406,6 +434,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.after_help(LONG_HELP)
.infer_long_args(true)
.arg(
Arg::new(OPT_HELP)
@ -424,6 +453,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.short('B')
.long("block-size")
.takes_value(true)
.value_name("SIZE")
.overrides_with_all(&[OPT_KILO, OPT_BLOCKSIZE])
.help(
"scale sizes by SIZE before printing them; e.g.\
@ -480,6 +510,7 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(OPT_OUTPUT)
.long("output")
.takes_value(true)
.value_name("FIELD_LIST")
.min_values(0)
.require_equals(true)
.use_value_delimiter(true)
@ -489,7 +520,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.default_values(&["source", "size", "used", "avail", "pcent", "target"])
.conflicts_with_all(&[OPT_INODES, OPT_PORTABILITY, OPT_PRINT_TYPE])
.help(
"use the output format defined by FIELD_LIST,\
"use the output format defined by FIELD_LIST, \
or print all fields if FIELD_LIST is omitted.",
),
)
@ -512,6 +543,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long("type")
.allow_invalid_utf8(true)
.takes_value(true)
.value_name("TYPE")
.multiple_occurrences(true)
.help("limit listing to file systems of type TYPE"),
)
@ -528,6 +560,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.long("exclude-type")
.allow_invalid_utf8(true)
.takes_value(true)
.value_name("TYPE")
.use_value_delimiter(true)
.multiple_occurrences(true)
.help("limit listing to file systems not of type TYPE"),

View file

@ -10,6 +10,7 @@
use number_prefix::NumberPrefix;
use unicode_width::UnicodeWidthStr;
use crate::blocks::{HumanReadable, SizeFormat};
use crate::columns::{Alignment, Column};
use crate::filesystem::Filesystem;
use crate::{BlockSize, Options};
@ -213,15 +214,10 @@ impl<'a> RowFormatter<'a> {
}
/// Get a human readable string giving the scaled version of the input number.
///
/// The scaling factor is defined in the `options` field.
///
/// This function is supposed to be used by `scaled_bytes()` and `scaled_inodes()` only.
fn scaled_human_readable(&self, size: u64) -> String {
let number_prefix = match self.options.block_size {
BlockSize::HumanReadableDecimal => NumberPrefix::decimal(size as f64),
BlockSize::HumanReadableBinary => NumberPrefix::binary(size as f64),
_ => unreachable!(),
fn scaled_human_readable(&self, size: u64, human_readable: HumanReadable) -> String {
let number_prefix = match human_readable {
HumanReadable::Decimal => NumberPrefix::decimal(size as f64),
HumanReadable::Binary => NumberPrefix::binary(size as f64),
};
match number_prefix {
NumberPrefix::Standalone(bytes) => bytes.to_string(),
@ -233,10 +229,12 @@ impl<'a> RowFormatter<'a> {
///
/// The scaling factor is defined in the `options` field.
fn scaled_bytes(&self, size: u64) -> String {
if let BlockSize::Bytes(d) = self.options.block_size {
(size / d).to_string()
} else {
self.scaled_human_readable(size)
match self.options.size_format {
SizeFormat::HumanReadable(h) => self.scaled_human_readable(size, h),
SizeFormat::StaticBlockSize => {
let BlockSize::Bytes(d) = self.options.block_size;
(size / d).to_string()
}
}
}
@ -244,10 +242,9 @@ impl<'a> RowFormatter<'a> {
///
/// The scaling factor is defined in the `options` field.
fn scaled_inodes(&self, size: u64) -> String {
if let BlockSize::Bytes(_) = self.options.block_size {
size.to_string()
} else {
self.scaled_human_readable(size)
match self.options.size_format {
SizeFormat::HumanReadable(h) => self.scaled_human_readable(size, h),
SizeFormat::StaticBlockSize => size.to_string(),
}
}
@ -305,7 +302,12 @@ impl Header {
for column in &options.columns {
let header = match column {
Column::Source => String::from("Filesystem"),
Column::Size => options.block_size.to_string(),
Column::Size => match options.size_format {
SizeFormat::HumanReadable(_) => String::from("Size"),
SizeFormat::StaticBlockSize => {
format!("{}-blocks", options.block_size)
}
},
Column::Used => String::from("Used"),
Column::Avail => String::from("Available"),
Column::Pcent => String::from("Use%"),
@ -424,6 +426,7 @@ impl fmt::Display for Table {
#[cfg(test)]
mod tests {
use crate::blocks::{HumanReadable, SizeFormat};
use crate::columns::Column;
use crate::table::{Header, Row, RowFormatter};
use crate::{BlockSize, Options};
@ -446,6 +449,30 @@ mod tests {
Column::Target,
];
impl Default for Row {
fn default() -> Self {
Self {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
}
}
}
#[test]
fn test_default_header() {
let options = Default::default();
@ -523,7 +550,7 @@ mod tests {
#[test]
fn test_header_with_human_readable_binary() {
let options = Options {
block_size: BlockSize::HumanReadableBinary,
size_format: SizeFormat::HumanReadable(HumanReadable::Binary),
..Default::default()
};
assert_eq!(
@ -542,7 +569,7 @@ mod tests {
#[test]
fn test_header_with_human_readable_si() {
let options = Options {
block_size: BlockSize::HumanReadableDecimal,
size_format: SizeFormat::HumanReadable(HumanReadable::Decimal),
..Default::default()
};
assert_eq!(
@ -565,9 +592,7 @@ mod tests {
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
@ -575,13 +600,7 @@ mod tests {
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
@ -598,7 +617,6 @@ mod tests {
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
@ -608,13 +626,7 @@ mod tests {
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
@ -631,23 +643,15 @@ mod tests {
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
@ -664,23 +668,9 @@ mod tests {
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(fmt.get_values(), vec!("1", "10"));
@ -689,12 +679,11 @@ mod tests {
#[test]
fn test_row_formatter_with_human_readable_si() {
let options = Options {
block_size: BlockSize::HumanReadableDecimal,
size_format: SizeFormat::HumanReadable(HumanReadable::Decimal),
columns: COLUMNS_WITH_FS_TYPE.to_vec(),
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
@ -704,13 +693,7 @@ mod tests {
bytes_avail: 3000,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
@ -730,12 +713,11 @@ mod tests {
#[test]
fn test_row_formatter_with_human_readable_binary() {
let options = Options {
block_size: BlockSize::HumanReadableBinary,
size_format: SizeFormat::HumanReadable(HumanReadable::Binary),
columns: COLUMNS_WITH_FS_TYPE.to_vec(),
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
@ -745,13 +727,7 @@ mod tests {
bytes_avail: 3072,
bytes_usage: Some(0.25),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
@ -771,32 +747,14 @@ mod tests {
#[test]
fn test_row_formatter_with_round_up_usage() {
let options = Options {
block_size: BlockSize::Bytes(1),
columns: vec![Column::Pcent],
..Default::default()
};
let row = Row {
file: Some("/path/to/file".to_string()),
fs_device: "my_device".to_string(),
fs_type: "my_type".to_string(),
fs_mount: "my_mount".to_string(),
bytes: 100,
bytes_used: 25,
bytes_avail: 75,
bytes_usage: Some(0.251),
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),
inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes_usage: Some(0.2),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options);
assert_eq!(
fmt.get_values(),
vec!("my_device", "100", "25", "75", "26%", "my_mount")
);
assert_eq!(fmt.get_values(), vec!("26%"));
}
}

View file

@ -20,7 +20,7 @@ use std::fs::File;
use std::fs::Metadata;
use std::io::BufRead;
use std::io::BufReader;
use std::io::{ErrorKind, Result};
use std::io::Result;
use std::iter;
#[cfg(not(windows))]
use std::os::unix::fs::MetadataExt;
@ -34,7 +34,8 @@ use std::str::FromStr;
use std::time::{Duration, UNIX_EPOCH};
use std::{error::Error, fmt::Display};
use uucore::display::{print_verbatim, Quotable};
use uucore::error::{set_exit_code, UError, UResult};
use uucore::error::FromIo;
use uucore::error::{UError, UResult};
use uucore::format_usage;
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling;
@ -102,7 +103,6 @@ const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2
struct Options {
all: bool,
util_name: String,
max_depth: Option<usize>,
total: bool,
separate_dirs: bool,
@ -309,13 +309,9 @@ fn du(
let read = match fs::read_dir(&my_stat.path) {
Ok(read) => read,
Err(e) => {
eprintln!(
"{}: cannot read directory {}: {}",
options.util_name,
my_stat.path.quote(),
e
show!(
e.map_err_context(|| format!("cannot read directory {}", my_stat.path.quote()))
);
set_exit_code(1);
return Box::new(iter::once(my_stat));
}
};
@ -368,18 +364,9 @@ fn du(
}
}
}
Err(error) => match error.kind() {
ErrorKind::PermissionDenied => {
let description = format!("cannot access {}", entry.path().quote());
let error_message = "Permission denied";
show_error_custom_description!(description, "{}", error_message);
set_exit_code(1);
}
_ => {
set_exit_code(1);
show_error!("cannot access {}: {}", entry.path().quote(), error);
}
},
Err(e) => show!(
e.map_err_context(|| format!("cannot access {}", entry.path().quote()))
),
}
}
Err(error) => show_error!("{}", error),
@ -567,7 +554,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let options = Options {
all: matches.is_present(options::ALL),
util_name: uucore::util_name().to_owned(),
max_depth,
total: matches.is_present(options::TOTAL),
separate_dirs: matches.is_present(options::SEPARATE_DIRS),

View file

@ -17,7 +17,7 @@ path = "src/expr.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
num-bigint = "0.4.0"
num-traits = "0.2.14"
num-traits = "0.2.15"
onig = { version = "~6.3", default-features = false }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }

View file

@ -12,12 +12,12 @@ categories = ["command-line-utilities"]
edition = "2021"
[build-dependencies]
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
num-traits = "0.2.15" # used in src/numerics.rs, which is included by build.rs
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
coz = { version = "0.1.3", optional = true }
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
num-traits = "0.2.15" # Needs at least version 0.2.15 for "OverflowingAdd"
rand = { version = "0.8", features = ["small_rng"] }
smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or later.
uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }

View file

@ -16,7 +16,7 @@ path = "src/hostid.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
[[bin]]

View file

@ -24,6 +24,9 @@ file_diff = "1.0.0"
libc = ">= 0.2"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode", "perms", "entries"] }
[dev-dependencies]
time = "0.3"
[[bin]]
name = "install"
path = "src/main.rs"

View file

@ -16,7 +16,7 @@ path = "src/kill.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["signals"] }
[[bin]]

View file

@ -15,7 +15,7 @@ edition = "2021"
path = "src/logname.rs"
[dependencies]
libc = "0.2.124"
libc = "0.2.125"
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }

View file

@ -16,7 +16,7 @@ path = "src/mkfifo.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
[[bin]]

View file

@ -17,7 +17,7 @@ path = "src/mknod.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "^0.2.124"
libc = "^0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["mode"] }
[[bin]]

View file

@ -206,20 +206,41 @@ pub fn uu_app<'a>() -> Command<'a> {
)
}
/// Parse a template string into prefix, suffix, and random components.
///
/// `temp` is the template string, with three or more consecutive `X`s
/// representing a placeholder for randomly generated characters (for
/// example, `"abc_XXX.txt"`). If `temp` ends in an `X`, then a suffix
/// can be specified by `suffix` instead.
///
/// # Errors
///
/// * If there are fewer than three consecutive `X`s in `temp`.
/// * If `suffix` is a [`Some`] object but `temp` does not end in `X`.
/// * If the suffix (specified either way) contains a path separator.
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(parse_template("XXX", None).unwrap(), ("", 3, ""));
/// assert_eq!(parse_template("abcXXX", None).unwrap(), ("abc", 3, ""));
/// assert_eq!(parse_template("XXXdef", None).unwrap(), ("", 3, "def"));
/// assert_eq!(parse_template("abcXXXdef", None).unwrap(), ("abc", 3, "def"));
/// ```
fn parse_template<'a>(
temp: &'a str,
suffix: Option<&'a str>,
) -> UResult<(&'a str, usize, &'a str)> {
) -> Result<(&'a str, usize, &'a str), MkTempError> {
let right = match temp.rfind('X') {
Some(r) => r + 1,
None => return Err(MkTempError::TooFewXs(temp.into()).into()),
None => return Err(MkTempError::TooFewXs(temp.into())),
};
let left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1);
let prefix = &temp[..left];
let rand = right - left;
if rand < 3 {
return Err(MkTempError::TooFewXs(temp.into()).into());
return Err(MkTempError::TooFewXs(temp.into()));
}
let mut suf = &temp[right..];
@ -228,12 +249,12 @@ fn parse_template<'a>(
if suf.is_empty() {
suf = s;
} else {
return Err(MkTempError::MustEndInX(temp.into()).into());
return Err(MkTempError::MustEndInX(temp.into()));
}
};
if suf.chars().any(is_separator) {
return Err(MkTempError::ContainsDirSeparator(suf.into()).into());
return Err(MkTempError::ContainsDirSeparator(suf.into()));
}
Ok((prefix, rand, suf))
@ -309,3 +330,41 @@ fn exec(dir: &Path, prefix: &str, rand: usize, suffix: &str, make_dir: bool) ->
println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned())
}
#[cfg(test)]
mod tests {
use crate::parse_template;
#[test]
fn test_parse_template_no_suffix() {
assert_eq!(parse_template("XXX", None).unwrap(), ("", 3, ""));
assert_eq!(parse_template("abcXXX", None).unwrap(), ("abc", 3, ""));
assert_eq!(parse_template("XXXdef", None).unwrap(), ("", 3, "def"));
assert_eq!(
parse_template("abcXXXdef", None).unwrap(),
("abc", 3, "def")
);
}
#[test]
fn test_parse_template_suffix() {
assert_eq!(parse_template("XXX", Some("def")).unwrap(), ("", 3, "def"));
assert_eq!(
parse_template("abcXXX", Some("def")).unwrap(),
("abc", 3, "def")
);
}
#[test]
fn test_parse_template_errors() {
// TODO This should be an error as well, but we are not
// catching it just yet. A future commit will correct this.
//
// assert!(parse_template("a/bXXX", None).is_err());
//
assert!(parse_template("XXXa/b", None).is_err());
assert!(parse_template("XX", None).is_err());
assert!(parse_template("XXXabc", Some("def")).is_err());
assert!(parse_template("XXX", Some("a/b")).is_err());
}
}

View file

@ -16,7 +16,7 @@ path = "src/nice.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
nix = { version = "0.24.1", default-features = false }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }

View file

@ -17,7 +17,7 @@ use std::ptr;
use clap::{crate_version, Arg, Command};
use uucore::{
error::{set_exit_code, UResult, USimpleError, UUsageError},
error::{set_exit_code, UClapError, UResult, USimpleError, UUsageError},
format_usage,
};
@ -35,7 +35,7 @@ const USAGE: &str = "{} [OPTIONS] [COMMAND [ARGS]]";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().get_matches_from(args);
let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?;
let mut niceness = unsafe {
nix::errno::Errno::clear();

View file

@ -16,7 +16,7 @@ path = "src/nohup.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
atty = "0.2"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] }

View file

@ -15,7 +15,7 @@ edition = "2021"
path = "src/nproc.rs"
[dependencies]
libc = "0.2.124"
libc = "0.2.125"
num_cpus = "1.10"
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] }

View file

@ -16,7 +16,7 @@ path = "src/pathchk.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
[[bin]]

View file

@ -221,10 +221,10 @@ impl Capitalize for str {
fn idle_string(when: i64) -> String {
thread_local! {
static NOW: time::Tm = time::now()
static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
}
NOW.with(|n| {
let duration = n.to_timespec().sec - when;
let duration = n.unix_timestamp() - when;
if duration < 60 {
// less than 1min
" ".to_owned()
@ -242,7 +242,11 @@ fn idle_string(when: i64) -> String {
}
fn time_string(ut: &Utmpx) -> String {
time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C
// "%b %e %H:%M"
let time_format: Vec<time::format_description::FormatItem> =
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
.unwrap();
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
}
fn gecos_to_fullname(pw: &Passwd) -> Option<String> {

View file

@ -17,7 +17,7 @@ path = "src/rmdir.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
libc = "0.2.124"
libc = "0.2.125"
[[bin]]
name = "rmdir"

View file

@ -19,7 +19,7 @@ path = "src/seq.rs"
bigdecimal = "0.3"
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
num-bigint = "0.4.0"
num-traits = "0.2.14"
num-traits = "0.2.15"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["memo"] }
[[bin]]

View file

@ -21,7 +21,7 @@ compare = "0.1.0"
ctrlc = { version = "3.0", features = ["termination"] }
fnv = "1.0.7"
itertools = "0.10.0"
memchr = "2.4.0"
memchr = "2.5.0"
ouroboros = "0.15.0"
rand = "0.8"
rayon = "1.5"

View file

@ -16,7 +16,7 @@ path = "src/sync.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["wide"] }
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "std", "winbase", "winerror"] }

View file

@ -16,7 +16,7 @@ path = "src/tail.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["ringbuffer", "lines"] }
[target.'cfg(windows)'.dependencies]

View file

@ -16,7 +16,7 @@ path = "src/tee.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
retain_mut = "=0.1.7" # ToDO: [2021-01-01; rivy; maint/MinSRV] ~ v0.1.5 uses const generics which aren't stabilized until rust v1.51.0
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] }

View file

@ -16,7 +16,7 @@ path = "src/test.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
[target.'cfg(target_os = "redox")'.dependencies]

View file

@ -16,7 +16,7 @@ path = "src/timeout.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
nix = { version = "0.24.1", default-features = false, features = ["signal"] }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["process", "signals"] }

View file

@ -17,7 +17,7 @@ path = "src/touch.rs"
[dependencies]
filetime = "0.2.1"
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
time = "0.1.40"
time = { version = "0.3", features = ["parsing", "formatting", "local-offset", "macros"] }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] }
[target.'cfg(target_os = "windows")'.dependencies]

View file

@ -6,8 +6,7 @@
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult
// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult mktime YYYYMMDDHHMM YYMMDDHHMM DATETIME YYYYMMDDHHMMS subsecond
pub extern crate filetime;
#[macro_use]
@ -17,6 +16,8 @@ use clap::{crate_version, Arg, ArgGroup, Command};
use filetime::*;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use time::macros::{format_description, offset, time};
use time::Duration;
use uucore::display::Quotable;
use uucore::error::{FromIo, UError, UResult, USimpleError};
use uucore::format_usage;
@ -41,14 +42,27 @@ pub mod options {
static ARG_FILES: &str = "files";
fn to_local(mut tm: time::Tm) -> time::Tm {
tm.tm_utcoff = time::now().tm_utcoff;
tm
// Convert a date/time to a date with a TZ offset
fn to_local(tm: time::PrimitiveDateTime) -> time::OffsetDateTime {
let offset = match time::OffsetDateTime::now_local() {
Ok(lo) => lo.offset(),
Err(e) => {
panic!("error: {}", e);
}
};
tm.assume_offset(offset)
}
fn local_tm_to_filetime(tm: time::Tm) -> FileTime {
let ts = tm.to_timespec();
FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32)
// Convert a date/time with a TZ offset into a FileTime
fn local_dt_to_filetime(dt: time::OffsetDateTime) -> FileTime {
FileTime::from_unix_time(dt.unix_timestamp(), dt.nanosecond())
}
// Convert a date/time, considering that the input is in UTC time
// Used for touch -d 1970-01-01 18:43:33.023456789 for example
fn dt_to_filename(tm: time::PrimitiveDateTime) -> FileTime {
let dt = tm.assume_offset(offset!(UTC));
local_dt_to_filetime(dt)
}
#[uucore::main]
@ -62,7 +76,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Try 'touch --help' for more information."##,
)
})?;
let (mut atime, mut mtime) =
if let Some(reference) = matches.value_of_os(options::sources::REFERENCE) {
stat(Path::new(reference), !matches.is_present(options::NO_DEREF))?
@ -72,7 +85,7 @@ Try 'touch --help' for more information."##,
} else if let Some(current) = matches.value_of(options::sources::CURRENT) {
parse_timestamp(current)?
} else {
local_tm_to_filetime(time::now())
local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap())
};
(timestamp, timestamp)
};
@ -248,38 +261,129 @@ fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> {
))
}
fn parse_date(str: &str) -> UResult<FileTime> {
const POSIX_LOCALE_FORMAT: &[time::format_description::FormatItem] = format_description!(
"[weekday repr:short] [month repr:short] [day padding:space] \
[hour]:[minute]:[second] [year]"
);
const ISO_8601_FORMAT: &[time::format_description::FormatItem] =
format_description!("[year]-[month]-[day]");
// "%Y%m%d%H%M.%S" 15 chars
const YYYYMMDDHHMM_DOT_SS_FORMAT: &[time::format_description::FormatItem] = format_description!(
"[year repr:full][month repr:numerical padding:zero]\
[day][hour][minute].[second]"
);
// "%Y-%m-%d %H:%M:%S.%SS" 12 chars
const YYYYMMDDHHMMSS_FORMAT: &[time::format_description::FormatItem] = format_description!(
"[year repr:full]-[month repr:numerical padding:zero]-\
[day] [hour]:[minute]:[second].[subsecond]"
);
// "%Y-%m-%d %H:%M:%S" 12 chars
const YYYYMMDDHHMMS_FORMAT: &[time::format_description::FormatItem] = format_description!(
"[year repr:full]-[month repr:numerical padding:zero]-\
[day] [hour]:[minute]:[second]"
);
// "%Y-%m-%d %H:%M" 12 chars
// Used for example in tests/touch/no-rights.sh
const YYYY_MM_DD_HH_MM_FORMAT: &[time::format_description::FormatItem] = format_description!(
"[year repr:full]-[month repr:numerical padding:zero]-\
[day] [hour]:[minute]"
);
// "%Y%m%d%H%M" 12 chars
const YYYYMMDDHHMM_FORMAT: &[time::format_description::FormatItem] = format_description!(
"[year repr:full][month repr:numerical padding:zero]\
[day][hour][minute]"
);
// "%y%m%d%H%M.%S" 13 chars
const YYMMDDHHMM_DOT_SS_FORMAT: &[time::format_description::FormatItem] = format_description!(
"[year repr:last_two padding:none][month][day]\
[hour][minute].[second]"
);
// "%y%m%d%H%M" 10 chars
const YYMMDDHHMM_FORMAT: &[time::format_description::FormatItem] = format_description!(
"[year repr:last_two padding:none][month padding:zero][day padding:zero]\
[hour repr:24 padding:zero][minute padding:zero]"
);
// "%Y-%m-%d %H:%M +offset"
// Used for example in tests/touch/relative.sh
const YYYYMMDDHHMM_OFFSET_FORMAT: &[time::format_description::FormatItem] = format_description!(
"[year]-[month]-[day] [hour repr:24]:[minute] \
[offset_hour sign:mandatory][offset_minute]"
);
fn parse_date(s: &str) -> UResult<FileTime> {
// This isn't actually compatible with GNU touch, but there doesn't seem to
// be any simple specification for what format this parameter allows and I'm
// not about to implement GNU parse_datetime.
// http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y
let formats = vec!["%c", "%F"];
for f in formats {
if let Ok(tm) = time::strptime(str, f) {
return Ok(local_tm_to_filetime(to_local(tm)));
// TODO: match on char count?
// "The preferred date and time representation for the current locale."
// "(In the POSIX locale this is equivalent to %a %b %e %H:%M:%S %Y.)"
// time 0.1.43 parsed this as 'a b e T Y'
// which is equivalent to the POSIX locale: %a %b %e %H:%M:%S %Y
// Tue Dec 3 ...
// ("%c", POSIX_LOCALE_FORMAT),
//
if let Ok(parsed) = time::PrimitiveDateTime::parse(s, &POSIX_LOCALE_FORMAT) {
return Ok(local_dt_to_filetime(to_local(parsed)));
}
// Also support other formats found in the GNU tests like
// in tests/misc/stat-nanoseconds.sh
// or tests/touch/no-rights.sh
for fmt in [
YYYYMMDDHHMMS_FORMAT,
YYYYMMDDHHMMSS_FORMAT,
YYYY_MM_DD_HH_MM_FORMAT,
YYYYMMDDHHMM_OFFSET_FORMAT,
] {
if let Ok(parsed) = time::PrimitiveDateTime::parse(s, &fmt) {
return Ok(dt_to_filename(parsed));
}
}
if let Ok(tm) = time::strptime(str, "@%s") {
// Don't convert to local time in this case - seconds since epoch are not time-zone dependent
return Ok(local_tm_to_filetime(tm));
// "Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)"
// ("%F", ISO_8601_FORMAT),
if let Ok(parsed) = time::Date::parse(s, &ISO_8601_FORMAT) {
return Ok(local_dt_to_filetime(to_local(
time::PrimitiveDateTime::new(parsed, time!(00:00)),
)));
}
Err(USimpleError::new(
1,
format!("Unable to parse date: {}", str),
))
// "@%s" is "The number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). (TZ) (Calculated from mktime(tm).)"
if s.bytes().next() == Some(b'@') {
if let Ok(ts) = &s[1..].parse::<i64>() {
// Don't convert to local time in this case - seconds since epoch are not time-zone dependent
return Ok(local_dt_to_filetime(
time::OffsetDateTime::from_unix_timestamp(*ts).unwrap(),
));
}
}
Err(USimpleError::new(1, format!("Unable to parse date: {}", s)))
}
fn parse_timestamp(s: &str) -> UResult<FileTime> {
let now = time::now();
let (format, ts) = match s.chars().count() {
15 => ("%Y%m%d%H%M.%S", s.to_owned()),
12 => ("%Y%m%d%H%M", s.to_owned()),
13 => ("%y%m%d%H%M.%S", s.to_owned()),
10 => ("%y%m%d%H%M", s.to_owned()),
11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)),
8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)),
// TODO: handle error
let now = time::OffsetDateTime::now_utc();
let (mut format, mut ts) = match s.chars().count() {
15 => (YYYYMMDDHHMM_DOT_SS_FORMAT, s.to_owned()),
12 => (YYYYMMDDHHMM_FORMAT, s.to_owned()),
13 => (YYMMDDHHMM_DOT_SS_FORMAT, s.to_owned()),
10 => (YYMMDDHHMM_FORMAT, s.to_owned()),
11 => (YYYYMMDDHHMM_DOT_SS_FORMAT, format!("{}{}", now.year(), s)),
8 => (YYYYMMDDHHMM_FORMAT, format!("{}{}", now.year(), s)),
_ => {
return Err(USimpleError::new(
1,
@ -287,30 +391,53 @@ fn parse_timestamp(s: &str) -> UResult<FileTime> {
))
}
};
let tm = time::strptime(&ts, format)
.map_err(|_| USimpleError::new(1, format!("invalid date format {}", s.quote())))?;
let mut local = to_local(tm);
local.tm_isdst = -1;
let ft = local_tm_to_filetime(local);
// We have to check that ft is valid time. Due to daylight saving
// time switch, local time can jump from 1:59 AM to 3:00 AM,
// in which case any time between 2:00 AM and 2:59 AM is not valid.
// Convert back to local time and see if we got the same value back.
let ts = time::Timespec {
sec: ft.unix_seconds(),
nsec: 0,
};
let tm2 = time::at(ts);
if tm.tm_hour != tm2.tm_hour {
return Err(USimpleError::new(
1,
format!("invalid date format {}", s.quote()),
));
// workaround time returning Err(TryFromParsed(InsufficientInformation)) for year w/
// repr:last_two
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1ccfac7c07c5d1c7887a11decf0e1996
if s.chars().count() == 10 {
format = YYYYMMDDHHMM_FORMAT;
ts = "20".to_owned() + &ts;
} else if s.chars().count() == 13 {
format = YYYYMMDDHHMM_DOT_SS_FORMAT;
ts = "20".to_owned() + &ts;
}
let leap_sec = if (format == YYYYMMDDHHMM_DOT_SS_FORMAT || format == YYMMDDHHMM_DOT_SS_FORMAT)
&& ts.ends_with(".60")
{
// Work around to disable leap seconds
// Used in gnu/tests/touch/60-seconds
ts = ts.replace(".60", ".59");
true
} else {
false
};
let tm = time::PrimitiveDateTime::parse(&ts, &format)
.map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", ts.quote())))?;
let mut local = to_local(tm);
if leap_sec {
// We are dealing with a leap second, add it
local = local.saturating_add(Duration::SECOND);
}
let ft = local_dt_to_filetime(local);
// // We have to check that ft is valid time. Due to daylight saving
// // time switch, local time can jump from 1:59 AM to 3:00 AM,
// // in which case any time between 2:00 AM and 2:59 AM is not valid.
// // Convert back to local time and see if we got the same value back.
// let ts = time::Timespec {
// sec: ft.unix_seconds(),
// nsec: 0,
// };
// let tm2 = time::at(ts);
// if tm.tm_hour != tm2.tm_hour {
// return Err(USimpleError::new(
// 1,
// format!("invalid date format {}", s.quote()),
// ));
// }
Ok(ft)
}

View file

@ -16,7 +16,7 @@ path = "src/tty.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
libc = "0.2.124"
libc = "0.2.125"
atty = "0.2"
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs"] }

View file

@ -111,9 +111,9 @@ fn process_utmpx() -> (Option<time_t>, usize) {
match line.record_type() {
USER_PROCESS => nusers += 1,
BOOT_TIME => {
let t = line.login_time().to_timespec();
if t.sec > 0 {
boot_time = Some(t.sec as time_t);
let dt = line.login_time();
if dt.second() > 0 {
boot_time = Some(dt.second() as time_t);
}
}
_ => continue,

View file

@ -275,10 +275,10 @@ struct Who {
fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> {
thread_local! {
static NOW: time::Tm = time::now()
static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
}
NOW.with(|n| {
let now = n.to_timespec().sec;
let now = n.unix_timestamp();
if boottime < when && now - 24 * 3600 < when && when <= now {
let seconds_idle = now - when;
if seconds_idle < 60 {
@ -298,7 +298,11 @@ fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> {
}
fn time_string(ut: &Utmpx) -> String {
time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C
// "%b %e %H:%M"
let time_format: Vec<time::format_description::FormatItem> =
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
.unwrap();
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
}
#[inline]

View file

@ -22,7 +22,7 @@ uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=[
winapi = { version = "0.3", features = ["lmcons"] }
[target.'cfg(unix)'.dependencies]
libc = "0.2.124"
libc = "0.2.125"
[[bin]]
name = "whoami"

View file

@ -26,12 +26,12 @@ wild = "2.0"
# * optional
itertools = { version="0.10.0", optional=true }
thiserror = { version="1.0", optional=true }
time = { version="<= 0.1.43", optional=true }
time = { version="<= 0.3", optional=true, features = ["formatting", "local-offset", "macros"] }
# * "problem" dependencies (pinned)
data-encoding = { version="2.1", optional=true }
data-encoding-macro = { version="0.1.12", optional=true }
z85 = { version="3.0.5", optional=true }
libc = { version="0.2.124", optional=true }
libc = { version="0.2.125", optional=true }
once_cell = "1.10.0"
os_display = "0.1.3"
@ -62,6 +62,6 @@ process = ["libc"]
ringbuffer = []
signals = []
utf8 = []
utmpx = ["time", "libc", "dns-lookup"]
utmpx = ["time", "time/macros", "libc", "dns-lookup"]
wide = []
pipes = ["nix"]

View file

@ -9,9 +9,11 @@
//! Set of functions to manage file systems
// spell-checker:ignore (arch) bitrig ; (fs) cifs smbfs
// spell-checker:ignore DATETIME subsecond (arch) bitrig ; (fs) cifs smbfs
extern crate time;
use time::macros::format_description;
use time::UtcOffset;
pub use crate::*; // import macros from `../../macros.rs`
@ -63,7 +65,6 @@ fn LPWSTR2String(buf: &[u16]) -> String {
String::from_utf16(&buf[..len]).unwrap()
}
use self::time::Timespec;
#[cfg(unix)]
use libc::{
mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK,
@ -732,11 +733,42 @@ where
}
}
// match strftime "%Y-%m-%d %H:%M:%S.%f %z"
const PRETTY_DATETIME_FORMAT: &[time::format_description::FormatItem] = format_description!(
"\
[year]-[month]-[day padding:zero] \
[hour]:[minute]:[second].[subsecond digits:9] \
[offset_hour sign:mandatory][offset_minute]"
);
pub fn pretty_time(sec: i64, nsec: i64) -> String {
// sec == seconds since UNIX_EPOCH
// nsec == nanoseconds since (UNIX_EPOCH + sec)
let tm = time::at(Timespec::new(sec, nsec as i32));
let res = time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap();
let ts_nanos: i128 = (sec * 1_000_000_000 + nsec).into();
// Return the date in UTC
let tm = match time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos) {
Ok(tm) => tm,
Err(e) => {
panic!("error: {}", e);
}
};
// Get the offset to convert to local time
// Because of DST (daylight saving), we get the local time back when
// the date was set
let local_offset = match UtcOffset::local_offset_at(tm) {
Ok(lo) => lo,
Err(e) => {
panic!("error: {}", e);
}
};
// Include the conversion to local time
let res = tm
.to_offset(local_offset)
.format(&PRETTY_DATETIME_FORMAT)
.unwrap();
if res.ends_with(" -0000") {
res.replace(" -0000", " +0000")
} else {

View file

@ -152,7 +152,11 @@ impl SubParser {
if parser.min_width_is_asterisk {
CanAsterisk::Asterisk
} else {
CanAsterisk::Fixed(parser.min_width_tmp.map(|x| x.parse::<isize>().unwrap()))
CanAsterisk::Fixed(
parser
.min_width_tmp
.map(|x| x.parse::<isize>().unwrap_or(1)),
)
},
if parser.second_field_is_asterisk {
CanAsterisk::Asterisk

View file

@ -32,7 +32,6 @@
//! ```
pub extern crate time;
use self::time::{Timespec, Tm};
use std::ffi::CString;
use std::io::Result as IOResult;
@ -189,11 +188,14 @@ impl Utmpx {
chars2string!(self.inner.ut_line)
}
/// A.K.A. ut.ut_tv
pub fn login_time(&self) -> Tm {
time::at(Timespec::new(
self.inner.ut_tv.tv_sec as i64,
self.inner.ut_tv.tv_usec as i32,
))
pub fn login_time(&self) -> time::OffsetDateTime {
let ts_nanos: i128 = (self.inner.ut_tv.tv_sec as i64 * 1_000_000_000_i64
+ self.inner.ut_tv.tv_usec as i64 * 1_000_i64)
.into();
let local_offset = time::OffsetDateTime::now_local().unwrap().offset();
time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos)
.unwrap()
.to_offset(local_offset)
}
/// A.K.A. ut.ut_exit
///

View file

@ -617,12 +617,75 @@ impl From<i32> for Box<dyn UError> {
}
}
/// Implementations for clap::Error
impl UError for clap::Error {
/// A wrapper for `clap::Error` that implements [`UError`]
///
/// Contains a custom error code. When `Display::fmt` is called on this struct
/// the [`clap::Error`] will be printed _directly to `stdout` or `stderr`_.
/// This is because `clap` only supports colored output when it prints directly.
///
/// [`ClapErrorWrapper`] is generally created by calling the
/// [`UClapError::with_exit_code`] method on [`clap::Error`] or using the [`From`]
/// implementation from [`clap::Error`] to `Box<dyn UError>`, which constructs
/// a [`ClapErrorWrapper`] with an exit code of `1`.
///
/// ```rust
/// use uucore::error::{ClapErrorWrapper, UError, UClapError};
/// let command = clap::Command::new("test");
/// let result: Result<_, ClapErrorWrapper> = command.try_get_matches().with_exit_code(125);
///
/// let command = clap::Command::new("test");
/// let result: Result<_, Box<dyn UError>> = command.try_get_matches().map_err(Into::into);
/// ```
#[derive(Debug)]
pub struct ClapErrorWrapper {
code: i32,
error: clap::Error,
}
/// Extension trait for `clap::Error` to adjust the exit code.
pub trait UClapError<T> {
fn with_exit_code(self, code: i32) -> T;
}
impl From<clap::Error> for Box<dyn UError> {
fn from(e: clap::Error) -> Self {
Box::new(ClapErrorWrapper { code: 1, error: e })
}
}
impl UClapError<ClapErrorWrapper> for clap::Error {
fn with_exit_code(self, code: i32) -> ClapErrorWrapper {
ClapErrorWrapper { code, error: self }
}
}
impl UClapError<Result<clap::ArgMatches, ClapErrorWrapper>>
for Result<clap::ArgMatches, clap::Error>
{
fn with_exit_code(self, code: i32) -> Result<clap::ArgMatches, ClapErrorWrapper> {
self.map_err(|e| e.with_exit_code(code))
}
}
impl UError for ClapErrorWrapper {
fn code(&self) -> i32 {
match self.kind() {
clap::ErrorKind::DisplayHelp | clap::ErrorKind::DisplayVersion => 0,
_ => 1,
// If the error is a DisplayHelp or DisplayVersion variant,
// we don't want to apply the custom error code, but leave
// it 0.
if let clap::ErrorKind::DisplayHelp | clap::ErrorKind::DisplayVersion = self.error.kind() {
0
} else {
self.code
}
}
}
impl Error for ClapErrorWrapper {}
// This is abuse of the Display trait
impl Display for ClapErrorWrapper {
fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
self.error.print().unwrap();
Ok(())
}
}

View file

@ -1032,8 +1032,8 @@ fn test_cp_no_deref_folder_to_folder() {
#[cfg(target_os = "linux")]
fn test_cp_archive() {
let (at, mut ucmd) = at_and_ucmd!();
let ts = time::now().to_timespec();
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
let ts = time::OffsetDateTime::now_local().unwrap();
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond() as u32);
// set the file creation/modification an hour ago
filetime::set_file_times(
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
@ -1135,8 +1135,8 @@ fn test_cp_archive_recursive() {
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_preserve_timestamps() {
let (at, mut ucmd) = at_and_ucmd!();
let ts = time::now().to_timespec();
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
let ts = time::OffsetDateTime::now_local().unwrap();
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
// set the file creation/modification an hour ago
filetime::set_file_times(
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
@ -1168,8 +1168,8 @@ fn test_cp_preserve_timestamps() {
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_no_preserve_timestamps() {
let (at, mut ucmd) = at_and_ucmd!();
let ts = time::now().to_timespec();
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
let ts = time::OffsetDateTime::now_local().unwrap();
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
// set the file creation/modification an hour ago
filetime::set_file_times(
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
@ -1506,6 +1506,18 @@ fn test_copy_through_dangling_symlink() {
.stderr_only("cp: not writing through dangling symlink 'target'");
}
#[test]
fn test_copy_through_dangling_symlink_no_dereference() {
let (at, mut ucmd) = at_and_ucmd!();
at.symlink_file("no-such-file", "dangle");
ucmd.arg("-P")
.arg("dangle")
.arg("d2")
.succeeds()
.no_stderr()
.no_stdout();
}
#[test]
#[cfg(unix)]
fn test_cp_archive_on_nonexistent_file() {

View file

@ -375,6 +375,26 @@ fn test_iuse_percentage() {
}
}
#[test]
fn test_default_block_size() {
let output = new_ucmd!()
.arg("--output=size")
.succeeds()
.stdout_move_str();
let header = output.lines().next().unwrap().to_string();
assert_eq!(header, "1K-blocks");
let output = new_ucmd!()
.arg("--output=size")
.env("POSIXLY_CORRECT", "1")
.succeeds()
.stdout_move_str();
let header = output.lines().next().unwrap().to_string();
assert_eq!(header, "512B-blocks");
}
#[test]
fn test_block_size_1024() {
fn get_header(block_size: u64) -> String {
@ -392,6 +412,11 @@ fn test_block_size_1024() {
assert_eq!(get_header(2 * 1024 * 1024), "2M-blocks");
assert_eq!(get_header(1024 * 1024 * 1024), "1G-blocks");
assert_eq!(get_header(34 * 1024 * 1024 * 1024), "34G-blocks");
// multiples of both 1024 and 1000
assert_eq!(get_header(128_000), "128kB-blocks");
assert_eq!(get_header(1000 * 1024), "1.1MB-blocks");
assert_eq!(get_header(1_000_000_000_000), "1TB-blocks");
}
#[test]
@ -413,10 +438,33 @@ fn test_block_size_with_suffix() {
assert_eq!(get_header("1KiB"), "1K-blocks");
assert_eq!(get_header("1MiB"), "1M-blocks");
assert_eq!(get_header("1GiB"), "1G-blocks");
// TODO enable the following asserts when #3193 is resolved
//assert_eq!(get_header("1KB"), "1kB-blocks");
//assert_eq!(get_header("1MB"), "1MB-blocks");
//assert_eq!(get_header("1GB"), "1GB-blocks");
assert_eq!(get_header("1KB"), "1kB-blocks");
assert_eq!(get_header("1MB"), "1MB-blocks");
assert_eq!(get_header("1GB"), "1GB-blocks");
}
#[test]
fn test_too_large_block_size() {
fn run_command(size: &str) {
new_ucmd!()
.arg(format!("--block-size={}", size))
.fails()
.stderr_contains(format!("--block-size argument '{}' too large", size));
}
let too_large_sizes = vec!["1Y", "1Z"];
for size in too_large_sizes {
run_command(size);
}
}
#[test]
fn test_invalid_block_size() {
new_ucmd!()
.arg("--block-size=x")
.fails()
.stderr_contains("invalid --block-size argument 'x'");
}
#[test]

View file

@ -435,9 +435,7 @@ fn test_du_no_permission() {
ts.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).succeeds();
let result = ts.ucmd().arg(SUB_DIR_LINKS).fails();
result.stderr_contains(
"du: cannot read directory 'subdir/links': Permission denied (os error 13)",
);
result.stderr_contains("du: cannot read directory 'subdir/links': Permission denied");
#[cfg(any(target_os = "linux", target_os = "android"))]
{

View file

@ -595,9 +595,9 @@ fn test_mv_update_option() {
at.touch(file_a);
at.touch(file_b);
let ts = time::now().to_timespec();
let now = FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32);
let later = FileTime::from_unix_time(ts.sec as i64 + 3600, ts.nsec as u32);
let ts = time::OffsetDateTime::now_local().unwrap();
let now = FileTime::from_unix_time(ts.unix_timestamp(), ts.nanosecond());
let later = FileTime::from_unix_time(ts.unix_timestamp() as i64 + 3600, ts.nanosecond() as u32);
filetime::set_file_times(at.plus_as_string(file_a), now, now).unwrap();
filetime::set_file_times(at.plus_as_string(file_b), now, later).unwrap();

View file

@ -58,3 +58,8 @@ fn test_command_where_command_takes_n_flag() {
.run()
.stdout_is("a");
}
#[test]
fn test_invalid_argument() {
new_ucmd!().arg("--invalid").fails().code_is(125);
}

View file

@ -446,6 +446,14 @@ fn sub_any_specifiers_after_period() {
.stdout_only("3");
}
#[test]
fn unspecified_left_justify_is_1_width() {
new_ucmd!()
.args(&["%-o"]) //spell-checker:disable-line
.succeeds()
.stdout_only("0");
}
#[test]
fn sub_any_specifiers_after_second_param() {
new_ucmd!()

View file

@ -283,6 +283,40 @@ fn test_char() {
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
}
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
#[test]
fn test_date() {
// Just test the date for the time 0.3 change
let args = [
"-c",
#[cfg(any(target_os = "linux", target_os = "android"))]
"%z",
#[cfg(target_os = "linux")]
"/bin/sh",
#[cfg(any(target_vendor = "apple"))]
"%z",
#[cfg(any(target_os = "android", target_vendor = "apple"))]
"/bin/sh",
];
let ts = TestScenario::new(util_name!());
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
// Just test the date for the time 0.3 change
let args = [
"-c",
#[cfg(any(target_os = "linux", target_os = "android"))]
"%z",
#[cfg(target_os = "linux")]
"/dev/ptmx",
#[cfg(any(target_vendor = "apple"))]
"%z",
#[cfg(any(target_os = "android", target_vendor = "apple"))]
"/dev/ptmx",
];
let ts = TestScenario::new(util_name!());
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
}
#[cfg(unix)]
#[test]
fn test_multi_files() {
@ -311,3 +345,58 @@ fn test_printf() {
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
}
#[cfg(unix)]
#[test]
#[cfg(disable_until_fixed)]
fn test_stdin_pipe_fifo1() {
// $ echo | stat -
// File: -
// Size: 0 Blocks: 0 IO Block: 4096 fifo
// use std::process::{Command, Stdio};
new_ucmd!()
.arg("-")
.set_stdin(std::process::Stdio::piped())
.run()
.no_stderr()
.stdout_contains("fifo")
.stdout_contains("File: -")
.succeeded();
}
#[cfg(unix)]
#[test]
#[cfg(disable_until_fixed)]
fn test_stdin_pipe_fifo2() {
// $ stat -
// File: -
// Size: 0 Blocks: 0 IO Block: 1024 character special file
new_ucmd!()
.arg("-")
.run()
.no_stderr()
.stdout_contains("character special file")
.stdout_contains("File: -")
.succeeded();
}
#[cfg(unix)]
#[test]
#[cfg(disable_until_fixed)]
fn test_stdin_redirect() {
// $ touch f && stat - < f
// File: -
// Size: 0 Blocks: 0 IO Block: 4096 regular empty file
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("f");
new_ucmd!()
.arg("-")
.set_stdin(std::fs::File::open("f").unwrap())
.run()
.no_stderr()
.stdout_contains("regular empty file")
.stdout_contains("File: -")
.succeeded();
}

View file

@ -1,9 +1,16 @@
// spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms
// spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms datetime mktime
// This test relies on
// --cfg unsound_local_offset
// https://github.com/time-rs/time/blob/deb8161b84f355b31e39ce09e40c4d6ce3fea837/src/sys/local_offset_at/unix.rs#L112-L120=
// See https://github.com/time-rs/time/issues/293#issuecomment-946382614=
// Defined in .cargo/config
extern crate touch;
use self::touch::filetime::{self, FileTime};
extern crate time;
use time::macros::{datetime, format_description};
use crate::common::util::*;
use std::fs::remove_file;
@ -32,11 +39,24 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) {
// Adjusts for local timezone
fn str_to_filetime(format: &str, s: &str) -> FileTime {
let mut tm = time::strptime(s, format).unwrap();
tm.tm_utcoff = time::now().tm_utcoff;
tm.tm_isdst = -1; // Unknown flag DST
let ts = tm.to_timespec();
FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32)
let format_description = match format {
"%y%m%d%H%M" => format_description!("[year repr:last_two][month][day][hour][minute]"),
"%y%m%d%H%M.%S" => {
format_description!("[year repr:last_two][month][day][hour][minute].[second]")
}
"%Y%m%d%H%M" => format_description!("[year][month][day][hour][minute]"),
"%Y%m%d%H%M.%S" => format_description!("[year][month][day][hour][minute].[second]"),
_ => panic!("unexpected dt format"),
};
let tm = time::PrimitiveDateTime::parse(s, &format_description).unwrap();
let d = match time::OffsetDateTime::now_local() {
Ok(now) => now,
Err(e) => {
panic!("Error {} retrieving the OffsetDateTime::now_local", e);
}
};
let offset_dt = tm.assume_offset(d.offset());
FileTime::from_unix_time(offset_dt.unix_timestamp(), tm.nanosecond())
}
#[test]
@ -83,7 +103,10 @@ fn test_touch_set_mdhm_time() {
let start_of_year = str_to_filetime(
"%Y%m%d%H%M",
&format!("{}01010000", 1900 + time::now().tm_year),
&format!(
"{}01010000",
time::OffsetDateTime::now_local().unwrap().year()
),
);
let (atime, mtime) = get_file_times(&at, file);
assert_eq!(atime, mtime);
@ -104,7 +127,7 @@ fn test_touch_set_mdhms_time() {
let start_of_year = str_to_filetime(
"%Y%m%d%H%M.%S",
&format!("{}01010000.00", 1900 + time::now().tm_year),
&format!("{}01010000.00", time::OffsetDateTime::now_utc().year()),
);
let (atime, mtime) = get_file_times(&at, file);
assert_eq!(atime, mtime);
@ -123,7 +146,7 @@ fn test_touch_set_ymdhm_time() {
assert!(at.file_exists(file));
let start_of_year = str_to_filetime("%y%m%d%H%M", "1501010000");
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000");
let (atime, mtime) = get_file_times(&at, file);
assert_eq!(atime, mtime);
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45240);
@ -141,7 +164,7 @@ fn test_touch_set_ymdhms_time() {
assert!(at.file_exists(file));
let start_of_year = str_to_filetime("%y%m%d%H%M.%S", "1501010000.00");
let start_of_year = str_to_filetime("%Y%m%d%H%M.%S", "201501010000.00");
let (atime, mtime) = get_file_times(&at, file);
assert_eq!(atime, mtime);
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45296);
@ -404,6 +427,86 @@ fn test_touch_set_date3() {
assert_eq!(mtime, expected);
}
#[test]
fn test_touch_set_date4() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_touch_set_date";
ucmd.args(&["-d", "1970-01-01 18:43:33", file])
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
let expected = FileTime::from_unix_time(67413, 0);
let (atime, mtime) = get_file_times(&at, file);
assert_eq!(atime, mtime);
assert_eq!(atime, expected);
assert_eq!(mtime, expected);
}
#[test]
fn test_touch_set_date5() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_touch_set_date";
ucmd.args(&["-d", "1970-01-01 18:43:33.023456789", file])
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
// Slightly different result on Windows for nano seconds
// TODO: investigate
#[cfg(windows)]
let expected = FileTime::from_unix_time(67413, 23456700);
#[cfg(not(windows))]
let expected = FileTime::from_unix_time(67413, 23456789);
let (atime, mtime) = get_file_times(&at, file);
assert_eq!(atime, mtime);
assert_eq!(atime, expected);
assert_eq!(mtime, expected);
}
#[test]
fn test_touch_set_date6() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_touch_set_date";
ucmd.args(&["-d", "2000-01-01 00:00", file])
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
let expected = FileTime::from_unix_time(946684800, 0);
let (atime, mtime) = get_file_times(&at, file);
assert_eq!(atime, mtime);
assert_eq!(atime, expected);
assert_eq!(mtime, expected);
}
#[test]
fn test_touch_set_date7() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_touch_set_date";
ucmd.args(&["-d", "2004-01-16 12:00 +0000", file])
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
let expected = FileTime::from_unix_time(1074254400, 0);
let (atime, mtime) = get_file_times(&at, file);
assert_eq!(atime, mtime);
assert_eq!(atime, expected);
assert_eq!(mtime, expected);
}
#[test]
fn test_touch_set_date_wrong_format() {
let (_at, mut ucmd) = at_and_ucmd!();
@ -430,18 +533,18 @@ fn test_touch_mtime_dst_succeeds() {
assert_eq!(target_time, mtime);
}
// is_dst_switch_hour returns true if timespec ts is just before the switch
// to Daylight Saving Time.
// For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 }
// for March 8 2020 01:00:00 AM
// is just before the switch because on that day clock jumps by 1 hour,
// so 1 minute after 01:59:00 is 03:00:00.
fn is_dst_switch_hour(ts: time::Timespec) -> bool {
let ts_after = ts + time::Duration::hours(1);
let tm = time::at(ts);
let tm_after = time::at(ts_after);
tm_after.tm_hour == tm.tm_hour + 2
}
// // is_dst_switch_hour returns true if timespec ts is just before the switch
// // to Daylight Saving Time.
// // For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 }
// // for March 8 2020 01:00:00 AM
// // is just before the switch because on that day clock jumps by 1 hour,
// // so 1 minute after 01:59:00 is 03:00:00.
// fn is_dst_switch_hour(ts: time::Timespec) -> bool {
// let ts_after = ts + time::Duration::hours(1);
// let tm = time::at(ts);
// let tm_after = time::at(ts_after);
// tm_after.tm_hour == tm.tm_hour + 2
// }
// get_dst_switch_hour returns date string for which touch -m -t fails.
// For example, in EST (UTC-5), that will be "202003080200" so
@ -450,23 +553,30 @@ fn is_dst_switch_hour(ts: time::Timespec) -> bool {
// In other locales it will be a different date/time, and in some locales
// it doesn't exist at all, in which case this function will return None.
fn get_dst_switch_hour() -> Option<String> {
let now = time::now();
// Start from January 1, 2020, 00:00.
let mut tm = time::strptime("20200101-0000", "%Y%m%d-%H%M").unwrap();
tm.tm_isdst = -1;
tm.tm_utcoff = now.tm_utcoff;
let mut ts = tm.to_timespec();
// Loop through all hours in year 2020 until we find the hour just
// before the switch to DST.
for _i in 0..(366 * 24) {
if is_dst_switch_hour(ts) {
let mut tm = time::at(ts);
tm.tm_hour += 1;
let s = time::strftime("%Y%m%d%H%M", &tm).unwrap();
return Some(s);
//let now = time::OffsetDateTime::now_local().unwrap();
let now = match time::OffsetDateTime::now_local() {
Ok(now) => now,
Err(e) => {
panic!("Error {} retrieving the OffsetDateTime::now_local", e);
}
ts = ts + time::Duration::hours(1);
}
};
// Start from January 1, 2020, 00:00.
let tm = datetime!(2020-01-01 00:00 UTC);
tm.to_offset(now.offset());
// let mut ts = tm.to_timespec();
// // Loop through all hours in year 2020 until we find the hour just
// // before the switch to DST.
// for _i in 0..(366 * 24) {
// // if is_dst_switch_hour(ts) {
// // let mut tm = time::at(ts);
// // tm.tm_hour += 1;
// // let s = time::strftime("%Y%m%d%H%M", &tm).unwrap();
// // return Some(s);
// // }
// ts = ts + time::Duration::hours(1);
// }
None
}
@ -573,3 +683,21 @@ fn test_no_dereference_no_file() {
.stderr_contains("setting times of 'not-a-file-1': No such file or directory")
.stderr_contains("setting times of 'not-a-file-2': No such file or directory");
}
#[test]
fn test_touch_leap_second() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_touch_leap_sec";
ucmd.args(&["-t", "197001010000.60", file])
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
let epoch = str_to_filetime("%Y%m%d%H%M", "197001010000");
let (atime, mtime) = get_file_times(&at, file);
assert_eq!(atime, mtime);
assert_eq!(atime.unix_seconds() - epoch.unix_seconds(), 60);
assert_eq!(mtime.unix_seconds() - epoch.unix_seconds(), 60);
}

View file

@ -680,6 +680,9 @@ fn gnu_tests() {
stderr: None,
exit: None,
},
/*
Disable as it fails too often. See:
https://github.com/uutils/coreutils/issues/3509
TestCase {
name: "112",
args: &["-D", "-c"],
@ -687,7 +690,7 @@ fn gnu_tests() {
stdout: Some(""),
stderr: Some("uniq: printing all duplicated lines and repeat counts is meaningless"),
exit: Some(1),
},
},*/
TestCase {
name: "113",
args: &["--all-repeated=separate"],

View file

@ -1362,6 +1362,70 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
))
}
/// This is a convenience wrapper to run a ucmd with root permissions.
/// It can be used to test programs when being root is needed
/// This runs 'sudo -E --non-interactive target/debug/coreutils util_name args`
/// This is primarily designed to run in an environment where whoami is in $path
/// and where non-interactive sudo is possible.
/// To check if i) non-interactive sudo is possible and ii) if sudo works, this runs:
/// 'sudo -E --non-interactive whoami' first.
///
/// This return an `Err()` if run inside CICD because there's no 'sudo'.
///
/// Example:
///
/// ```no_run
/// use crate::common::util::*;
/// #[test]
/// fn test_xyz() {
/// let ts = TestScenario::new("whoami");
/// let expected = "root\n".to_string();
/// if let Ok(result) = run_ucmd_as_root(&ts, &[]) {
/// result.stdout_is(expected);
/// } else {
/// println!("TEST SKIPPED");
/// }
/// }
///```
#[cfg(unix)]
pub fn run_ucmd_as_root(
ts: &TestScenario,
args: &[&str],
) -> std::result::Result<CmdResult, String> {
if !is_ci() {
// check if we can run 'sudo'
log_info("run", "sudo -E --non-interactive whoami");
match Command::new("sudo")
.env("LC_ALL", "C")
.args(&["-E", "--non-interactive", "whoami"])
.output()
{
Ok(output) if String::from_utf8_lossy(&output.stdout).eq("root\n") => {
// we can run sudo and we're root
// run ucmd as root:
Ok(ts
.cmd_keepenv("sudo")
.env("LC_ALL", "C")
.arg("-E")
.arg("--non-interactive")
.arg(&ts.bin_path)
.arg(&ts.util_name)
.args(args)
.run())
}
Ok(output)
if String::from_utf8_lossy(&output.stderr).eq("sudo: a password is required\n") =>
{
Err("Cannot run non-interactive sudo".to_string())
}
Ok(_output) => Err("\"sudo whoami\" didn't return \"root\"".to_string()),
Err(e) => Err(format!("{}: {}", UUTILS_WARNING, e)),
}
} else {
Err(format!("{}: {}", UUTILS_INFO, "cannot run inside CI"))
}
}
/// Sanity checks for test utils
#[cfg(test)]
mod tests {
@ -1712,4 +1776,32 @@ mod tests {
std::assert_eq!(host_name_for("gwho"), "gwho");
}
}
#[test]
#[cfg(unix)]
#[cfg(feature = "whoami")]
fn test_run_ucmd_as_root() {
if !is_ci() {
// Skip test if we can't guarantee non-interactive `sudo`, or if we're not "root"
if let Ok(output) = Command::new("sudo")
.env("LC_ALL", "C")
.args(&["-E", "--non-interactive", "whoami"])
.output()
{
if output.status.success() && String::from_utf8_lossy(&output.stdout).eq("root\n") {
let ts = TestScenario::new("whoami");
std::assert_eq!(
run_ucmd_as_root(&ts, &[]).unwrap().stdout_str().trim(),
"root"
);
} else {
println!("TEST SKIPPED (we're not root)");
}
} else {
println!("TEST SKIPPED (cannot run sudo)");
}
} else {
println!("TEST SKIPPED (cannot run inside CI)");
}
}
}

View file

@ -1,3 +1,4 @@
#!/bin/bash
# spell-checker:ignore termux keyevent sdcard binutils unmatch adb's dumpsys logcat pkill
# There are three shells: the host's, adb, and termux. Only adb lets us run

View file

@ -62,14 +62,23 @@ for binary in $(./build-aux/gen-lists-of-programs.sh --list-progs); do
}
done
./bootstrap
./configure --quiet --disable-gcc-warnings
#Add timeout to to protect against hangs
sed -i 's|^"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver
# Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils
sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${UU_BUILD_DIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile
sed -i 's| tr | /usr/bin/tr |' tests/init.sh
make -j "$(nproc)"
if test -f gnu-built; then
echo "GNU build already found. Skip"
echo "'rm -f $(pwd)/gnu-built' to force the build"
echo "Note: the customization of the tests will still happen"
exit 0
else
./bootstrap
./configure --quiet --disable-gcc-warnings
#Add timeout to to protect against hangs
sed -i 's|^"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver
# Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils
sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${UU_BUILD_DIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile
sed -i 's| tr | /usr/bin/tr |' tests/init.sh
make -j "$(nproc)"
touch gnu-built
fi
# Handle generated factor tests
t_first=00
t_max=36
@ -128,7 +137,8 @@ sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail-2/tail-n0f.s
sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh
sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh
sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh
sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh
# tests/ls/abmon-align.sh - https://github.com/uutils/coreutils/issues/3505
sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh tests/ls/abmon-align.sh
sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh
sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh
sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh