mirror of
https://github.com/uutils/coreutils
synced 2024-11-16 01:38:04 +00:00
Merge branch 'main' into mkdir-fix
This commit is contained in:
commit
ea592e5c03
24 changed files with 542 additions and 223 deletions
9
.github/workflows/CICD.yml
vendored
9
.github/workflows/CICD.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/GnuTests.yml
vendored
2
.github/workflows/GnuTests.yml
vendored
|
@ -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
4
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -54,6 +54,7 @@ endif
|
|||
PROGS := \
|
||||
base32 \
|
||||
base64 \
|
||||
basenc \
|
||||
basename \
|
||||
cat \
|
||||
cksum \
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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\'");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue