Merge branch 'main' into mkdir-fix

This commit is contained in:
Pyokyeong Son 2022-03-30 12:30:27 +09:00 committed by GitHub
commit ea592e5c03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 542 additions and 223 deletions

View file

@ -67,7 +67,7 @@ jobs:
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
toolchain: nightly-2022-03-21
default: true
profile: minimal
- name: Install `cargo-udeps`
@ -86,7 +86,7 @@ jobs:
fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
#
cargo +nightly udeps ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all-targets &> udeps.log || cat udeps.log
cargo +nightly-2022-03-21 udeps ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all-targets &> udeps.log || cat udeps.log
grep --ignore-case "all deps seem to have been used" udeps.log || { printf "%s\n" "::${fault_type} ::${fault_prefix}: \`cargo udeps\`: style violation (unused dependency found)" ; fault=true ; }
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
@ -483,7 +483,7 @@ jobs:
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
toolchain: nightly-2022-03-21
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: Test
@ -919,7 +919,6 @@ jobs:
strategy:
fail-fast: true
matrix:
# job: [ { os: ubuntu-latest }, { os: macos-latest }, { os: windows-latest } ]
job:
- { os: ubuntu-latest , features: unix }
- { os: macos-latest , features: macos }
@ -936,7 +935,7 @@ jobs:
## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# toolchain
TOOLCHAIN="nightly" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support
TOOLCHAIN="nightly-2022-03-21" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support
# * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files
case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
# * use requested TOOLCHAIN if specified

View file

@ -218,7 +218,7 @@ jobs:
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
toolchain: nightly-2022-03-21
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt

4
Cargo.lock generated
View file

@ -1663,9 +1663,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.2.10"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0"
dependencies = [
"bitflags",
]

View file

@ -54,6 +54,7 @@ endif
PROGS := \
base32 \
base64 \
basenc \
basename \
cat \
cksum \

View file

@ -6,10 +6,8 @@
// spell-checker:ignore datastructures rstat rposition cflags ctable
use crate::conversion_tables::ConversionTable;
use crate::datastructures::InternalError;
use crate::datastructures::ConversionMode;
use crate::progress::ReadStat;
use crate::Input;
use std::io::Read;
const NEWLINE: u8 = b'\n';
const SPACE: u8 = b' ';
@ -65,105 +63,68 @@ fn unblock(buf: &[u8], cbs: usize) -> Vec<u8> {
})
}
/// A helper for teasing out which options must be applied and in which order.
/// Some user options, such as the presence of conversion tables, will determine whether the input is assumed to be ascii. The parser sets the Input::non_ascii flag accordingly.
/// Examples:
/// - If conv=ebcdic or conv=ibm is specified then block, unblock or swab must be performed before the conversion happens since the source will start in ascii.
/// - If conv=ascii is specified then block, unblock or swab must be performed after the conversion since the source starts in ebcdic.
/// - If no conversion is specified then the source is assumed to be in ascii.
/// For more info see `info dd`
pub(crate) fn conv_block_unblock_helper<R: Read>(
/// Apply the specified conversion, blocking, and/or unblocking in the right order.
///
/// The `mode` specifies the combination of conversion, blocking, and
/// unblocking to apply and the order in which to apply it. This
/// function is responsible only for applying the operations.
///
/// `buf` is the buffer of input bytes to transform. This function
/// mutates this input and also returns a new buffer of bytes
/// representing the result of the transformation.
///
/// `rstat` maintains a running total of the number of partial and
/// complete blocks read before calling this function. In certain
/// settings of `mode`, this function will update the number of
/// records truncated; that's why `rstat` is borrowed mutably.
pub(crate) fn conv_block_unblock_helper(
mut buf: Vec<u8>,
i: &mut Input<R>,
mode: &ConversionMode,
rstat: &mut ReadStat,
) -> Result<Vec<u8>, InternalError> {
// Local Predicate Fns -------------------------------------------------
fn should_block_then_conv<R: Read>(i: &Input<R>) -> bool {
!i.non_ascii && i.cflags.block.is_some()
}
fn should_conv_then_block<R: Read>(i: &Input<R>) -> bool {
i.non_ascii && i.cflags.block.is_some()
}
fn should_unblock_then_conv<R: Read>(i: &Input<R>) -> bool {
!i.non_ascii && i.cflags.unblock.is_some()
}
fn should_conv_then_unblock<R: Read>(i: &Input<R>) -> bool {
i.non_ascii && i.cflags.unblock.is_some()
}
fn conv_only<R: Read>(i: &Input<R>) -> bool {
i.cflags.ctable.is_some() && i.cflags.block.is_none() && i.cflags.unblock.is_none()
}
// Local Helper Fns ----------------------------------------------------
) -> Vec<u8> {
// TODO This function has a mutable input `buf` but also returns a
// completely new `Vec`; that seems fishy. Could we either make
// the input immutable or make the function not return anything?
fn apply_conversion(buf: &mut [u8], ct: &ConversionTable) {
for idx in 0..buf.len() {
buf[idx] = ct[buf[idx] as usize];
}
}
// --------------------------------------------------------------------
if conv_only(i) {
// no block/unblock
let ct = i.cflags.ctable.unwrap();
apply_conversion(&mut buf, ct);
Ok(buf)
} else if should_block_then_conv(i) {
// ascii input so perform the block first
let cbs = i.cflags.block.unwrap();
let mut blocks = block(&buf, cbs, i.cflags.sync.is_some(), rstat);
if let Some(ct) = i.cflags.ctable {
match mode {
ConversionMode::ConvertOnly(ct) => {
apply_conversion(&mut buf, ct);
buf
}
ConversionMode::BlockThenConvert(ct, cbs, sync) => {
let mut blocks = block(&buf, *cbs, *sync, rstat);
for buf in &mut blocks {
apply_conversion(buf, ct);
}
blocks.into_iter().flatten().collect()
}
let blocks = blocks.into_iter().flatten().collect();
Ok(blocks)
} else if should_conv_then_block(i) {
// Non-ascii so perform the conversion first
let cbs = i.cflags.block.unwrap();
if let Some(ct) = i.cflags.ctable {
ConversionMode::ConvertThenBlock(ct, cbs, sync) => {
apply_conversion(&mut buf, ct);
block(&buf, *cbs, *sync, rstat)
.into_iter()
.flatten()
.collect()
}
let blocks = block(&buf, cbs, i.cflags.sync.is_some(), rstat)
ConversionMode::BlockOnly(cbs, sync) => block(&buf, *cbs, *sync, rstat)
.into_iter()
.flatten()
.collect();
Ok(blocks)
} else if should_unblock_then_conv(i) {
// ascii input so perform the unblock first
let cbs = i.cflags.unblock.unwrap();
let mut buf = unblock(&buf, cbs);
if let Some(ct) = i.cflags.ctable {
.collect(),
ConversionMode::UnblockThenConvert(ct, cbs) => {
let mut buf = unblock(&buf, *cbs);
apply_conversion(&mut buf, ct);
buf
}
Ok(buf)
} else if should_conv_then_unblock(i) {
// Non-ascii input so perform the conversion first
let cbs = i.cflags.unblock.unwrap();
if let Some(ct) = i.cflags.ctable {
ConversionMode::ConvertThenUnblock(ct, cbs) => {
apply_conversion(&mut buf, ct);
unblock(&buf, *cbs)
}
let buf = unblock(&buf, cbs);
Ok(buf)
} else {
// The following error should not happen, as it results from
// insufficient command line data. This case should be caught
// by the parser before making it this far.
// Producing this error is an alternative to risking an unwrap call
// on 'cbs' if the required data is not provided.
Err(InternalError::InvalidConvBlockUnblockCase)
ConversionMode::UnblockOnly(cbs) => unblock(&buf, *cbs),
}
}

View file

@ -14,12 +14,27 @@ use crate::conversion_tables::*;
type Cbs = usize;
/// How to apply conversion, blocking, and/or unblocking.
///
/// Certain settings of the `conv` parameter to `dd` require a
/// combination of conversion, blocking, or unblocking, applied in a
/// certain order. The variants of this enumeration give the different
/// ways of combining those three operations.
#[derive(Debug, PartialEq)]
pub(crate) enum ConversionMode<'a> {
ConvertOnly(&'a ConversionTable),
BlockOnly(Cbs, bool),
UnblockOnly(Cbs),
BlockThenConvert(&'a ConversionTable, Cbs, bool),
ConvertThenBlock(&'a ConversionTable, Cbs, bool),
UnblockThenConvert(&'a ConversionTable, Cbs),
ConvertThenUnblock(&'a ConversionTable, Cbs),
}
/// Stores all Conv Flags that apply to the input
#[derive(Debug, Default, PartialEq)]
pub struct IConvFlags {
pub ctable: Option<&'static ConversionTable>,
pub block: Option<Cbs>,
pub unblock: Option<Cbs>,
pub(crate) struct IConvFlags {
pub mode: Option<ConversionMode<'static>>,
pub swab: bool,
pub sync: Option<u8>,
pub noerror: bool,
@ -91,19 +106,11 @@ pub enum CountType {
pub enum InternalError {
WrongInputType,
WrongOutputType,
InvalidConvBlockUnblockCase,
}
impl std::fmt::Display for InternalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::WrongInputType | Self::WrongOutputType => {
write!(f, "Internal dd error: Wrong Input/Output data type")
}
Self::InvalidConvBlockUnblockCase => {
write!(f, "Invalid Conversion, Block, or Unblock data")
}
}
write!(f, "Internal dd error: Wrong Input/Output data type")
}
}

View file

@ -45,7 +45,6 @@ const BUF_INIT_BYTE: u8 = 0xDD;
struct Input<R: Read> {
src: R,
non_ascii: bool,
ibs: usize,
print_level: Option<StatusLevel>,
count: Option<CountType>,
@ -56,7 +55,6 @@ struct Input<R: Read> {
impl Input<io::Stdin> {
fn new(matches: &Matches) -> UResult<Self> {
let ibs = parseargs::parse_ibs(matches)?;
let non_ascii = parseargs::parse_input_non_ascii(matches)?;
let print_level = parseargs::parse_status_level(matches)?;
let cflags = parseargs::parse_conv_flag_input(matches)?;
let iflags = parseargs::parse_iflags(matches)?;
@ -67,7 +65,6 @@ impl Input<io::Stdin> {
let mut i = Self {
src: io::stdin(),
non_ascii,
ibs,
print_level,
count,
@ -131,7 +128,6 @@ fn make_linux_iflags(iflags: &IFlags) -> Option<libc::c_int> {
impl Input<File> {
fn new(matches: &Matches) -> UResult<Self> {
let ibs = parseargs::parse_ibs(matches)?;
let non_ascii = parseargs::parse_input_non_ascii(matches)?;
let print_level = parseargs::parse_status_level(matches)?;
let cflags = parseargs::parse_conv_flag_input(matches)?;
let iflags = parseargs::parse_iflags(matches)?;
@ -163,7 +159,6 @@ impl Input<File> {
let i = Self {
src,
non_ascii,
ibs,
print_level,
count,
@ -607,16 +602,6 @@ impl Write for Output<io::Stdout> {
/// Read helper performs read operations common to all dd reads, and dispatches the buffer to relevant helper functions as dictated by the operations requested by the user.
fn read_helper<R: Read>(i: &mut Input<R>, bsize: usize) -> std::io::Result<(ReadStat, Vec<u8>)> {
// Local Predicate Fns -----------------------------------------------
fn is_conv<R: Read>(i: &Input<R>) -> bool {
i.cflags.ctable.is_some()
}
fn is_block<R: Read>(i: &Input<R>) -> bool {
i.cflags.block.is_some()
}
fn is_unblock<R: Read>(i: &Input<R>) -> bool {
i.cflags.unblock.is_some()
}
// Local Helper Fns -------------------------------------------------
fn perform_swab(buf: &mut [u8]) {
for base in (1..buf.len()).step_by(2) {
@ -639,11 +624,13 @@ fn read_helper<R: Read>(i: &mut Input<R>, bsize: usize) -> std::io::Result<(Read
if i.cflags.swab {
perform_swab(&mut buf);
}
if is_conv(i) || is_block(i) || is_unblock(i) {
let buf = conv_block_unblock_helper(buf, i, &mut rstat).unwrap();
Ok((rstat, buf))
} else {
Ok((rstat, buf))
match i.cflags.mode {
Some(ref mode) => {
let buf = conv_block_unblock_helper(buf, mode, &mut rstat);
Ok((rstat, buf))
}
None => Ok((rstat, buf)),
}
}
@ -1089,7 +1076,6 @@ mod tests {
src: LazyReader {
src: File::open("./test-resources/deadbeef-16.test").unwrap(),
},
non_ascii: false,
ibs: 16,
print_level: None,
count: None,
@ -1136,7 +1122,6 @@ mod tests {
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test")
.unwrap(),
},
non_ascii: false,
ibs: 521,
print_level: None,
count: None,

View file

@ -535,9 +535,50 @@ fn parse_flag_list<T: std::str::FromStr<Err = ParseError>>(
.collect()
}
/// Given the various command-line parameters, determine the conversion mode.
///
/// The `conv` command-line option can take many different values,
/// each of which may combine with others. For example, `conv=ascii`,
/// `conv=lcase`, `conv=sync`, and so on. The arguments to this
/// function represent the settings of those various command-line
/// parameters. This function translates those settings to a
/// [`ConversionMode`].
fn conversion_mode(
ctable: Option<&ConversionTable>,
block: Option<usize>,
unblock: Option<usize>,
non_ascii: bool,
is_sync: bool,
) -> Option<ConversionMode> {
match (ctable, block, unblock) {
(Some(ct), None, None) => Some(ConversionMode::ConvertOnly(ct)),
(Some(ct), Some(cbs), None) => {
if non_ascii {
Some(ConversionMode::ConvertThenBlock(ct, cbs, is_sync))
} else {
Some(ConversionMode::BlockThenConvert(ct, cbs, is_sync))
}
}
(Some(ct), None, Some(cbs)) => {
if non_ascii {
Some(ConversionMode::ConvertThenUnblock(ct, cbs))
} else {
Some(ConversionMode::UnblockThenConvert(ct, cbs))
}
}
(None, Some(cbs), None) => Some(ConversionMode::BlockOnly(cbs, is_sync)),
(None, None, Some(cbs)) => Some(ConversionMode::UnblockOnly(cbs)),
(None, None, None) => None,
// The remaining variants should never happen because the
// argument parsing above should result in an error before
// getting to this line of code.
_ => unreachable!(),
}
}
/// Parse Conversion Options (Input Variety)
/// Construct and validate a IConvFlags
pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError> {
pub(crate) fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError> {
let mut iconvflags = IConvFlags::default();
let mut fmt = None;
let mut case = None;
@ -546,6 +587,9 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
let flags = parse_flag_list(options::CONV, matches)?;
let cbs = parse_cbs(matches)?;
let mut block = None;
let mut unblock = None;
for flag in flags {
match flag {
ConvFlag::FmtEtoA => {
@ -565,7 +609,7 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
//
// -- https://www.gnu.org/software/coreutils/manual/html_node/dd-invocation.html
if cbs.is_some() {
iconvflags.unblock = cbs;
unblock = cbs;
}
}
}
@ -585,7 +629,7 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
//
// -- https://www.gnu.org/software/coreutils/manual/html_node/dd-invocation.html
if cbs.is_some() {
iconvflags.block = cbs;
block = cbs;
}
}
}
@ -603,13 +647,13 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
case = Some(flag);
}
}
ConvFlag::Block => match (cbs, iconvflags.unblock) {
(Some(cbs), None) => iconvflags.block = Some(cbs),
ConvFlag::Block => match (cbs, unblock) {
(Some(cbs), None) => block = Some(cbs),
(None, _) => return Err(ParseError::BlockUnblockWithoutCBS),
(_, Some(_)) => return Err(ParseError::MultipleBlockUnblock),
},
ConvFlag::Unblock => match (cbs, iconvflags.block) {
(Some(cbs), None) => iconvflags.unblock = Some(cbs),
ConvFlag::Unblock => match (cbs, block) {
(Some(cbs), None) => unblock = Some(cbs),
(None, _) => return Err(ParseError::BlockUnblockWithoutCBS),
(_, Some(_)) => return Err(ParseError::MultipleBlockUnblock),
},
@ -630,7 +674,7 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
// block implies sync with ' '
// unblock implies sync with 0
// So the final value can't be set until all flags are parsed.
let sync = if is_sync && (iconvflags.block.is_some() || iconvflags.unblock.is_some()) {
let sync = if is_sync && (block.is_some() || unblock.is_some()) {
Some(b' ')
} else if is_sync {
Some(0u8)
@ -638,8 +682,27 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
None
};
// Some user options, such as the presence of conversion tables,
// will determine whether the input is assumed to be ascii. This
// parser sets the non_ascii flag accordingly.
//
// Examples:
//
// - If conv=ebcdic or conv=ibm is specified then block,
// unblock or swab must be performed before the conversion
// happens since the source will start in ascii.
// - If conv=ascii is specified then block, unblock or swab
// must be performed after the conversion since the source
// starts in ebcdic.
// - If no conversion is specified then the source is assumed
// to be in ascii.
//
// For more info see `info dd`.
let non_ascii = parseargs::parse_input_non_ascii(matches)?;
let mode = conversion_mode(ctable, block, unblock, non_ascii, is_sync);
Ok(IConvFlags {
ctable,
mode,
sync,
..iconvflags
})

View file

@ -170,8 +170,11 @@ fn test_all_top_level_args_no_leading_dashes() {
);
assert_eq!(
IConvFlags {
ctable: Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE),
unblock: Some(1), // because ascii implies unblock
// ascii implies unblock
mode: Some(ConversionMode::ConvertThenUnblock(
&EBCDIC_TO_ASCII_LCASE_TO_UCASE,
1
)),
..IConvFlags::default()
},
parse_conv_flag_input(&matches).unwrap()
@ -269,8 +272,11 @@ fn test_all_top_level_args_with_leading_dashes() {
);
assert_eq!(
IConvFlags {
ctable: Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE),
unblock: Some(1), // because ascii implies unblock
// ascii implies unblock
mode: Some(ConversionMode::ConvertThenUnblock(
&EBCDIC_TO_ASCII_LCASE_TO_UCASE,
1
)),
..IConvFlags::default()
},
parse_conv_flag_input(&matches).unwrap()

View file

@ -55,19 +55,31 @@ pub(crate) enum Column {
Capacity,
}
/// An error while defining which columns to display in the output table.
#[derive(Debug)]
pub(crate) enum ColumnError {
/// If a column appears more than once in the `--output` argument.
MultipleColumns(String),
}
impl Column {
/// Convert from command-line arguments to sequence of columns.
///
/// The set of columns that will appear in the output table can be
/// specified by command-line arguments. This function converts
/// those arguments to a [`Vec`] of [`Column`] variants.
pub(crate) fn from_matches(matches: &ArgMatches) -> Vec<Self> {
///
/// # Errors
///
/// This function returns an error if a column is specified more
/// than once in the command-line argument.
pub(crate) fn from_matches(matches: &ArgMatches) -> Result<Vec<Self>, ColumnError> {
match (
matches.is_present(OPT_PRINT_TYPE),
matches.is_present(OPT_INODES),
matches.occurrences_of(OPT_OUTPUT) > 0,
) {
(false, false, false) => vec![
(false, false, false) => Ok(vec![
Self::Source,
Self::Size,
Self::Used,
@ -76,29 +88,37 @@ impl Column {
Self::Capacity,
Self::Pcent,
Self::Target,
],
]),
(false, false, true) => {
matches
.values_of(OPT_OUTPUT)
.unwrap()
.map(|s| {
// Unwrapping here should not panic because the
// command-line argument parsing library should be
// responsible for ensuring each comma-separated
// string is a valid column label.
Self::parse(s).unwrap()
})
.collect()
// Unwrapping should not panic because in this arm of
// the `match` statement, we know that `OPT_OUTPUT`
// is non-empty.
let names = matches.values_of(OPT_OUTPUT).unwrap();
let mut seen: Vec<&str> = vec![];
let mut columns = vec![];
for name in names {
if seen.contains(&name) {
return Err(ColumnError::MultipleColumns(name.to_string()));
}
seen.push(name);
// Unwrapping here should not panic because the
// command-line argument parsing library should be
// responsible for ensuring each comma-separated
// string is a valid column label.
let column = Self::parse(name).unwrap();
columns.push(column);
}
Ok(columns)
}
(false, true, false) => vec![
(false, true, false) => Ok(vec![
Self::Source,
Self::Itotal,
Self::Iused,
Self::Iavail,
Self::Ipcent,
Self::Target,
],
(true, false, false) => vec![
]),
(true, false, false) => Ok(vec![
Self::Source,
Self::Fstype,
Self::Size,
@ -108,8 +128,8 @@ impl Column {
Self::Capacity,
Self::Pcent,
Self::Target,
],
(true, true, false) => vec![
]),
(true, true, false) => Ok(vec![
Self::Source,
Self::Fstype,
Self::Itotal,
@ -117,7 +137,7 @@ impl Column {
Self::Iavail,
Self::Ipcent,
Self::Target,
],
]),
// The command-line arguments -T and -i are each mutually
// exclusive with --output, so the command-line argument
// parser should reject those combinations before we get

View file

@ -11,17 +11,19 @@ mod columns;
mod filesystem;
mod table;
use uucore::error::{UResult, USimpleError};
use uucore::display::Quotable;
use uucore::error::{UError, UResult};
use uucore::format_usage;
use uucore::fsext::{read_fs_list, MountInfo};
use clap::{crate_version, Arg, ArgMatches, Command};
use std::error::Error;
use std::fmt;
use std::path::Path;
use crate::blocks::{block_size_from_matches, BlockSize};
use crate::columns::Column;
use crate::columns::{Column, ColumnError};
use crate::filesystem::Filesystem;
use crate::table::{DisplayRow, Header, Row};
@ -103,8 +105,12 @@ impl Default for Options {
}
}
#[derive(Debug)]
enum OptionsError {
InvalidBlockSize,
/// An error getting the columns to display in the output table.
ColumnError(ColumnError),
}
impl fmt::Display for OptionsError {
@ -115,6 +121,11 @@ impl fmt::Display for OptionsError {
// TODO This needs to vary based on whether `--block-size`
// or `-B` were provided.
Self::InvalidBlockSize => write!(f, "invalid --block-size argument"),
Self::ColumnError(ColumnError::MultipleColumns(s)) => write!(
f,
"option --output: field {} used more than once",
s.quote()
),
}
}
}
@ -131,7 +142,7 @@ impl Options {
include: matches.values_of_lossy(OPT_TYPE),
exclude: matches.values_of_lossy(OPT_EXCLUDE_TYPE),
show_total: matches.is_present(OPT_TOTAL),
columns: Column::from_matches(matches),
columns: Column::from_matches(matches).map_err(OptionsError::ColumnError)?,
})
}
}
@ -243,7 +254,10 @@ fn get_all_filesystems(opt: &Options) -> Vec<Filesystem> {
// Convert each `MountInfo` into a `Filesystem`, which contains
// both the mount information and usage information.
mounts.into_iter().filter_map(Filesystem::new).collect()
mounts
.into_iter()
.filter_map(|m| Filesystem::new(m, None))
.collect()
}
/// For each path, get the filesystem that contains that path.
@ -270,6 +284,28 @@ where
.collect()
}
#[derive(Debug)]
enum DfError {
/// A problem while parsing command-line options.
OptionsError(OptionsError),
}
impl Error for DfError {}
impl UError for DfError {
fn usage(&self) -> bool {
matches!(self, Self::OptionsError(OptionsError::ColumnError(_)))
}
}
impl fmt::Display for DfError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::OptionsError(e) => e.fmt(f),
}
}
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().get_matches_from(args);
@ -281,7 +317,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}
}
let opt = Options::from(&matches).map_err(|e| USimpleError::new(1, format!("{}", e)))?;
let opt = Options::from(&matches).map_err(DfError::OptionsError)?;
// Get the list of filesystems to display in the output table.
let filesystems: Vec<Filesystem> = match matches.values_of(OPT_PATHS) {
@ -385,7 +421,10 @@ pub fn uu_app<'a>() -> Command<'a> {
Arg::new(OPT_OUTPUT)
.long("output")
.takes_value(true)
.min_values(0)
.require_equals(true)
.use_value_delimiter(true)
.multiple_occurrences(true)
.possible_values(OUTPUT_FIELD_LIST)
.default_missing_values(&OUTPUT_FIELD_LIST)
.default_values(&["source", "size", "used", "avail", "pcent", "target"])

View file

@ -23,6 +23,13 @@ use uucore::fsext::{FsUsage, MountInfo};
/// space available on the filesystem and the amount of space used.
#[derive(Debug, Clone)]
pub(crate) struct Filesystem {
/// The file given on the command line if any.
///
/// When invoking `df` with a positional argument, it displays
/// usage information for the filesystem that contains the given
/// file. If given, this field contains that filename.
pub file: Option<String>,
/// Information about the mounted device, mount directory, and related options.
pub mount_info: MountInfo,
@ -66,7 +73,7 @@ where
impl Filesystem {
// TODO: resolve uuid in `mount_info.dev_name` if exists
pub(crate) fn new(mount_info: MountInfo) -> Option<Self> {
pub(crate) fn new(mount_info: MountInfo, file: Option<String>) -> Option<Self> {
let _stat_path = if !mount_info.mount_dir.is_empty() {
mount_info.mount_dir.clone()
} else {
@ -84,7 +91,11 @@ impl Filesystem {
let usage = FsUsage::new(statfs(_stat_path).ok()?);
#[cfg(windows)]
let usage = FsUsage::new(Path::new(&_stat_path));
Some(Self { mount_info, usage })
Some(Self {
mount_info,
usage,
file,
})
}
/// Find and create the filesystem that best matches a given path.
@ -107,11 +118,12 @@ impl Filesystem {
where
P: AsRef<Path>,
{
let file = path.as_ref().display().to_string();
let canonicalize = true;
let mount_info = mount_info_from_path(mounts, path, canonicalize)?;
// TODO Make it so that we do not need to clone the `mount_info`.
let mount_info = (*mount_info).clone();
Self::new(mount_info)
Self::new(mount_info, Some(file))
}
}

View file

@ -24,6 +24,9 @@ use std::ops::AddAssign;
/// A row comprises several pieces of information, including the
/// filesystem device, the mountpoint, the number of bytes used, etc.
pub(crate) struct Row {
/// The filename given on the command-line, if given.
file: Option<String>,
/// Name of the device on which the filesystem lives.
fs_device: String,
@ -73,6 +76,7 @@ pub(crate) struct Row {
impl Row {
pub(crate) fn new(source: &str) -> Self {
Self {
file: None,
fs_device: source.into(),
fs_type: "-".into(),
fs_mount: "-".into(),
@ -101,6 +105,7 @@ impl AddAssign for Row {
let inodes = self.inodes + rhs.inodes;
let inodes_used = self.inodes_used + rhs.inodes_used;
*self = Self {
file: None,
fs_device: "total".into(),
fs_type: "-".into(),
fs_mount: "-".into(),
@ -144,23 +149,28 @@ impl From<Filesystem> for Row {
ffree,
..
} = fs.usage;
let bused = blocks - bfree;
Self {
file: fs.file,
fs_device: dev_name,
fs_type,
fs_mount: mount_dir,
bytes: blocksize * blocks,
bytes_used: blocksize * (blocks - bfree),
bytes_used: blocksize * bused,
bytes_avail: blocksize * bavail,
bytes_usage: if blocks == 0 {
None
} else {
Some(((blocks - bfree) as f64) / blocks as f64)
// We use "(bused + bavail)" instead of "blocks" because on some filesystems (e.g.
// ext4) "blocks" also includes reserved blocks we ignore for the usage calculation.
// https://www.gnu.org/software/coreutils/faq/coreutils-faq.html#df-Size-and-Used-and-Available-do-not-add-up
Some((bused as f64) / (bused + bavail) as f64)
},
#[cfg(target_os = "macos")]
bytes_capacity: if bavail == 0 {
None
} else {
Some(bavail as f64 / ((blocks - bfree + bavail) as f64))
Some(bavail as f64 / ((bused + bavail) as f64))
},
inodes: files,
inodes_used: files - ffree,
@ -246,8 +256,9 @@ impl fmt::Display for DisplayRow<'_> {
Column::Ipcent => {
write!(f, "{0: >5} ", DisplayRow::percentage(self.row.inodes_usage))?;
}
// TODO Implement this.
Column::File => {}
Column::File => {
write!(f, "{0: <16}", self.row.file.as_ref().unwrap_or(&"-".into()))?;
}
Column::Fstype => write!(f, "{0: <5} ", self.row.fs_type)?,
#[cfg(target_os = "macos")]
Column::Capacity => write!(
@ -406,6 +417,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(),
@ -437,6 +449,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(),
@ -468,6 +481,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(),
@ -499,6 +513,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(),
@ -530,6 +545,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(),
@ -560,6 +576,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(),

View file

@ -2478,7 +2478,7 @@ fn display_file_name(
if let Some(ls_colors) = &config.color {
if let Ok(metadata) = path.p_buf.symlink_metadata() {
name = color_name(ls_colors, &path.p_buf, name, &metadata);
name = color_name(ls_colors, &path.p_buf, &name, &metadata, config);
}
}
@ -2562,13 +2562,15 @@ fn display_file_name(
name.push_str(&color_name(
ls_colors,
&target_data.p_buf,
target.to_string_lossy().into_owned(),
&target.to_string_lossy(),
&target_metadata,
config,
));
}
} else {
// If no coloring is required, we just use target as is.
name.push_str(&target.to_string_lossy());
// Apply the right quoting
name.push_str(&escape_name(target.as_os_str(), &config.quoting_style));
}
}
}
@ -2593,10 +2595,19 @@ fn display_file_name(
}
}
fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String {
fn color_name(
ls_colors: &LsColors,
path: &Path,
name: &str,
md: &Metadata,
config: &Config,
) -> String {
match ls_colors.style_for_path_with_metadata(path, Some(md)) {
Some(style) => style.to_ansi_term_style().paint(name).to_string(),
None => name,
Some(style) => {
let p = escape_name(OsStr::new(&name), &config.quoting_style);
return style.to_ansi_term_style().paint(p).to_string();
}
None => escape_name(OsStr::new(&name), &config.quoting_style),
}
}

View file

@ -11,7 +11,7 @@
extern crate uucore;
use clap::{crate_version, Arg, ArgMatches, Command, OsValues};
use std::path::Path;
use std::path::{Path, PathBuf};
use uucore::display::Quotable;
#[cfg(not(windows))]
use uucore::error::FromIo;
@ -146,8 +146,17 @@ pub fn uu_app<'a>() -> Command<'a> {
*/
fn exec(dirs: OsValues, recursive: bool, mode: u32, verbose: bool) -> UResult<()> {
for dir in dirs {
let path = Path::new(dir);
show_if_err!(mkdir(path, recursive, mode, verbose));
// Special case to match GNU's behavior:
// mkdir -p foo/. should work and just create foo/
// std::fs::create_dir("foo/."); fails in pure Rust
let path = if recursive && dir.to_string_lossy().ends_with("/.") {
// Do a simple dance to strip the "/."
Path::new(dir).components().collect::<PathBuf>()
} else {
// Normal case
PathBuf::from(dir)
};
show_if_err!(mkdir(path.as_path(), recursive, mode, verbose));
}
Ok(())
}
@ -193,7 +202,6 @@ fn create_dir(path: &Path, recursive: bool, verbose: bool) -> UResult<()> {
}
}
}
match std::fs::create_dir(path) {
Ok(()) => {
if verbose {

View file

@ -7,7 +7,7 @@
// spell-checker:ignore (ToDO) cmdline evec seps rvec fdata
use clap::{crate_version, Arg, Command};
use clap::{crate_version, Arg, Command, Values};
use rand::prelude::SliceRandom;
use rand::RngCore;
use std::fs::File;
@ -75,17 +75,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
};
let options = Options {
head_count: match matches.value_of(options::HEAD_COUNT) {
Some(count) => match count.parse::<usize>() {
head_count: {
let mut headcounts: Values<'_> =
matches.values_of(options::HEAD_COUNT).unwrap_or_default();
match parse_head_count(&mut headcounts) {
Ok(val) => val,
Err(_) => {
return Err(USimpleError::new(
1,
format!("invalid line count: {}", count.quote()),
));
}
},
None => std::usize::MAX,
Err(msg) => return Err(USimpleError::new(1, msg)),
}
},
output: matches.value_of(options::OUTPUT).map(String::from),
random_source: matches.value_of(options::RANDOM_SOURCE).map(String::from),
@ -152,6 +148,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.short('n')
.long(options::HEAD_COUNT)
.takes_value(true)
.multiple_occurrences(true)
.value_name("COUNT")
.help("output at most COUNT lines"),
)
@ -299,6 +296,17 @@ fn parse_range(input_range: &str) -> Result<(usize, usize), String> {
}
}
fn parse_head_count(headcounts: &mut Values<'_>) -> Result<usize, String> {
let mut result = std::usize::MAX;
for count in headcounts {
match count.parse::<usize>() {
Ok(pv) => result = std::cmp::min(result, pv),
Err(_) => return Err(format!("invalid line count: {}", count.quote())),
}
}
Ok(result)
}
enum WrappedRng {
RngFile(rand_read_adapter::ReadRng<File>),
RngDefault(rand::rngs::ThreadRng),

View file

@ -68,14 +68,18 @@ impl Config {
_ => uucore::signals::signal_by_name_or_value("TERM").unwrap(),
};
let kill_after = options
.value_of(options::KILL_AFTER)
.map(|time| uucore::parse_time::from_str(time).unwrap());
let kill_after = match options.value_of(options::KILL_AFTER) {
None => None,
Some(kill_after) => match uucore::parse_time::from_str(kill_after) {
Ok(k) => Some(k),
Err(err) => return Err(UUsageError::new(ExitStatus::TimeoutFailed.into(), err)),
},
};
let duration =
match uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()) {
Ok(duration) => duration,
Err(err) => return Err(UUsageError::new(1, err)),
Err(err) => return Err(UUsageError::new(ExitStatus::TimeoutFailed.into(), err)),
};
let preserve_status: bool = options.is_present(options::PRESERVE_STATUS);
@ -134,7 +138,9 @@ pub fn uu_app<'a>() -> Command<'a> {
)
.arg(
Arg::new(options::KILL_AFTER)
.long(options::KILL_AFTER)
.short('k')
.help("also send a KILL signal if COMMAND is still running this long after the initial signal was sent")
.takes_value(true))
.arg(
Arg::new(options::PRESERVE_STATUS)

View file

@ -21,6 +21,13 @@ use crate::display::Quotable;
/// one hundred twenty three seconds or "4.5d" meaning four and a half
/// days. If no unit is specified, the unit is assumed to be seconds.
///
/// The only allowed suffixes are
///
/// * "s" for seconds,
/// * "m" for minutes,
/// * "h" for hours,
/// * "d" for days.
///
/// This function uses [`Duration::saturating_mul`] to compute the
/// number of seconds, so it does not overflow. If overflow would have
/// occurred, [`Duration::MAX`] is returned instead.
@ -46,10 +53,10 @@ pub fn from_str(string: &str) -> Result<Duration, String> {
}
let slice = &string[..len - 1];
let (numstr, times) = match string.chars().next_back().unwrap() {
's' | 'S' => (slice, 1),
'm' | 'M' => (slice, 60),
'h' | 'H' => (slice, 60 * 60),
'd' | 'D' => (slice, 60 * 60 * 24),
's' => (slice, 1),
'm' => (slice, 60),
'h' => (slice, 60 * 60),
'd' => (slice, 60 * 60 * 24),
val if !val.is_alphabetic() => (string, 1),
_ => {
if string == "inf" || string == "infinity" {
@ -114,4 +121,13 @@ mod tests {
fn test_negative() {
assert!(from_str("-1").is_err());
}
/// Test that capital letters are not allowed in suffixes.
#[test]
fn test_no_capital_letters() {
assert!(from_str("1S").is_err());
assert!(from_str("1M").is_err());
assert!(from_str("1H").is_err());
assert!(from_str("1D").is_err());
}
}

View file

@ -1,4 +1,4 @@
// spell-checker:ignore udev
// spell-checker:ignore udev pcent
use crate::common::util::*;
#[test]
@ -80,6 +80,11 @@ fn test_output_option() {
new_ucmd!().arg("--output=invalid_option").fails();
}
#[test]
fn test_output_option_without_equals_sign() {
new_ucmd!().arg("--output").arg(".").succeeds();
}
#[test]
fn test_type_option() {
new_ucmd!().args(&["-t", "ext4", "-t", "ext3"]).succeeds();
@ -134,33 +139,24 @@ fn test_total() {
#[test]
fn test_use_percentage() {
// Example output:
//
// Filesystem 1K-blocks Used Available Use% Mounted on
// udev 3858016 0 3858016 0% /dev
// ...
// /dev/loop14 63488 63488 0 100% /snap/core20/1361
let output = new_ucmd!().succeeds().stdout_move_str();
let output = new_ucmd!()
.args(&["--output=used,avail,pcent"])
.succeeds()
.stdout_move_str();
// Skip the header line.
let lines: Vec<&str> = output.lines().skip(1).collect();
for line in lines {
let mut iter = line.split_whitespace();
iter.next();
let reported_size = iter.next().unwrap().parse::<f64>().unwrap();
let reported_used = iter.next().unwrap().parse::<f64>().unwrap();
// Skip "Available" column
iter.next();
if cfg!(target_os = "macos") {
// Skip "Capacity" column
iter.next();
}
let reported_avail = iter.next().unwrap().parse::<f64>().unwrap();
let reported_percentage = iter.next().unwrap();
let reported_percentage = reported_percentage[..reported_percentage.len() - 1]
.parse::<u8>()
.unwrap();
let computed_percentage = (100.0 * (reported_used / reported_size)).ceil() as u8;
let computed_percentage =
(100.0 * (reported_used / (reported_used + reported_avail))).ceil() as u8;
assert_eq!(computed_percentage, reported_percentage);
}
@ -222,4 +218,65 @@ fn test_output_selects_columns() {
);
}
// ToDO: more tests...
#[test]
fn test_output_multiple_occurrences() {
let output = new_ucmd!()
.args(&["--output=source", "--output=target"])
.succeeds()
.stdout_move_str();
assert_eq!(
output.lines().next().unwrap(),
"Filesystem Mounted on "
);
}
// TODO Fix the spacing.
#[test]
fn test_output_file_all_filesystems() {
// When run with no positional arguments, `df` lets "-" represent
// the "File" entry for each row.
let output = new_ucmd!()
.arg("--output=file")
.succeeds()
.stdout_move_str();
let mut lines = output.lines();
assert_eq!(lines.next().unwrap(), "File ");
for line in lines {
assert_eq!(line, "- ");
}
}
// TODO Fix the spacing.
#[test]
fn test_output_file_specific_files() {
// Create three files.
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
at.touch("c");
// When run with positional arguments, the filesystems should
// appear in the "File" column.
let output = ucmd
.args(&["--output=file", "a", "b", "c"])
.succeeds()
.stdout_move_str();
let actual: Vec<&str> = output.lines().collect();
assert_eq!(
actual,
vec![
"File ",
"a ",
"b ",
"c "
]
);
}
#[test]
fn test_output_field_no_more_than_once() {
new_ucmd!()
.arg("--output=target,source,target")
.fails()
.usage_error("option --output: field 'target' used more than once");
}

View file

@ -2913,3 +2913,42 @@ fn test_ls_multiple_a_A() {
.stdout_does_not_contain(".")
.stdout_does_not_contain("..");
}
#[test]
fn test_ls_quoting() {
let scene = TestScenario::new(util_name!());
scene
.ccmd("ln")
.arg("-s")
.arg("'need quoting'")
.arg("symlink")
.succeeds();
scene
.ucmd()
.arg("-l")
.arg("--quoting-style=shell-escape")
.arg("symlink")
.succeeds()
.stdout_contains("\'need quoting\'");
}
#[test]
fn test_ls_quoting_color() {
let scene = TestScenario::new(util_name!());
scene
.ccmd("ln")
.arg("-s")
.arg("'need quoting'")
.arg("symlink")
.succeeds();
scene
.ucmd()
.arg("-l")
.arg("--quoting-style=shell-escape")
.arg("--color=auto")
.arg("symlink")
.succeeds()
.stdout_contains("\'need quoting\'");
}

View file

@ -15,7 +15,10 @@ static TEST_DIR6: &str = "mkdir_test6";
static TEST_FILE7: &str = "mkdir_test7";
static TEST_DIR8: &str = "mkdir_test8/mkdir_test8_1/mkdir_test8_2";
static TEST_DIR9: &str = "mkdir_test9/../mkdir_test9_1/../mkdir_test9_2";
static TEST_DIR10: &str = "mkdir_test10";
static TEST_DIR10: &str = "mkdir_test10/.";
static TEST_DIR11: &str = "mkdir_test11/..";
static TEST_DIR12: &str = "mkdir_test12";
#[test]
fn test_mkdir_mkdir() {
@ -129,6 +132,32 @@ fn test_recursive_reporting() {
.stdout_contains("created directory 'mkdir_test9/../mkdir_test9_1/../mkdir_test9_2'");
}
#[test]
fn test_mkdir_trailing_dot() {
let scene2 = TestScenario::new("ls");
new_ucmd!()
.arg("-p")
.arg("-v")
.arg("mkdir_test10-2")
.succeeds();
new_ucmd!()
.arg("-p")
.arg("-v")
.arg(TEST_DIR10)
.succeeds()
.stdout_contains("created directory 'mkdir_test10'");
new_ucmd!()
.arg("-p")
.arg("-v")
.arg(TEST_DIR11)
.succeeds()
.stdout_contains("created directory 'mkdir_test11'");
let result = scene2.cmd("ls").arg("-al").run();
println!("ls dest {}", result.stdout_str());
}
#[test]
#[cfg(not(windows))]
fn test_umask_compliance() {
@ -137,8 +166,8 @@ fn test_umask_compliance() {
let original_umask = unsafe { umask(umask_set) };
ucmd.arg(TEST_DIR10).succeeds();
let perms = at.metadata(TEST_DIR10).permissions().mode() as mode_t;
ucmd.arg(TEST_DIR12).succeeds();
let perms = at.metadata(TEST_DIR12).permissions().mode() as mode_t;
assert_eq!(perms, (!umask_set & 0o0777) + 0o40000); // before compare, add the set GUID, UID bits
unsafe {
@ -150,4 +179,4 @@ fn test_umask_compliance() {
// tests all permission combinations
test_single_case(i);
}
}
}

View file

@ -196,3 +196,19 @@ fn test_shuf_invalid_input_line_count() {
.fails()
.stderr_contains("invalid line count: 'a'");
}
#[test]
fn test_shuf_multiple_input_line_count() {
let result = new_ucmd!()
.args(&["-i10-200", "-n", "10", "-n", "5"])
.succeeds();
result.no_stderr();
let result_count = result
.stdout_str()
.split('\n')
.filter(|x| !x.is_empty())
.count();
assert_eq!(result_count, 5, "Output should have 5 items");
}

View file

@ -16,6 +16,16 @@ fn test_invalid_time_interval() {
new_ucmd!()
.args(&["xyz", "sleep", "0"])
.fails()
.code_is(125)
.usage_error("invalid time interval 'xyz'");
}
#[test]
fn test_invalid_kill_after() {
new_ucmd!()
.args(&["-k", "xyz", "1", "sleep", "0"])
.fails()
.code_is(125)
.usage_error("invalid time interval 'xyz'");
}
@ -105,3 +115,13 @@ fn test_invalid_signal() {
.fails()
.usage_error("'invalid': invalid signal");
}
/// Test that the long form of the `--kill-after` argument is recognized.
#[test]
fn test_kill_after_long() {
new_ucmd!()
.args(&["--kill-after=1", "1", "sleep", "0"])
.succeeds()
.no_stdout()
.no_stderr();
}

View file

@ -104,7 +104,6 @@ sed -i '/INT_OFLOW/ D' tests/misc/printf.sh
# Use the system coreutils where the test fails due to error in a util that is not the one being tested
sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh
sed -i 's|ls -|/usr/bin/ls -|' tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh
sed -i 's|timeout \([[:digit:]]\)| /usr/bin/timeout \1|' tests/tail-2/inotify-rotate.sh tests/tail-2/inotify-dir-recreate.sh tests/tail-2/inotify-rotate-resources.sh tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.sh tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh tests/tail-2/pid.sh tests/dd/stats.sh tests/tail-2/follow-name.sh tests/misc/shuf.sh # Don't break the function called 'grep_timeout'
sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/mv/i-2.sh tests/misc/shuf.sh
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