From eb8cdcf44aa4bc452493465e09b4de2dbd994ba0 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sat, 20 Feb 2021 15:07:47 +0000 Subject: [PATCH 001/104] Re-add fixed test --- .github/workflows/GNU.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index b1cbc1502..c599a454c 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -55,7 +55,6 @@ jobs: grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||' sed -i -e 's|^seq |/usr/bin/seq |' tests/factor/t*sh sed -i -e '/tests\/misc\/cat-self.sh/ D' Makefile # issue #1707 - sed -i -e '/tests\/misc\/numfmt.pl/ D' Makefile # issue #1708 sed -i -e '/tests\/chown\/preserve-root.sh/ D' Makefile # issue #1709 sed -i -e '/tests\/cp\/file-perm-race.sh/ D' Makefile # issue #1710 sed -i -e '/tests\/cp\/special-f.sh/ D' Makefile # issue #1710 From a73c34c7355a198e48c86fa1308ed9a2fad87f65 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sat, 20 Feb 2021 17:17:03 +0000 Subject: [PATCH 002/104] Stop tests failing on utils that aren't the focu of the test --- .github/workflows/GNU.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index c599a454c..6ec44cf22 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -74,6 +74,16 @@ jobs: sed -i -e '/tests\/tail-2\/pid.sh/ D' Makefile # hangs on github, tail doesn't support -f sed -i -e'/incompat4/ D' -e"/options '-co' are incompatible/ d" tests/misc/sort.pl # Sort doesn't correctly check for incompatible options, waits for input + # Use the system coreutils where the test fails due to error in a util that is not the one being tested + sed -i -e 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/chgrp/basic.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh + sed -i -e 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh + sed -i -e 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh + sed -i -e 's|timeout |/usr/bin/timeout |' tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh misc/sort-NaN-infloop.log tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/inotify-rotate.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh + sed -i -e 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh + sed -i -e 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh + sed -i -e 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh + sed -i -e 's|truncate |/usr/bin/truncate |' tests/split/fail.sh + test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" - name: Run GNU tests shell: bash From b3dea739f4d4ff1433ccf05f769cfaa1371184f6 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 21 Feb 2021 09:36:03 +0000 Subject: [PATCH 003/104] Fix script name --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 6ec44cf22..f9390a3f0 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -78,7 +78,7 @@ jobs: sed -i -e 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/chgrp/basic.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh sed -i -e 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh sed -i -e 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh - sed -i -e 's|timeout |/usr/bin/timeout |' tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh misc/sort-NaN-infloop.log tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/inotify-rotate.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh + sed -i -e 's|timeout |/usr/bin/timeout |' tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.log tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/inotify-rotate.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh sed -i -e 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh sed -i -e 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh sed -i -e 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh From 910f6d102f529b10c75fb3fa5696da0b9c15cc21 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 21 Feb 2021 09:53:11 +0000 Subject: [PATCH 004/104] Fix script name --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index f9390a3f0..9e0d66eee 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -78,7 +78,7 @@ jobs: sed -i -e 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/chgrp/basic.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh sed -i -e 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh sed -i -e 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh - sed -i -e 's|timeout |/usr/bin/timeout |' tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.log tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/inotify-rotate.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh + sed -i -e 's|timeout |/usr/bin/timeout |' 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/inotify-rotate.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh sed -i -e 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh sed -i -e 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh sed -i -e 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh From f2013e47b3789fc22fd8bed0383154d5d1235c8d Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 21 Feb 2021 11:14:14 +0000 Subject: [PATCH 005/104] Use which to find system utils --- .github/workflows/GNU.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 9e0d66eee..15bab8b3f 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -75,14 +75,14 @@ jobs: sed -i -e'/incompat4/ D' -e"/options '-co' are incompatible/ d" tests/misc/sort.pl # Sort doesn't correctly check for incompatible options, waits for input # Use the system coreutils where the test fails due to error in a util that is not the one being tested - sed -i -e 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/chgrp/basic.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh - sed -i -e 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh - sed -i -e 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh - sed -i -e 's|timeout |/usr/bin/timeout |' 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/inotify-rotate.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh - sed -i -e 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh - sed -i -e 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh - sed -i -e 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh - sed -i -e 's|truncate |/usr/bin/truncate |' tests/split/fail.sh + sed -i -e "s|stat|$(which stat)|" tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh + sed -i -e "s|ls -|$(which ls) -|" tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh + sed -i -e "s|mkdir |$(which mkdir) |" tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh + sed -i -e "s|timeout |$(which timeout) |" 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/inotify-rotate.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh + sed -i -e "s|chmod |$(which chmod) |" tests/du/inacc-dir.sh tests/mkdir/p-3.sh + sed -i -e "s|sort |$(which sort) |" tests/ls/hyperlink.sh + sed -i -e "s|split |$(which split) |" tests/misc/factor-parallel.sh + sed -i -e "s|truncate |$(which truncate) |" tests/split/fail.sh test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" - name: Run GNU tests From cebb58c5b486853a7dedaba0e68a8b022f83dc64 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 21 Feb 2021 13:36:16 +0000 Subject: [PATCH 006/104] Rename install for testing --- .github/workflows/GNU.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 15bab8b3f..c92d90076 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -37,6 +37,7 @@ jobs: sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify expect python3-sphinx pushd uutils make PROFILE=release + cp target/release/install target/release/ginstall # The GNU tests rename this script before running, to avoid confusion with the make target BUILDDIR="$PWD/target/release/" popd GNULIB_SRCDIR="$PWD/gnulib" From 4e90de44cc0e8f28b8d86b9218985b71da472017 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 21 Feb 2021 15:51:03 +0000 Subject: [PATCH 007/104] Move timeout to per-test script Move to a timeout applied to each script and re add the tests that are hanging so they show as failing --- .github/workflows/GNU.yml | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index c92d90076..82494bdd7 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -44,6 +44,8 @@ jobs: pushd gnu/ ./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR" ./configure --quiet --disable-gcc-warnings + #Add timeout to tests to protest against hangs + sed -i -e 's|"\$@|timeout 600 "\$@|' build-aux/test-driver # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh @@ -55,25 +57,13 @@ jobs: done grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||' sed -i -e 's|^seq |/usr/bin/seq |' tests/factor/t*sh - sed -i -e '/tests\/misc\/cat-self.sh/ D' Makefile # issue #1707 - sed -i -e '/tests\/chown\/preserve-root.sh/ D' Makefile # issue #1709 - sed -i -e '/tests\/cp\/file-perm-race.sh/ D' Makefile # issue #1710 - sed -i -e '/tests\/cp\/special-f.sh/ D' Makefile # issue #1710 - sed -i -e '/tests\/mv\/mv-special-1.sh/ D' Makefile # issue #1710 - sed -i -e '/tests\/dd\/stats.sh/ D' Makefile # issue #1710 - sed -i -e '/tests\/cp\/existing-perm-race.sh/ D' Makefile # hangs, cp doesn't implement --copy-contents - # Remove tests that rely on seq with large numbers. See issue #1703 - sed -i -e '/tests\/misc\/seq.pl/ D' \ - -e '/tests\/misc\/seq-precision.sh/D' \ - Makefile + # Remove tests checking for --version & --help # Not really interesting for us and logs are too big sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \ -e '/tests\/misc\/help-version.sh/ D' \ -e '/tests\/misc\/help-version-getopt.sh/ D' \ Makefile - sed -i -e '/tests\/tail-2\/pid.sh/ D' Makefile # hangs on github, tail doesn't support -f - sed -i -e'/incompat4/ D' -e"/options '-co' are incompatible/ d" tests/misc/sort.pl # Sort doesn't correctly check for incompatible options, waits for input # Use the system coreutils where the test fails due to error in a util that is not the one being tested sed -i -e "s|stat|$(which stat)|" tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh @@ -93,7 +83,7 @@ jobs: GNULIB_DIR="${PWD}/gnulib" pushd gnu - unbuffer timeout -sKILL 3600 make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : + unbuffer make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : - name: Extract tests info shell: bash run: | From 89f74948e0db872bd9b4722ec8dd822bef7e34db Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 21 Feb 2021 15:52:18 +0000 Subject: [PATCH 008/104] Typo --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 82494bdd7..1541be1a4 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -44,7 +44,7 @@ jobs: pushd gnu/ ./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR" ./configure --quiet --disable-gcc-warnings - #Add timeout to tests to protest against hangs + #Add timeout to to protect against hangs sed -i -e 's|"\$@|timeout 600 "\$@|' build-aux/test-driver # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile From 16c8b3cbba351898335c0e6b21857b3fe57920b3 Mon Sep 17 00:00:00 2001 From: James Robson Date: Mon, 22 Feb 2021 09:19:43 +0000 Subject: [PATCH 009/104] Use system timeout command --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 1541be1a4..2ce9e44a5 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -45,7 +45,7 @@ jobs: ./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR" ./configure --quiet --disable-gcc-warnings #Add timeout to to protect against hangs - sed -i -e 's|"\$@|timeout 600 "\$@|' build-aux/test-driver + sed -i -e "s|\"\$@|$(which timeout) 600 \"\$@|" build-aux/test-driver # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh From b92b88a8229e1d80455d6a3a97fdf7ead2b25f1e Mon Sep 17 00:00:00 2001 From: James Robson Date: Mon, 22 Feb 2021 19:29:00 +0000 Subject: [PATCH 010/104] Add 4 hour global timeout for protection --- .github/workflows/GNU.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 2ce9e44a5..0af969220 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -6,6 +6,7 @@ jobs: gnu: name: Run GNU tests runs-on: ubuntu-latest + timeout-minutes: 240 # Kill after 4 hours in case something gets stuck steps: # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code uutil From dc49415829ba8eddd0486bd2503b29df3cca65ff Mon Sep 17 00:00:00 2001 From: James Robson Date: Mon, 22 Feb 2021 19:29:45 +0000 Subject: [PATCH 011/104] Stop seq-precision.sh causing jams in make --- .github/workflows/GNU.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 0af969220..024112f8a 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -76,6 +76,9 @@ jobs: sed -i -e "s|split |$(which split) |" tests/misc/factor-parallel.sh sed -i -e "s|truncate |$(which truncate) |" tests/split/fail.sh + #Add specific timeout to seq-precision.sh because otherwise seq will fill logs and cause something to jam in make + sed -i -e "s| seq |$(which timeout) 1 seq |" tests/misc/seq-precision.sh + test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" - name: Run GNU tests shell: bash From b19afebad8a62037aff0e1ea3c05b25808885b8c Mon Sep 17 00:00:00 2001 From: James Robson Date: Tue, 23 Feb 2021 09:58:06 +0000 Subject: [PATCH 012/104] Shorten the timeout on seq-precision --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 024112f8a..b9b014dbf 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -77,7 +77,7 @@ jobs: sed -i -e "s|truncate |$(which truncate) |" tests/split/fail.sh #Add specific timeout to seq-precision.sh because otherwise seq will fill logs and cause something to jam in make - sed -i -e "s| seq |$(which timeout) 1 seq |" tests/misc/seq-precision.sh + sed -i -e "s| seq |$(which timeout) 0.1 seq |" tests/misc/seq-precision.sh test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" - name: Run GNU tests From e89387c089344a6763f94239673437909aa66c44 Mon Sep 17 00:00:00 2001 From: James Robson Date: Tue, 23 Feb 2021 19:49:11 +0000 Subject: [PATCH 013/104] Try removing seq-precision --- .github/workflows/GNU.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index b9b014dbf..30c109cda 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -76,9 +76,8 @@ jobs: sed -i -e "s|split |$(which split) |" tests/misc/factor-parallel.sh sed -i -e "s|truncate |$(which truncate) |" tests/split/fail.sh - #Add specific timeout to seq-precision.sh because otherwise seq will fill logs and cause something to jam in make - sed -i -e "s| seq |$(which timeout) 0.1 seq |" tests/misc/seq-precision.sh - + # take seq-precision out + sed -i '/seq-precision.sh/ D' Makefile test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" - name: Run GNU tests shell: bash From 4cca2b651ad72a9683d9def7f8b2168e9bfab8f0 Mon Sep 17 00:00:00 2001 From: James Robson Date: Wed, 24 Feb 2021 17:28:20 +0000 Subject: [PATCH 014/104] Keep producing logs even if make hangs --- .github/workflows/GNU.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 30c109cda..a6e740319 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -6,7 +6,6 @@ jobs: gnu: name: Run GNU tests runs-on: ubuntu-latest - timeout-minutes: 240 # Kill after 4 hours in case something gets stuck steps: # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code uutil @@ -86,7 +85,7 @@ jobs: GNULIB_DIR="${PWD}/gnulib" pushd gnu - unbuffer make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : + unbuffer timeout -sKILL 4h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make - name: Extract tests info shell: bash run: | From 1cef9aa0462e5e9c8517965d983262493785487b Mon Sep 17 00:00:00 2001 From: James Robson Date: Wed, 24 Feb 2021 17:47:30 +0000 Subject: [PATCH 015/104] Add timeouts to other tests that hang --- .github/workflows/GNU.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index a6e740319..cebd5550e 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -69,14 +69,17 @@ jobs: sed -i -e "s|stat|$(which stat)|" tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh sed -i -e "s|ls -|$(which ls) -|" tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh sed -i -e "s|mkdir |$(which mkdir) |" tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh - sed -i -e "s|timeout |$(which timeout) |" 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/inotify-rotate.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh + sed -i -e "s|timeout |$(which timeout) |" 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/inotify-rotate.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 sed -i -e "s|chmod |$(which chmod) |" tests/du/inacc-dir.sh tests/mkdir/p-3.sh sed -i -e "s|sort |$(which sort) |" tests/ls/hyperlink.sh sed -i -e "s|split |$(which split) |" tests/misc/factor-parallel.sh sed -i -e "s|truncate |$(which truncate) |" tests/split/fail.sh - # take seq-precision out - sed -i '/seq-precision.sh/ D' Makefile + #Add specific timeout to tests that currently hang to limit time spent waiting + sed -i -e "s| seq |$(which timeout) 0.1 seq |" tests/misc/seq-precision.sh + sed -i -e "s|cat |$(which timeout) 0.1 cat |" tests/misc/cat-self.sh + + test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" - name: Run GNU tests shell: bash From 03619d867ebd00ff87addf05b4e66cd4a1ee070e Mon Sep 17 00:00:00 2001 From: James Robson Date: Thu, 25 Feb 2021 18:18:30 +0000 Subject: [PATCH 016/104] More tight timeouts --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index cebd5550e..6739a12b5 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -76,7 +76,7 @@ jobs: sed -i -e "s|truncate |$(which truncate) |" tests/split/fail.sh #Add specific timeout to tests that currently hang to limit time spent waiting - sed -i -e "s| seq |$(which timeout) 0.1 seq |" tests/misc/seq-precision.sh + sed -i -e "s|seq \\$|$(which timeout) 0.1 seq \$|" tests/misc/seq-precision.sh tests/misc/seq-long-double.sh sed -i -e "s|cat |$(which timeout) 0.1 cat |" tests/misc/cat-self.sh From bbce17911596c54891154988b86e32241e9351e6 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sat, 27 Feb 2021 13:25:13 +0000 Subject: [PATCH 017/104] Remove unbuffer This causes the make process to hang for some reason, and it itsn't providing any real advantage so it's taken out --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 6739a12b5..fb2e231d4 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -88,7 +88,7 @@ jobs: GNULIB_DIR="${PWD}/gnulib" pushd gnu - unbuffer timeout -sKILL 4h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make + timeout -sKILL 4h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make - name: Extract tests info shell: bash run: | From a395af7ee761aef1041567edecebe46bf6382fcd Mon Sep 17 00:00:00 2001 From: James Robson Date: Sat, 27 Feb 2021 14:35:31 +0000 Subject: [PATCH 018/104] Create *sum binaries for tests --- .github/workflows/GNU.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index fb2e231d4..528d62863 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -38,6 +38,12 @@ jobs: pushd uutils make PROFILE=release cp target/release/install target/release/ginstall # The GNU tests rename this script before running, to avoid confusion with the make target + # Create *sum binaries + for sum in b2sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum + do + sum_path="target/release/${sum}" + fest -f "${sum_path}" || cp target/release/hashsum "${sum_path}" + done BUILDDIR="$PWD/target/release/" popd GNULIB_SRCDIR="$PWD/gnulib" From 20082971be5d47fd13ad2ae928de8574cedfc8fb Mon Sep 17 00:00:00 2001 From: James Robson Date: Thu, 4 Mar 2021 17:33:31 +0000 Subject: [PATCH 019/104] Use system sha1sum in factor tests --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 528d62863..3a9864ae1 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -62,7 +62,7 @@ jobs: make tests/factor/t${i}.sh done grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||' - sed -i -e 's|^seq |/usr/bin/seq |' tests/factor/t*sh + sed -i -e 's|^seq |/usr/bin/seq |' -e "s|sha1sum |$(which sha1sum) |" tests/factor/t*sh # Remove tests checking for --version & --help # Not really interesting for us and logs are too big From 3b9399513293f9225b8050326507ac9cc44487b8 Mon Sep 17 00:00:00 2001 From: James Robson Date: Thu, 4 Mar 2021 19:11:38 +0000 Subject: [PATCH 020/104] generate all factor scripts --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 3a9864ae1..91796214c 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -57,7 +57,7 @@ jobs: sed -i 's| tr | /usr/bin/tr |' tests/init.sh make # Generate the factor tests, so they can be fixed - for i in $(seq -w 1 36) + for i in $(seq -w 0 36) do make tests/factor/t${i}.sh done From e42479b79b5ba63a87ef902da684ec3ed28f2f1e Mon Sep 17 00:00:00 2001 From: James Robson Date: Thu, 4 Mar 2021 20:50:57 +0000 Subject: [PATCH 021/104] fail tests for any binary not built --- .github/workflows/GNU.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 91796214c..f6729d0d1 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -37,17 +37,26 @@ jobs: sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify expect python3-sphinx pushd uutils make PROFILE=release - cp target/release/install target/release/ginstall # The GNU tests rename this script before running, to avoid confusion with the make target + BUILDDIR="$PWD/target/release/" + cp ${BUILDDIR}/install ${BUILDDIR}/ginstall # The GNU tests rename this script before running, to avoid confusion with the make target # Create *sum binaries for sum in b2sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum do - sum_path="target/release/${sum}" - fest -f "${sum_path}" || cp target/release/hashsum "${sum_path}" + sum_path="${BUILDDIR}/${sum}" + test -f "${sum_path}" || cp "${BUILDDIR}/hashsum" "${sum_path}" done - BUILDDIR="$PWD/target/release/" + test -f "${BUILDDIR}/[" || cp "${BUILDDIR}/test" "${BUILDDIR}/[" popd GNULIB_SRCDIR="$PWD/gnulib" pushd gnu/ + + # Any binaries that aren't built become `false` so their tests fail + for binary in $(./build-aux/gen-lists-of-programs.sh --list-progs) + do + bin_path="${BUILDDIR}/${binary}" + test -f "${bin_path}" || cp "${BUILDDIR}/false" "${bin_path}" + done + ./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR" ./configure --quiet --disable-gcc-warnings #Add timeout to to protect against hangs From b098bd5ec2e7523c7eb659bee927edf32c8680bc Mon Sep 17 00:00:00 2001 From: James Robson Date: Fri, 5 Mar 2021 21:40:16 +0000 Subject: [PATCH 022/104] Fix tests still failing for the wrong reason --- .github/workflows/GNU.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index f6729d0d1..4d3e39613 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -84,9 +84,10 @@ jobs: sed -i -e "s|stat|$(which stat)|" tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh sed -i -e "s|ls -|$(which ls) -|" tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh sed -i -e "s|mkdir |$(which mkdir) |" tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh - sed -i -e "s|timeout |$(which timeout) |" 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/inotify-rotate.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 + sed -i -e "s|timeout |$(which timeout) |" 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 + sed -i -e "s| timeout | $(which timeout) |" tests/tail-2/inotify-rotate.sh # Don't break the function called 'grep_timeout' sed -i -e "s|chmod |$(which chmod) |" tests/du/inacc-dir.sh tests/mkdir/p-3.sh - sed -i -e "s|sort |$(which sort) |" tests/ls/hyperlink.sh + sed -i -e "s|sort |$(which sort) |" tests/ls/hyperlink.sh tests/misc/test-N.sh sed -i -e "s|split |$(which split) |" tests/misc/factor-parallel.sh sed -i -e "s|truncate |$(which truncate) |" tests/split/fail.sh From 8dae8b798a1de60689271df83676bfe60ab8ebb5 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sat, 6 Mar 2021 16:35:22 +0000 Subject: [PATCH 023/104] Revert "Remove unbuffer" This reverts commit bbce17911596c54891154988b86e32241e9351e6. --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 4d3e39613..2cfc535ce 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -104,7 +104,7 @@ jobs: GNULIB_DIR="${PWD}/gnulib" pushd gnu - timeout -sKILL 4h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make + unbuffer timeout -sKILL 4h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make - name: Extract tests info shell: bash run: | From 78ec6d1e5a20e697e7e79afead4ec34c5eba07d1 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sat, 6 Mar 2021 21:24:28 +0000 Subject: [PATCH 024/104] Revert "Revert "Remove unbuffer"" This reverts commit 8dae8b798a1de60689271df83676bfe60ab8ebb5. --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 2cfc535ce..4d3e39613 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -104,7 +104,7 @@ jobs: GNULIB_DIR="${PWD}/gnulib" pushd gnu - unbuffer timeout -sKILL 4h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make + timeout -sKILL 4h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make - name: Extract tests info shell: bash run: | From 5ec1bba5e8095f49f904cdd74f3db3c32bd0e058 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 13 Mar 2021 12:42:52 +0100 Subject: [PATCH 025/104] touch: use arggroup for sources --- src/uu/touch/src/touch.rs | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 1cd3b2a70..1cb551fd2 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -13,7 +13,7 @@ pub extern crate filetime; #[macro_use] extern crate uucore; -use clap::{App, Arg}; +use clap::{App, Arg, ArgGroup}; use filetime::*; use std::fs::{self, File}; use std::io::Error; @@ -129,6 +129,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .min_values(1), ) + .group(ArgGroup::with_name("sources").args(&[ + options::sources::CURRENT, + options::sources::DATE, + options::sources::REFERENCE, + ])) .get_matches_from(args); let files: Vec = matches @@ -136,19 +141,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - if matches.is_present(options::sources::DATE) - && (matches.is_present(options::sources::REFERENCE) - || matches.is_present(options::sources::CURRENT)) - || matches.is_present(options::sources::REFERENCE) - && (matches.is_present(options::sources::DATE) - || matches.is_present(options::sources::CURRENT)) - || matches.is_present(options::sources::CURRENT) - && (matches.is_present(options::sources::DATE) - || matches.is_present(options::sources::REFERENCE)) - { - panic!("Invalid options: cannot specify reference time from more than one source"); - } - let (mut atime, mut mtime) = if matches.is_present(options::sources::REFERENCE) { stat( &matches.value_of(options::sources::REFERENCE).unwrap()[..], @@ -188,10 +180,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; // Minor optimization: if no reference time was specified, we're done. - if !(matches.is_present(options::sources::DATE) - || matches.is_present(options::sources::REFERENCE) - || matches.is_present(options::sources::CURRENT)) - { + if !matches.is_present("sources") { continue; } } From dfc7a9505424539ee02aa0c051f16ddb98539ef4 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 13 Mar 2021 12:46:54 +0100 Subject: [PATCH 026/104] tests/touch: add tests for multiple sources --- tests/by-util/test_touch.rs | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 7e04beced..52b0c1f51 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -203,6 +203,44 @@ fn test_touch_set_only_mtime_failed() { ucmd.args(&["-t", "2015010112342", "-m", file]).fails(); } +#[test] +fn test_touch_set_both_time_and_reference() { + let (at, mut ucmd) = at_and_ucmd!(); + let ref_file = "test_touch_reference"; + let file = "test_touch_set_both_time_and_reference"; + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + + at.touch(ref_file); + set_file_times(&at, ref_file, start_of_year, start_of_year); + assert!(at.file_exists(ref_file)); + + ucmd.args(&["-t", "2015010112342", "-r", ref_file]).fails(); +} + +#[test] +fn test_touch_set_both_date_and_reference() { + let (at, mut ucmd) = at_and_ucmd!(); + let ref_file = "test_touch_reference"; + let file = "test_touch_set_both_date_and_reference"; + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + + at.touch(ref_file); + set_file_times(&at, ref_file, start_of_year, start_of_year); + assert!(at.file_exists(ref_file)); + + ucmd.args(&["-d", "Thu Jan 01 12:34:00 2015", "-r", ref_file]).fails(); +} + +#[test] +fn test_touch_set_both_time_and_date() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_both_time_and_date"; + + ucmd.args(&["-t", "2015010112342", "-d", "Thu Jan 01 12:34:00 2015", file]).fails(); +} + #[test] fn test_touch_set_only_mtime() { let (at, mut ucmd) = at_and_ucmd!(); From 86422a70d2b1c3b737175ddec4189d5568b81536 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 13 Mar 2021 12:47:20 +0100 Subject: [PATCH 027/104] touch: turn macros into functions --- src/uu/touch/src/touch.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 1cb551fd2..e362329b3 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -36,23 +36,15 @@ pub mod options { static ARG_FILES: &str = "files"; -// Since touch's date/timestamp parsing doesn't account for timezone, the -// returned value from time::strptime() is UTC. We get system's timezone to -// localize the time. -macro_rules! to_local( - ($exp:expr) => ({ - let mut tm = $exp; - tm.tm_utcoff = time::now().tm_utcoff; - tm - }) -); +fn to_local(mut tm: time::Tm) -> time::Tm { + tm.tm_utcoff = time::now().tm_utcoff; + tm +} -macro_rules! local_tm_to_filetime( - ($exp:expr) => ({ - let ts = $exp.to_timespec(); - FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) - }) -); +fn local_tm_to_filetime(tm: time::Tm) -> FileTime { + let ts = tm.to_timespec(); + FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) +} fn get_usage() -> String { format!("{0} [OPTION]... [USER]", executable!()) @@ -161,7 +153,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; (timestamp, timestamp) } else { - let now = local_tm_to_filetime!(time::now()); + let now = local_tm_to_filetime(time::now()); (now, now) }; @@ -249,7 +241,7 @@ fn parse_date(str: &str) -> FileTime { // not about to implement GNU parse_datetime. // http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y match time::strptime(str, "%c") { - Ok(tm) => local_tm_to_filetime!(to_local!(tm)), + Ok(tm) => local_tm_to_filetime(to_local(tm)), Err(e) => panic!("Unable to parse date\n{}", e), } } @@ -267,7 +259,7 @@ fn parse_timestamp(s: &str) -> FileTime { }; match time::strptime(&ts, format) { - Ok(tm) => local_tm_to_filetime!(to_local!(tm)), + Ok(tm) => local_tm_to_filetime(to_local(tm)), Err(e) => panic!("Unable to parse timestamp\n{}", e), } } From ed2787a6dfee224a2e9f8df03f6d8101f0afece7 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 13 Mar 2021 13:25:36 +0100 Subject: [PATCH 028/104] test/touch: fmt --- tests/by-util/test_touch.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 52b0c1f51..9921c16b5 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -215,7 +215,8 @@ fn test_touch_set_both_time_and_reference() { set_file_times(&at, ref_file, start_of_year, start_of_year); assert!(at.file_exists(ref_file)); - ucmd.args(&["-t", "2015010112342", "-r", ref_file]).fails(); + ucmd.args(&["-t", "2015010112342", "-r", ref_file, file]) + .fails(); } #[test] @@ -230,15 +231,23 @@ fn test_touch_set_both_date_and_reference() { set_file_times(&at, ref_file, start_of_year, start_of_year); assert!(at.file_exists(ref_file)); - ucmd.args(&["-d", "Thu Jan 01 12:34:00 2015", "-r", ref_file]).fails(); + ucmd.args(&["-d", "Thu Jan 01 12:34:00 2015", "-r", ref_file, file]) + .fails(); } #[test] fn test_touch_set_both_time_and_date() { - let (at, mut ucmd) = at_and_ucmd!(); + let (_at, mut ucmd) = at_and_ucmd!(); let file = "test_touch_set_both_time_and_date"; - ucmd.args(&["-t", "2015010112342", "-d", "Thu Jan 01 12:34:00 2015", file]).fails(); + ucmd.args(&[ + "-t", + "2015010112342", + "-d", + "Thu Jan 01 12:34:00 2015", + file, + ]) + .fails(); } #[test] From 44c390c29095b6f1498402877608556e5c7d1e13 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 13 Mar 2021 13:52:08 +0100 Subject: [PATCH 029/104] touch: constant for the sources ArgGroup --- src/uu/touch/src/touch.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index e362329b3..39405900e 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -22,6 +22,8 @@ use std::path::Path; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Update the access and modification times of each FILE to the current time."; pub mod options { + // Both SOURCES and sources are needed as we need to be able to refer to the ArgGroup. + pub static SOURCES: &str = "sources"; pub mod sources { pub static DATE: &str = "date"; pub static REFERENCE: &str = "reference"; @@ -121,7 +123,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .min_values(1), ) - .group(ArgGroup::with_name("sources").args(&[ + .group(ArgGroup::with_name(options::SOURCES).args(&[ options::sources::CURRENT, options::sources::DATE, options::sources::REFERENCE, @@ -172,7 +174,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; // Minor optimization: if no reference time was specified, we're done. - if !matches.is_present("sources") { + if !matches.is_present(options::SOURCES) { continue; } } From 9e98d24f5f370d28ad83e243e221edeb650e14bb Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 13 Mar 2021 23:43:36 +0100 Subject: [PATCH 030/104] ls: move from getopts to clap --- src/uu/ls/Cargo.toml | 2 +- src/uu/ls/src/ls.rs | 376 ++++++++++++++++++++++++++----------------- 2 files changed, 228 insertions(+), 150 deletions(-) diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 10d7cc2da..59901f807 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/ls.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" lazy_static = "1.0.1" number_prefix = "0.4" term_grid = "0.1.5" diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index b8defa397..ec17ec2b3 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -13,6 +13,7 @@ extern crate lazy_static; #[macro_use] extern crate uucore; +use clap::{App, Arg}; use number_prefix::NumberPrefix; use std::cmp::Reverse; #[cfg(unix)] @@ -33,14 +34,20 @@ use unicode_width::UnicodeWidthStr; #[cfg(unix)] use uucore::libc::{mode_t, S_ISGID, S_ISUID, S_ISVTX, S_IWOTH, S_IXGRP, S_IXOTH, S_IXUSR}; -static NAME: &str = "ls"; -static SUMMARY: &str = ""; -static LONG_HELP: &str = " +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = " By default, ls will list the files and contents of any directories on the command line, expect that it will ignore files and directories whose names start with '.' "; +fn get_usage() -> String { + format!( + "{0} [OPTION]... [FILE]...", + executable!() + ) +} + #[cfg(unix)] static DEFAULT_COLORS: &str = "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:"; @@ -52,7 +59,7 @@ lazy_static! { let codes = LS_COLORS.split(':'); let mut map = HashMap::new(); for c in codes { - let p: Vec<_> = c.split('=').collect(); + let p: Vec<_> = c.splitn(1, '=').collect(); if p.len() == 2 { map.insert(p[0], p[1]); } @@ -65,107 +72,175 @@ lazy_static! { static ref END_CODE: &'static str = COLOR_MAP.get("ec").unwrap_or(&""); } +pub mod options { + pub static ONE: &str = "1"; + pub static ALL: &str = "all"; + pub static ALMOST_ALL: &str = "almost-all"; + pub static IGNORE_BACKUPS: &str = "ignore-backups"; + pub static COLUMNS: &str = "c"; + pub static DIRECTORY: &str = "directory"; + pub static CLASSIFY: &str = "classify"; + pub static HUMAN_READABLE: &str = "human-readable"; + pub static INODE: &str = "inode"; + pub static DEREFERENCE: &str = "dereference"; + pub static LONG: &str = "long"; + pub static NUMERIC_UID_GID: &str = "numeric-uid-gid"; + pub static REVERSE: &str = "reverse"; + pub static RECURSIVE: &str = "recursive"; + pub static SORT_SIZE: &str = "S"; + pub static SORT_TIME: &str = "t"; + pub static SORT_NONE: &str = "U"; + pub static COLOR: &str = "color"; + pub static PATHS: &str = "paths"; +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let syntax = format!( - "[OPTION]... DIRECTORY - {0} [OPTION]... [FILE]...", - NAME - ); - let matches = app!(&syntax, SUMMARY, LONG_HELP) - .optflag("1", "", "list one file per line.") - .optflag( - "a", - "all", - "Do not ignore hidden files (files with names that start with '.').", + let usage = get_usage(); + + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .arg( + Arg::with_name(options::ONE) + .short(options::ONE) + .help("list one file per line."), ) - .optflag( - "A", - "almost-all", - "In a directory, do not ignore all file names that start with '.', only ignore \ - '.' and '..'.", + .arg( + Arg::with_name(options::ALL) + .short("a") + .long(options::ALL) + .help("Do not ignore hidden files (files with names that start with '.')."), ) - .optflag("B", "ignore-backups", "Ignore entries which end with ~.") - .optflag( - "c", - "", - "If the long listing format (e.g., -l, -o) is being used, print the status \ - change time (the ‘ctime’ in the inode) instead of the modification time. When \ - explicitly sorting by time (--sort=time or -t) or when not using a long listing \ - format, sort according to the status change time.", + .arg( + Arg::with_name(options::ALMOST_ALL) + .short("A") + .long(options::ALMOST_ALL) + .help( + "In a directory, do not ignore all file names that start with '.', only ignore \ + '.' and '..'.", + ), ) - .optflag( - "d", - "directory", - "Only list the names of directories, rather than listing directory contents. \ - This will not follow symbolic links unless one of `--dereference-command-line \ - (-H)`, `--dereference (-L)`, or `--dereference-command-line-symlink-to-dir` is \ - specified.", + .arg( + Arg::with_name(options::IGNORE_BACKUPS) + .short("B") + .long(options::IGNORE_BACKUPS) + .help("Ignore entries which end with ~."), ) - .optflag( - "F", - "classify", - "Append a character to each file name indicating the file type. Also, for \ - regular files that are executable, append '*'. The file type indicators are \ - '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ - '>' for doors, and nothing for regular files.", + .arg( + Arg::with_name(options::COLUMNS) + .short(options::COLUMNS) + .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + change time (the ‘ctime’ in the inode) instead of the modification time. When \ + explicitly sorting by time (--sort=time or -t) or when not using a long listing \ + format, sort according to the status change time.", + )) + .arg( + Arg::with_name(options::DIRECTORY) + .short("d") + .long(options::DIRECTORY) + .help( + "Only list the names of directories, rather than listing directory contents. \ + This will not follow symbolic links unless one of `--dereference-command-line \ + (-H)`, `--dereference (-L)`, or `--dereference-command-line-symlink-to-dir` is \ + specified.", + ), ) - .optflag( - "h", - "human-readable", - "Print human readable file sizes (e.g. 1K 234M 56G).", + .arg( + Arg::with_name(options::CLASSIFY) + .short("F") + .long(options::CLASSIFY) + .help("Append a character to each file name indicating the file type. Also, for \ + regular files that are executable, append '*'. The file type indicators are \ + '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ + '>' for doors, and nothing for regular files.", + )) + .arg( + Arg::with_name(options::HUMAN_READABLE) + .short("h") + .long(options::HUMAN_READABLE) + .help("Print human readable file sizes (e.g. 1K 234M 56G)."), ) - .optflag("i", "inode", "print the index number of each file") - .optflag( - "L", - "dereference", - "When showing file information for a symbolic link, show information for the \ - file the link references rather than the link itself.", + .arg( + Arg::with_name(options::INODE) + .short("i") + .long(options::INODE) + .help("print the index number of each file"), ) - .optflag("l", "long", "Display detailed information.") - .optflag("n", "numeric-uid-gid", "-l with numeric UIDs and GIDs.") - .optflag( - "r", - "reverse", - "Reverse whatever the sorting method is--e.g., list files in reverse \ - alphabetical order, youngest first, smallest first, or whatever.", + .arg( + Arg::with_name(options::DEREFERENCE) + .short("L") + .long(options::DEREFERENCE) + .help( + "When showing file information for a symbolic link, show information for the \ + file the link references rather than the link itself.", + ), ) - .optflag( - "R", - "recursive", - "List the contents of all directories recursively.", + .arg( + Arg::with_name(options::LONG) + .short("l") + .long(options::LONG) + .help("Display detailed information."), ) - .optflag("S", "", "Sort by file size, largest first.") - .optflag( - "t", - "", - "Sort by modification time (the 'mtime' in the inode), newest first.", + .arg( + Arg::with_name(options::NUMERIC_UID_GID) + .short("n") + .long(options::NUMERIC_UID_GID) + .help("-l with numeric UIDs and GIDs."), ) - .optflag( - "U", - "", - "Do not sort; list the files in whatever order they are stored in the \ - directory. This is especially useful when listing very large directories, \ - since not doing any sorting can be noticeably faster.", + .arg( + Arg::with_name(options::REVERSE) + .short("r") + .long(options::REVERSE) + .help("Reverse whatever the sorting method is--e.g., list files in reverse \ + alphabetical order, youngest first, smallest first, or whatever.", + )) + .arg( + Arg::with_name(options::RECURSIVE) + .short("R") + .long(options::RECURSIVE) + .help("List the contents of all directories recursively."), ) - .optflagopt( - "", - "color", - "Color output based on file type.", - "always|auto|never", + .arg( + Arg::with_name(options::SORT_SIZE) + .short(options::SORT_SIZE) + .help("Sort by file size, largest first."), ) - .parse(args); + .arg( + Arg::with_name(options::SORT_TIME) + .short(options::SORT_TIME) + .help("Sort by modification time (the 'mtime' in the inode), newest first."), + ) + .arg( + Arg::with_name(options::SORT_NONE) + .short(options::SORT_NONE) + .help("Do not sort; list the files in whatever order they are stored in the \ + directory. This is especially useful when listing very large directories, \ + since not doing any sorting can be noticeably faster.", + )) + .arg( + Arg::with_name(options::COLOR) + .long(options::COLOR) + .help("Color output based on file type.") + .possible_values(&["always", "yes", "force", "tty", "if-tty", "auto", "never", "no", "none"]) + .require_equals(true) + .empty_values(true), + ) + .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)) + .get_matches_from(args); list(matches) } -fn list(options: getopts::Matches) -> i32 { - let locs: Vec = if options.free.is_empty() { - vec![String::from(".")] - } else { - options.free.to_vec() - }; +fn list(options: clap::ArgMatches) -> i32 { + let locs: Vec = options + .values_of(options::PATHS) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_else(|| vec![String::from(".")]); + + let number_of_locs = locs.len(); let mut files = Vec::::new(); let mut dirs = Vec::::new(); @@ -181,9 +256,9 @@ fn list(options: getopts::Matches) -> i32 { } let mut dir = false; - if p.is_dir() && !options.opt_present("d") { + if p.is_dir() && !options.is_present(options::DIRECTORY) { dir = true; - if options.opt_present("l") && !(options.opt_present("L")) { + if options.is_present(options::LONG) && !options.is_present(options::DEREFERENCE) { if let Ok(md) = p.symlink_metadata() { if md.file_type().is_symlink() && !p.ends_with("/") { dir = false; @@ -202,7 +277,7 @@ fn list(options: getopts::Matches) -> i32 { sort_entries(&mut dirs, &options); for dir in dirs { - if options.free.len() > 1 { + if number_of_locs > 1 { println!("\n{}:", dir.to_string_lossy()); } enter_directory(&dir, &options); @@ -215,10 +290,10 @@ fn list(options: getopts::Matches) -> i32 { } #[cfg(any(unix, target_os = "redox"))] -fn sort_entries(entries: &mut Vec, options: &getopts::Matches) { - let mut reverse = options.opt_present("r"); - if options.opt_present("t") { - if options.opt_present("c") { +fn sort_entries(entries: &mut Vec, options: &clap::ArgMatches) { + let mut reverse = options.is_present(options::REVERSE); + if options.is_present(options::SORT_TIME) { + if options.is_present(options::COLUMNS) { entries.sort_by_key(|k| { Reverse(get_metadata(k, options).map(|md| md.ctime()).unwrap_or(0)) }); @@ -232,10 +307,10 @@ fn sort_entries(entries: &mut Vec, options: &getopts::Matches) { ) }); } - } else if options.opt_present("S") { + } else if options.is_present(options::SORT_SIZE) { entries.sort_by_key(|k| get_metadata(k, options).map(|md| md.size()).unwrap_or(0)); reverse = !reverse; - } else if !options.opt_present("U") { + } else if !options.is_present(options::SORT_NONE) { entries.sort(); } @@ -257,9 +332,9 @@ fn is_hidden(file_path: &DirEntry) -> std::io::Result { } #[cfg(windows)] -fn sort_entries(entries: &mut Vec, options: &getopts::Matches) { - let mut reverse = options.opt_present("r"); - if options.opt_present("t") { +fn sort_entries(entries: &mut Vec, options: &clap::ArgMatches) { + let mut reverse = options.is_present(options::REVERSE); + if options.is_present(options::SORT_TIME) { entries.sort_by_key(|k| { // Newest first Reverse( @@ -268,14 +343,14 @@ fn sort_entries(entries: &mut Vec, options: &getopts::Matches) { .unwrap_or(std::time::UNIX_EPOCH), ) }); - } else if options.opt_present("S") { + } else if options.is_present(options::SORT_SIZE) { entries.sort_by_key(|k| { get_metadata(k, options) .map(|md| md.file_size()) .unwrap_or(0) }); reverse = !reverse; - } else if !options.opt_present("U") { + } else if !options.is_present(options::SORT_NONE) { entries.sort(); } @@ -284,19 +359,22 @@ fn sort_entries(entries: &mut Vec, options: &getopts::Matches) { } } -fn should_display(entry: &DirEntry, options: &getopts::Matches) -> bool { +fn should_display(entry: &DirEntry, options: &clap::ArgMatches) -> bool { let ffi_name = entry.file_name(); let name = ffi_name.to_string_lossy(); - if !options.opt_present("a") && !options.opt_present("A") && is_hidden(entry).unwrap() { + if !options.is_present(options::ALL) + && !options.is_present(options::ALMOST_ALL) + && is_hidden(entry).unwrap() + { return false; } - if options.opt_present("B") && name.ends_with('~') { + if options.is_present(options::IGNORE_BACKUPS) && name.ends_with('~') { return false; } true } -fn enter_directory(dir: &PathBuf, options: &getopts::Matches) { +fn enter_directory(dir: &PathBuf, options: &clap::ArgMatches) { let mut entries: Vec<_> = safe_unwrap!(fs::read_dir(dir).and_then(Iterator::collect)); entries.retain(|e| should_display(e, options)); @@ -304,7 +382,7 @@ fn enter_directory(dir: &PathBuf, options: &getopts::Matches) { let mut entries: Vec<_> = entries.iter().map(DirEntry::path).collect(); sort_entries(&mut entries, options); - if options.opt_present("a") { + if options.is_present(options::ALL) { let mut display_entries = entries.clone(); display_entries.insert(0, dir.join("..")); display_entries.insert(0, dir.join(".")); @@ -313,7 +391,7 @@ fn enter_directory(dir: &PathBuf, options: &getopts::Matches) { display_items(&entries, Some(dir), options); } - if options.opt_present("R") { + if options.is_present(options::RECURSIVE) { for e in entries.iter().filter(|p| p.is_dir()) { println!("\n{}:", e.to_string_lossy()); enter_directory(&e, options); @@ -321,15 +399,15 @@ fn enter_directory(dir: &PathBuf, options: &getopts::Matches) { } } -fn get_metadata(entry: &PathBuf, options: &getopts::Matches) -> std::io::Result { - if options.opt_present("L") { +fn get_metadata(entry: &PathBuf, options: &clap::ArgMatches) -> std::io::Result { + if options.is_present(options::DEREFERENCE) { entry.metadata().or_else(|_| entry.symlink_metadata()) } else { entry.symlink_metadata() } } -fn display_dir_entry_size(entry: &PathBuf, options: &getopts::Matches) -> (usize, usize) { +fn display_dir_entry_size(entry: &PathBuf, options: &clap::ArgMatches) -> (usize, usize) { if let Ok(md) = get_metadata(entry, options) { ( display_symlink_count(&md).len(), @@ -341,17 +419,11 @@ fn display_dir_entry_size(entry: &PathBuf, options: &getopts::Matches) -> (usize } fn pad_left(string: String, count: usize) -> String { - if count > string.len() { - let pad = count - string.len(); - let pad = String::from_utf8(vec![b' '; pad]).unwrap(); - format!("{}{}", pad, string) - } else { - string - } + format!("{:>width$}", string, width = count) } -fn display_items(items: &[PathBuf], strip: Option<&Path>, options: &getopts::Matches) { - if options.opt_present("long") || options.opt_present("numeric-uid-gid") { +fn display_items(items: &[PathBuf], strip: Option<&Path>, options: &clap::ArgMatches) { + if options.is_present(options::LONG) || options.is_present(options::NUMERIC_UID_GID) { let (mut max_links, mut max_size) = (1, 1); for item in items { let (links, size) = display_dir_entry_size(item, options); @@ -362,7 +434,7 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, options: &getopts::Mat display_item_long(item, strip, max_links, max_size, options); } } else { - if !options.opt_present("1") { + if !options.is_present(options::ONE) { let names = items.iter().filter_map(|i| { let md = get_metadata(i, options); match md { @@ -410,7 +482,7 @@ fn display_item_long( strip: Option<&Path>, max_links: usize, max_size: usize, - options: &getopts::Matches, + options: &clap::ArgMatches, ) { let md = match get_metadata(item, options) { Err(e) => { @@ -436,8 +508,8 @@ fn display_item_long( } #[cfg(unix)] -fn get_inode(metadata: &Metadata, options: &getopts::Matches) -> String { - if options.opt_present("inode") { +fn get_inode(metadata: &Metadata, options: &clap::ArgMatches) -> String { + if options.is_present(options::INODE) { format!("{:8} ", metadata.ino()) } else { "".to_string() @@ -445,7 +517,7 @@ fn get_inode(metadata: &Metadata, options: &getopts::Matches) -> String { } #[cfg(not(unix))] -fn get_inode(_metadata: &Metadata, _options: &getopts::Matches) -> String { +fn get_inode(_metadata: &Metadata, _options: &clap::ArgMatches) -> String { "".to_string() } @@ -455,8 +527,8 @@ fn get_inode(_metadata: &Metadata, _options: &getopts::Matches) -> String { use uucore::entries; #[cfg(unix)] -fn display_uname(metadata: &Metadata, options: &getopts::Matches) -> String { - if options.opt_present("numeric-uid-gid") { +fn display_uname(metadata: &Metadata, options: &clap::ArgMatches) -> String { + if options.is_present(options::NUMERIC_UID_GID) { metadata.uid().to_string() } else { entries::uid2usr(metadata.uid()).unwrap_or_else(|_| metadata.uid().to_string()) @@ -464,8 +536,8 @@ fn display_uname(metadata: &Metadata, options: &getopts::Matches) -> String { } #[cfg(unix)] -fn display_group(metadata: &Metadata, options: &getopts::Matches) -> String { - if options.opt_present("numeric-uid-gid") { +fn display_group(metadata: &Metadata, options: &clap::ArgMatches) -> String { + if options.is_present(options::NUMERIC_UID_GID) { metadata.gid().to_string() } else { entries::gid2grp(metadata.gid()).unwrap_or_else(|_| metadata.gid().to_string()) @@ -474,19 +546,19 @@ fn display_group(metadata: &Metadata, options: &getopts::Matches) -> String { #[cfg(not(unix))] #[allow(unused_variables)] -fn display_uname(metadata: &Metadata, _options: &getopts::Matches) -> String { +fn display_uname(metadata: &Metadata, _options: &clap::ArgMatches) -> String { "somebody".to_string() } #[cfg(not(unix))] #[allow(unused_variables)] -fn display_group(metadata: &Metadata, _options: &getopts::Matches) -> String { +fn display_group(metadata: &Metadata, _options: &clap::ArgMatches) -> String { "somegroup".to_string() } #[cfg(unix)] -fn display_date(metadata: &Metadata, options: &getopts::Matches) -> String { - let secs = if options.opt_present("c") { +fn display_date(metadata: &Metadata, options: &clap::ArgMatches) -> String { + let secs = if options.is_present(options::COLUMNS) { metadata.ctime() } else { metadata.mtime() @@ -497,7 +569,7 @@ fn display_date(metadata: &Metadata, options: &getopts::Matches) -> String { #[cfg(not(unix))] #[allow(unused_variables)] -fn display_date(metadata: &Metadata, options: &getopts::Matches) -> String { +fn display_date(metadata: &Metadata, options: &clap::ArgMatches) -> String { if let Ok(mtime) = metadata.modified() { let time = time::at(Timespec::new( mtime @@ -512,8 +584,10 @@ fn display_date(metadata: &Metadata, options: &getopts::Matches) -> String { } } -fn display_file_size(metadata: &Metadata, options: &getopts::Matches) -> String { - if options.opt_present("human-readable") { +fn display_file_size(metadata: &Metadata, options: &clap::ArgMatches) -> String { + // NOTE: The human-readable behaviour deviates from the GNU ls. + // The GNU ls uses binary prefixes by default. + if options.is_present(options::HUMAN_READABLE) { match NumberPrefix::decimal(metadata.len() as f64) { NumberPrefix::Standalone(bytes) => bytes.to_string(), NumberPrefix::Prefixed(prefix, bytes) => { @@ -551,15 +625,15 @@ fn display_file_name( path: &Path, strip: Option<&Path>, metadata: &Metadata, - options: &getopts::Matches, + options: &clap::ArgMatches, ) -> Cell { let mut name = get_file_name(path, strip); - if !options.opt_present("long") { + if !options.is_present(options::LONG) { name = get_inode(metadata, options) + &name; } - if options.opt_present("classify") { + if options.is_present(options::CLASSIFY) { let file_type = metadata.file_type(); if file_type.is_dir() { name.push('/'); @@ -568,7 +642,7 @@ fn display_file_name( } } - if options.opt_present("long") && metadata.file_type().is_symlink() { + if options.is_present(options::LONG) && metadata.file_type().is_symlink() { if let Ok(target) = path.read_link() { // We don't bother updating width here because it's not used for long listings let target_name = target.to_string_lossy().to_string(); @@ -613,23 +687,27 @@ fn display_file_name( path: &Path, strip: Option<&Path>, metadata: &Metadata, - options: &getopts::Matches, + options: &clap::ArgMatches, ) -> Cell { let mut name = get_file_name(path, strip); - if !options.opt_present("long") { + if !options.is_present(options::LONG) { name = get_inode(metadata, options) + &name; } let mut width = UnicodeWidthStr::width(&*name); - let color = match options.opt_str("color") { - None => atty::is(atty::Stream::Stdout), - Some(val) => match val.as_ref() { - "always" | "yes" | "force" => true, - "auto" | "tty" | "if-tty" => atty::is(atty::Stream::Stdout), - /* "never" | "no" | "none" | */ _ => false, - }, + let color = if options.is_present(options::COLOR) { + match options.value_of(options::COLOR) { + None => atty::is(atty::Stream::Stdout), + Some(val) => match val { + "" | "always" | "yes" | "force" => true, + "auto" | "tty" | "if-tty" => atty::is(atty::Stream::Stdout), + /* "never" | "no" | "none" | */ _ => false, + }, + } + } else { + false }; - let classify = options.opt_present("classify"); + let classify = options.is_present(options::CLASSIFY); let ext; if color || classify { @@ -693,7 +771,7 @@ fn display_file_name( } } - if options.opt_present("long") && metadata.file_type().is_symlink() { + if options.is_present(options::LONG) && metadata.file_type().is_symlink() { if let Ok(target) = path.read_link() { // We don't bother updating width here because it's not used for long listings let code = if target.exists() { "fi" } else { "mi" }; From 7c8e8b2d4cce76b7c3bdddd11a1dcc4b683d6e4e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 14 Mar 2021 12:22:32 +0100 Subject: [PATCH 031/104] ls: refactor arguments into a config struct --- src/uu/ls/src/ls.rs | 419 ++++++++++++++++++++++++++------------------ 1 file changed, 253 insertions(+), 166 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index ec17ec2b3..d61a45702 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -42,10 +42,7 @@ static ABOUT: &str = " "; fn get_usage() -> String { - format!( - "{0} [OPTION]... [FILE]...", - executable!() - ) + format!("{0} [OPTION]... [FILE]...", executable!()) } #[cfg(unix)] @@ -73,11 +70,10 @@ lazy_static! { } pub mod options { - pub static ONE: &str = "1"; + pub static ONELINE: &str = "1"; pub static ALL: &str = "all"; pub static ALMOST_ALL: &str = "almost-all"; pub static IGNORE_BACKUPS: &str = "ignore-backups"; - pub static COLUMNS: &str = "c"; pub static DIRECTORY: &str = "directory"; pub static CLASSIFY: &str = "classify"; pub static HUMAN_READABLE: &str = "human-readable"; @@ -90,10 +86,128 @@ pub mod options { pub static SORT_SIZE: &str = "S"; pub static SORT_TIME: &str = "t"; pub static SORT_NONE: &str = "U"; + pub static SORT_CTIME: &str = "c"; pub static COLOR: &str = "color"; pub static PATHS: &str = "paths"; } +#[derive(PartialEq, Eq)] +enum DisplayOptions { + Columns, + Long, + OneLine, +} + +enum Sort { + None, + Name, + Size, + Time, + CTime, +} + +enum SizeFormats { + Bytes, + Binary, // Powers of 1024, --human-readable +} + +#[derive(PartialEq, Eq)] +enum Files { + All, + AlmostAll, + Normal, +} + +struct Config { + display: DisplayOptions, + files: Files, + sort: Sort, + + recursive: bool, + reverse: bool, + dereference: bool, + classify: bool, + ignore_backups: bool, + size_format: SizeFormats, + numeric_uid_gid: bool, + directory: bool, + #[cfg(unix)] + inode: bool, + #[cfg(unix)] + color: bool, +} + +impl Config { + fn from(options: clap::ArgMatches) -> Config { + let display = if options.is_present(options::LONG) { + DisplayOptions::Long + } else if options.is_present(options::ONELINE) { + DisplayOptions::OneLine + } else { + DisplayOptions::Columns + }; + + let files = if options.is_present(options::ALL) { + Files::All + } else if options.is_present(options::ALMOST_ALL) { + Files::AlmostAll + } else { + Files::Normal + }; + + let sort = if options.is_present(options::SORT_TIME) { + Sort::Time + } else if options.is_present(options::SORT_CTIME) { + Sort::CTime + } else if options.is_present(options::SORT_SIZE) { + Sort::Size + } else if options.is_present(options::SORT_NONE) { + Sort::None + } else { + Sort::Name + }; + + #[cfg(unix)] + let color = if options.is_present(options::COLOR) { + match options.value_of(options::COLOR) { + None => atty::is(atty::Stream::Stdout), + Some(val) => match val { + "" | "always" | "yes" | "force" => true, + "auto" | "tty" | "if-tty" => atty::is(atty::Stream::Stdout), + /* "never" | "no" | "none" | */ _ => false, + }, + } + } else { + false + }; + + let size_format = if options.is_present(options::HUMAN_READABLE) { + SizeFormats::Binary + } else { + SizeFormats::Bytes + }; + + Config { + display, + files, + sort, + + recursive: options.is_present(options::RECURSIVE), + reverse: options.is_present(options::REVERSE), + dereference: options.is_present(options::DEREFERENCE), + classify: options.is_present(options::CLASSIFY), + ignore_backups: options.is_present(options::IGNORE_BACKUPS), + size_format, + numeric_uid_gid: options.is_present(options::NUMERIC_UID_GID), + directory: options.is_present(options::DIRECTORY), + #[cfg(unix)] + color, + #[cfg(unix)] + inode: options.is_present(options::INODE), + } + } +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); @@ -104,8 +218,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .about(ABOUT) .usage(&usage[..]) .arg( - Arg::with_name(options::ONE) - .short(options::ONE) + Arg::with_name(options ::ONELINE) + .short(options ::ONELINE) .help("list one file per line."), ) .arg( @@ -130,8 +244,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("Ignore entries which end with ~."), ) .arg( - Arg::with_name(options::COLUMNS) - .short(options::COLUMNS) + Arg::with_name(options::SORT_CTIME) + .short(options::SORT_CTIME) .help("If the long listing format (e.g., -l, -o) is being used, print the status \ change time (the ‘ctime’ in the inode) instead of the modification time. When \ explicitly sorting by time (--sort=time or -t) or when not using a long listing \ @@ -231,15 +345,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)) .get_matches_from(args); - list(matches) -} - -fn list(options: clap::ArgMatches) -> i32 { - let locs: Vec = options + let locs = matches .values_of(options::PATHS) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_else(|| vec![String::from(".")]); + list(locs, Config::from(matches)) +} + +fn list(locs: Vec, config: Config) -> i32 { let number_of_locs = locs.len(); let mut files = Vec::::new(); @@ -256,9 +370,9 @@ fn list(options: clap::ArgMatches) -> i32 { } let mut dir = false; - if p.is_dir() && !options.is_present(options::DIRECTORY) { + if p.is_dir() && !config.directory { dir = true; - if options.is_present(options::LONG) && !options.is_present(options::DEREFERENCE) { + if config.display == DisplayOptions::Long && !config.dereference { if let Ok(md) = p.symlink_metadata() { if md.file_type().is_symlink() && !p.ends_with("/") { dir = false; @@ -272,15 +386,15 @@ fn list(options: clap::ArgMatches) -> i32 { files.push(p); } } - sort_entries(&mut files, &options); - display_items(&files, None, &options); + sort_entries(&mut files, &config); + display_items(&files, None, &config); - sort_entries(&mut dirs, &options); + sort_entries(&mut dirs, &config); for dir in dirs { if number_of_locs > 1 { println!("\n{}:", dir.to_string_lossy()); } - enter_directory(&dir, &options); + enter_directory(&dir, &config); } if has_failed { 1 @@ -290,128 +404,118 @@ fn list(options: clap::ArgMatches) -> i32 { } #[cfg(any(unix, target_os = "redox"))] -fn sort_entries(entries: &mut Vec, options: &clap::ArgMatches) { - let mut reverse = options.is_present(options::REVERSE); - if options.is_present(options::SORT_TIME) { - if options.is_present(options::COLUMNS) { - entries.sort_by_key(|k| { - Reverse(get_metadata(k, options).map(|md| md.ctime()).unwrap_or(0)) - }); - } else { - entries.sort_by_key(|k| { - // Newest first - Reverse( - get_metadata(k, options) - .and_then(|md| md.modified()) - .unwrap_or(std::time::UNIX_EPOCH), - ) - }); - } - } else if options.is_present(options::SORT_SIZE) { - entries.sort_by_key(|k| get_metadata(k, options).map(|md| md.size()).unwrap_or(0)); - reverse = !reverse; - } else if !options.is_present(options::SORT_NONE) { - entries.sort(); - } - - if reverse { - entries.reverse(); - } -} - -#[cfg(windows)] -fn is_hidden(file_path: &DirEntry) -> std::io::Result { - let metadata = fs::metadata(file_path.path())?; - let attr = metadata.file_attributes(); - Ok(((attr & 0x2) > 0) || file_path.file_name().to_string_lossy().starts_with('.')) -} - -#[cfg(unix)] -fn is_hidden(file_path: &DirEntry) -> std::io::Result { - Ok(file_path.file_name().to_string_lossy().starts_with('.')) -} - -#[cfg(windows)] -fn sort_entries(entries: &mut Vec, options: &clap::ArgMatches) { - let mut reverse = options.is_present(options::REVERSE); - if options.is_present(options::SORT_TIME) { - entries.sort_by_key(|k| { - // Newest first +fn sort_entries(entries: &mut Vec, config: &Config) { + match config.sort { + Sort::CTime => entries + .sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.ctime()).unwrap_or(0))), + Sort::Time => entries.sort_by_key(|k| { Reverse( - get_metadata(k, options) + get_metadata(k, config) .and_then(|md| md.modified()) .unwrap_or(std::time::UNIX_EPOCH), ) - }); - } else if options.is_present(options::SORT_SIZE) { - entries.sort_by_key(|k| { - get_metadata(k, options) - .map(|md| md.file_size()) - .unwrap_or(0) - }); - reverse = !reverse; - } else if !options.is_present(options::SORT_NONE) { - entries.sort(); + }), + Sort::Size => entries + .sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.size()).unwrap_or(0))), + Sort::Name => entries.sort(), + Sort::None => {} } - if reverse { + if config.reverse { entries.reverse(); } } -fn should_display(entry: &DirEntry, options: &clap::ArgMatches) -> bool { +#[cfg(windows)] +fn is_hidden(file_path: &DirEntry) -> bool { + let metadata = fs::metadata(file_path.path()).unwrap(); + let attr = metadata.file_attributes(); + ((attr & 0x2) > 0) || file_path.file_name().to_string_lossy().starts_with('.') +} + +#[cfg(unix)] +fn is_hidden(file_path: &DirEntry) -> bool { + file_path.file_name().to_string_lossy().starts_with('.') +} + +#[cfg(windows)] +fn sort_entries(entries: &mut Vec, config: &Config) { + match config.sort { + Sort::CTime | Sort::Time => entries.sort_by_key(|k| { + // Newest first + Reverse( + get_metadata(k, config) + .and_then(|md| md.modified()) + .unwrap_or(std::time::UNIX_EPOCH), + ) + }), + Sort::Size => entries.sort_by_key(|k| { + // Largest first + Reverse( + get_metadata(k, config) + .map(|md| md.file_size()) + .unwrap_or(0), + ) + }), + Sort::Name => entries.sort(), + Sort::None => {}, + } + + if config.reverse { + entries.reverse(); + } +} + +fn should_display(entry: &DirEntry, config: &Config) -> bool { let ffi_name = entry.file_name(); let name = ffi_name.to_string_lossy(); - if !options.is_present(options::ALL) - && !options.is_present(options::ALMOST_ALL) - && is_hidden(entry).unwrap() - { + if config.files == Files::Normal && is_hidden(entry) { return false; } - if options.is_present(options::IGNORE_BACKUPS) && name.ends_with('~') { + if config.ignore_backups && name.ends_with('~') { return false; } true } -fn enter_directory(dir: &PathBuf, options: &clap::ArgMatches) { +fn enter_directory(dir: &PathBuf, config: &Config) { let mut entries: Vec<_> = safe_unwrap!(fs::read_dir(dir).and_then(Iterator::collect)); - entries.retain(|e| should_display(e, options)); + entries.retain(|e| should_display(e, config)); let mut entries: Vec<_> = entries.iter().map(DirEntry::path).collect(); - sort_entries(&mut entries, options); + sort_entries(&mut entries, config); - if options.is_present(options::ALL) { + if config.files == Files::All { let mut display_entries = entries.clone(); display_entries.insert(0, dir.join("..")); display_entries.insert(0, dir.join(".")); - display_items(&display_entries, Some(dir), options); + display_items(&display_entries, Some(dir), config); } else { - display_items(&entries, Some(dir), options); + display_items(&entries, Some(dir), config); } - if options.is_present(options::RECURSIVE) { + if config.recursive { for e in entries.iter().filter(|p| p.is_dir()) { println!("\n{}:", e.to_string_lossy()); - enter_directory(&e, options); + enter_directory(&e, config); } } } -fn get_metadata(entry: &PathBuf, options: &clap::ArgMatches) -> std::io::Result { - if options.is_present(options::DEREFERENCE) { +fn get_metadata(entry: &PathBuf, config: &Config) -> std::io::Result { + if config.dereference { entry.metadata().or_else(|_| entry.symlink_metadata()) } else { entry.symlink_metadata() } } -fn display_dir_entry_size(entry: &PathBuf, options: &clap::ArgMatches) -> (usize, usize) { - if let Ok(md) = get_metadata(entry, options) { +fn display_dir_entry_size(entry: &PathBuf, config: &Config) -> (usize, usize) { + if let Ok(md) = get_metadata(entry, config) { ( display_symlink_count(&md).len(), - display_file_size(&md, options).len(), + display_file_size(&md, config).len(), ) } else { (0, 0) @@ -422,28 +526,28 @@ fn pad_left(string: String, count: usize) -> String { format!("{:>width$}", string, width = count) } -fn display_items(items: &[PathBuf], strip: Option<&Path>, options: &clap::ArgMatches) { - if options.is_present(options::LONG) || options.is_present(options::NUMERIC_UID_GID) { +fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) { + if config.display == DisplayOptions::Long || config.numeric_uid_gid { let (mut max_links, mut max_size) = (1, 1); for item in items { - let (links, size) = display_dir_entry_size(item, options); + let (links, size) = display_dir_entry_size(item, config); max_links = links.max(max_links); max_size = size.max(max_size); } for item in items { - display_item_long(item, strip, max_links, max_size, options); + display_item_long(item, strip, max_links, max_size, config); } } else { - if !options.is_present(options::ONE) { + if config.display != DisplayOptions::OneLine { let names = items.iter().filter_map(|i| { - let md = get_metadata(i, options); + let md = get_metadata(i, config); match md { Err(e) => { let filename = get_file_name(i, strip); show_error!("'{}': {}", filename, e); None } - Ok(md) => Some(display_file_name(&i, strip, &md, options)), + Ok(md) => Some(display_file_name(&i, strip, &md, config)), } }); @@ -467,9 +571,9 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, options: &clap::ArgMat // Couldn't display a grid, either because we don't know // the terminal width or because fit_into_width failed for i in items { - let md = get_metadata(i, options); + let md = get_metadata(i, config); if let Ok(md) = md { - println!("{}", display_file_name(&i, strip, &md, options).contents); + println!("{}", display_file_name(&i, strip, &md, config).contents); } } } @@ -482,9 +586,9 @@ fn display_item_long( strip: Option<&Path>, max_links: usize, max_size: usize, - options: &clap::ArgMatches, + config: &Config, ) { - let md = match get_metadata(item, options) { + let md = match get_metadata(item, config) { Err(e) => { let filename = get_file_name(&item, strip); show_error!("{}: {}", filename, e); @@ -495,21 +599,21 @@ fn display_item_long( println!( "{}{}{} {} {} {} {} {} {}", - get_inode(&md, options), + get_inode(&md, config), display_file_type(md.file_type()), display_permissions(&md), pad_left(display_symlink_count(&md), max_links), - display_uname(&md, options), - display_group(&md, options), - pad_left(display_file_size(&md, options), max_size), - display_date(&md, options), - display_file_name(&item, strip, &md, options).contents + display_uname(&md, config), + display_group(&md, config), + pad_left(display_file_size(&md, config), max_size), + display_date(&md, config), + display_file_name(&item, strip, &md, config).contents ); } #[cfg(unix)] -fn get_inode(metadata: &Metadata, options: &clap::ArgMatches) -> String { - if options.is_present(options::INODE) { +fn get_inode(metadata: &Metadata, config: &Config) -> String { + if config.inode { format!("{:8} ", metadata.ino()) } else { "".to_string() @@ -517,7 +621,7 @@ fn get_inode(metadata: &Metadata, options: &clap::ArgMatches) -> String { } #[cfg(not(unix))] -fn get_inode(_metadata: &Metadata, _options: &clap::ArgMatches) -> String { +fn get_inode(_metadata: &Metadata, _config: &Config) -> String { "".to_string() } @@ -527,8 +631,8 @@ fn get_inode(_metadata: &Metadata, _options: &clap::ArgMatches) -> String { use uucore::entries; #[cfg(unix)] -fn display_uname(metadata: &Metadata, options: &clap::ArgMatches) -> String { - if options.is_present(options::NUMERIC_UID_GID) { +fn display_uname(metadata: &Metadata, config: &Config) -> String { + if config.numeric_uid_gid { metadata.uid().to_string() } else { entries::uid2usr(metadata.uid()).unwrap_or_else(|_| metadata.uid().to_string()) @@ -536,8 +640,8 @@ fn display_uname(metadata: &Metadata, options: &clap::ArgMatches) -> String { } #[cfg(unix)] -fn display_group(metadata: &Metadata, options: &clap::ArgMatches) -> String { - if options.is_present(options::NUMERIC_UID_GID) { +fn display_group(metadata: &Metadata, config: &Config) -> String { + if config.numeric_uid_gid { metadata.gid().to_string() } else { entries::gid2grp(metadata.gid()).unwrap_or_else(|_| metadata.gid().to_string()) @@ -545,31 +649,29 @@ fn display_group(metadata: &Metadata, options: &clap::ArgMatches) -> String { } #[cfg(not(unix))] -#[allow(unused_variables)] -fn display_uname(metadata: &Metadata, _options: &clap::ArgMatches) -> String { +fn display_uname(_metadata: &Metadata, _config: &Config) -> String { "somebody".to_string() } #[cfg(not(unix))] #[allow(unused_variables)] -fn display_group(metadata: &Metadata, _options: &clap::ArgMatches) -> String { +fn display_group(_metadata: &Metadata, _config: &Config) -> String { "somegroup".to_string() } #[cfg(unix)] -fn display_date(metadata: &Metadata, options: &clap::ArgMatches) -> String { - let secs = if options.is_present(options::COLUMNS) { - metadata.ctime() - } else { - metadata.mtime() +fn display_date(metadata: &Metadata, config: &Config) -> String { + let secs = match config.sort { + Sort::CTime => metadata.ctime(), + Sort::Time => metadata.mtime(), + _ => 0, }; let time = time::at(Timespec::new(secs, 0)); strftime("%F %R", &time).unwrap() } #[cfg(not(unix))] -#[allow(unused_variables)] -fn display_date(metadata: &Metadata, options: &clap::ArgMatches) -> String { +fn display_date(metadata: &Metadata, _config: &Config) -> String { if let Ok(mtime) = metadata.modified() { let time = time::at(Timespec::new( mtime @@ -584,18 +686,17 @@ fn display_date(metadata: &Metadata, options: &clap::ArgMatches) -> String { } } -fn display_file_size(metadata: &Metadata, options: &clap::ArgMatches) -> String { +fn display_file_size(metadata: &Metadata, config: &Config) -> String { // NOTE: The human-readable behaviour deviates from the GNU ls. // The GNU ls uses binary prefixes by default. - if options.is_present(options::HUMAN_READABLE) { - match NumberPrefix::decimal(metadata.len() as f64) { + match config.size_format { + SizeFormats::Binary => match NumberPrefix::decimal(metadata.len() as f64) { NumberPrefix::Standalone(bytes) => bytes.to_string(), NumberPrefix::Prefixed(prefix, bytes) => { format!("{:.2}{}", bytes, prefix).to_uppercase() } - } - } else { - metadata.len().to_string() + }, + SizeFormats::Bytes => metadata.len().to_string(), } } @@ -625,15 +726,15 @@ fn display_file_name( path: &Path, strip: Option<&Path>, metadata: &Metadata, - options: &clap::ArgMatches, + config: &Config, ) -> Cell { let mut name = get_file_name(path, strip); - if !options.is_present(options::LONG) { - name = get_inode(metadata, options) + &name; + if config.display == DisplayOptions::Long { + name = get_inode(metadata, config) + &name; } - if options.is_present(options::CLASSIFY) { + if config.classify { let file_type = metadata.file_type(); if file_type.is_dir() { name.push('/'); @@ -642,7 +743,7 @@ fn display_file_name( } } - if options.is_present(options::LONG) && metadata.file_type().is_symlink() { + if config.display == DisplayOptions::Long && metadata.file_type().is_symlink() { if let Ok(target) = path.read_link() { // We don't bother updating width here because it's not used for long listings let target_name = target.to_string_lossy().to_string(); @@ -687,30 +788,17 @@ fn display_file_name( path: &Path, strip: Option<&Path>, metadata: &Metadata, - options: &clap::ArgMatches, + config: &Config, ) -> Cell { let mut name = get_file_name(path, strip); - if !options.is_present(options::LONG) { - name = get_inode(metadata, options) + &name; + if config.display != DisplayOptions::Long { + name = get_inode(metadata, config) + &name; } let mut width = UnicodeWidthStr::width(&*name); - let color = if options.is_present(options::COLOR) { - match options.value_of(options::COLOR) { - None => atty::is(atty::Stream::Stdout), - Some(val) => match val { - "" | "always" | "yes" | "force" => true, - "auto" | "tty" | "if-tty" => atty::is(atty::Stream::Stdout), - /* "never" | "no" | "none" | */ _ => false, - }, - } - } else { - false - }; - let classify = options.is_present(options::CLASSIFY); let ext; - if color || classify { + if config.color || config.classify { let file_type = metadata.file_type(); let (code, sym) = if file_type.is_dir() { @@ -760,10 +848,10 @@ fn display_file_name( ("", None) }; - if color { + if config.color { name = color_name(name, code); } - if classify { + if config.classify { if let Some(s) = sym { name.push(s); width += 1; @@ -771,7 +859,7 @@ fn display_file_name( } } - if options.is_present(options::LONG) && metadata.file_type().is_symlink() { + if config.display == DisplayOptions::Long && metadata.file_type().is_symlink() { if let Ok(target) = path.read_link() { // We don't bother updating width here because it's not used for long listings let code = if target.exists() { "fi" } else { "mi" }; @@ -788,8 +876,7 @@ fn display_file_name( } #[cfg(not(unix))] -#[allow(unused_variables)] -fn display_symlink_count(metadata: &Metadata) -> String { +fn display_symlink_count(_metadata: &Metadata) -> String { // Currently not sure of how to get this on Windows, so I'm punting. // Git Bash looks like it may do the same thing. String::from("1") From 0717a5f3014b52b862c177222afcd257cbe633f3 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 14 Mar 2021 13:32:15 +0100 Subject: [PATCH 032/104] ls: formatting --- src/uu/ls/src/ls.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index d61a45702..d9bbd0f88 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -122,7 +122,6 @@ struct Config { display: DisplayOptions, files: Files, sort: Sort, - recursive: bool, reverse: bool, dereference: bool, @@ -191,7 +190,7 @@ impl Config { display, files, sort, - + recursive: options.is_present(options::RECURSIVE), reverse: options.is_present(options::REVERSE), dereference: options.is_present(options::DEREFERENCE), @@ -458,7 +457,7 @@ fn sort_entries(entries: &mut Vec, config: &Config) { ) }), Sort::Name => entries.sort(), - Sort::None => {}, + Sort::None => {} } if config.reverse { From d8c3d1d51dd7de9d237364ef931c5a957d4a3974 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 14 Mar 2021 15:32:30 +0000 Subject: [PATCH 033/104] Use system utils --- .github/workflows/GNU.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 4d3e39613..ff88fb5a3 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -90,6 +90,8 @@ jobs: sed -i -e "s|sort |$(which sort) |" tests/ls/hyperlink.sh tests/misc/test-N.sh sed -i -e "s|split |$(which split) |" tests/misc/factor-parallel.sh sed -i -e "s|truncate |$(which truncate) |" tests/split/fail.sh + sed -i -e "s|dd |$(which dd) |" tests/du/8gb.sh tests/tail-2/big-4gb.sh + sed -i -e "s|id -|$(which id) -|" tests/misc/runcon-no-reorder.sh #Add specific timeout to tests that currently hang to limit time spent waiting sed -i -e "s|seq \\$|$(which timeout) 0.1 seq \$|" tests/misc/seq-precision.sh tests/misc/seq-long-double.sh From 7c219fd128968f786c9806408b5f3bb21c711b48 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 14 Mar 2021 15:34:22 +0000 Subject: [PATCH 034/104] clean up sed commands --- .github/workflows/GNU.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index ff88fb5a3..374160c42 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -60,7 +60,7 @@ jobs: ./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR" ./configure --quiet --disable-gcc-warnings #Add timeout to to protect against hangs - sed -i -e "s|\"\$@|$(which timeout) 600 \"\$@|" build-aux/test-driver + sed -i "s|\"\$@|$(which timeout) 600 \"\$@|" build-aux/test-driver # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh @@ -81,21 +81,21 @@ jobs: Makefile # Use the system coreutils where the test fails due to error in a util that is not the one being tested - sed -i -e "s|stat|$(which stat)|" tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh - sed -i -e "s|ls -|$(which ls) -|" tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh - sed -i -e "s|mkdir |$(which mkdir) |" tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh - sed -i -e "s|timeout |$(which timeout) |" 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 - sed -i -e "s| timeout | $(which timeout) |" tests/tail-2/inotify-rotate.sh # Don't break the function called 'grep_timeout' - sed -i -e "s|chmod |$(which chmod) |" tests/du/inacc-dir.sh tests/mkdir/p-3.sh - sed -i -e "s|sort |$(which sort) |" tests/ls/hyperlink.sh tests/misc/test-N.sh - sed -i -e "s|split |$(which split) |" tests/misc/factor-parallel.sh - sed -i -e "s|truncate |$(which truncate) |" tests/split/fail.sh - sed -i -e "s|dd |$(which dd) |" tests/du/8gb.sh tests/tail-2/big-4gb.sh - sed -i -e "s|id -|$(which id) -|" tests/misc/runcon-no-reorder.sh + sed -i "s|stat|$(which stat)|" tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh + sed -i "s|ls -|$(which ls) -|" tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh + sed -i "s|mkdir |$(which mkdir) |" tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh + sed -i "s|timeout |$(which timeout) |" 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 + sed -i "s| timeout | $(which timeout) |" tests/tail-2/inotify-rotate.sh # Don't break the function called 'grep_timeout' + sed -i "s|chmod |$(which chmod) |" tests/du/inacc-dir.sh tests/mkdir/p-3.sh + sed -i "s|sort |$(which sort) |" tests/ls/hyperlink.sh tests/misc/test-N.sh + sed -i "s|split |$(which split) |" tests/misc/factor-parallel.sh + sed -i "s|truncate |$(which truncate) |" tests/split/fail.sh + sed -i "s|dd |$(which dd) |" tests/du/8gb.sh tests/tail-2/big-4gb.sh + sed -i "s|id -|$(which id) -|" tests/misc/runcon-no-reorder.sh #Add specific timeout to tests that currently hang to limit time spent waiting - sed -i -e "s|seq \\$|$(which timeout) 0.1 seq \$|" tests/misc/seq-precision.sh tests/misc/seq-long-double.sh - sed -i -e "s|cat |$(which timeout) 0.1 cat |" tests/misc/cat-self.sh + sed -i "s|seq \\$|$(which timeout) 0.1 seq \$|" tests/misc/seq-precision.sh tests/misc/seq-long-double.sh + sed -i "s|cat |$(which timeout) 0.1 cat |" tests/misc/cat-self.sh test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" From 570e45649613f0515db32213acf004c7214bd7f9 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 14 Mar 2021 15:59:17 +0000 Subject: [PATCH 035/104] clean up workflow script --- .github/workflows/GNU.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 374160c42..299f333e1 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -34,11 +34,11 @@ jobs: shell: bash run: | sudo apt-get update - sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify expect python3-sphinx + sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx pushd uutils make PROFILE=release BUILDDIR="$PWD/target/release/" - cp ${BUILDDIR}/install ${BUILDDIR}/ginstall # The GNU tests rename this script before running, to avoid confusion with the make target + cp "${BUILDDIR}/install" "${BUILDDIR}/ginstall" # The GNU tests rename this script before running, to avoid confusion with the make target # Create *sum binaries for sum in b2sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum do @@ -54,7 +54,7 @@ jobs: for binary in $(./build-aux/gen-lists-of-programs.sh --list-progs) do bin_path="${BUILDDIR}/${binary}" - test -f "${bin_path}" || cp "${BUILDDIR}/false" "${bin_path}" + test -f "${bin_path}" || { echo "'${binary}' was not built with uutils, using the 'false' program"; cp "${BUILDDIR}/false" "${bin_path}"; } done ./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR" @@ -66,7 +66,7 @@ jobs: sed -i 's| tr | /usr/bin/tr |' tests/init.sh make # Generate the factor tests, so they can be fixed - for i in $(seq -w 0 36) + for i in {00..36} do make tests/factor/t${i}.sh done @@ -106,7 +106,7 @@ jobs: GNULIB_DIR="${PWD}/gnulib" pushd gnu - timeout -sKILL 4h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make + timeout -sKILL 2h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make - name: Extract tests info shell: bash run: | From 5d7a8514715ecc815a777834c37c9a9e81a80b5c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 14 Mar 2021 21:30:21 +0100 Subject: [PATCH 036/104] ls: fix --color behaviour --- src/uu/ls/src/ls.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index d9bbd0f88..20fffdedc 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -56,7 +56,7 @@ lazy_static! { let codes = LS_COLORS.split(':'); let mut map = HashMap::new(); for c in codes { - let p: Vec<_> = c.splitn(1, '=').collect(); + let p: Vec<_> = c.splitn(2, '=').collect(); if p.len() == 2 { map.insert(p[0], p[1]); } @@ -337,9 +337,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::COLOR) .long(options::COLOR) .help("Color output based on file type.") - .possible_values(&["always", "yes", "force", "tty", "if-tty", "auto", "never", "no", "none"]) + .takes_value(true) .require_equals(true) - .empty_values(true), + .min_values(0), ) .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)) .get_matches_from(args); From c454d2640cfdee55337f0cb128184873e33ff2c3 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 14 Mar 2021 21:32:21 +0100 Subject: [PATCH 037/104] ls: structure options some more --- src/uu/ls/src/ls.rs | 110 +++++++++++++++++++++++++++++++------------- 1 file changed, 77 insertions(+), 33 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 20fffdedc..0d8d9e3c3 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -7,6 +7,45 @@ // spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf +// Missing features from GNU Coreutils: +// --author +// -b, --escape +// --block-size=SIZE +// -c +// -D, --Dired +// -f +// --file-type +// --format=WORD +// --full-time +// -g +// --group-directories-first +// -G, --no-group +// --si +// -H, --dereference-command-line +// --dereference-command-line-symlink-to-dir +// --hide=PATTERN +// --hyperlink[=WHEN] +// --indicator-style=WORD +// -I, --ignore +// -k, --kibibytes +// -m +// -N, --literal +// -o +// -p, --indicator-style=slash +// -q, --hide-control-chars +// --show-control-chars +// -Q, --quote-name +// --quoting-style=WORD +// --time=WORD +// --time-style=TIME_STYLE +// -T, --tabsize=COLS +// -u +// -v +// -w, --width=COLS +// -x +// -X +// -Z, --context + #[cfg(unix)] #[macro_use] extern crate lazy_static; @@ -70,23 +109,29 @@ lazy_static! { } pub mod options { - pub static ONELINE: &str = "1"; - pub static ALL: &str = "all"; - pub static ALMOST_ALL: &str = "almost-all"; + pub mod display { + pub static ONELINE: &str = "1"; + pub static LONG: &str = "long"; + } + pub mod files { + pub static ALL: &str = "all"; + pub static ALMOST_ALL: &str = "almost-all"; + } + pub mod sort { + pub static SIZE: &str = "S"; + pub static TIME: &str = "t"; + pub static NONE: &str = "U"; + pub static CTIME: &str = "c"; + } pub static IGNORE_BACKUPS: &str = "ignore-backups"; pub static DIRECTORY: &str = "directory"; pub static CLASSIFY: &str = "classify"; pub static HUMAN_READABLE: &str = "human-readable"; pub static INODE: &str = "inode"; pub static DEREFERENCE: &str = "dereference"; - pub static LONG: &str = "long"; pub static NUMERIC_UID_GID: &str = "numeric-uid-gid"; pub static REVERSE: &str = "reverse"; pub static RECURSIVE: &str = "recursive"; - pub static SORT_SIZE: &str = "S"; - pub static SORT_TIME: &str = "t"; - pub static SORT_NONE: &str = "U"; - pub static SORT_CTIME: &str = "c"; pub static COLOR: &str = "color"; pub static PATHS: &str = "paths"; } @@ -138,29 +183,29 @@ struct Config { impl Config { fn from(options: clap::ArgMatches) -> Config { - let display = if options.is_present(options::LONG) { + let display = if options.is_present(options::display::LONG) { DisplayOptions::Long - } else if options.is_present(options::ONELINE) { + } else if options.is_present(options::display::ONELINE) { DisplayOptions::OneLine } else { DisplayOptions::Columns }; - let files = if options.is_present(options::ALL) { + let files = if options.is_present(options::files::ALL) { Files::All - } else if options.is_present(options::ALMOST_ALL) { + } else if options.is_present(options::files::ALMOST_ALL) { Files::AlmostAll } else { Files::Normal }; - let sort = if options.is_present(options::SORT_TIME) { + let sort = if options.is_present(options::sort::TIME) { Sort::Time - } else if options.is_present(options::SORT_CTIME) { + } else if options.is_present(options::sort::CTIME) { Sort::CTime - } else if options.is_present(options::SORT_SIZE) { + } else if options.is_present(options::sort::SIZE) { Sort::Size - } else if options.is_present(options::SORT_NONE) { + } else if options.is_present(options::sort::NONE) { Sort::None } else { Sort::Name @@ -190,7 +235,6 @@ impl Config { display, files, sort, - recursive: options.is_present(options::RECURSIVE), reverse: options.is_present(options::REVERSE), dereference: options.is_present(options::DEREFERENCE), @@ -217,20 +261,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .about(ABOUT) .usage(&usage[..]) .arg( - Arg::with_name(options ::ONELINE) - .short(options ::ONELINE) + Arg::with_name(options::display::ONELINE) + .short(options::display::ONELINE) .help("list one file per line."), ) .arg( - Arg::with_name(options::ALL) + Arg::with_name(options::files::ALL) .short("a") - .long(options::ALL) + .long(options::files::ALL) .help("Do not ignore hidden files (files with names that start with '.')."), ) .arg( - Arg::with_name(options::ALMOST_ALL) + Arg::with_name(options::files::ALMOST_ALL) .short("A") - .long(options::ALMOST_ALL) + .long(options::files::ALMOST_ALL) .help( "In a directory, do not ignore all file names that start with '.', only ignore \ '.' and '..'.", @@ -243,8 +287,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("Ignore entries which end with ~."), ) .arg( - Arg::with_name(options::SORT_CTIME) - .short(options::SORT_CTIME) + Arg::with_name(options::sort::CTIME) + .short(options::sort::CTIME) .help("If the long listing format (e.g., -l, -o) is being used, print the status \ change time (the ‘ctime’ in the inode) instead of the modification time. When \ explicitly sorting by time (--sort=time or -t) or when not using a long listing \ @@ -292,9 +336,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ), ) .arg( - Arg::with_name(options::LONG) + Arg::with_name(options::display::LONG) .short("l") - .long(options::LONG) + .long(options::display::LONG) .help("Display detailed information."), ) .arg( @@ -317,18 +361,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("List the contents of all directories recursively."), ) .arg( - Arg::with_name(options::SORT_SIZE) - .short(options::SORT_SIZE) + Arg::with_name(options::sort::SIZE) + .short(options::sort::SIZE) .help("Sort by file size, largest first."), ) .arg( - Arg::with_name(options::SORT_TIME) - .short(options::SORT_TIME) + Arg::with_name(options::sort::TIME) + .short(options::sort::TIME) .help("Sort by modification time (the 'mtime' in the inode), newest first."), ) .arg( - Arg::with_name(options::SORT_NONE) - .short(options::SORT_NONE) + Arg::with_name(options::sort::NONE) + .short(options::sort::NONE) .help("Do not sort; list the files in whatever order they are stored in the \ directory. This is especially useful when listing very large directories, \ since not doing any sorting can be noticeably faster.", From c86c18cbb54ba33b175729cd362584d9df3b82a9 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 14 Mar 2021 23:11:11 +0100 Subject: [PATCH 038/104] ls: implement -c and -u --- src/uu/ls/src/ls.rs | 90 +++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0d8d9e3c3..1719579fc 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -54,7 +54,6 @@ extern crate uucore; use clap::{App, Arg}; use number_prefix::NumberPrefix; -use std::cmp::Reverse; #[cfg(unix)] use std::collections::HashMap; use std::fs; @@ -66,6 +65,10 @@ use std::os::unix::fs::MetadataExt; #[cfg(windows)] use std::os::windows::fs::MetadataExt; use std::path::{Path, PathBuf}; +use std::cmp::Reverse; +#[cfg(not(unix))] +use std::time::{SystemTime, UNIX_EPOCH}; + use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use time::{strftime, Timespec}; #[cfg(unix)] @@ -121,7 +124,10 @@ pub mod options { pub static SIZE: &str = "S"; pub static TIME: &str = "t"; pub static NONE: &str = "U"; - pub static CTIME: &str = "c"; + } + pub mod time { + pub static ACCESS: &str = "u"; + pub static CHANGE: &str = "c"; } pub static IGNORE_BACKUPS: &str = "ignore-backups"; pub static DIRECTORY: &str = "directory"; @@ -148,7 +154,6 @@ enum Sort { Name, Size, Time, - CTime, } enum SizeFormats { @@ -163,6 +168,12 @@ enum Files { Normal, } +enum Time { + Modification, + Access, + Change, +} + struct Config { display: DisplayOptions, files: Files, @@ -175,6 +186,7 @@ struct Config { size_format: SizeFormats, numeric_uid_gid: bool, directory: bool, + time: Time, #[cfg(unix)] inode: bool, #[cfg(unix)] @@ -201,8 +213,6 @@ impl Config { let sort = if options.is_present(options::sort::TIME) { Sort::Time - } else if options.is_present(options::sort::CTIME) { - Sort::CTime } else if options.is_present(options::sort::SIZE) { Sort::Size } else if options.is_present(options::sort::NONE) { @@ -211,6 +221,14 @@ impl Config { Sort::Name }; + let time = if options.is_present(options::time::ACCESS) { + Time::Access + } else if options.is_present(options::time::CHANGE) { + Time::Change + } else { + Time::Modification + }; + #[cfg(unix)] let color = if options.is_present(options::COLOR) { match options.value_of(options::COLOR) { @@ -243,6 +261,7 @@ impl Config { size_format, numeric_uid_gid: options.is_present(options::NUMERIC_UID_GID), directory: options.is_present(options::DIRECTORY), + time, #[cfg(unix)] color, #[cfg(unix)] @@ -287,13 +306,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("Ignore entries which end with ~."), ) .arg( - Arg::with_name(options::sort::CTIME) - .short(options::sort::CTIME) - .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + Arg::with_name(options::time::CHANGE) + .short(options::time::CHANGE) + .help("If the long listing format (e.g., -l, -o) is being used, print the status \ change time (the ‘ctime’ in the inode) instead of the modification time. When \ explicitly sorting by time (--sort=time or -t) or when not using a long listing \ format, sort according to the status change time.", )) + .arg( + Arg::with_name(options::time::ACCESS) + .short(options::time::ACCESS) + .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + access time instead of the modification time. When explicitly sorting by time \ + (--sort=time or -t) or when not using a long listing format, sort according to the \ + status change time.") + ) .arg( Arg::with_name(options::DIRECTORY) .short("d") @@ -449,13 +476,11 @@ fn list(locs: Vec, config: Config) -> i32 { #[cfg(any(unix, target_os = "redox"))] fn sort_entries(entries: &mut Vec, config: &Config) { match config.sort { - Sort::CTime => entries - .sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.ctime()).unwrap_or(0))), Sort::Time => entries.sort_by_key(|k| { Reverse( get_metadata(k, config) - .and_then(|md| md.modified()) - .unwrap_or(std::time::UNIX_EPOCH), + .map(|md| get_time(&md, config)) + .unwrap_or(0), ) }), Sort::Size => entries @@ -484,13 +509,9 @@ fn is_hidden(file_path: &DirEntry) -> bool { #[cfg(windows)] fn sort_entries(entries: &mut Vec, config: &Config) { match config.sort { - Sort::CTime | Sort::Time => entries.sort_by_key(|k| { + Sort::Time => entries.sort_by_key(|k| { // Newest first - Reverse( - get_metadata(k, config) - .and_then(|md| md.modified()) - .unwrap_or(std::time::UNIX_EPOCH), - ) + Reverse(get_time(get_metadata(k, config), config).unwrap_or(std::time::UNIX_EPOCH)) }), Sort::Size => entries.sort_by_key(|k| { // Largest first @@ -702,23 +723,38 @@ fn display_group(_metadata: &Metadata, _config: &Config) -> String { "somegroup".to_string() } +// The implementations for get_time are separated because some options, such +// as ctime will not be available +#[cfg(unix)] +fn get_time(md: &Metadata, config: &Config) -> i64 { + match config.time { + Time::Change => md.ctime(), + Time::Modification => md.mtime(), + Time::Access => md.atime(), + } +} + +#[cfg(not(unix))] +fn get_time(md: &Metadata, config: &Config) -> Option { + match config.time { + Time::Modification => md.modification().ok(), + Time::Access => md.access().ok(), + _ => None, + } +} + #[cfg(unix)] fn display_date(metadata: &Metadata, config: &Config) -> String { - let secs = match config.sort { - Sort::CTime => metadata.ctime(), - Sort::Time => metadata.mtime(), - _ => 0, - }; + let secs = get_time(metadata, config); let time = time::at(Timespec::new(secs, 0)); strftime("%F %R", &time).unwrap() } #[cfg(not(unix))] -fn display_date(metadata: &Metadata, _config: &Config) -> String { - if let Ok(mtime) = metadata.modified() { +fn display_date(metadata: &Metadata, config: &Config) -> String { + if let Some(time) = get_time(metadata, config) { let time = time::at(Timespec::new( - mtime - .duration_since(std::time::UNIX_EPOCH) + time.duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() as i64, 0, From 7bde2e78a97ddc8d390c8391457db8a015b92c08 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 14 Mar 2021 23:34:52 +0100 Subject: [PATCH 039/104] ls: simplify --color and remove it on windows --- src/uu/ls/src/ls.rs | 46 +++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 1719579fc..6f74d459b 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -138,6 +138,7 @@ pub mod options { pub static NUMERIC_UID_GID: &str = "numeric-uid-gid"; pub static REVERSE: &str = "reverse"; pub static RECURSIVE: &str = "recursive"; + #[cfg(unix)] pub static COLOR: &str = "color"; pub static PATHS: &str = "paths"; } @@ -230,17 +231,13 @@ impl Config { }; #[cfg(unix)] - let color = if options.is_present(options::COLOR) { - match options.value_of(options::COLOR) { - None => atty::is(atty::Stream::Stdout), - Some(val) => match val { - "" | "always" | "yes" | "force" => true, - "auto" | "tty" | "if-tty" => atty::is(atty::Stream::Stdout), - /* "never" | "no" | "none" | */ _ => false, - }, - } - } else { - false + let color = match options.value_of(options::COLOR) { + None => options.is_present(options::COLOR), + Some(val) => match val { + "" | "always" | "yes" | "force" => true, + "auto" | "tty" | "if-tty" => atty::is(atty::Stream::Stdout), + /* "never" | "no" | "none" | */ _ => false, + }, }; let size_format = if options.is_present(options::HUMAN_READABLE) { @@ -275,7 +272,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let matches = App::new(executable!()) + let mut app = App::new(executable!()) .version(VERSION) .about(ABOUT) .usage(&usage[..]) @@ -319,7 +316,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("If the long listing format (e.g., -l, -o) is being used, print the status \ access time instead of the modification time. When explicitly sorting by time \ (--sort=time or -t) or when not using a long listing format, sort according to the \ - status change time.") + access time.") ) .arg( Arg::with_name(options::DIRECTORY) @@ -404,16 +401,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { directory. This is especially useful when listing very large directories, \ since not doing any sorting can be noticeably faster.", )) - .arg( + .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)); + + #[cfg(unix)] + { + app = app.arg( Arg::with_name(options::COLOR) - .long(options::COLOR) - .help("Color output based on file type.") - .takes_value(true) - .require_equals(true) - .min_values(0), - ) - .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)) - .get_matches_from(args); + .long(options::COLOR) + .help("Color output based on file type.") + .takes_value(true) + .require_equals(true) + .min_values(0), + ); + } + + let matches = app.get_matches_from(args); let locs = matches .values_of(options::PATHS) From 61a95239cee641da0dbb9993378d458588bc4296 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 15 Mar 2021 09:30:50 +0100 Subject: [PATCH 040/104] ls: rename display to format, set arg overrides --- src/uu/ls/src/ls.rs | 212 +++++++++++++++++++++++++++++++------------- 1 file changed, 152 insertions(+), 60 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6f74d459b..69d1e251d 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -112,9 +112,10 @@ lazy_static! { } pub mod options { - pub mod display { + pub mod format { pub static ONELINE: &str = "1"; pub static LONG: &str = "long"; + pub static COLUMNS: &str = "C"; } pub mod files { pub static ALL: &str = "all"; @@ -129,6 +130,9 @@ pub mod options { pub static ACCESS: &str = "u"; pub static CHANGE: &str = "c"; } + pub static FORMAT: &str = "format"; + pub static SORT: &str = "sort"; + pub static TIME: &str = "time"; pub static IGNORE_BACKUPS: &str = "ignore-backups"; pub static DIRECTORY: &str = "directory"; pub static CLASSIFY: &str = "classify"; @@ -144,7 +148,7 @@ pub mod options { } #[derive(PartialEq, Eq)] -enum DisplayOptions { +enum Format { Columns, Long, OneLine, @@ -176,7 +180,7 @@ enum Time { } struct Config { - display: DisplayOptions, + format: Format, files: Files, sort: Sort, recursive: bool, @@ -196,12 +200,22 @@ struct Config { impl Config { fn from(options: clap::ArgMatches) -> Config { - let display = if options.is_present(options::display::LONG) { - DisplayOptions::Long - } else if options.is_present(options::display::ONELINE) { - DisplayOptions::OneLine + let format = if let Some(format_) = options.value_of(options::FORMAT) { + match format_ { + "long" | "verbose" => Format::Long, + "single-column" => Format::OneLine, + "columns" => Format::Columns, + // below should never happen as clap already restricts the values. + _ => panic!("Invalid field for --format") + } + } else if options.is_present(options::format::LONG) { + Format::Long + } else if options.is_present(options::format::ONELINE) { + Format::OneLine + } else if options.is_present(options::format::COLUMNS) { + Format::Columns } else { - DisplayOptions::Columns + Format::Columns }; let files = if options.is_present(options::files::ALL) { @@ -212,7 +226,16 @@ impl Config { Files::Normal }; - let sort = if options.is_present(options::sort::TIME) { + let sort = if let Some(field) = options.value_of(options::SORT) { + match field { + "none" => Sort::None, + "name" => Sort::Name, + "time" => Sort::Time, + "size" => Sort::Size, + // below should never happen as clap already restricts the values. + _ => panic!("Invalid field for --sort") + } + } else if options.is_present(options::sort::TIME) { Sort::Time } else if options.is_present(options::sort::SIZE) { Sort::Size @@ -222,7 +245,14 @@ impl Config { Sort::Name }; - let time = if options.is_present(options::time::ACCESS) { + let time = if let Some(field) = options.value_of(options::TIME) { + match field { + "ctime" | "status" => Time::Change, + "access" | "atime" | "use" => Time::Access, + // below should never happen as clap already restricts the values. + _ => panic!("Invalid field for --time") + } + } else if options.is_present(options::time::ACCESS) { Time::Access } else if options.is_present(options::time::CHANGE) { Time::Change @@ -247,7 +277,7 @@ impl Config { }; Config { - display, + format, files, sort, recursive: options.is_present(options::RECURSIVE), @@ -276,11 +306,110 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .version(VERSION) .about(ABOUT) .usage(&usage[..]) + + // Format arguments .arg( - Arg::with_name(options::display::ONELINE) - .short(options::display::ONELINE) - .help("list one file per line."), + Arg::with_name(options::FORMAT) + .long(options::FORMAT) + .help("Set the display format.") + .takes_value(true) + .possible_values(&["long", "verbose", "single-column", "columns"]) + .hide_possible_values(true) + .require_equals(true) + .overrides_with_all(&[ + options::FORMAT, + options::format::COLUMNS, + options::format::ONELINE, + options::format::LONG, + ]), ) + .arg( + Arg::with_name(options::format::COLUMNS) + .short(options::format::COLUMNS) + .help("Display the files in columns.") + ) + .arg( + Arg::with_name(options::format::ONELINE) + .short(options::format::ONELINE) + .help("List one file per line.") + ) + .arg( + Arg::with_name(options::format::LONG) + .short("l") + .long(options::format::LONG) + .help("Display detailed information.") + ) + + // Time arguments + .arg( + Arg::with_name(options::TIME) + .long(options::TIME) + .help("Show time in :\n\ + \taccess time (-u): atime, access, use;\n\ + \tchange time (-t): ctime, status.") + .value_name("field") + .takes_value(true) + .possible_values(&["atime", "access", "use", "ctime", "status"]) + .hide_possible_values(true) + .require_equals(true) + .overrides_with_all(&[ + options::TIME, + options::time::ACCESS, + options::time::CHANGE, + ]) + ) + .arg( + Arg::with_name(options::time::CHANGE) + .short(options::time::CHANGE) + .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + change time (the ‘ctime’ in the inode) instead of the modification time. When \ + explicitly sorting by time (--sort=time or -t) or when not using a long listing \ + format, sort according to the status change time.", + )) + .arg( + Arg::with_name(options::time::ACCESS) + .short(options::time::ACCESS) + .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + access time instead of the modification time. When explicitly sorting by time \ + (--sort=time or -t) or when not using a long listing format, sort according to the \ + access time.") + ) + + // Sort arguments + .arg( + Arg::with_name(options::SORT) + .long(options::SORT) + .help("Sort by : name, none (-U), time (-t) or size (-S)") + .value_name("field") + .takes_value(true) + .possible_values(&["name", "none", "time", "size"]) + .require_equals(true) + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + ]) + ) + .arg( + Arg::with_name(options::sort::SIZE) + .short(options::sort::SIZE) + .help("Sort by file size, largest first."), + ) + .arg( + Arg::with_name(options::sort::TIME) + .short(options::sort::TIME) + .help("Sort by modification time (the 'mtime' in the inode), newest first."), + ) + .arg( + Arg::with_name(options::sort::NONE) + .short(options::sort::NONE) + .help("Do not sort; list the files in whatever order they are stored in the \ + directory. This is especially useful when listing very large directories, \ + since not doing any sorting can be noticeably faster.") + ) + + // Other Flags .arg( Arg::with_name(options::files::ALL) .short("a") @@ -302,22 +431,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::IGNORE_BACKUPS) .help("Ignore entries which end with ~."), ) - .arg( - Arg::with_name(options::time::CHANGE) - .short(options::time::CHANGE) - .help("If the long listing format (e.g., -l, -o) is being used, print the status \ - change time (the ‘ctime’ in the inode) instead of the modification time. When \ - explicitly sorting by time (--sort=time or -t) or when not using a long listing \ - format, sort according to the status change time.", - )) - .arg( - Arg::with_name(options::time::ACCESS) - .short(options::time::ACCESS) - .help("If the long listing format (e.g., -l, -o) is being used, print the status \ - access time instead of the modification time. When explicitly sorting by time \ - (--sort=time or -t) or when not using a long listing format, sort according to the \ - access time.") - ) .arg( Arg::with_name(options::DIRECTORY) .short("d") @@ -359,12 +472,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { file the link references rather than the link itself.", ), ) - .arg( - Arg::with_name(options::display::LONG) - .short("l") - .long(options::display::LONG) - .help("Display detailed information."), - ) .arg( Arg::with_name(options::NUMERIC_UID_GID) .short("n") @@ -384,23 +491,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::RECURSIVE) .help("List the contents of all directories recursively."), ) - .arg( - Arg::with_name(options::sort::SIZE) - .short(options::sort::SIZE) - .help("Sort by file size, largest first."), - ) - .arg( - Arg::with_name(options::sort::TIME) - .short(options::sort::TIME) - .help("Sort by modification time (the 'mtime' in the inode), newest first."), - ) - .arg( - Arg::with_name(options::sort::NONE) - .short(options::sort::NONE) - .help("Do not sort; list the files in whatever order they are stored in the \ - directory. This is especially useful when listing very large directories, \ - since not doing any sorting can be noticeably faster.", - )) + + // Positional arguments .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)); #[cfg(unix)] @@ -444,7 +536,7 @@ fn list(locs: Vec, config: Config) -> i32 { if p.is_dir() && !config.directory { dir = true; - if config.display == DisplayOptions::Long && !config.dereference { + if config.format == Format::Long && !config.dereference { if let Ok(md) = p.symlink_metadata() { if md.file_type().is_symlink() && !p.ends_with("/") { dir = false; @@ -593,7 +685,7 @@ fn pad_left(string: String, count: usize) -> String { } fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) { - if config.display == DisplayOptions::Long || config.numeric_uid_gid { + if config.format == Format::Long || config.numeric_uid_gid { let (mut max_links, mut max_size) = (1, 1); for item in items { let (links, size) = display_dir_entry_size(item, config); @@ -604,7 +696,7 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) { display_item_long(item, strip, max_links, max_size, config); } } else { - if config.display != DisplayOptions::OneLine { + if config.format != Format::OneLine { let names = items.iter().filter_map(|i| { let md = get_metadata(i, config); match md { @@ -811,7 +903,7 @@ fn display_file_name( ) -> Cell { let mut name = get_file_name(path, strip); - if config.display == DisplayOptions::Long { + if config.format == Format::Long { name = get_inode(metadata, config) + &name; } @@ -824,7 +916,7 @@ fn display_file_name( } } - if config.display == DisplayOptions::Long && metadata.file_type().is_symlink() { + if config.format == Format::Long && metadata.file_type().is_symlink() { if let Ok(target) = path.read_link() { // We don't bother updating width here because it's not used for long listings let target_name = target.to_string_lossy().to_string(); @@ -872,7 +964,7 @@ fn display_file_name( config: &Config, ) -> Cell { let mut name = get_file_name(path, strip); - if config.display != DisplayOptions::Long { + if config.format != Format::Long { name = get_inode(metadata, config) + &name; } let mut width = UnicodeWidthStr::width(&*name); @@ -940,7 +1032,7 @@ fn display_file_name( } } - if config.display == DisplayOptions::Long && metadata.file_type().is_symlink() { + if config.format == Format::Long && metadata.file_type().is_symlink() { if let Ok(target) = path.read_link() { // We don't bother updating width here because it's not used for long listings let code = if target.exists() { "fi" } else { "mi" }; From 5656a717c9f7445ea9b2695255e47e449aa2d183 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 15 Mar 2021 09:31:13 +0100 Subject: [PATCH 041/104] ls: make name sort case insensitive --- src/uu/ls/src/ls.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 69d1e251d..7e98a7fd3 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -579,7 +579,8 @@ fn sort_entries(entries: &mut Vec, config: &Config) { }), Sort::Size => entries .sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.size()).unwrap_or(0))), - Sort::Name => entries.sort(), + // The default sort in GNU ls is case insensitive + Sort::Name => entries.sort_by_key(|k| k.to_string_lossy().to_lowercase()), Sort::None => {} } From 01fd207c81dc09b7efc7cff411b345c5441b98d4 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 15 Mar 2021 09:53:19 +0100 Subject: [PATCH 042/104] ls: remove list of missing features --- src/uu/ls/src/ls.rs | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 7e98a7fd3..c2d544a2c 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -7,45 +7,6 @@ // spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf -// Missing features from GNU Coreutils: -// --author -// -b, --escape -// --block-size=SIZE -// -c -// -D, --Dired -// -f -// --file-type -// --format=WORD -// --full-time -// -g -// --group-directories-first -// -G, --no-group -// --si -// -H, --dereference-command-line -// --dereference-command-line-symlink-to-dir -// --hide=PATTERN -// --hyperlink[=WHEN] -// --indicator-style=WORD -// -I, --ignore -// -k, --kibibytes -// -m -// -N, --literal -// -o -// -p, --indicator-style=slash -// -q, --hide-control-chars -// --show-control-chars -// -Q, --quote-name -// --quoting-style=WORD -// --time=WORD -// --time-style=TIME_STYLE -// -T, --tabsize=COLS -// -u -// -v -// -w, --width=COLS -// -x -// -X -// -Z, --context - #[cfg(unix)] #[macro_use] extern crate lazy_static; From a4c79c92ae3172164a5adb8a1badaedf82a7f3de Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 15 Mar 2021 10:24:24 +0100 Subject: [PATCH 043/104] ls: fix windows issues --- src/uu/ls/src/ls.rs | 109 +++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 71 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index c2d544a2c..187b39006 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -15,6 +15,7 @@ extern crate uucore; use clap::{App, Arg}; use number_prefix::NumberPrefix; +use std::cmp::Reverse; #[cfg(unix)] use std::collections::HashMap; use std::fs; @@ -26,9 +27,6 @@ use std::os::unix::fs::MetadataExt; #[cfg(windows)] use std::os::windows::fs::MetadataExt; use std::path::{Path, PathBuf}; -use std::cmp::Reverse; -#[cfg(not(unix))] -use std::time::{SystemTime, UNIX_EPOCH}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use time::{strftime, Timespec}; @@ -167,7 +165,7 @@ impl Config { "single-column" => Format::OneLine, "columns" => Format::Columns, // below should never happen as clap already restricts the values. - _ => panic!("Invalid field for --format") + _ => panic!("Invalid field for --format"), } } else if options.is_present(options::format::LONG) { Format::Long @@ -194,7 +192,7 @@ impl Config { "time" => Sort::Time, "size" => Sort::Size, // below should never happen as clap already restricts the values. - _ => panic!("Invalid field for --sort") + _ => panic!("Invalid field for --sort"), } } else if options.is_present(options::sort::TIME) { Sort::Time @@ -211,7 +209,7 @@ impl Config { "ctime" | "status" => Time::Change, "access" | "atime" | "use" => Time::Access, // below should never happen as clap already restricts the values. - _ => panic!("Invalid field for --time") + _ => panic!("Invalid field for --time"), } } else if options.is_present(options::time::ACCESS) { Time::Access @@ -263,7 +261,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let mut app = App::new(executable!()) + let app = App::new(executable!()) .version(VERSION) .about(ABOUT) .usage(&usage[..]) @@ -455,18 +453,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // Positional arguments .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)); - + #[cfg(unix)] - { - app = app.arg( + let app = { + app.arg( Arg::with_name(options::COLOR) - .long(options::COLOR) - .help("Color output based on file type.") - .takes_value(true) - .require_equals(true) - .min_values(0), - ); - } + .long(options::COLOR) + .help("Color output based on file type.") + .takes_value(true) + .require_equals(true) + .min_values(0), + ) + }; let matches = app.get_matches_from(args); @@ -528,18 +526,18 @@ fn list(locs: Vec, config: Config) -> i32 { } } -#[cfg(any(unix, target_os = "redox"))] fn sort_entries(entries: &mut Vec, config: &Config) { match config.sort { Sort::Time => entries.sort_by_key(|k| { Reverse( get_metadata(k, config) - .map(|md| get_time(&md, config)) + .ok() + .and_then(|md| get_time(&md, config)) .unwrap_or(0), ) }), Sort::Size => entries - .sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.size()).unwrap_or(0))), + .sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.len()).unwrap_or(0))), // The default sort in GNU ls is case insensitive Sort::Name => entries.sort_by_key(|k| k.to_string_lossy().to_lowercase()), Sort::None => {} @@ -562,30 +560,6 @@ fn is_hidden(file_path: &DirEntry) -> bool { file_path.file_name().to_string_lossy().starts_with('.') } -#[cfg(windows)] -fn sort_entries(entries: &mut Vec, config: &Config) { - match config.sort { - Sort::Time => entries.sort_by_key(|k| { - // Newest first - Reverse(get_time(get_metadata(k, config), config).unwrap_or(std::time::UNIX_EPOCH)) - }), - Sort::Size => entries.sort_by_key(|k| { - // Largest first - Reverse( - get_metadata(k, config) - .map(|md| md.file_size()) - .unwrap_or(0), - ) - }), - Sort::Name => entries.sort(), - Sort::None => {} - } - - if config.reverse { - entries.reverse(); - } -} - fn should_display(entry: &DirEntry, config: &Config) -> bool { let ffi_name = entry.file_name(); let name = ffi_name.to_string_lossy(); @@ -782,42 +756,35 @@ fn display_group(_metadata: &Metadata, _config: &Config) -> String { // The implementations for get_time are separated because some options, such // as ctime will not be available #[cfg(unix)] -fn get_time(md: &Metadata, config: &Config) -> i64 { - match config.time { +fn get_time(md: &Metadata, config: &Config) -> Option { + Some(match config.time { Time::Change => md.ctime(), Time::Modification => md.mtime(), Time::Access => md.atime(), - } + }) } #[cfg(not(unix))] -fn get_time(md: &Metadata, config: &Config) -> Option { - match config.time { - Time::Modification => md.modification().ok(), - Time::Access => md.access().ok(), - _ => None, - } +fn get_time(md: &Metadata, config: &Config) -> Option { + let time = match config.time { + Time::Modification => md.modified().ok()?, + Time::Access => md.accessed().ok()?, + _ => return None, + }; + Some( + time.duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() as i64, + ) } -#[cfg(unix)] fn display_date(metadata: &Metadata, config: &Config) -> String { - let secs = get_time(metadata, config); - let time = time::at(Timespec::new(secs, 0)); - strftime("%F %R", &time).unwrap() -} - -#[cfg(not(unix))] -fn display_date(metadata: &Metadata, config: &Config) -> String { - if let Some(time) = get_time(metadata, config) { - let time = time::at(Timespec::new( - time.duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() as i64, - 0, - )); - strftime("%F %R", &time).unwrap() - } else { - "???".to_string() + match get_time(metadata, config) { + Some(secs) => { + let time = time::at(Timespec::new(secs, 0)); + strftime("%F %R", &time).unwrap() + } + None => "???".into(), } } From f28d5f4a73c19775c3892d09c808aa4a98f473c7 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 15 Mar 2021 12:07:10 +0100 Subject: [PATCH 044/104] ls: attempt to fix windows sorting issues --- src/uu/ls/src/ls.rs | 48 +++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 187b39006..90e4e2b22 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -27,6 +27,7 @@ use std::os::unix::fs::MetadataExt; #[cfg(windows)] use std::os::windows::fs::MetadataExt; use std::path::{Path, PathBuf}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use time::{strftime, Timespec}; @@ -532,8 +533,8 @@ fn sort_entries(entries: &mut Vec, config: &Config) { Reverse( get_metadata(k, config) .ok() - .and_then(|md| get_time(&md, config)) - .unwrap_or(0), + .and_then(|md| get_system_time(&md, config)) + .unwrap_or(UNIX_EPOCH), ) }), Sort::Size => entries @@ -756,34 +757,35 @@ fn display_group(_metadata: &Metadata, _config: &Config) -> String { // The implementations for get_time are separated because some options, such // as ctime will not be available #[cfg(unix)] -fn get_time(md: &Metadata, config: &Config) -> Option { - Some(match config.time { - Time::Change => md.ctime(), - Time::Modification => md.mtime(), - Time::Access => md.atime(), - }) +fn get_system_time(md: &Metadata, config: &Config) -> Option { + match config.time { + Time::Change => Some(UNIX_EPOCH + Duration::new(md.ctime() as u64, md.ctime_nsec() as u32)), + Time::Modification => md.modified().ok(), + Time::Access => md.accessed().ok(), + } } #[cfg(not(unix))] -fn get_time(md: &Metadata, config: &Config) -> Option { - let time = match config.time { - Time::Modification => md.modified().ok()?, - Time::Access => md.accessed().ok()?, - _ => return None, - }; - Some( - time.duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() as i64, - ) +fn get_system_time(md: &Metadata, config: &Config) -> Option { + match config.time { + Time::Modification => md.modified().ok(), + Time::Access => md.accessed().ok(), + _ => None, + } +} + +fn get_time(md: &Metadata, config: &Config) -> Option { + let duration = get_system_time(md, config)? + .duration_since(UNIX_EPOCH) + .ok()?; + let secs = duration.as_secs() as i64; + let nsec = duration.subsec_nanos() as i32; + Some(time::at(Timespec::new(secs, nsec))) } fn display_date(metadata: &Metadata, config: &Config) -> String { match get_time(metadata, config) { - Some(secs) => { - let time = time::at(Timespec::new(secs, 0)); - strftime("%F %R", &time).unwrap() - } + Some(time) => strftime("%F %R", &time).unwrap(), None => "???".into(), } } From 20094127c3cbe6e74dc77ed75c81944716a5c761 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 15 Mar 2021 12:21:08 +0100 Subject: [PATCH 045/104] ls: --color back on windows as noop --- src/uu/ls/src/ls.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 90e4e2b22..24adaa649 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -102,7 +102,6 @@ pub mod options { pub static NUMERIC_UID_GID: &str = "numeric-uid-gid"; pub static REVERSE: &str = "reverse"; pub static RECURSIVE: &str = "recursive"; - #[cfg(unix)] pub static COLOR: &str = "color"; pub static PATHS: &str = "paths"; } @@ -451,13 +450,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::RECURSIVE) .help("List the contents of all directories recursively."), ) - - // Positional arguments - .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)); - - #[cfg(unix)] - let app = { - app.arg( + .arg( Arg::with_name(options::COLOR) .long(options::COLOR) .help("Color output based on file type.") @@ -465,7 +458,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .require_equals(true) .min_values(0), ) - }; + + // Positional arguments + .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)); let matches = app.get_matches_from(args); From 10135dccef1d3c0844963831acadf1127dbdfb17 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 15 Mar 2021 13:46:21 +0100 Subject: [PATCH 046/104] ls: fix unused import and improve coverage --- src/uu/ls/src/ls.rs | 10 +-- tests/by-util/test_ls.rs | 127 +++++++++++++++++++++++++++++++++++---- 2 files changed, 120 insertions(+), 17 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 24adaa649..5c4cdaa80 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -27,7 +27,9 @@ use std::os::unix::fs::MetadataExt; #[cfg(windows)] use std::os::windows::fs::MetadataExt; use std::path::{Path, PathBuf}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +#[cfg(unix)] +use std::time::Duration; +use std::time::{SystemTime, UNIX_EPOCH}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use time::{strftime, Timespec}; @@ -165,7 +167,7 @@ impl Config { "single-column" => Format::OneLine, "columns" => Format::Columns, // below should never happen as clap already restricts the values. - _ => panic!("Invalid field for --format"), + _ => unreachable!("Invalid field for --format"), } } else if options.is_present(options::format::LONG) { Format::Long @@ -192,7 +194,7 @@ impl Config { "time" => Sort::Time, "size" => Sort::Size, // below should never happen as clap already restricts the values. - _ => panic!("Invalid field for --sort"), + _ => unreachable!("Invalid field for --sort"), } } else if options.is_present(options::sort::TIME) { Sort::Time @@ -209,7 +211,7 @@ impl Config { "ctime" | "status" => Time::Change, "access" | "atime" | "use" => Time::Access, // below should never happen as clap already restricts the values. - _ => panic!("Invalid field for --time"), + _ => unreachable!("Invalid field for --time"), } } else if options.is_present(options::time::ACCESS) { Time::Access diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 422db8df9..e8ef18966 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -57,6 +57,36 @@ fn test_ls_a() { assert!(!result.stdout.contains("..")); } +#[test] +fn test_ls_columns() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch(&at.plus_as_string("test-columns-1")); + at.touch(&at.plus_as_string("test-columns-2")); + + // Columns is the default + let result = scene.ucmd().run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + + #[cfg(not(windows))] + assert_eq!(result.stdout, "test-columns-1\ntest-columns-2\n"); + #[cfg(windows)] + assert_eq!(result.stdout, "test-columns-1 test-columns-2\n"); + + for option in &["-C", "--format=columns"] { + let result = scene.ucmd().arg(option).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + #[cfg(not(windows))] + assert_eq!(result.stdout, "test-columns-1\ntest-columns-2\n"); + #[cfg(windows)] + assert_eq!(result.stdout, "test-columns-1 test-columns-2\n"); + } +} + #[test] fn test_ls_long() { #[cfg(not(windows))] @@ -71,16 +101,20 @@ fn test_ls_long() { } } - let (at, mut ucmd) = at_and_ucmd!(); + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; at.touch(&at.plus_as_string("test-long")); - let result = ucmd.arg("-l").arg("test-long").succeeds(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - #[cfg(not(windows))] - assert!(result.stdout.contains("-rw-rw-r--")); - #[cfg(windows)] - assert!(result.stdout.contains("---------- 1 somebody somegroup")); + for arg in &["-l", "--long", "--format=long", "--format=verbose"] { + let result = scene.ucmd().arg(arg).arg("test-long").succeeds(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + #[cfg(not(windows))] + assert!(result.stdout.contains("-rw-rw-r--")); + + #[cfg(windows)] + assert!(result.stdout.contains("---------- 1 somebody somegroup")); + } #[cfg(not(windows))] { @@ -90,6 +124,24 @@ fn test_ls_long() { } } +#[test] +fn test_ls_oneline() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch(&at.plus_as_string("test-oneline-1")); + at.touch(&at.plus_as_string("test-oneline-2")); + + // Bit of a weird situation: in the tests oneline and columns have the same output, + // except on Windows. + for option in &["-1", "--format=single-column"] { + let result = scene.ucmd().arg(option).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + assert_eq!(result.stdout, "test-oneline-1\ntest-oneline-2\n"); + } +} + #[test] fn test_ls_deref() { let scene = TestScenario::new(util_name!()); @@ -166,27 +218,54 @@ fn test_ls_order_size() { } #[test] -fn test_ls_order_creation() { +fn test_ls_long_ctime() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("test-long-ctime-1"); + let result = scene.ucmd().arg("-lc").succeeds(); + + // Should show the time on Unix, but question marks on windows. + #[cfg(unix)] + assert!(result.stdout.contains(":")); + #[cfg(not(unix))] + assert!(result.stdout.contains("???")); +} + +#[test] +fn test_ls_order_time() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("test-1"); at.append("test-1", "1"); - sleep(Duration::from_millis(500)); + sleep(Duration::from_millis(100)); at.touch("test-2"); at.append("test-2", "22"); - sleep(Duration::from_millis(500)); + sleep(Duration::from_millis(100)); at.touch("test-3"); at.append("test-3", "333"); - sleep(Duration::from_millis(500)); + sleep(Duration::from_millis(100)); at.touch("test-4"); at.append("test-4", "4444"); + sleep(Duration::from_millis(100)); + + // Read test-3, only changing access time + at.read("test-3"); + + // Set permissions of test-2, only changing ctime + std::fs::set_permissions( + at.plus_as_string("test-2"), + at.metadata("test-2").permissions(), + ) + .unwrap(); let result = scene.ucmd().arg("-al").run(); println!("stderr = {:?}", result.stderr); println!("stdout = {:?}", result.stdout); assert!(result.success); + // ctime was changed at write, so the order is 4 3 2 1 let result = scene.ucmd().arg("-t").run(); println!("stderr = {:?}", result.stderr); println!("stdout = {:?}", result.stdout); @@ -196,7 +275,7 @@ fn test_ls_order_creation() { #[cfg(windows)] assert_eq!(result.stdout, "test-4 test-3 test-2 test-1\n"); - let result = scene.ucmd().arg("-t").arg("-r").run(); + let result = scene.ucmd().arg("-tr").run(); println!("stderr = {:?}", result.stderr); println!("stdout = {:?}", result.stdout); assert!(result.success); @@ -204,6 +283,28 @@ fn test_ls_order_creation() { assert_eq!(result.stdout, "test-1\ntest-2\ntest-3\ntest-4\n"); #[cfg(windows)] assert_eq!(result.stdout, "test-1 test-2 test-3 test-4\n"); + + // 3 was accessed last in the read + // So the order should be 2 3 4 1 + let result = scene.ucmd().arg("-tu").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + #[cfg(not(windows))] + assert_eq!(result.stdout, "test-3\ntest-4\ntest-2\ntest-1\n"); + #[cfg(windows)] + assert_eq!(result.stdout, "test-3 test-4 test-2 test-1\n"); + + // test-2 had the last ctime change when the permissions were set + // So the order should be 2 4 3 1 + #[cfg(unix)] + { + let result = scene.ucmd().arg("-tc").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + assert_eq!(result.stdout, "test-2\ntest-4\ntest-3\ntest-1\n"); + } } #[test] From fd957dd1487c1d25d733ae655cc945c339337145 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 15 Mar 2021 14:09:29 +0100 Subject: [PATCH 047/104] ls: fix access time on windows --- tests/by-util/test_ls.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index e8ef18966..fea912695 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -292,8 +292,11 @@ fn test_ls_order_time() { assert!(result.success); #[cfg(not(windows))] assert_eq!(result.stdout, "test-3\ntest-4\ntest-2\ntest-1\n"); + + // Access time does not seem to be set on Windows on read call + // so the order is 4 3 2 1 #[cfg(windows)] - assert_eq!(result.stdout, "test-3 test-4 test-2 test-1\n"); + assert_eq!(result.stdout, "test-4 test-3 test-2 test-1\n"); // test-2 had the last ctime change when the permissions were set // So the order should be 2 4 3 1 From 13e61c3234234a2614d172a3236bf0e691548c68 Mon Sep 17 00:00:00 2001 From: Peter Sherman Date: Mon, 15 Mar 2021 15:56:11 +0000 Subject: [PATCH 048/104] head: add support for -z/--zero-terminated --- src/uu/head/src/head.rs | 15 +++++++++++++-- tests/by-util/test_head.rs | 8 ++++++++ tests/fixtures/head/zero_terminated.expected | Bin 0 -> 182 bytes tests/fixtures/head/zero_terminated.txt | Bin 0 -> 7605 bytes 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/head/zero_terminated.expected create mode 100644 tests/fixtures/head/zero_terminated.txt diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 56d7e8452..9e92dd8c7 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -29,6 +29,7 @@ enum FilterMode { struct Settings { mode: FilterMode, verbose: bool, + zero_terminated: bool, } impl Default for Settings { @@ -36,6 +37,7 @@ impl Default for Settings { Settings { mode: FilterMode::Lines(10), verbose: false, + zero_terminated: false, } } } @@ -69,6 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) .optflag("q", "quiet", "never print headers giving file names") .optflag("v", "verbose", "always print headers giving file names") + .optflag("z", "zero-terminated", "line delimiter is NUL, not newline") .optflag("h", "help", "display this help and exit") .optflag("V", "version", "output version information and exit") .parse(new_args); @@ -113,6 +116,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let quiet = matches.opt_present("q"); let verbose = matches.opt_present("v"); + settings.zero_terminated = matches.opt_present("z"); let files = matches.free; // GNU implementation allows multiple declarations of "-q" and "-v" with the @@ -129,6 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.verbose = true; } + if files.is_empty() { let mut buffer = BufReader::new(stdin()); head(&mut buffer, &settings); @@ -203,8 +208,14 @@ fn head(reader: &mut BufReader, settings: &Settings) -> bool { } } FilterMode::Lines(count) => { - for line in reader.lines().take(count) { - println!("{}", line.unwrap()); + if settings.zero_terminated { + for line in reader.split(0).take(count) { + print!("{}\0", String::from_utf8(line.unwrap()).unwrap()) + } + } else { + for line in reader.lines().take(count) { + println!("{}", line.unwrap()); + } } } FilterMode::NLines(count) => { diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 4324290cb..eec82b51f 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -86,6 +86,14 @@ fn test_verbose() { .stdout_is_fixture("lorem_ipsum_verbose.expected"); } +#[test] +fn test_zero_terminated() { + new_ucmd!() + .args(&["-z", "zero_terminated.txt"]) + .run() + .stdout_is_fixture("zero_terminated.expected"); +} + #[test] #[ignore] fn test_spams_newline() { diff --git a/tests/fixtures/head/zero_terminated.expected b/tests/fixtures/head/zero_terminated.expected new file mode 100644 index 0000000000000000000000000000000000000000..3c4cc058c3e55c81d16ec3ab18981d16fdb67cf1 GIT binary patch literal 182 zcmY+5Q4Yc|3`2LKR6>I5RG~Exk)~CfX*)h_LKFM4{cID3i73!hoZY+JXUEt`V*g3F zTyNy-5n0=d|AAG*qGCAUd(35jZzixxLKKTjwT!N8`LqmLJ4%%H9iNLbt4mYFhbRVN JZAgwheE{GdJ+S}) literal 0 HcmV?d00001 diff --git a/tests/fixtures/head/zero_terminated.txt b/tests/fixtures/head/zero_terminated.txt new file mode 100644 index 0000000000000000000000000000000000000000..6c7968122e22adf98694d530a1e23750b288616c GIT binary patch literal 7605 zcmb7J+nVDh4DJg#-eh+A%@gzq+G{>+Dxd#eh*5{TffL9(K7$6v9RkB(bZUoSfdNW)H3nq^8T;l+sguV?vCi<0|0NZ_g_r)MoQ zbAn#TGBNM0Xl+9szX(-~JV%@C=}BbWh+5#uVdj8ng@6{~x2EcmFX!rEuO{nJ->^vrG#xln1Cl2q&n-`Nw(uRej6SCH3;a1PGC!EtHM2f96fSFvYD ztrquqXKTnPu#o+LeaE19m1wn}Us4jpVHe7~4|LS%NytAEN&HLP!r)RSz~m=70z>^t z6@+$_eZ;~c4F|kqyRRAxSfwWln>)vNj-gGFtSE;GT&o4YA6ylJp@x>DLYFFtNww@f z`cXLJIF=fVHJ$wYK_DgVAjyrcPU!S7cqr=MJH#!_F25vn0L}bX6Bv7+5i?$ z><;tDuQh!#)r>kn_nfBxm>LOG(Sb`iVp9+C@5m)?WE^X1&Df7>pt%vpYv}~{2f_c# zW#JM5O&6D%Nxkg_on*;x&)|K}6q5#eA2=1WiIo6(d{zj-vQwW?F zTVu`#P0n#49X=3Km0S|vO30rX5hKs?=E7<4^p&kZ%xwkY(aH>5Y{Zhv%zYwYT($3Vfyk1)1jeBd}yZCLuK zZ)Om9d_qT;t%QSR+%V0_cs1rxLo^_Ma4F!LHUie2UnM*Q5a=GrD8>6D#p2E~&i_y1WL!x8l)IB0s4hc5w!16LhO2z5Gz+k3C|g&Q{Q`$~M7dX-n5mx`eLaCmmwrc+U0L@(9i zX2PCflaP@S!TvoQ?qx@>;mB@uM-HC7BV_O4JMlM9-^eLEv?EzR_2(<24s&GH12$$2 zb^QYj>E*B^lj6gwBo_f8dL$gqJ_IF5Blvcy;Q+$_Qe;U|03F$bZr3>#Gho?A2D0$anCjwmMWB%jiRdDUXBq+?H0FF1fZ=rWV?P%A1pO1*Q==-N?(_Aev$$e};cMXLr z+OqyGmEgB!)u9S}{HPX>AA7pixNX>DRrqox@2fqk^}sDuW1tRW^MpH5d!Mz{|NQ+I M`G5x+?XQdf1Dc4iZ~y=R literal 0 HcmV?d00001 From e3e5bf0178432234c4cc2d8d03ec7df6bee5bc64 Mon Sep 17 00:00:00 2001 From: Peter Sherman Date: Mon, 15 Mar 2021 20:48:49 +0000 Subject: [PATCH 049/104] Format head.rs --- src/uu/head/src/head.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 9e92dd8c7..ae5807c22 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -133,7 +133,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.verbose = true; } - if files.is_empty() { let mut buffer = BufReader::new(stdin()); head(&mut buffer, &settings); From c5792a4c477fdf80b3932935e452615b225832e1 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 17 Mar 2021 23:15:03 +0100 Subject: [PATCH 050/104] tests/ls: add tests for colors --- tests/by-util/test_ls.rs | 51 ++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index fea912695..52678b2fa 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -374,19 +374,56 @@ fn test_ls_recursive() { assert!(result.stdout.contains("a\\b:\nb")); } +#[cfg(unix)] #[test] fn test_ls_ls_color() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir("a"); + at.mkdir("a/nested_dir"); at.mkdir("z"); - at.touch(&at.plus_as_string("a/a")); - scene.ucmd().arg("--color").succeeds(); - scene.ucmd().arg("--color=always").succeeds(); - scene.ucmd().arg("--color=never").succeeds(); - scene.ucmd().arg("--color").arg("a").succeeds(); - scene.ucmd().arg("--color=always").arg("a/a").succeeds(); - scene.ucmd().arg("--color=never").arg("z").succeeds(); + at.touch(&at.plus_as_string("a/nested_file")); + at.touch("test-color"); + + let a_with_colors = "\x1b[01;34ma\x1b[0m"; + let z_with_colors = "\x1b[01;34mz\x1b[0m"; + let nested_dir_with_colors = "\x1b[01;34mnested_dir\x1b[0m"; + + // Color is disabled by default + let result = scene.ucmd().succeeds(); + assert!(!result.stdout.contains(a_with_colors)); + assert!(!result.stdout.contains(z_with_colors)); + + // Color should be enabled + let result = scene.ucmd().arg("--color").succeeds(); + assert!(result.stdout.contains(a_with_colors)); + assert!(result.stdout.contains(z_with_colors)); + + // Color should be enabled + let result = scene.ucmd().arg("--color=always").succeeds(); + assert!(result.stdout.contains(a_with_colors)); + assert!(result.stdout.contains(z_with_colors)); + + // Color should be disabled + let result = scene.ucmd().arg("--color=never").succeeds(); + assert!(!result.stdout.contains(a_with_colors)); + assert!(!result.stdout.contains(z_with_colors)); + + // Nested dir should be shown and colored + let result = scene.ucmd().arg("--color").arg("a").succeeds(); + assert!(result.stdout.contains(nested_dir_with_colors)); + + // Color has no effect + let result = scene + .ucmd() + .arg("--color=always") + .arg("a/nested_file") + .succeeds(); + assert!(result.stdout.contains("a/nested_file\n")); + + // No output + let result = scene.ucmd().arg("--color=never").arg("z").succeeds(); + assert_eq!(result.stdout, ""); } #[cfg(not(any(target_os = "macos", target_os = "windows")))] // Truncate not available on mac or win From e5b577fb27c33c8c539dfbaf3b050e71e47fb7e2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 18 Mar 2021 10:13:29 +0100 Subject: [PATCH 051/104] Update cargo.lock --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9dcc23ff2..78aef58bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,7 +193,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -404,7 +404,7 @@ dependencies = [ "oorandom 11.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "plotters 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", "serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", @@ -517,7 +517,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1035,7 +1035,7 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.4" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1471,7 +1471,7 @@ version = "0.0.4" dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -1557,7 +1557,7 @@ dependencies = [ name = "uu_expand" version = "0.0.4" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -1634,7 +1634,7 @@ dependencies = [ "hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1838,7 +1838,7 @@ dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -1939,7 +1939,7 @@ dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -2598,7 +2598,7 @@ dependencies = [ "checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" "checksum redox_syscall 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" "checksum redox_termios 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" -"checksum regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "54fd1046a3107eb58f42de31d656fee6853e5d276c455fd943742dce89fc3dd3" +"checksum regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" "checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" "checksum regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)" = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" "checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" From 1d271991af968e0884bfef4e91f9b8d3753915b0 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 18 Mar 2021 10:24:30 +0100 Subject: [PATCH 052/104] Rustfmt new tests --- tests/by-util/test_cat.rs | 22 +++++++++++----------- tests/by-util/test_nice.rs | 4 +++- tests/by-util/test_nl.rs | 16 ++++++++-------- tests/by-util/test_rm.rs | 2 +- tests/by-util/test_rmdir.rs | 8 ++++---- tests/by-util/test_unlink.rs | 6 +++--- tests/by-util/test_wc.rs | 2 +- 7 files changed, 31 insertions(+), 29 deletions(-) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index a3e321139..b194eb9b0 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -10,16 +10,16 @@ fn test_output_multi_files_print_all_chars() { .succeeds() .stdout_only( " 1\tabcde$\n 2\tfghij$\n 3\tklmno$\n 4\tpqrst$\n \ - 5\tuvwxyz$\n 6\t^@^A^B^C^D^E^F^G^H^I$\n \ - 7\t^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\\^]^^^_ \ - !\"#$%&\'()*+,-./0123456789:;\ - <=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~^?M-^@M-^AM-^\ - BM-^CM-^DM-^EM-^FM-^GM-^HM-^IM-^JM-^KM-^LM-^MM-^NM-^OM-^PM-^QM-^RM-^SM-^TM-^UM-^V\ - M-^WM-^XM-^YM-^ZM-^[M-^\\M-^]M-^^M-^_M- \ - M-!M-\"M-#M-$M-%M-&M-\'M-(M-)M-*M-+M-,M--M-.M-/M-0M-1M-2M-3M-4M-5M-6M-7M-8M-9M-:\ - M-;M-M-?M-@M-AM-BM-CM-DM-EM-FM-GM-HM-IM-JM-KM-LM-MM-NM-OM-PM-QM-RM-SM-TM-U\ - M-VM-WM-XM-YM-ZM-[M-\\M-]M-^M-_M-`M-aM-bM-cM-dM-eM-fM-gM-hM-iM-jM-kM-lM-mM-nM-oM-\ - pM-qM-rM-sM-tM-uM-vM-wM-xM-yM-zM-{M-|M-}M-~M-^?", + 5\tuvwxyz$\n 6\t^@^A^B^C^D^E^F^G^H^I$\n \ + 7\t^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\\^]^^^_ \ + !\"#$%&\'()*+,-./0123456789:;\ + <=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~^?M-^@M-^AM-^\ + BM-^CM-^DM-^EM-^FM-^GM-^HM-^IM-^JM-^KM-^LM-^MM-^NM-^OM-^PM-^QM-^RM-^SM-^TM-^UM-^V\ + M-^WM-^XM-^YM-^ZM-^[M-^\\M-^]M-^^M-^_M- \ + M-!M-\"M-#M-$M-%M-&M-\'M-(M-)M-*M-+M-,M--M-.M-/M-0M-1M-2M-3M-4M-5M-6M-7M-8M-9M-:\ + M-;M-M-?M-@M-AM-BM-CM-DM-EM-FM-GM-HM-IM-JM-KM-LM-MM-NM-OM-PM-QM-RM-SM-TM-U\ + M-VM-WM-XM-YM-ZM-[M-\\M-]M-^M-_M-`M-aM-bM-cM-dM-eM-fM-gM-hM-iM-jM-kM-lM-mM-nM-oM-\ + pM-qM-rM-sM-tM-uM-vM-wM-xM-yM-zM-{M-|M-}M-~M-^?", ); } @@ -30,7 +30,7 @@ fn test_numbered_lines_no_trailing_newline() { .succeeds() .stdout_only( " 1\ttext without a trailing newlineabcde\n 2\tfghij\n \ - 3\tklmno\n 4\tpqrst\n 5\tuvwxyz\n", + 3\tklmno\n 4\tpqrst\n 5\tuvwxyz\n", ); } diff --git a/tests/by-util/test_nice.rs b/tests/by-util/test_nice.rs index e10314f57..7e704fc00 100644 --- a/tests/by-util/test_nice.rs +++ b/tests/by-util/test_nice.rs @@ -15,7 +15,9 @@ fn test_negative_adjustment() { // correctly. let res = new_ucmd!().args(&["-n", "-1", "true"]).run(); - assert!(res.stderr.starts_with("nice: warning: setpriority: Permission denied")); + assert!(res + .stderr + .starts_with("nice: warning: setpriority: Permission denied")); } #[test] diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index fca73c37b..3ca039d19 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -23,8 +23,8 @@ fn test_padding_without_overflow() { .run() .stdout_is( "000001xL1\n001001xL2\n002001xL3\n003001xL4\n004001xL5\n005001xL6\n006001xL7\n0070\ - 01xL8\n008001xL9\n009001xL10\n010001xL11\n011001xL12\n012001xL13\n013001xL14\n014\ - 001xL15\n", + 01xL8\n008001xL9\n009001xL10\n010001xL11\n011001xL12\n012001xL13\n013001xL14\n014\ + 001xL15\n", ); } @@ -35,7 +35,7 @@ fn test_padding_with_overflow() { .run() .stdout_is( "0001xL1\n1001xL2\n2001xL3\n3001xL4\n4001xL5\n5001xL6\n6001xL7\n7001xL8\n8001xL9\n\ - 9001xL10\n10001xL11\n11001xL12\n12001xL13\n13001xL14\n14001xL15\n", + 9001xL10\n10001xL11\n11001xL12\n12001xL13\n13001xL14\n14001xL15\n", ); } @@ -45,15 +45,15 @@ fn test_sections_and_styles() { ( "section.txt", "\nHEADER1\nHEADER2\n\n1 |BODY1\n2 \ - |BODY2\n\nFOOTER1\nFOOTER2\n\nNEXTHEADER1\nNEXTHEADER2\n\n1 \ - |NEXTBODY1\n2 |NEXTBODY2\n\nNEXTFOOTER1\nNEXTFOOTER2\n", + |BODY2\n\nFOOTER1\nFOOTER2\n\nNEXTHEADER1\nNEXTHEADER2\n\n1 \ + |NEXTBODY1\n2 |NEXTBODY2\n\nNEXTFOOTER1\nNEXTFOOTER2\n", ), ( "joinblanklines.txt", "1 |Nonempty\n2 |Nonempty\n3 |Followed by 10x empty\n\n\n\n\n4 \ - |\n\n\n\n\n5 |\n6 |Followed by 5x empty\n\n\n\n\n7 |\n8 \ - |Followed by 4x empty\n\n\n\n\n9 |Nonempty\n10 |Nonempty\n11 \ - |Nonempty.\n", + |\n\n\n\n\n5 |\n6 |Followed by 5x empty\n\n\n\n\n7 |\n8 \ + |Followed by 4x empty\n\n\n\n\n9 |Nonempty\n10 |Nonempty\n11 \ + |Nonempty.\n", ), ] { new_ucmd!() diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 27568957a..c3635d202 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -148,7 +148,7 @@ fn test_rm_errors() { // rm: error: could not remove directory 'test_rm_errors_directory' (did you mean to pass '-r'?) ucmd.arg(dir).fails().stderr_is( "rm: error: could not remove directory 'test_rm_errors_directory' (did you mean \ - to pass '-r' or '-R'?)\n", + to pass '-r' or '-R'?)\n", ); } diff --git a/tests/by-util/test_rmdir.rs b/tests/by-util/test_rmdir.rs index 5f87b5af6..34531cf22 100644 --- a/tests/by-util/test_rmdir.rs +++ b/tests/by-util/test_rmdir.rs @@ -40,7 +40,7 @@ fn test_rmdir_nonempty_directory_no_parents() { ucmd.arg(dir).fails().stderr_is( "rmdir: error: failed to remove 'test_rmdir_nonempty_no_parents': Directory not \ - empty\n", + empty\n", ); assert!(at.dir_exists(dir)); @@ -60,9 +60,9 @@ fn test_rmdir_nonempty_directory_with_parents() { ucmd.arg("-p").arg(dir).fails().stderr_is( "rmdir: error: failed to remove 'test_rmdir_nonempty/with/parents': Directory not \ - empty\nrmdir: error: failed to remove 'test_rmdir_nonempty/with': Directory not \ - empty\nrmdir: error: failed to remove 'test_rmdir_nonempty': Directory not \ - empty\n", + empty\nrmdir: error: failed to remove 'test_rmdir_nonempty/with': Directory not \ + empty\nrmdir: error: failed to remove 'test_rmdir_nonempty': Directory not \ + empty\n", ); assert!(at.dir_exists(dir)); diff --git a/tests/by-util/test_unlink.rs b/tests/by-util/test_unlink.rs index daac6d3b3..fa8f962c4 100644 --- a/tests/by-util/test_unlink.rs +++ b/tests/by-util/test_unlink.rs @@ -23,7 +23,7 @@ fn test_unlink_multiple_files() { ucmd.arg(file_a).arg(file_b).fails().stderr_is( "unlink: error: extra operand: 'test_unlink_multiple_file_b'\nTry 'unlink --help' \ - for more information.\n", + for more information.\n", ); } @@ -36,7 +36,7 @@ fn test_unlink_directory() { ucmd.arg(dir).fails().stderr_is( "unlink: error: cannot unlink 'test_unlink_empty_directory': Not a regular file \ - or symlink\n", + or symlink\n", ); } @@ -46,6 +46,6 @@ fn test_unlink_nonexistent() { new_ucmd!().arg(file).fails().stderr_is( "unlink: error: Cannot stat 'test_unlink_nonexistent': No such file or directory \ - (os error 2)\n", + (os error 2)\n", ); } diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index fc9c00ecc..b064d7e0e 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -81,6 +81,6 @@ fn test_multiple_default() { .run() .stdout_is( " 13 109 772 lorem_ipsum.txt\n 18 204 1115 moby_dick.txt\n 5 57 302 \ - alice_in_wonderland.txt\n 36 370 2189 total\n", + alice_in_wonderland.txt\n 36 370 2189 total\n", ); } From ed7e24c5b03fad4c020af2f67e7f7cba9d9da29e Mon Sep 17 00:00:00 2001 From: aspen Date: Wed, 17 Mar 2021 21:29:32 -0400 Subject: [PATCH 053/104] uu_more: update nix to 0.13 --- Cargo.lock | 40 +++++++++++----------------------------- src/uu/more/Cargo.toml | 6 +++--- src/uu/more/src/more.rs | 22 +++++++++++----------- 3 files changed, 25 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9dcc23ff2..9d9fb6c24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,11 +59,6 @@ name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "bitflags" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "bitflags" version = "1.2.1" @@ -193,7 +188,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -404,7 +399,7 @@ dependencies = [ "oorandom 11.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "plotters 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", "serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", @@ -517,7 +512,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -726,17 +721,6 @@ dependencies = [ "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "nix" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "nix" version = "0.13.1" @@ -1035,7 +1019,7 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.4" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1471,7 +1455,7 @@ version = "0.0.4" dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -1557,7 +1541,7 @@ dependencies = [ name = "uu_expand" version = "0.0.4" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -1634,7 +1618,7 @@ dependencies = [ "hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1802,7 +1786,7 @@ name = "uu_more" version = "0.0.4" dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", @@ -1838,7 +1822,7 @@ dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -1939,7 +1923,7 @@ dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -2490,7 +2474,6 @@ dependencies = [ "checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" "checksum bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" "checksum bit-vec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" -"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" "checksum block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814" @@ -2561,7 +2544,6 @@ dependencies = [ "checksum memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" "checksum memoffset 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" -"checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487" "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" "checksum num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" "checksum num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" @@ -2598,7 +2580,7 @@ dependencies = [ "checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" "checksum redox_syscall 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" "checksum redox_termios 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" -"checksum regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "54fd1046a3107eb58f42de31d656fee6853e5d276c455fd943742dce89fc3dd3" +"checksum regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" "checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" "checksum regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)" = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" "checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 3c1746bd4..acd9378b2 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -16,15 +16,15 @@ path = "src/more.rs" [dependencies] getopts = "0.2.18" -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" } +uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" } [target.'cfg(target_os = "redox")'.dependencies] redox_termios = "0.1" redox_syscall = "0.1" [target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies] -nix = "0.8.1" +nix = "<=0.13" [[bin]] name = "more" diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index de4e10187..524b0fbc4 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -17,7 +17,7 @@ use std::io::{stdout, Read, Write}; #[cfg(all(unix, not(target_os = "fuchsia")))] extern crate nix; #[cfg(all(unix, not(target_os = "fuchsia")))] -use nix::sys::termios; +use nix::sys::termios::{self, LocalFlags, SetArg}; #[cfg(target_os = "redox")] extern crate redox_termios; @@ -92,10 +92,10 @@ fn help(usage: &str) { fn setup_term() -> termios::Termios { let mut term = termios::tcgetattr(0).unwrap(); // Unset canonical mode, so we get characters immediately - term.c_lflag.remove(termios::ICANON); + term.local_flags.remove(LocalFlags::ICANON); // Disable local echo - term.c_lflag.remove(termios::ECHO); - termios::tcsetattr(0, termios::TCSADRAIN, &term).unwrap(); + term.local_flags.remove(LocalFlags::ECHO); + termios::tcsetattr(0, SetArg::TCSADRAIN, &term).unwrap(); term } @@ -110,8 +110,8 @@ fn setup_term() -> redox_termios::Termios { let mut term = redox_termios::Termios::default(); let fd = syscall::dup(0, b"termios").unwrap(); syscall::read(fd, &mut term).unwrap(); - term.c_lflag &= !redox_termios::ICANON; - term.c_lflag &= !redox_termios::ECHO; + term.local_flags &= !redox_termios::ICANON; + term.local_flags &= !redox_termios::ECHO; syscall::write(fd, &term).unwrap(); let _ = syscall::close(fd); term @@ -119,9 +119,9 @@ fn setup_term() -> redox_termios::Termios { #[cfg(all(unix, not(target_os = "fuchsia")))] fn reset_term(term: &mut termios::Termios) { - term.c_lflag.insert(termios::ICANON); - term.c_lflag.insert(termios::ECHO); - termios::tcsetattr(0, termios::TCSADRAIN, &term).unwrap(); + term.local_flags.insert(LocalFlags::ICANON); + term.local_flags.insert(LocalFlags::ECHO); + termios::tcsetattr(0, SetArg::TCSADRAIN, &term).unwrap(); } #[cfg(any(windows, target_os = "fuchsia"))] @@ -132,8 +132,8 @@ fn reset_term(_: &mut usize) {} fn reset_term(term: &mut redox_termios::Termios) { let fd = syscall::dup(0, b"termios").unwrap(); syscall::read(fd, term).unwrap(); - term.c_lflag |= redox_termios::ICANON; - term.c_lflag |= redox_termios::ECHO; + term.local_flags |= redox_termios::ICANON; + term.local_flags |= redox_termios::ECHO; syscall::write(fd, &term).unwrap(); let _ = syscall::close(fd); } From 4e29b693f89907cc7cf401565dcd6d355a34bf55 Mon Sep 17 00:00:00 2001 From: aspen Date: Wed, 17 Mar 2021 21:32:34 -0400 Subject: [PATCH 054/104] uutils: change every `target_os = "macos"` to `target_vendor = "apple"` --- Cargo.lock | 20 ++++++++++---------- src/uu/chroot/src/chroot.rs | 2 +- src/uu/df/src/df.rs | 22 +++++++++++----------- src/uu/id/src/id.rs | 2 +- src/uu/nohup/src/nohup.rs | 2 +- src/uu/nproc/src/nproc.rs | 6 +++--- src/uu/stat/src/fsext.rs | 20 ++++++++++---------- src/uu/stdbuf/build.rs | 4 ++-- src/uu/stdbuf/src/stdbuf.rs | 4 ++-- src/uu/uname/src/uname.rs | 2 +- src/uu/who/src/who.rs | 14 ++------------ src/uucore/src/lib/features/entries.rs | 8 ++++---- src/uucore/src/lib/features/signals.rs | 2 +- src/uucore/src/lib/features/utmpx.rs | 4 ++-- tests/by-util/test_chgrp.rs | 2 +- tests/by-util/test_du.rs | 20 ++++++++++---------- tests/by-util/test_hostname.rs | 2 +- tests/by-util/test_ls.rs | 2 +- 18 files changed, 64 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9dcc23ff2..78aef58bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,7 +193,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -404,7 +404,7 @@ dependencies = [ "oorandom 11.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "plotters 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", "serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", @@ -517,7 +517,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1035,7 +1035,7 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.4" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1471,7 +1471,7 @@ version = "0.0.4" dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -1557,7 +1557,7 @@ dependencies = [ name = "uu_expand" version = "0.0.4" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -1634,7 +1634,7 @@ dependencies = [ "hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1838,7 +1838,7 @@ dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -1939,7 +1939,7 @@ dependencies = [ "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -2598,7 +2598,7 @@ dependencies = [ "checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" "checksum redox_syscall 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" "checksum redox_termios 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" -"checksum regex 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "54fd1046a3107eb58f42de31d656fee6853e5d276c455fd943742dce89fc3dd3" +"checksum regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" "checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" "checksum regex-syntax 0.6.23 (registry+https://github.com/rust-lang/crates.io-index)" = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" "checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 773207363..ab654abf8 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -164,7 +164,7 @@ fn set_main_group(group: &str) { } } -#[cfg(any(target_os = "macos", target_os = "freebsd"))] +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] fn set_groups(groups: Vec) -> libc::c_int { unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) } } diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index ed2865728..57caf7970 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -32,15 +32,15 @@ use std::ffi::CString; #[cfg(unix)] use std::mem; -#[cfg(any(target_os = "macos", target_os = "freebsd"))] +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] use libc::c_int; -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] use libc::statfs; -#[cfg(any(target_os = "macos", target_os = "freebsd"))] +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] use std::ffi::CStr; -#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "windows"))] +#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "windows"))] use std::ptr; -#[cfg(any(target_os = "macos", target_os = "freebsd"))] +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] use std::slice; #[cfg(target_os = "freebsd")] @@ -137,7 +137,7 @@ struct MountInfo { #[cfg(all( target_os = "freebsd", - not(all(target_os = "macos", target_arch = "x86_64")) + not(all(target_vendor = "apple", target_arch = "x86_64")) ))] #[repr(C)] #[derive(Copy, Clone)] @@ -209,20 +209,20 @@ fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } -#[cfg(any(target_os = "freebsd", target_os = "macos"))] +#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] extern "C" { - #[cfg(all(target_os = "macos", target_arch = "x86_64"))] + #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] #[link_name = "getmntinfo$INODE64"] fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; #[cfg(any( all(target_os = "freebsd"), - all(target_os = "macos", target_arch = "aarch64") + all(target_vendor = "apple", target_arch = "aarch64") ))] fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; } -#[cfg(any(target_os = "freebsd", target_os = "macos"))] +#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] impl From for MountInfo { fn from(statfs: statfs) -> Self { let mut info = MountInfo { @@ -585,7 +585,7 @@ fn read_fs_list() -> Vec { }) .collect::>() } - #[cfg(any(target_os = "freebsd", target_os = "macos"))] + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] { let mut mptr: *mut statfs = ptr::null_mut(); let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) }; diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index f07a850fa..4536622c7 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -291,7 +291,7 @@ fn pretty(possible_pw: Option) { } } -#[cfg(any(target_os = "macos", target_os = "freebsd"))] +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] fn pline(possible_uid: Option) { let uid = possible_uid.unwrap_or_else(getuid); let pw = Passwd::locate(uid).unwrap(); diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 67e281e38..5fce208da 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -23,7 +23,7 @@ use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interact static NAME: &str = "nohup"; static VERSION: &str = env!("CARGO_PKG_VERSION"); -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] extern "C" { fn _vprocmgr_detach_from_console(flags: u32) -> *const libc::c_int; } diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 4eb538618..285cf764f 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -15,7 +15,7 @@ use std::env; #[cfg(target_os = "linux")] pub const _SC_NPROCESSORS_CONF: libc::c_int = 83; -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] pub const _SC_NPROCESSORS_CONF: libc::c_int = libc::_SC_NPROCESSORS_CONF; #[cfg(target_os = "freebsd")] pub const _SC_NPROCESSORS_CONF: libc::c_int = 57; @@ -89,7 +89,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { #[cfg(any( target_os = "linux", - target_os = "macos", + target_vendor = "apple", target_os = "freebsd", target_os = "netbsd" ))] @@ -109,7 +109,7 @@ fn num_cpus_all() -> usize { // Other platforms (e.g., windows), num_cpus::get() directly. #[cfg(not(any( target_os = "linux", - target_os = "macos", + target_vendor = "apple", target_os = "freebsd", target_os = "netbsd" )))] diff --git a/src/uu/stat/src/fsext.rs b/src/uu/stat/src/fsext.rs index 11c8f8095..d90099892 100644 --- a/src/uu/stat/src/fsext.rs +++ b/src/uu/stat/src/fsext.rs @@ -149,7 +149,7 @@ use std::path::Path; #[cfg(any( target_os = "linux", - target_os = "macos", + target_vendor = "apple", target_os = "android", target_os = "freebsd" ))] @@ -165,7 +165,7 @@ use uucore::libc::statvfs as Sstatfs; #[cfg(any( target_os = "linux", - target_os = "macos", + target_vendor = "apple", target_os = "android", target_os = "freebsd" ))] @@ -211,11 +211,11 @@ impl FsMeta for Sstatfs { fn free_fnodes(&self) -> u64 { self.f_ffree as u64 } - #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] + #[cfg(any(target_os = "linux", target_vendor = "apple", target_os = "freebsd"))] fn fs_type(&self) -> i64 { self.f_type as i64 } - #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))] + #[cfg(not(any(target_os = "linux", target_vendor = "apple", target_os = "freebsd")))] fn fs_type(&self) -> i64 { // FIXME: statvfs doesn't have an equivalent, so we need to do something else unimplemented!() @@ -225,12 +225,12 @@ impl FsMeta for Sstatfs { fn iosize(&self) -> u64 { self.f_frsize as u64 } - #[cfg(any(target_os = "macos", target_os = "freebsd"))] + #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] fn iosize(&self) -> u64 { self.f_iosize as u64 } // XXX: dunno if this is right - #[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "linux")))] + #[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))] fn iosize(&self) -> u64 { self.f_bsize as u64 } @@ -241,13 +241,13 @@ impl FsMeta for Sstatfs { // // Solaris, Irix and POSIX have a system call statvfs(2) that returns a // struct statvfs, containing an unsigned long f_fsid - #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "linux"))] + #[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux"))] fn fsid(&self) -> u64 { let f_fsid: &[u32; 2] = unsafe { &*(&self.f_fsid as *const uucore::libc::fsid_t as *const [u32; 2]) }; (u64::from(f_fsid[0])) << 32 | u64::from(f_fsid[1]) } - #[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "linux")))] + #[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))] fn fsid(&self) -> u64 { self.f_fsid as u64 } @@ -256,7 +256,7 @@ impl FsMeta for Sstatfs { fn namelen(&self) -> u64 { self.f_namelen as u64 } - #[cfg(target_os = "macos")] + #[cfg(target_vendor = "apple")] fn namelen(&self) -> u64 { 1024 } @@ -265,7 +265,7 @@ impl FsMeta for Sstatfs { self.f_namemax as u64 } // XXX: should everything just use statvfs? - #[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "linux")))] + #[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))] fn namelen(&self) -> u64 { self.f_namemax as u64 } diff --git a/src/uu/stdbuf/build.rs b/src/uu/stdbuf/build.rs index c005072d9..b14d503cf 100644 --- a/src/uu/stdbuf/build.rs +++ b/src/uu/stdbuf/build.rs @@ -4,12 +4,12 @@ use std::env; use std::fs; use std::path::Path; -#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows")))] +#[cfg(not(any(target_vendor = "apple", target_os = "windows")))] mod platform { pub const DYLIB_EXT: &str = ".so"; } -#[cfg(any(target_os = "macos", target_os = "ios"))] +#[cfg(any(target_vendor = "apple"))] mod platform { pub const DYLIB_EXT: &str = ".dylib"; } diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 86523144c..67ed9a838 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -57,7 +57,7 @@ fn preload_strings() -> (&'static str, &'static str) { ("LD_PRELOAD", "so") } -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] fn preload_strings() -> (&'static str, &'static str) { ("DYLD_LIBRARY_PATH", "dylib") } @@ -67,7 +67,7 @@ fn preload_strings() -> (&'static str, &'static str) { target_os = "freebsd", target_os = "netbsd", target_os = "dragonflybsd", - target_os = "macos" + target_vendor = "apple" )))] fn preload_strings() -> (&'static str, &'static str) { crash!(1, "Command not supported for this operating system!") diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 6575aa9fd..4586a084f 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -39,7 +39,7 @@ const HOST_OS: &str = "Windows NT"; const HOST_OS: &str = "FreeBSD"; #[cfg(target_os = "openbsd")] const HOST_OS: &str = "OpenBSD"; -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] const HOST_OS: &str = "Darwin"; #[cfg(target_os = "fuchsia")] const HOST_OS: &str = "Fuchsia"; diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index b028be0a0..8c7ff3211 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -60,12 +60,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { "count", "all login names and number of users logged on", ); - #[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "linux", - target_os = "android" - ))] + #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] opts.optflag("r", "runlevel", "print current runlevel"); opts.optflag("s", "short", "print only name, line, and time (default)"); opts.optflag("t", "time", "print last system clock change"); @@ -305,12 +300,7 @@ impl Who { #[allow(unused_assignments)] let mut res = false; - #[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "linux", - target_os = "android" - ))] + #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] { res = record == utmpx::RUN_LVL; } diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index a921af2d0..d2dce2461 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -34,7 +34,7 @@ //! assert!(entries::Group::locate(root_group).is_ok()); //! ``` -#[cfg(any(target_os = "freebsd", target_os = "macos"))] +#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] use libc::time_t; use libc::{c_char, c_int, gid_t, uid_t}; use libc::{getgrgid, getgrnam, getgroups, getpwnam, getpwuid, group, passwd}; @@ -119,19 +119,19 @@ impl Passwd { } /// AKA passwd.pw_class - #[cfg(any(target_os = "freebsd", target_os = "macos"))] + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] pub fn user_access_class(&self) -> Cow { cstr2cow!(self.inner.pw_class) } /// AKA passwd.pw_change - #[cfg(any(target_os = "freebsd", target_os = "macos"))] + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] pub fn passwd_change_time(&self) -> time_t { self.inner.pw_change } /// AKA passwd.pw_expire - #[cfg(any(target_os = "freebsd", target_os = "macos"))] + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] pub fn expiration(&self) -> time_t { self.inner.pw_expire } diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 294669eab..d22fa1791 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -197,7 +197,7 @@ No Name Default Action Description */ -#[cfg(any(target_os = "macos", target_os = "freebsd"))] +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] pub static ALL_SIGNALS: [Signal<'static>; 31] = [ Signal { name: "HUP", diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 31cd3b72c..0308d8a5e 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -47,7 +47,7 @@ use libc::utmpx; pub use libc::endutxent; pub use libc::getutxent; pub use libc::setutxent; -#[cfg(any(target_os = "macos", target_os = "linux"))] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] pub use libc::utmpxname; #[cfg(target_os = "freebsd")] pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int { @@ -85,7 +85,7 @@ mod ut { pub use libc::USER_PROCESS; } -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] mod ut { pub static DEFAULT_FILE: &str = "/var/run/utmpx"; diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index d5afaf3a7..613f52fd2 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -115,7 +115,7 @@ fn test_reference() { } #[test] -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] fn test_reference() { new_ucmd!() .arg("-v") diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index c9704a658..a79f820fb 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -12,7 +12,7 @@ fn test_du_basics() { assert!(result.success); assert_eq!(result.stderr, ""); } -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] fn _du_basics(s: String) { let answer = "32\t./subdir 8\t./subdir/deeper @@ -21,7 +21,7 @@ fn _du_basics(s: String) { "; assert_eq!(s, answer); } -#[cfg(not(target_os = "macos"))] +#[cfg(not(target_vendor = "apple"))] fn _du_basics(s: String) { let answer = "28\t./subdir 8\t./subdir/deeper @@ -41,11 +41,11 @@ fn test_du_basics_subdir() { _du_basics_subdir(result.stdout); } -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] fn _du_basics_subdir(s: String) { assert_eq!(s, "4\tsubdir/deeper\n"); } -#[cfg(not(target_os = "macos"))] +#[cfg(not(target_vendor = "apple"))] fn _du_basics_subdir(s: String) { // MS-WSL linux has altered expected output if !is_wsl() { @@ -80,12 +80,12 @@ fn test_du_soft_link() { _du_soft_link(result.stdout); } -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] fn _du_soft_link(s: String) { // 'macos' host variants may have `du` output variation for soft links assert!((s == "12\tsubdir/links\n") || (s == "16\tsubdir/links\n")); } -#[cfg(not(target_os = "macos"))] +#[cfg(not(target_vendor = "apple"))] fn _du_soft_link(s: String) { // MS-WSL linux has altered expected output if !is_wsl() { @@ -109,11 +109,11 @@ fn test_du_hard_link() { _du_hard_link(result.stdout); } -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] fn _du_hard_link(s: String) { assert_eq!(s, "12\tsubdir/links\n") } -#[cfg(not(target_os = "macos"))] +#[cfg(not(target_vendor = "apple"))] fn _du_hard_link(s: String) { // MS-WSL linux has altered expected output if !is_wsl() { @@ -133,11 +133,11 @@ fn test_du_d_flag() { _du_d_flag(result.stdout); } -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] fn _du_d_flag(s: String) { assert_eq!(s, "16\t./subdir\n20\t./\n"); } -#[cfg(not(target_os = "macos"))] +#[cfg(not(target_vendor = "apple"))] fn _du_d_flag(s: String) { // MS-WSL linux has altered expected output if !is_wsl() { diff --git a/tests/by-util/test_hostname.rs b/tests/by-util/test_hostname.rs index a526ddd88..804d47642 100644 --- a/tests/by-util/test_hostname.rs +++ b/tests/by-util/test_hostname.rs @@ -11,7 +11,7 @@ fn test_hostname() { } // FixME: fails for "MacOS" -#[cfg(not(target_os = "macos"))] +#[cfg(not(target_vendor = "apple"))] #[test] fn test_hostname_ip() { let result = new_ucmd!().arg("-i").run(); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 422db8df9..a1c3d7569 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -285,7 +285,7 @@ fn test_ls_ls_color() { scene.ucmd().arg("--color=never").arg("z").succeeds(); } -#[cfg(not(any(target_os = "macos", target_os = "windows")))] // Truncate not available on mac or win +#[cfg(not(any(target_vendor = "apple", target_os = "windows")))] // Truncate not available on mac or win #[test] fn test_ls_human() { let scene = TestScenario::new(util_name!()); From 5ec87dc70a08399e4a029cf1f924d8780a9d613e Mon Sep 17 00:00:00 2001 From: Marco Satti Date: Fri, 19 Mar 2021 16:54:01 +0800 Subject: [PATCH 055/104] date: Implement setting the date on Unix & Windows (#1798) * date: implement set date for unix and windows Parsing the date string is not fully implemented yet, as in it relies on the internals of chrono - things like "Mon, 14 Aug 2006 02:34:56 -0600" do not work, nor does "2006-08-14 02:34:56" (no TZ / local time). This is no different to using the "--date" option however, and will get fixed when `parse_date` is a bit smarter. Only supports unix and Windows platforms for now. --- Cargo.lock | 2 + src/uu/date/Cargo.toml | 6 ++ src/uu/date/src/date.rs | 127 +++++++++++++++++++++++++++++++------ tests/by-util/test_date.rs | 41 ++++++++++++ 4 files changed, 157 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d9fb6c24..2ba6cf6a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1475,8 +1475,10 @@ version = "0.0.4" dependencies = [ "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index 4e3227f02..c62cfe2b3 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -20,6 +20,12 @@ clap = "2.33" uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +[target.'cfg(unix)'.dependencies] +libc = "0.2" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3", features = ["minwinbase", "sysinfoapi", "minwindef"] } + [[bin]] name = "date" path = "src/main.rs" diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 5ee4b1610..43573437d 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -12,13 +12,20 @@ #[macro_use] extern crate uucore; +use chrono::{DateTime, FixedOffset, Local, Offset, Utc}; +#[cfg(windows)] +use chrono::{Datelike, Timelike}; use clap::{App, Arg}; - -use chrono::offset::Utc; -use chrono::{DateTime, FixedOffset, Local, Offset}; +#[cfg(all(unix, not(target_os = "macos")))] +use libc::{clock_settime, timespec, CLOCK_REALTIME}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; +#[cfg(windows)] +use winapi::{ + shared::minwindef::WORD, + um::{minwinbase::SYSTEMTIME, sysinfoapi::SetSystemTime}, +}; // Options const DATE: &str = "date"; @@ -62,6 +69,11 @@ static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format. for date and time to the indicated precision. Example: 2006-08-14 02:34:56-06:00"; +#[cfg(not(target_os = "macos"))] +static OPT_SET_HELP_STRING: &str = "set time described by STRING"; +#[cfg(target_os = "macos")] +static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)"; + /// Settings for this program, parsed from the command line struct Settings { utc: bool, @@ -186,7 +198,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .short("s") .long(OPT_SET) .takes_value(true) - .help("set time described by STRING"), + .help(OPT_SET_HELP_STRING), ) .arg( Arg::with_name(OPT_UNIVERSAL) @@ -222,18 +234,31 @@ pub fn uumain(args: impl uucore::Args) -> i32 { DateSource::Now }; + let set_to = match matches.value_of(OPT_SET).map(parse_date) { + None => None, + Some(Err((input, _err))) => { + eprintln!("date: invalid date '{}'", input); + return 1; + } + Some(Ok(date)) => Some(date), + }; + let settings = Settings { utc: matches.is_present(OPT_UNIVERSAL), format, date_source, - // TODO: Handle this option: - set_to: None, + set_to, }; - if let Some(_time) = settings.set_to { - unimplemented!(); - // Probably need to use this syscall: - // https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html + if let Some(date) = settings.set_to { + // All set time functions expect UTC datetimes. + let date: DateTime = if settings.utc { + date.with_timezone(&Utc) + } else { + date.into() + }; + + return set_system_datetime(date); } else { // Declare a file here because it needs to outlive the `dates` iterator. let file: File; @@ -247,15 +272,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { now.with_timezone(now.offset()) }; - /// Parse a `String` into a `DateTime`. - /// If it fails, return a tuple of the `String` along with its `ParseError`. - fn parse_date( - s: String, - ) -> Result, (String, chrono::format::ParseError)> { - // TODO: The GNU date command can parse a wide variety of inputs. - s.parse().map_err(|e| (s, e)) - } - // Iterate over all dates - whether it's a single date or a file. let dates: Box> = match settings.date_source { DateSource::Custom(ref input) => { @@ -314,3 +330,76 @@ fn make_format_string(settings: &Settings) -> &str { Format::Default => "%c", } } + +/// Parse a `String` into a `DateTime`. +/// If it fails, return a tuple of the `String` along with its `ParseError`. +fn parse_date + Clone>( + s: S, +) -> Result, (String, chrono::format::ParseError)> { + // TODO: The GNU date command can parse a wide variety of inputs. + s.as_ref().parse().map_err(|e| (s.as_ref().into(), e)) +} + +#[cfg(not(any(unix, windows)))] +fn set_system_datetime(_date: DateTime) -> i32 { + unimplemented!("setting date not implemented (unsupported target)"); +} + +#[cfg(target_os = "macos")] +fn set_system_datetime(_date: DateTime) -> i32 { + eprintln!("date: setting the date is not supported by macOS"); + return 1; +} + +#[cfg(all(unix, not(target_os = "macos")))] +/// System call to set date (unix). +/// See here for more: +/// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html +/// https://linux.die.net/man/3/clock_settime +/// https://www.gnu.org/software/libc/manual/html_node/Time-Types.html +fn set_system_datetime(date: DateTime) -> i32 { + let timespec = timespec { + tv_sec: date.timestamp() as _, + tv_nsec: date.timestamp_subsec_nanos() as _, + }; + + let result = unsafe { clock_settime(CLOCK_REALTIME, ×pec) }; + + if result != 0 { + let error = std::io::Error::last_os_error(); + eprintln!("date: cannot set date: {}", error); + error.raw_os_error().unwrap() + } else { + 0 + } +} + +#[cfg(windows)] +/// System call to set date (Windows). +/// See here for more: +/// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-setsystemtime +/// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime +fn set_system_datetime(date: DateTime) -> i32 { + let system_time = SYSTEMTIME { + wYear: date.year() as WORD, + wMonth: date.month() as WORD, + // Ignored + wDayOfWeek: 0, + wDay: date.day() as WORD, + wHour: date.hour() as WORD, + wMinute: date.minute() as WORD, + wSecond: date.second() as WORD, + // TODO: be careful of leap seconds - valid range is [0, 999] - how to handle? + wMilliseconds: ((date.nanosecond() / 1_000_000) % 1000) as WORD, + }; + + let result = unsafe { SetSystemTime(&system_time) }; + + if result == 0 { + let error = std::io::Error::last_os_error(); + eprintln!("date: cannot set date: {}", error); + error.raw_os_error().unwrap() + } else { + 0 + } +} diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 0837878b2..652edfa25 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -2,6 +2,8 @@ extern crate regex; use self::regex::Regex; use crate::common::util::*; +#[cfg(all(unix, not(target_os = "macos")))] +use rust_users::*; #[test] fn test_date_email() { @@ -131,3 +133,42 @@ fn test_date_format_full_day() { let re = Regex::new(r"\S+ \d{4}-\d{2}-\d{2}").unwrap(); assert!(re.is_match(&result.stdout.trim())); } + +#[test] +#[cfg(all(unix, not(target_os = "macos")))] +fn test_date_set_valid() { + if get_effective_uid() == 0 { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg("--set").arg("2020-03-12 13:30:00+08:00").succeeds(); + result.no_stdout().no_stderr(); + } +} + +#[test] +#[cfg(any(windows, all(unix, not(target_os = "macos"))))] +fn test_date_set_invalid() { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg("--set").arg("123abcd").fails(); + let result = result.no_stdout(); + assert!(result.stderr.starts_with("date: invalid date ")); +} + +#[test] +#[cfg(all(unix, not(target_os = "macos")))] +fn test_date_set_permissions_error() { + if !(get_effective_uid() == 0 || is_wsl()) { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails(); + let result = result.no_stdout(); + assert!(result.stderr.starts_with("date: cannot set date: ")); + } +} + +#[test] +#[cfg(target_os = "macos")] +fn test_date_set_mac_unavailable() { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails(); + let result = result.no_stdout(); + assert!(result.stderr.starts_with("date: setting the date is not supported by macOS")); +} From 621511dcaca907ccc9b6e425dcfafa0ec452b798 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 19 Mar 2021 14:24:25 +0100 Subject: [PATCH 056/104] Update cargo.lock --- Cargo.lock | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ba6cf6a2..1c47c5c5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "advapi32-sys" version = "0.2.0" @@ -652,10 +654,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "js-sys" -version = "0.3.48" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "wasm-bindgen 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -838,8 +840,8 @@ dependencies = [ "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "plotters-backend 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "plotters-svg 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "web-sys 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1731,7 +1733,7 @@ name = "uu_ls" version = "0.0.4" dependencies = [ "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2358,16 +2360,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "wasm-bindgen" -version = "0.2.71" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.71" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bumpalo 3.6.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2376,42 +2378,42 @@ dependencies = [ "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.71" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro-support 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro-support 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.71" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-backend 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.71" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "web-sys" -version = "0.3.48" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "js-sys 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2534,7 +2536,7 @@ dependencies = [ "checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" "checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" "checksum itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" -"checksum js-sys 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)" = "dc9f84f9b115ce7843d60706df1422a916680bfdfcbdb0447c5614ff9d7e4d78" +"checksum js-sys 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)" = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)" = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" @@ -2626,12 +2628,12 @@ dependencies = [ "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -"checksum wasm-bindgen 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7" -"checksum wasm-bindgen-backend 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8" -"checksum wasm-bindgen-macro 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b" -"checksum wasm-bindgen-macro-support 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" -"checksum wasm-bindgen-shared 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "7d6f8ec44822dd71f5f221a5847fb34acd9060535c1211b70a05844c0f6383b1" -"checksum web-sys 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)" = "ec600b26223b2948cedfde2a0aa6756dcf1fef616f43d7b3097aaf53a6c4d92b" +"checksum wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe" +"checksum wasm-bindgen-backend 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3" +"checksum wasm-bindgen-macro 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b" +"checksum wasm-bindgen-macro-support 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d" +"checksum wasm-bindgen-shared 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa" +"checksum web-sys 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)" = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310" "checksum wild 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" From 976fa95ce83487a21a6151386f5c8528ca74c0a3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 19 Mar 2021 14:25:05 +0100 Subject: [PATCH 057/104] fix(ls): fix a clippy warning "this `if` has identical blocks" --- src/uu/ls/src/ls.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 5c4cdaa80..10bba98f7 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -173,8 +173,6 @@ impl Config { Format::Long } else if options.is_present(options::format::ONELINE) { Format::OneLine - } else if options.is_present(options::format::COLUMNS) { - Format::Columns } else { Format::Columns }; From 118b802fe8bcc584be5a3fd65cf8e23396de28dc Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 19 Mar 2021 15:14:25 +0100 Subject: [PATCH 058/104] ls: --si and more compatible size formatting --- src/uu/ls/src/ls.rs | 66 +++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 5c4cdaa80..f7075c4df 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -92,13 +92,16 @@ pub mod options { pub static ACCESS: &str = "u"; pub static CHANGE: &str = "c"; } + pub mod size { + pub static HUMAN_READABLE: &str = "human-readable"; + pub static SI: &str = "si"; + } pub static FORMAT: &str = "format"; pub static SORT: &str = "sort"; pub static TIME: &str = "time"; pub static IGNORE_BACKUPS: &str = "ignore-backups"; pub static DIRECTORY: &str = "directory"; pub static CLASSIFY: &str = "classify"; - pub static HUMAN_READABLE: &str = "human-readable"; pub static INODE: &str = "inode"; pub static DEREFERENCE: &str = "dereference"; pub static NUMERIC_UID_GID: &str = "numeric-uid-gid"; @@ -122,9 +125,10 @@ enum Sort { Time, } -enum SizeFormats { +enum SizeFormat { Bytes, - Binary, // Powers of 1024, --human-readable + Binary, // Powers of 1024, --human-readable, -h + Decimal, // Powers of 1000, --si } #[derive(PartialEq, Eq)] @@ -149,7 +153,7 @@ struct Config { dereference: bool, classify: bool, ignore_backups: bool, - size_format: SizeFormats, + size_format: SizeFormat, numeric_uid_gid: bool, directory: bool, time: Time, @@ -231,10 +235,12 @@ impl Config { }, }; - let size_format = if options.is_present(options::HUMAN_READABLE) { - SizeFormats::Binary + let size_format = if options.is_present(options::size::HUMAN_READABLE) { + SizeFormat::Binary + } else if options.is_present(options::size::SI) { + SizeFormat::Decimal } else { - SizeFormats::Bytes + SizeFormat::Bytes }; Config { @@ -413,10 +419,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { '>' for doors, and nothing for regular files.", )) .arg( - Arg::with_name(options::HUMAN_READABLE) + Arg::with_name(options::size::HUMAN_READABLE) .short("h") - .long(options::HUMAN_READABLE) - .help("Print human readable file sizes (e.g. 1K 234M 56G)."), + .long(options::size::HUMAN_READABLE) + .help("Print human readable file sizes (e.g. 1K 234M 56G).") + .overrides_with(options::size::SI), + ) + .arg( + Arg::with_name(options::size::SI) + .long(options::size::SI) + .help("Print human readable file sizes using powers of 1000 instead of 1024.") ) .arg( Arg::with_name(options::INODE) @@ -787,17 +799,37 @@ fn display_date(metadata: &Metadata, config: &Config) -> String { } } +// There are a few peculiarities to how GNU formats the sizes: +// 1. One decimal place is given if and only if the size is smaller than 10 +// 2. It rounds sizes up. +// 3. The human-readable format uses powers for 1024, but does not display the "i" +// that is commonly used to denote Kibi, Mebi, etc. +// 4. Kibi and Kilo are denoted differently ("k" and "K", respectively) +fn format_prefixed(prefixed: NumberPrefix) -> String { + match prefixed { + NumberPrefix::Standalone(bytes) => bytes.to_string(), + NumberPrefix::Prefixed(prefix, bytes) => { + // Remove the "i" from "Ki", "Mi", etc. if present + let prefix_str = prefix.symbol().trim_end_matches("i"); + + // Check whether we get more than 10 if we round up to the first decimal + // because we want do display 9.81 as "9.9", not as "10". + if (10.0 * bytes).ceil() >= 100.0 { + format!("{:.0}{}", bytes.ceil(), prefix_str) + } else { + format!("{:.1}{}", (10.0 * bytes).ceil() / 10.0, prefix_str) + } + } + } +} + fn display_file_size(metadata: &Metadata, config: &Config) -> String { // NOTE: The human-readable behaviour deviates from the GNU ls. // The GNU ls uses binary prefixes by default. match config.size_format { - SizeFormats::Binary => match NumberPrefix::decimal(metadata.len() as f64) { - NumberPrefix::Standalone(bytes) => bytes.to_string(), - NumberPrefix::Prefixed(prefix, bytes) => { - format!("{:.2}{}", bytes, prefix).to_uppercase() - } - }, - SizeFormats::Bytes => metadata.len().to_string(), + SizeFormat::Binary => format_prefixed(NumberPrefix::binary(metadata.len() as f64)), + SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(metadata.len() as f64)), + SizeFormat::Bytes => metadata.len().to_string(), } } From 39b07f670f8b35599c4a62d978715631ae19dea5 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 19 Mar 2021 15:15:24 +0100 Subject: [PATCH 059/104] tests/ls: adapt tests to --si and new size formats --- tests/by-util/test_ls.rs | 82 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index d01aaffd9..e0063aa1a 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -428,28 +428,94 @@ fn test_ls_ls_color() { #[cfg(not(any(target_vendor = "apple", target_os = "windows")))] // Truncate not available on mac or win #[test] -fn test_ls_human() { +fn test_ls_human_si() { let scene = TestScenario::new(util_name!()); - let file = "test_human"; - let result = scene.cmd("truncate").arg("-s").arg("+1000").arg(file).run(); + let file1 = "test_human-1"; + let result = scene + .cmd("truncate") + .arg("-s") + .arg("+1000") + .arg(file1) + .run(); println!("stderr = {:?}", result.stderr); println!("stdout = {:?}", result.stdout); - let result = scene.ucmd().arg("-hl").arg(file).run(); + + let result = scene.ucmd().arg("-hl").arg(file1).run(); println!("stderr = {:?}", result.stderr); println!("stdout = {:?}", result.stdout); assert!(result.success); - assert!(result.stdout.contains("1.00K")); + assert!(result.stdout.contains(" 1000 ")); + + let result = scene.ucmd().arg("-l").arg("--si").arg(file1).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + assert!(result.stdout.contains(" 1.0k ")); + scene .cmd("truncate") .arg("-s") .arg("+1000k") - .arg(file) + .arg(file1) .run(); - let result = scene.ucmd().arg("-hl").arg(file).run(); + + let result = scene.ucmd().arg("-hl").arg(file1).run(); println!("stderr = {:?}", result.stderr); println!("stdout = {:?}", result.stdout); assert!(result.success); - assert!(result.stdout.contains("1.02M")); + assert!(result.stdout.contains(" 1001K ")); + + let result = scene.ucmd().arg("-l").arg("--si").arg(file1).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + assert!(result.stdout.contains(" 1.1M ")); + + let file2 = "test-human-2"; + let result = scene + .cmd("truncate") + .arg("-s") + .arg("+12300k") + .arg(file2) + .run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + let result = scene.ucmd().arg("-hl").arg(file2).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + // GNU rounds up, so we must too. + assert!(result.stdout.contains(" 13M ")); + + let result = scene.ucmd().arg("-l").arg("--si").arg(file2).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + // GNU rounds up, so we must too. + assert!(result.stdout.contains(" 13M ")); + + let file3 = "test-human-3"; + let result = scene + .cmd("truncate") + .arg("-s") + .arg("+9999") + .arg(file3) + .run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + + let result = scene.ucmd().arg("-hl").arg(file3).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + assert!(result.stdout.contains(" 9.8K ")); + + let result = scene.ucmd().arg("-l").arg("--si").arg(file3).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + assert!(result.stdout.contains(" 10k ")); } #[cfg(windows)] From 9132d32315fccd8a040cc8da4797a492f3cf228e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 19 Mar 2021 18:01:43 +0100 Subject: [PATCH 060/104] Rustfmt the tests --- tests/by-util/test_date.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 652edfa25..458e62004 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -139,7 +139,10 @@ fn test_date_format_full_day() { fn test_date_set_valid() { if get_effective_uid() == 0 { let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("--set").arg("2020-03-12 13:30:00+08:00").succeeds(); + let result = ucmd + .arg("--set") + .arg("2020-03-12 13:30:00+08:00") + .succeeds(); result.no_stdout().no_stderr(); } } @@ -170,5 +173,7 @@ fn test_date_set_mac_unavailable() { let (_, mut ucmd) = at_and_ucmd!(); let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails(); let result = result.no_stdout(); - assert!(result.stderr.starts_with("date: setting the date is not supported by macOS")); + assert!(result + .stderr + .starts_with("date: setting the date is not supported by macOS")); } From c6927d97c8600e9cdcc571aacf377032a6d06e37 Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Fri, 19 Mar 2021 22:15:35 +0200 Subject: [PATCH 061/104] cp: add support for -x/--one-file-system (#1840) --- Cargo.toml | 1 + src/uu/cp/src/cp.rs | 11 +-- tests/by-util/test_cp.rs | 73 +++++++++++++++++++ tests/fixtures/cp/dir_with_mount/copy_me.txt | 0 .../cp/dir_with_mount/copy_me/copy_me.txt | 0 5 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/cp/dir_with_mount/copy_me.txt create mode 100644 tests/fixtures/cp/dir_with_mount/copy_me/copy_me.txt diff --git a/Cargo.toml b/Cargo.toml index 9b55abe5c..bf11e66fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -347,6 +347,7 @@ tempfile = "= 3.1.0" time = "0.1" unindent = "0.1" uucore = { version=">=0.0.7", package="uucore", path="src/uucore", features=["entries"] } +walkdir = "2.2" [target.'cfg(unix)'.dev-dependencies] rust-users = { version="0.10", package="users" } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 0922af241..01ad4a8aa 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -431,6 +431,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS) .short("d") .help("same as --no-dereference --preserve=links")) + .arg(Arg::with_name(OPT_ONE_FILE_SYSTEM) + .short("x") + .long(OPT_ONE_FILE_SYSTEM) + .help("stay on this file system")) // TODO: implement the following args .arg(Arg::with_name(OPT_COPY_CONTENTS) @@ -442,10 +446,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .value_name("WHEN") .help("NotImplemented: control creation of sparse files. See below")) - .arg(Arg::with_name(OPT_ONE_FILE_SYSTEM) - .short("x") - .long(OPT_ONE_FILE_SYSTEM) - .help("NotImplemented: stay on this file system")) .arg(Arg::with_name(OPT_CONTEXT) .long(OPT_CONTEXT) .takes_value(true) @@ -563,6 +563,7 @@ impl Options { let not_implemented_opts = vec![ OPT_COPY_CONTENTS, OPT_SPARSE, + #[cfg(not(any(windows, unix)))] OPT_ONE_FILE_SYSTEM, OPT_CONTEXT, #[cfg(windows)] @@ -937,7 +938,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult #[cfg(any(windows, target_os = "redox"))] let mut hard_links: Vec<(String, u64)> = vec![]; - for path in WalkDir::new(root) { + for path in WalkDir::new(root).same_file_system(options.one_file_system) { let p = or_continue!(path); let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink(); let path = if (options.no_dereference || options.dereference) && is_symlink { diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index b96bd4e29..a00ed2fd2 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -31,6 +31,12 @@ static TEST_COPY_FROM_FOLDER: &str = "hello_dir_with_file/"; static TEST_COPY_FROM_FOLDER_FILE: &str = "hello_dir_with_file/hello_world.txt"; static TEST_COPY_TO_FOLDER_NEW: &str = "hello_dir_new"; static TEST_COPY_TO_FOLDER_NEW_FILE: &str = "hello_dir_new/hello_world.txt"; +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +static TEST_MOUNT_COPY_FROM_FOLDER: &str = "dir_with_mount"; +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +static TEST_MOUNT_MOUNTPOINT: &str = "mount"; +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +static TEST_MOUNT_OTHER_FILESYSTEM_FILE: &str = "mount/DO_NOT_copy_me.txt"; #[test] fn test_cp_cp() { @@ -1001,3 +1007,70 @@ fn test_cp_target_file_dev_null() { assert!(at.file_exists(file2)); } + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +fn test_cp_one_file_system() { + use crate::common::util::AtPath; + use walkdir::WalkDir; + + let scene = TestScenario::new(util_name!()); + + // Test must be run as root (or with `sudo -E`) + if scene.cmd("whoami").run().stdout != "root\n" { + return; + } + + let at = scene.fixtures.clone(); + let at_src = AtPath::new(&at.plus(TEST_MOUNT_COPY_FROM_FOLDER)); + let at_dst = AtPath::new(&at.plus(TEST_COPY_TO_FOLDER_NEW)); + + // Prepare the mount + at_src.mkdir(TEST_MOUNT_MOUNTPOINT); + let mountpoint_path = &at_src.plus_as_string(TEST_MOUNT_MOUNTPOINT); + + let _r = scene + .cmd("mount") + .arg("-t") + .arg("tmpfs") + .arg("-o") + .arg("size=640k") // ought to be enough + .arg("tmpfs") + .arg(mountpoint_path) + .run(); + assert!(_r.code == Some(0), _r.stderr); + + at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE); + + // Begin testing -x flag + let result = scene + .ucmd() + .arg("-rx") + .arg(TEST_MOUNT_COPY_FROM_FOLDER) + .arg(TEST_COPY_TO_FOLDER_NEW) + .run(); + + // Ditch the mount before the asserts + let _r = scene.cmd("umount").arg(mountpoint_path).run(); + assert!(_r.code == Some(0), _r.stderr); + + assert!(result.success); + assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE)); + // Check if the other files were copied from the source folder hirerarchy + for entry in WalkDir::new(at_src.as_string()) { + let entry = entry.unwrap(); + let relative_src = entry + .path() + .strip_prefix(at_src.as_string()) + .unwrap() + .to_str() + .unwrap(); + + let ft = entry.file_type(); + match (ft.is_dir(), ft.is_file(), ft.is_symlink()) { + (true, _, _) => assert!(at_dst.dir_exists(relative_src)), + (_, true, _) => assert!(at_dst.file_exists(relative_src)), + (_, _, _) => panic!(), + } + } +} diff --git a/tests/fixtures/cp/dir_with_mount/copy_me.txt b/tests/fixtures/cp/dir_with_mount/copy_me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/cp/dir_with_mount/copy_me/copy_me.txt b/tests/fixtures/cp/dir_with_mount/copy_me/copy_me.txt new file mode 100644 index 000000000..e69de29bb From 0e217e202a2f23bb73c881c15c083a8e1eb55d7f Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 19 Mar 2021 22:58:50 +0100 Subject: [PATCH 062/104] tee: move from getopts to clap (#1814) --- src/uu/tee/Cargo.toml | 2 +- src/uu/tee/src/tee.rs | 118 ++++++++++++++++++------------------------ 2 files changed, 50 insertions(+), 70 deletions(-) diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index ee18e888f..99a6ec23e 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/tee.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33.3" libc = "0.2.42" uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["libc"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index e29945382..c54fa0d16 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -5,6 +5,10 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. +#[macro_use] +extern crate uucore; + +use clap::{App, Arg}; use std::fs::OpenOptions; use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; use std::path::{Path, PathBuf}; @@ -12,80 +16,61 @@ use std::path::{Path, PathBuf}; #[cfg(unix)] use uucore::libc; -static NAME: &str = "tee"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Copy standard input to each FILE, and also to standard output."; -pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); - - match options(&args).and_then(exec) { - Ok(_) => 0, - Err(_) => 1, - } +mod options { + pub const APPEND: &str = "append"; + pub const IGNORE_INTERRUPTS: &str = "ignore-interrupts"; + pub const FILE: &str = "file"; } #[allow(dead_code)] struct Options { - program: String, append: bool, ignore_interrupts: bool, - print_and_exit: Option, files: Vec, } -fn options(args: &[String]) -> Result { - let mut opts = getopts::Options::new(); - - opts.optflag("a", "append", "append to the given FILEs, do not overwrite"); - opts.optflag( - "i", - "ignore-interrupts", - "ignore interrupt signals (ignored on non-Unix platforms)", - ); - opts.optflag("h", "help", "display this help and exit"); - opts.optflag("V", "version", "output version information and exit"); - - opts.parse(&args[1..]) - .map_err(|e| Error::new(ErrorKind::Other, format!("{}", e))) - .map(|m| { - let version = format!("{} {}", NAME, VERSION); - let arguments = "[OPTION]... [FILE]..."; - let brief = "Copy standard input to each FILE, and also to standard output."; - let comment = "If a FILE is -, it refers to a file named - ."; - let help = format!( - "{}\n\nUsage:\n {} {}\n\n{}\n{}", - version, - NAME, - arguments, - opts.usage(brief), - comment - ); - let names: Vec = m.free.clone().into_iter().collect(); - let to_print = if m.opt_present("help") { - Some(help) - } else if m.opt_present("version") { - Some(version) - } else { - None - }; - Options { - program: NAME.to_owned(), - append: m.opt_present("append"), - ignore_interrupts: m.opt_present("ignore-interrupts"), - print_and_exit: to_print, - files: names, - } - }) - .map_err(|message| warn(format!("{}", message).as_ref())) +fn get_usage() -> String { + format!("{0} [OPTION]... [FILE]...", executable!()) } -fn exec(options: Options) -> Result<()> { - match options.print_and_exit { - Some(text) => { - println!("{}", text); - Ok(()) - } - None => tee(options), +pub fn uumain(args: impl uucore::Args) -> i32 { + let usage = get_usage(); + + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .after_help("If a FILE is -, it refers to a file named - .") + .arg( + Arg::with_name(options::APPEND) + .long(options::APPEND) + .short("a") + .help("append to the given FILEs, do not overwrite"), + ) + .arg( + Arg::with_name(options::IGNORE_INTERRUPTS) + .long(options::IGNORE_INTERRUPTS) + .short("i") + .help("ignore interrupt signals (ignored on non-Unix platforms)"), + ) + .arg(Arg::with_name(options::FILE).multiple(true)) + .get_matches_from(args); + + let options = Options { + append: matches.is_present(options::APPEND), + ignore_interrupts: matches.is_present(options::IGNORE_INTERRUPTS), + files: matches + .values_of(options::FILE) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(), + }; + + match tee(options) { + Ok(_) => 0, + Err(_) => 1, } } @@ -173,7 +158,7 @@ impl Write for NamedWriter { match self.inner.write(buf) { Err(f) => { self.inner = Box::new(sink()) as Box; - warn(format!("{}: {}", self.path.display(), f.to_string()).as_ref()); + show_warning!("{}: {}", self.path.display(), f.to_string()); Err(f) } okay => okay, @@ -184,7 +169,7 @@ impl Write for NamedWriter { match self.inner.flush() { Err(f) => { self.inner = Box::new(sink()) as Box; - warn(format!("{}: {}", self.path.display(), f.to_string()).as_ref()); + show_warning!("{}: {}", self.path.display(), f.to_string()); Err(f) } okay => okay, @@ -200,15 +185,10 @@ impl Read for NamedReader { fn read(&mut self, buf: &mut [u8]) -> Result { match self.inner.read(buf) { Err(f) => { - warn(format!("{}: {}", Path::new("stdin").display(), f.to_string()).as_ref()); + show_warning!("{}: {}", Path::new("stdin").display(), f.to_string()); Err(f) } okay => okay, } } } - -fn warn(message: &str) -> Error { - eprintln!("{}: {}", NAME, message); - Error::new(ErrorKind::Other, format!("{}: {}", NAME, message)) -} From 785897efbd404d755bd6fb2a1a5cdb536ae414a1 Mon Sep 17 00:00:00 2001 From: Marco Satti Date: Sat, 20 Mar 2021 15:17:18 +0800 Subject: [PATCH 063/104] date: add more tests for setting (alt. formats) --- tests/by-util/test_date.rs | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 458e62004..216446266 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -177,3 +177,42 @@ fn test_date_set_mac_unavailable() { .stderr .starts_with("date: setting the date is not supported by macOS")); } + +#[test] +#[cfg(all(unix, not(target_os = "macos")))] +fn test_date_set_valid_2() { + if get_effective_uid() == 0 { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd + .arg("--set") + .arg("Sat 20 Mar 2021 14:53:01 AWST") + .succeeds(); + result.no_stdout().no_stderr(); + } +} + +#[test] +#[cfg(all(unix, not(target_os = "macos")))] +fn test_date_set_valid_3() { + if get_effective_uid() == 0 { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd + .arg("--set") + .arg("Sat 20 Mar 2021 14:53:01") // Local timezone + .succeeds(); + result.no_stdout().no_stderr(); + } +} + +#[test] +#[cfg(all(unix, not(target_os = "macos")))] +fn test_date_set_valid_4() { + if get_effective_uid() == 0 { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd + .arg("--set") + .arg("2020-03-11 21:45:00") // Local timezone + .succeeds(); + result.no_stdout().no_stderr(); + } +} From d1fc42a7c901362a33f832567022d362ed31a33e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 20 Mar 2021 10:28:06 +0100 Subject: [PATCH 064/104] refresh cargo.lock with recent updates --- Cargo.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 1c47c5c5f..8d4318dec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,6 +297,7 @@ dependencies = [ "uu_whoami 0.0.4", "uu_yes 0.0.4", "uucore 0.0.7", + "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2128,7 +2129,7 @@ dependencies = [ name = "uu_tee" version = "0.0.4" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", From e9adc5067bf66f4e0c9d96623eabe48acf76a437 Mon Sep 17 00:00:00 2001 From: Alex Lyon Date: Sat, 20 Mar 2021 02:33:04 -0700 Subject: [PATCH 065/104] cksum: generate CRC table in a const fn (#1744) --- src/uu/cksum/build.rs | 46 ------------------ src/uu/cksum/src/cksum.rs | 99 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 51 deletions(-) delete mode 100644 src/uu/cksum/build.rs diff --git a/src/uu/cksum/build.rs b/src/uu/cksum/build.rs deleted file mode 100644 index a9edd0d59..000000000 --- a/src/uu/cksum/build.rs +++ /dev/null @@ -1,46 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// (c) Alex Lyon -// (c) Michael Gehring -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::Path; - -const CRC_TABLE_LEN: usize = 256; - -fn main() { - let out_dir = env::var("OUT_DIR").unwrap(); - - let mut table = Vec::with_capacity(CRC_TABLE_LEN); - for num in 0..CRC_TABLE_LEN { - table.push(crc_entry(num as u8) as u32); - } - let file = File::create(&Path::new(&out_dir).join("crc_table.rs")).unwrap(); - write!( - &file, - "#[allow(clippy::unreadable_literal)]\nconst CRC_TABLE: [u32; {}] = {:?};", - CRC_TABLE_LEN, table - ) - .unwrap(); -} - -#[inline] -fn crc_entry(input: u8) -> u32 { - let mut crc = (input as u32) << 24; - - for _ in 0..8 { - if crc & 0x8000_0000 != 0 { - crc <<= 1; - crc ^= 0x04c1_1db7; - } else { - crc <<= 1; - } - } - - crc -} diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index f589e6f81..e1c75fffc 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -14,11 +14,101 @@ use std::fs::File; use std::io::{self, stdin, BufReader, Read}; use std::path::Path; -include!(concat!(env!("OUT_DIR"), "/crc_table.rs")); +// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 +const CRC_TABLE_LEN: usize = 256; +const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table(); -static SYNTAX: &str = "[OPTIONS] [FILE]..."; -static SUMMARY: &str = "Print CRC and size for each file"; -static LONG_HELP: &str = ""; +const SYNTAX: &str = "[OPTIONS] [FILE]..."; +const SUMMARY: &str = "Print CRC and size for each file"; +const LONG_HELP: &str = ""; + +// this is basically a hack to get "loops" to work on Rust 1.33. Once we update to Rust 1.46 or +// greater, we can just use while loops +macro_rules! unroll { + (256, |$i:ident| $s:expr) => {{ + unroll!(@ 32, 0 * 32, $i, $s); + unroll!(@ 32, 1 * 32, $i, $s); + unroll!(@ 32, 2 * 32, $i, $s); + unroll!(@ 32, 3 * 32, $i, $s); + unroll!(@ 32, 4 * 32, $i, $s); + unroll!(@ 32, 5 * 32, $i, $s); + unroll!(@ 32, 6 * 32, $i, $s); + unroll!(@ 32, 7 * 32, $i, $s); + }}; + (8, |$i:ident| $s:expr) => {{ + unroll!(@ 8, 0, $i, $s); + }}; + + (@ 32, $start:expr, $i:ident, $s:expr) => {{ + unroll!(@ 8, $start + 0 * 8, $i, $s); + unroll!(@ 8, $start + 1 * 8, $i, $s); + unroll!(@ 8, $start + 2 * 8, $i, $s); + unroll!(@ 8, $start + 3 * 8, $i, $s); + }}; + (@ 8, $start:expr, $i:ident, $s:expr) => {{ + unroll!(@ 4, $start, $i, $s); + unroll!(@ 4, $start + 4, $i, $s); + }}; + (@ 4, $start:expr, $i:ident, $s:expr) => {{ + unroll!(@ 2, $start, $i, $s); + unroll!(@ 2, $start + 2, $i, $s); + }}; + (@ 2, $start:expr, $i:ident, $s:expr) => {{ + unroll!(@ 1, $start, $i, $s); + unroll!(@ 1, $start + 1, $i, $s); + }}; + (@ 1, $start:expr, $i:ident, $s:expr) => {{ + let $i = $start; + let _ = $s; + }}; +} + +const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] { + let mut table = [0; CRC_TABLE_LEN]; + + // NOTE: works on Rust 1.46 + //let mut i = 0; + //while i < CRC_TABLE_LEN { + // table[i] = crc_entry(i as u8) as u32; + // + // i += 1; + //} + unroll!(256, |i| { + table[i] = crc_entry(i as u8) as u32; + }); + + table +} + +const fn crc_entry(input: u8) -> u32 { + let mut crc = (input as u32) << 24; + + // NOTE: this does not work on Rust 1.33, but *does* on 1.46 + //let mut i = 0; + //while i < 8 { + // if crc & 0x8000_0000 != 0 { + // crc <<= 1; + // crc ^= 0x04c1_1db7; + // } else { + // crc <<= 1; + // } + // + // i += 1; + //} + unroll!(8, |_i| { + let if_cond = crc & 0x8000_0000; + let if_body = (crc << 1) ^ 0x04c1_1db7; + let else_body = crc << 1; + + // NOTE: i feel like this is easier to understand than emulating an if statement in bitwise + // ops + let cond_table = [else_body, if_body]; + + crc = cond_table[(if_cond != 0) as usize]; + }); + + crc +} #[inline] fn crc_update(crc: u32, input: u8) -> u32 { @@ -68,7 +158,6 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> { Err(err) => return Err(err), } } - //Ok((0 as u32,0 as usize)) } pub fn uumain(args: impl uucore::Args) -> i32 { From 8b9ac0c7c3231ebde87b875e95dc67d02e9e753a Mon Sep 17 00:00:00 2001 From: nicoo Date: Sat, 20 Mar 2021 11:46:58 +0100 Subject: [PATCH 066/104] =?UTF-8?q?Revert=20#1571=20=E2=80=9Cperf/factor?= =?UTF-8?q?=20~=20deduplicate=20divisors=E2=80=9D=20(#1842)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was a draft PR, not ready for merging, and its premature inclusion caused repeated issues, see 368f47381b7441ddb23bb7460417e0d1ce33e330 & friends. Close #1841. This reverts commits 3743a3e1e7a87bdd0ea7b923dc42386bfc14ccd5, ce218e01b6fcfadf4a9e55348d98f36dafff6c6b, and b7b0c76b8ea60297b43777d17d7322d1d45a9e91. --- src/uu/factor/src/factor.rs | 145 +++++++++++------------------------- src/uu/factor/src/table.rs | 5 +- 2 files changed, 45 insertions(+), 105 deletions(-) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 2715bad71..7d2e16a11 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -9,7 +9,7 @@ use smallvec::SmallVec; use std::cell::RefCell; use std::fmt; -use crate::numeric::{gcd, Arithmetic, Montgomery}; +use crate::numeric::{Arithmetic, Montgomery}; use crate::{miller_rabin, rho, table}; type Exponent = u8; @@ -29,20 +29,15 @@ impl Decomposition { fn add(&mut self, factor: u64, exp: Exponent) { debug_assert!(exp > 0); - // Assert the factor doesn't already exist in the Decomposition object - debug_assert_eq!(self.0.iter_mut().find(|(f, _)| *f == factor), None); - self.0.push((factor, exp)) - } - - fn is_one(&self) -> bool { - self.0.is_empty() - } - - fn pop(&mut self) -> Option<(u64, Exponent)> { - self.0.pop() + if let Some((_, e)) = self.0.iter_mut().find(|(f, _)| *f == factor) { + *e += exp; + } else { + self.0.push((factor, exp)) + } } + #[cfg(test)] fn product(&self) -> u64 { self.0 .iter() @@ -86,11 +81,11 @@ impl Factors { self.0.borrow_mut().add(prime, exp) } - #[cfg(test)] pub fn push(&mut self, prime: u64) { self.add(prime, 1) } + #[cfg(test)] fn product(&self) -> u64 { self.0.borrow().product() } @@ -111,116 +106,62 @@ impl fmt::Display for Factors { } } -fn _find_factor(num: u64) -> Option { +fn _factor(num: u64, f: Factors) -> Factors { use miller_rabin::Result::*; + // Shadow the name, so the recursion automatically goes from “Big” arithmetic to small. + let _factor = |n, f| { + if n < (1 << 32) { + _factor::>(n, f) + } else { + _factor::(n, f) + } + }; + + if num == 1 { + return f; + } + let n = A::new(num); - match miller_rabin::test::(n) { - Prime => None, - Composite(d) => Some(d), - Pseudoprime => Some(rho::find_divisor::(n)), - } + let divisor = match miller_rabin::test::(n) { + Prime => { + let mut r = f; + r.push(num); + return r; + } + + Composite(d) => d, + Pseudoprime => rho::find_divisor::(n), + }; + + let f = _factor(divisor, f); + _factor(num / divisor, f) } -fn find_factor(num: u64) -> Option { - if num < (1 << 32) { - _find_factor::>(num) - } else { - _find_factor::>(num) - } -} - -pub fn factor(num: u64) -> Factors { +pub fn factor(mut n: u64) -> Factors { let mut factors = Factors::one(); - if num < 2 { + if n < 2 { return factors; } - let mut n = num; - let n_zeros = num.trailing_zeros(); + let n_zeros = n.trailing_zeros(); if n_zeros > 0 { factors.add(2, n_zeros as Exponent); n >>= n_zeros; } - debug_assert_eq!(num, n * factors.product()); if n == 1 { return factors; } - table::factor(&mut n, &mut factors); - debug_assert_eq!(num, n * factors.product()); + let (factors, n) = table::factor(n, factors); - if n == 1 { - return factors; + if n < (1 << 32) { + _factor::>(n, factors) + } else { + _factor::>(n, factors) } - - let mut dec = Decomposition::one(); - dec.add(n, 1); - - while !dec.is_one() { - // Check correctness invariant - debug_assert_eq!(num, factors.product() * dec.product()); - - let (factor, exp) = dec.pop().unwrap(); - - if let Some(divisor) = find_factor(factor) { - let mut gcd_queue = Decomposition::one(); - - let quotient = factor / divisor; - let mut trivial_gcd = quotient == divisor; - if trivial_gcd { - gcd_queue.add(divisor, exp + 1); - } else { - gcd_queue.add(divisor, exp); - gcd_queue.add(quotient, exp); - } - - while !trivial_gcd { - debug_assert_eq!(factor, gcd_queue.product()); - - let mut tmp = Decomposition::one(); - trivial_gcd = true; - for i in 0..gcd_queue.0.len() - 1 { - let (mut a, exp_a) = gcd_queue.0[i]; - let (mut b, exp_b) = gcd_queue.0[i + 1]; - - if a == 1 { - continue; - } - - let g = gcd(a, b); - if g != 1 { - trivial_gcd = false; - a /= g; - b /= g; - } - if a != 1 { - tmp.add(a, exp_a); - } - if g != 1 { - tmp.add(g, exp_a + exp_b); - } - - if i + 1 != gcd_queue.0.len() - 1 { - gcd_queue.0[i + 1].0 = b; - } else if b != 1 { - tmp.add(b, exp_b); - } - } - gcd_queue = tmp; - } - - debug_assert_eq!(factor, gcd_queue.product()); - dec.0.extend(gcd_queue.0); - } else { - // factor is prime - factors.add(factor, exp); - } - } - - factors } #[cfg(test)] diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index b62e801cb..d6ef796fc 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -14,8 +14,7 @@ use crate::Factors; include!(concat!(env!("OUT_DIR"), "/prime_table.rs")); -pub(crate) fn factor(n: &mut u64, factors: &mut Factors) { - let mut num = *n; +pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) { for &(prime, inv, ceil) in P_INVS_U64 { if num == 1 { break; @@ -43,5 +42,5 @@ pub(crate) fn factor(n: &mut u64, factors: &mut Factors) { } } - *n = num; + (factors, num) } From 0f7423dfa6e44c11593f1acaca98c372b1dbe50f Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Sat, 20 Mar 2021 14:37:37 +0200 Subject: [PATCH 067/104] install: fix bug #1823 --- src/uu/install/src/install.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 9d1acdc7e..a675549d1 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -426,18 +426,25 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> let mut all_successful = true; for sourcepath in files.iter() { - let targetpath = match sourcepath.as_os_str().to_str() { - Some(name) => target_dir.join(name), - None => { - show_error!( - "cannot stat '{}': No such file or directory", - sourcepath.display() - ); + if !sourcepath.exists() { + show_error!( + "cannot stat '{}': No such file or directory", + sourcepath.display() + ); - all_successful = false; - continue; - } - }; + all_successful = false; + continue; + } + + if sourcepath.is_dir() { + show_info!("omitting directory '{}'", sourcepath.display()); + all_successful = false; + continue; + } + + let mut targetpath = target_dir.clone().to_path_buf(); + let filename = sourcepath.components().last().unwrap(); + targetpath.push(filename); if copy(sourcepath, &targetpath, b).is_err() { all_successful = false; From 7a9128197621e0faf75e8f7c095b8f197d01c0ff Mon Sep 17 00:00:00 2001 From: Dominik Bittner Date: Sat, 20 Mar 2021 13:49:53 +0100 Subject: [PATCH 068/104] Install: remove path when copining files - add a test for copying a file from one directory to another - add the desired behavior Fixes #1823 --- tests/by-util/test_install.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 7b3706f9e..9f4d9af18 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -358,3 +358,18 @@ fn test_install_target_file_dev_null() { ucmd.arg(file1).arg(file2).succeeds().no_stderr(); assert!(at.file_exists(file2)); } + +#[test] +fn test_install_copy_file_leading_dot() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir1 = "test_install_target_new_file_dir_l"; + let dir2 = "test_install_target_new_file_dir_m"; + let file1 = "test_install_target_file_file_l1"; + + at.mkdir(dir1); + at.mkdir(dir2); + at.touch(&format!("{}/{}", dir1, file1)); + + ucmd.arg(format!("{}/{}", dir1, file1)).arg(dir2).succeeds().no_stderr(); + assert!(at.file_exists(&format!("{}/{}", dir2, file1))); +} From ecddaf577a7e8dd7fc3d3c07e4c3ac56fd96e392 Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Sat, 20 Mar 2021 15:44:41 +0200 Subject: [PATCH 069/104] install: rustfmt test --- tests/by-util/test_install.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 9f4d9af18..a5ce040f7 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -370,6 +370,9 @@ fn test_install_copy_file_leading_dot() { at.mkdir(dir2); at.touch(&format!("{}/{}", dir1, file1)); - ucmd.arg(format!("{}/{}", dir1, file1)).arg(dir2).succeeds().no_stderr(); + ucmd.arg(format!("{}/{}", dir1, file1)) + .arg(dir2) + .succeeds() + .no_stderr(); assert!(at.file_exists(&format!("{}/{}", dir2, file1))); } From f8125a1040ff34745d101913a90892515e60ad88 Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Sat, 20 Mar 2021 16:11:29 +0200 Subject: [PATCH 070/104] install: match GNU warning output --- src/uu/install/src/install.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index a675549d1..1ac9bc743 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -427,7 +427,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> let mut all_successful = true; for sourcepath in files.iter() { if !sourcepath.exists() { - show_error!( + show_info!( "cannot stat '{}': No such file or directory", sourcepath.display() ); From 9b0eee9066238c6197b316259388437095429879 Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Sat, 20 Mar 2021 20:07:19 +0200 Subject: [PATCH 071/104] install: added additional tests --- tests/by-util/test_install.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index a5ce040f7..88f4c85cc 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -360,7 +360,7 @@ fn test_install_target_file_dev_null() { } #[test] -fn test_install_copy_file_leading_dot() { +fn test_install_nested_paths_copy_file() { let (at, mut ucmd) = at_and_ucmd!(); let dir1 = "test_install_target_new_file_dir_l"; let dir2 = "test_install_target_new_file_dir_m"; @@ -376,3 +376,34 @@ fn test_install_copy_file_leading_dot() { .no_stderr(); assert!(at.file_exists(&format!("{}/{}", dir2, file1))); } + +#[test] +fn test_install_failing_omitting_directory() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir1 = "source_dir"; + let file1 = "source_file"; + let dir2 = "target_dir"; + + at.mkdir(dir1); + at.mkdir(dir2); + at.touch(file1); + + let r = ucmd.arg(dir1).arg(file1).arg(dir2).run(); + assert!(r.code == Some(1)); + assert!(r.stderr.contains("omitting directory")); +} + +#[test] +fn test_install_failing_no_such_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file1 = "source_file"; + let file2 = "inexistent_file"; + let dir1 = "target_dir"; + + at.mkdir(dir1); + at.touch(file1); + + let r = ucmd.arg(file1).arg(file2).arg(dir1).run(); + assert!(r.code == Some(1)); + assert!(r.stderr.contains("No such file or directory")); +} From 220ca78c9bc81cd16425b9c79da06997161ae6b6 Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Sat, 20 Mar 2021 20:31:52 +0200 Subject: [PATCH 072/104] install: normalize test filenames --- tests/by-util/test_install.rs | 80 +++++++++++++++++------------------ 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 88f4c85cc..89dfb0e56 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -17,9 +17,9 @@ fn test_install_help() { #[test] fn test_install_basic() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_install_target_dir_dir_a"; - let file1 = "test_install_target_dir_file_a1"; - let file2 = "test_install_target_dir_file_a2"; + let dir = "target_dir"; + let file1 = "source_file1"; + let file2 = "source_file2"; at.touch(file1); at.touch(file2); @@ -34,7 +34,7 @@ fn test_install_basic() { #[test] fn test_install_twice_dir() { - let dir = "test_install_target_dir_dir_a"; + let dir = "dir"; let scene = TestScenario::new(util_name!()); scene.ucmd().arg("-d").arg(dir).succeeds(); @@ -47,9 +47,9 @@ fn test_install_twice_dir() { #[test] fn test_install_failing_not_dir() { let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; - let file2 = "test_install_target_dir_file_a2"; - let file3 = "test_install_target_dir_file_a3"; + let file1 = "file1"; + let file2 = "file2"; + let file3 = "file3"; at.touch(file1); at.touch(file2); @@ -66,8 +66,8 @@ fn test_install_failing_not_dir() { #[test] fn test_install_unimplemented_arg() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_install_target_dir_dir_b"; - let file = "test_install_target_dir_file_b"; + let dir = "target_dir"; + let file = "source_file"; let context_arg = "--context"; at.touch(file); @@ -86,9 +86,9 @@ fn test_install_unimplemented_arg() { #[test] fn test_install_component_directories() { let (at, mut ucmd) = at_and_ucmd!(); - let component1 = "test_install_target_dir_component_c1"; - let component2 = "test_install_target_dir_component_c2"; - let component3 = "test_install_target_dir_component_c3"; + let component1 = "component1"; + let component2 = "component2"; + let component3 = "component3"; let directories_arg = "-d"; ucmd.args(&[directories_arg, component1, component2, component3]) @@ -104,10 +104,10 @@ fn test_install_component_directories() { fn test_install_mode_numeric() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - let dir = "test_install_target_dir_dir_e"; - let dir2 = "test_install_target_dir_dir_e2"; + let dir = "dir1"; + let dir2 = "dir2"; - let file = "test_install_target_dir_file_e"; + let file = "file"; let mode_arg = "--mode=333"; at.touch(file); @@ -145,8 +145,8 @@ fn test_install_mode_numeric() { #[test] fn test_install_mode_symbolic() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_install_target_dir_dir_f"; - let file = "test_install_target_dir_file_f"; + let dir = "target_dir"; + let file = "source_file"; let mode_arg = "--mode=o+wx"; at.touch(file); @@ -163,8 +163,8 @@ fn test_install_mode_symbolic() { #[test] fn test_install_mode_failing() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_install_target_dir_dir_g"; - let file = "test_install_target_dir_file_g"; + let dir = "target_dir"; + let file = "source_file"; let mode_arg = "--mode=999"; at.touch(file); @@ -185,7 +185,7 @@ fn test_install_mode_failing() { #[test] fn test_install_mode_directories() { let (at, mut ucmd) = at_and_ucmd!(); - let component = "test_install_target_dir_component_h"; + let component = "component"; let directories_arg = "-d"; let mode_arg = "--mode=333"; @@ -203,8 +203,8 @@ fn test_install_mode_directories() { #[test] fn test_install_target_file() { let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_file_file_i1"; - let file2 = "test_install_target_file_file_i2"; + let file1 = "source_file"; + let file2 = "target_file"; at.touch(file1); at.touch(file2); @@ -217,8 +217,8 @@ fn test_install_target_file() { #[test] fn test_install_target_new_file() { let (at, mut ucmd) = at_and_ucmd!(); - let file = "test_install_target_new_filer_file_j"; - let dir = "test_install_target_new_file_dir_j"; + let file = "file"; + let dir = "target_dir"; at.touch(file); at.mkdir(dir); @@ -234,8 +234,8 @@ fn test_install_target_new_file() { #[test] fn test_install_target_new_file_with_group() { let (at, mut ucmd) = at_and_ucmd!(); - let file = "test_install_target_new_filer_file_j"; - let dir = "test_install_target_new_file_dir_j"; + let file = "file"; + let dir = "target_dir"; let gid = get_effective_gid(); at.touch(file); @@ -264,8 +264,8 @@ fn test_install_target_new_file_with_group() { #[test] fn test_install_target_new_file_with_owner() { let (at, mut ucmd) = at_and_ucmd!(); - let file = "test_install_target_new_filer_file_j"; - let dir = "test_install_target_new_file_dir_j"; + let file = "file"; + let dir = "target_dir"; let uid = get_effective_uid(); at.touch(file); @@ -294,9 +294,9 @@ fn test_install_target_new_file_with_owner() { #[test] fn test_install_target_new_file_failing_nonexistent_parent() { let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_new_file_failing_file_k1"; - let file2 = "test_install_target_new_file_failing_file_k2"; - let dir = "test_install_target_new_file_failing_dir_k"; + let file1 = "source_file"; + let file2 = "target_file"; + let dir = "target_dir"; at.touch(file1); @@ -312,8 +312,8 @@ fn test_install_target_new_file_failing_nonexistent_parent() { #[test] fn test_install_preserve_timestamps() { let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; - let file2 = "test_install_target_dir_file_a2"; + let file1 = "source_file"; + let file2 = "target_file"; at.touch(file1); ucmd.arg(file1).arg(file2).arg("-p").succeeds().no_stderr(); @@ -338,8 +338,8 @@ fn test_install_preserve_timestamps() { #[test] fn test_install_copy_file() { let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; - let file2 = "test_install_target_dir_file_a2"; + let file1 = "source_file"; + let file2 = "target_file"; at.touch(file1); ucmd.arg(file1).arg(file2).succeeds().no_stderr(); @@ -353,7 +353,7 @@ fn test_install_copy_file() { fn test_install_target_file_dev_null() { let (at, mut ucmd) = at_and_ucmd!(); let file1 = "/dev/null"; - let file2 = "test_install_target_file_file_i2"; + let file2 = "target_file"; ucmd.arg(file1).arg(file2).succeeds().no_stderr(); assert!(at.file_exists(file2)); @@ -362,9 +362,9 @@ fn test_install_target_file_dev_null() { #[test] fn test_install_nested_paths_copy_file() { let (at, mut ucmd) = at_and_ucmd!(); - let dir1 = "test_install_target_new_file_dir_l"; - let dir2 = "test_install_target_new_file_dir_m"; - let file1 = "test_install_target_file_file_l1"; + let file1 = "source_file"; + let dir1 = "source_dir"; + let dir2 = "target_dir"; at.mkdir(dir1); at.mkdir(dir2); @@ -380,8 +380,8 @@ fn test_install_nested_paths_copy_file() { #[test] fn test_install_failing_omitting_directory() { let (at, mut ucmd) = at_and_ucmd!(); - let dir1 = "source_dir"; let file1 = "source_file"; + let dir1 = "source_dir"; let dir2 = "target_dir"; at.mkdir(dir1); From 0a661a6da27af00751ccaf9ce1f89b5018739399 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sat, 20 Mar 2021 22:15:47 +0000 Subject: [PATCH 073/104] Remove use of which to set system binaries --- .github/workflows/GNU.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 299f333e1..1fcc7c721 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -60,7 +60,7 @@ jobs: ./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR" ./configure --quiet --disable-gcc-warnings #Add timeout to to protect against hangs - sed -i "s|\"\$@|$(which timeout) 600 \"\$@|" build-aux/test-driver + sed -i 's|"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh @@ -71,7 +71,7 @@ jobs: make tests/factor/t${i}.sh done grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||' - sed -i -e 's|^seq |/usr/bin/seq |' -e "s|sha1sum |$(which sha1sum) |" tests/factor/t*sh + sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh # Remove tests checking for --version & --help # Not really interesting for us and logs are too big @@ -81,21 +81,21 @@ jobs: Makefile # 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|$(which stat)|" tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh - sed -i "s|ls -|$(which ls) -|" tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh - sed -i "s|mkdir |$(which mkdir) |" tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh - sed -i "s|timeout |$(which timeout) |" 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 - sed -i "s| timeout | $(which timeout) |" tests/tail-2/inotify-rotate.sh # Don't break the function called 'grep_timeout' - sed -i "s|chmod |$(which chmod) |" tests/du/inacc-dir.sh tests/mkdir/p-3.sh - sed -i "s|sort |$(which sort) |" tests/ls/hyperlink.sh tests/misc/test-N.sh - sed -i "s|split |$(which split) |" tests/misc/factor-parallel.sh - sed -i "s|truncate |$(which truncate) |" tests/split/fail.sh - sed -i "s|dd |$(which dd) |" tests/du/8gb.sh tests/tail-2/big-4gb.sh - sed -i "s|id -|$(which id) -|" tests/misc/runcon-no-reorder.sh + sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh + sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh + sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh + sed -i 's|timeout |/usr/bin/timeout |' 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 + sed -i 's| timeout | /usr/bin/timeout |' tests/tail-2/inotify-rotate.sh # Don't break the function called 'grep_timeout' + sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.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 + sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh + sed -i '|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh + sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh #Add specific timeout to tests that currently hang to limit time spent waiting - sed -i "s|seq \\$|$(which timeout) 0.1 seq \$|" tests/misc/seq-precision.sh tests/misc/seq-long-double.sh - sed -i "s|cat |$(which timeout) 0.1 cat |" tests/misc/cat-self.sh + sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh + sed -i 's|cat |/usr/bin/timeout 0.1 cat |' tests/misc/cat-self.sh test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" From 4352d47ac31f6aab4968cfa3aa1cb54f75eed7ed Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 21 Mar 2021 14:45:34 +0000 Subject: [PATCH 074/104] Don't fail the job if test-suite.log is missing --- .github/workflows/GNU.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 1fcc7c721..79ef69b8c 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -110,13 +110,18 @@ jobs: - name: Extract tests info shell: bash run: | - TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-) - PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-) - SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-) - FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-) - XPASS=$( grep "# XPASS:" gnu/tests/test-suite.log|cut -d' ' -f2-) - ERROR=$( grep "# ERROR:" gnu/tests/test-suite.log|cut -d' ' -f2-) - echo "::warning ::GNU testsuite = $TOTAL / $PASS / $FAIL / $ERROR" + if test -f gnu/tests/test-suite.log + then + TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-) + PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-) + SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-) + FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-) + XPASS=$( grep "# XPASS:" gnu/tests/test-suite.log|cut -d' ' -f2-) + ERROR=$( grep "# ERROR:" gnu/tests/test-suite.log|cut -d' ' -f2-) + echo "::warning ::GNU testsuite = $TOTAL / $PASS / $FAIL / $ERROR" + else + echo "::error ::Failed to get summary of test results" + fi - uses: actions/upload-artifact@v2 with: From 0f77b54aeb8c44ca61979d9a772abe0b2cba29d4 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 21 Mar 2021 15:11:48 +0000 Subject: [PATCH 075/104] Fix typo --- .github/workflows/GNU.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 79ef69b8c..59ce82420 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -90,7 +90,7 @@ jobs: sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh - sed -i '|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh + sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh #Add specific timeout to tests that currently hang to limit time spent waiting From 25d4a083877db99af4887a1304afd245a94f0491 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 21 Mar 2021 16:18:06 +0100 Subject: [PATCH 076/104] ls: long format author, group and owner (#1850) This PR adds the options to customize what information is shown in long format regarding author, group & owner. Specifically it adds: - `--author`: shows the author, which is always the same as the owner. GNU has this feature because GNU/Hurd supports a difference between author and owner, but I don't think Rust supports GNU/Hurd, so I just used the owner. - `-G` & `--no-group`: hide the group information. - `-o`: hide the group and use long format (equivalent to `-lG`). - `-g`: hide the owner and use long format. The `-o` and `-g` options have some interesting behaviour that I had to account for. Some examples: - `-og` hides both group and owner. - `-ol` still hides the group. Same behaviour with variations such as `-o --format=long`, `-gl`, `-g --format=long` and `-ogl`. - They even retain some information when overridden by another format: `-oCl` (or `-o --format=vertical --format=long`) still hides the group. My previous solution for handling the behaviour where `-l1` shows the long format did not fit with these additions, so I had to rewrite that as well. The tests only cover the how many names (author, group and owner) are present in the output, so it can't distinguish between, for example, author & group and group & owner. --- src/uu/ls/src/ls.rs | 240 +++++++++++++++++++++++++++++++-------- tests/by-util/test_ls.rs | 151 ++++++++++++++++++++++++ 2 files changed, 345 insertions(+), 46 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6ade9e2e6..a935eef54 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -78,6 +78,8 @@ pub mod options { pub static ONELINE: &str = "1"; pub static LONG: &str = "long"; pub static COLUMNS: &str = "C"; + pub static LONG_NO_OWNER: &str = "g"; + pub static LONG_NO_GROUP: &str = "o"; } pub mod files { pub static ALL: &str = "all"; @@ -96,6 +98,8 @@ pub mod options { pub static HUMAN_READABLE: &str = "human-readable"; pub static SI: &str = "si"; } + pub static AUTHOR: &str = "author"; + pub static NO_GROUP: &str = "no-group"; pub static FORMAT: &str = "format"; pub static SORT: &str = "sort"; pub static TIME: &str = "time"; @@ -161,26 +165,85 @@ struct Config { inode: bool, #[cfg(unix)] color: bool, + long: LongFormat, +} + +// Fields that can be removed or added to the long format +struct LongFormat { + author: bool, + group: bool, + owner: bool, } impl Config { fn from(options: clap::ArgMatches) -> Config { - let format = if let Some(format_) = options.value_of(options::FORMAT) { - match format_ { - "long" | "verbose" => Format::Long, - "single-column" => Format::OneLine, - "columns" => Format::Columns, - // below should never happen as clap already restricts the values. - _ => unreachable!("Invalid field for --format"), - } + let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) { + ( + match format_ { + "long" | "verbose" => Format::Long, + "single-column" => Format::OneLine, + "columns" | "vertical" => Format::Columns, + // below should never happen as clap already restricts the values. + _ => unreachable!("Invalid field for --format"), + }, + options::FORMAT, + ) } else if options.is_present(options::format::LONG) { - Format::Long - } else if options.is_present(options::format::ONELINE) { - Format::OneLine + (Format::Long, options::format::LONG) } else { - Format::Columns + (Format::Columns, options::format::COLUMNS) }; + // The -o and -g options are tricky. They cannot override with each + // other because it's possible to combine them. For example, the option + // -og should hide both owner and group. Furthermore, they are not + // reset if -l or --format=long is used. So these should just show the + // group: -gl or "-g --format=long". Finally, they are also not reset + // when switching to a different format option inbetween like this: + // -ogCl or "-og --format=vertical --format=long". + // + // -1 has a similar issue: it does nothing if the format is long. This + // actually makes it distinct from the --format=singe-column option, + // which always applies. + // + // The idea here is to not let these options override with the other + // options, but manually check the last index they occur. If this index + // is larger than the index for the other format options, we apply the + // long format. + match options.indices_of(opt).map(|x| x.max().unwrap()) { + None => { + if options.is_present(options::format::LONG_NO_GROUP) + || options.is_present(options::format::LONG_NO_OWNER) + { + format = Format::Long; + } else if options.is_present(options::format::ONELINE) { + format = Format::OneLine; + } + } + Some(mut idx) => { + if let Some(indices) = options.indices_of(options::format::LONG_NO_OWNER) { + let i = indices.max().unwrap(); + if i > idx { + format = Format::Long; + idx = i; + } + } + if let Some(indices) = options.indices_of(options::format::LONG_NO_GROUP) { + let i = indices.max().unwrap(); + if i > idx { + format = Format::Long; + idx = i; + } + } + if let Some(indices) = options.indices_of(options::format::ONELINE) { + let i = indices.max().unwrap(); + if i > idx && format != Format::Long { + format = Format::OneLine; + } + } + } + } + let files = if options.is_present(options::files::ALL) { Files::All } else if options.is_present(options::files::ALMOST_ALL) { @@ -241,6 +304,18 @@ impl Config { SizeFormat::Bytes }; + let long = { + let author = options.is_present(options::AUTHOR); + let group = !options.is_present(options::NO_GROUP) + && !options.is_present(options::format::LONG_NO_GROUP); + let owner = !options.is_present(options::format::LONG_NO_OWNER); + LongFormat { + author, + group, + owner, + } + }; + Config { format, files, @@ -258,6 +333,7 @@ impl Config { color, #[cfg(unix)] inode: options.is_present(options::INODE), + long, } } } @@ -284,7 +360,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .overrides_with_all(&[ options::FORMAT, options::format::COLUMNS, - options::format::ONELINE, options::format::LONG, ]), ) @@ -292,17 +367,40 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::format::COLUMNS) .short(options::format::COLUMNS) .help("Display the files in columns.") - ) - .arg( - Arg::with_name(options::format::ONELINE) - .short(options::format::ONELINE) - .help("List one file per line.") + .overrides_with_all(&[ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + ]), ) .arg( Arg::with_name(options::format::LONG) .short("l") .long(options::format::LONG) .help("Display detailed information.") + .overrides_with_all(&[ + options::FORMAT, + options::format::COLUMNS, + options::format::ONELINE, + options::format::LONG, + ]), + ) + // The next three arguments do not override with the other format + // options, see the comment in Config::from for the reason. + .arg( + Arg::with_name(options::format::ONELINE) + .short(options::format::ONELINE) + .help("List one file per line.") + ) + .arg( + Arg::with_name(options::format::LONG_NO_GROUP) + .short(options::format::LONG_NO_GROUP) + .help("Long format without group information. Identical to --format=long with --no-group.") + ) + .arg( + Arg::with_name(options::format::LONG_NO_OWNER) + .short(options::format::LONG_NO_OWNER) + .help("Long format without owner information.") ) // Time arguments @@ -329,8 +427,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("If the long listing format (e.g., -l, -o) is being used, print the status \ change time (the ‘ctime’ in the inode) instead of the modification time. When \ explicitly sorting by time (--sort=time or -t) or when not using a long listing \ - format, sort according to the status change time.", - )) + format, sort according to the status change time.") + .overrides_with_all(&[ + options::TIME, + options::time::ACCESS, + options::time::CHANGE, + ]) + ) .arg( Arg::with_name(options::time::ACCESS) .short(options::time::ACCESS) @@ -338,6 +441,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { access time instead of the modification time. When explicitly sorting by time \ (--sort=time or -t) or when not using a long listing format, sort according to the \ access time.") + .overrides_with_all(&[ + options::TIME, + options::time::ACCESS, + options::time::CHANGE, + ]) ) // Sort arguments @@ -359,12 +467,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(options::sort::SIZE) .short(options::sort::SIZE) - .help("Sort by file size, largest first."), + .help("Sort by file size, largest first.") + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + ]) ) .arg( Arg::with_name(options::sort::TIME) .short(options::sort::TIME) - .help("Sort by modification time (the 'mtime' in the inode), newest first."), + .help("Sort by modification time (the 'mtime' in the inode), newest first.") + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + ]) ) .arg( Arg::with_name(options::sort::NONE) @@ -372,8 +492,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("Do not sort; list the files in whatever order they are stored in the \ directory. This is especially useful when listing very large directories, \ since not doing any sorting can be noticeably faster.") + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + ]) ) + // Long format options + .arg( + Arg::with_name(options::NO_GROUP) + .long(options::NO_GROUP) + .short("-G") + .help("Do not show group in long format.") + ) + .arg( + Arg::with_name(options::AUTHOR) + .long(options::AUTHOR) + .help("Show author in long format. On the supported platforms, the author \ + always matches the file owner.") + ) // Other Flags .arg( Arg::with_name(options::files::ALL) @@ -699,32 +838,45 @@ fn display_item_long( Ok(md) => md, }; - println!( - "{}{}{} {} {} {} {} {} {}", - get_inode(&md, config), + #[cfg(unix)] + { + if config.inode { + print!("{} ", get_inode(&md)); + } + } + + print!( + "{}{} {}", display_file_type(md.file_type()), display_permissions(&md), pad_left(display_symlink_count(&md), max_links), - display_uname(&md, config), - display_group(&md, config), + ); + + if config.long.owner { + print!(" {}", display_uname(&md, config)); + } + + if config.long.group { + print!(" {}", display_group(&md, config)); + } + + // Author is only different from owner on GNU/Hurd, so we reuse + // the owner, since GNU/Hurd is not currently supported by Rust. + if config.long.author { + print!(" {}", display_uname(&md, config)); + } + + println!( + " {} {} {}", pad_left(display_file_size(&md, config), max_size), display_date(&md, config), - display_file_name(&item, strip, &md, config).contents + display_file_name(&item, strip, &md, config).contents, ); } #[cfg(unix)] -fn get_inode(metadata: &Metadata, config: &Config) -> String { - if config.inode { - format!("{:8} ", metadata.ino()) - } else { - "".to_string() - } -} - -#[cfg(not(unix))] -fn get_inode(_metadata: &Metadata, _config: &Config) -> String { - "".to_string() +fn get_inode(metadata: &Metadata) -> String { + format!("{:8}", metadata.ino()) } // Currently getpwuid is `linux` target only. If it's broken out into @@ -808,7 +960,7 @@ fn format_prefixed(prefixed: NumberPrefix) -> String { NumberPrefix::Standalone(bytes) => bytes.to_string(), NumberPrefix::Prefixed(prefix, bytes) => { // Remove the "i" from "Ki", "Mi", etc. if present - let prefix_str = prefix.symbol().trim_end_matches("i"); + let prefix_str = prefix.symbol().trim_end_matches('i'); // Check whether we get more than 10 if we round up to the first decimal // because we want do display 9.81 as "9.9", not as "10". @@ -861,10 +1013,6 @@ fn display_file_name( ) -> Cell { let mut name = get_file_name(path, strip); - if config.format == Format::Long { - name = get_inode(metadata, config) + &name; - } - if config.classify { let file_type = metadata.file_type(); if file_type.is_dir() { @@ -922,8 +1070,8 @@ fn display_file_name( config: &Config, ) -> Cell { let mut name = get_file_name(path, strip); - if config.format != Format::Long { - name = get_inode(metadata, config) + &name; + if config.format != Format::Long && config.inode { + name = get_inode(metadata) + " " + &name; } let mut width = UnicodeWidthStr::width(&*name); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index e0063aa1a..ac5e0c8b3 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -124,6 +124,108 @@ fn test_ls_long() { } } +#[test] +fn test_ls_long_formats() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch(&at.plus_as_string("test-long-formats")); + + // Regex for three names, so all of author, group and owner + let re_three = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){3}0").unwrap(); + + // Regex for two names, either: + // - group and owner + // - author and owner + // - author and group + let re_two = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){2}0").unwrap(); + + // Regex for one name: author, group or owner + let re_one = Regex::new(r"[xrw-]{9} \d [-0-9_a-z]+ 0").unwrap(); + + // Regex for no names + let re_zero = Regex::new(r"[xrw-]{9} \d 0").unwrap(); + + let result = scene + .ucmd() + .arg("-l") + .arg("--author") + .arg("test-long-formats") + .run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(re_three.is_match(&result.stdout)); + + let result = scene + .ucmd() + .arg("-l1") + .arg("--author") + .arg("test-long-formats") + .run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(re_three.is_match(&result.stdout)); + + for arg in &[ + "-l", // only group and owner + "-g --author", // only author and group + "-o --author", // only author and owner + "-lG --author", // only author and owner + "-l --no-group --author", // only author and owner + ] { + let result = scene + .ucmd() + .args(&arg.split(" ").collect::>()) + .arg("test-long-formats") + .succeeds(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(re_two.is_match(&result.stdout)); + } + + for arg in &[ + "-g", // only group + "-gl", // only group + "-o", // only owner + "-ol", // only owner + "-oG", // only owner + "-lG", // only owner + "-l --no-group", // only owner + "-gG --author", // only author + ] { + let result = scene + .ucmd() + .args(&arg.split(" ").collect::>()) + .arg("test-long-formats") + .succeeds(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(re_one.is_match(&result.stdout)); + } + + for arg in &[ + "-og", + "-ogl", + "-lgo", + "-gG", + "-g --no-group", + "-og --no-group", + "-og --format=long", + "-ogCl", + "-og --format=vertical -l", + "-og1", + "-og1l", + ] { + let result = scene + .ucmd() + .args(&arg.split(" ").collect::>()) + .arg("test-long-formats") + .succeeds(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(re_zero.is_match(&result.stdout)); + } +} + #[test] fn test_ls_oneline() { let scene = TestScenario::new(util_name!()); @@ -426,6 +528,55 @@ fn test_ls_ls_color() { assert_eq!(result.stdout, ""); } +#[cfg(unix)] +#[test] +fn test_ls_inode() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file = "test_inode"; + at.touch(file); + + let re_short = Regex::new(r" *(\d+) test_inode").unwrap(); + let re_long = Regex::new(r" *(\d+) [xrw-]{10} \d .+ test_inode").unwrap(); + + let result = scene.ucmd().arg("test_inode").arg("-i").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(re_short.is_match(&result.stdout)); + let inode_short = re_short + .captures(&result.stdout) + .unwrap() + .get(1) + .unwrap() + .as_str(); + + let result = scene.ucmd().arg("test_inode").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(!re_short.is_match(&result.stdout)); + assert!(!result.stdout.contains(inode_short)); + + let result = scene.ucmd().arg("-li").arg("test_inode").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(re_long.is_match(&result.stdout)); + let inode_long = re_long + .captures(&result.stdout) + .unwrap() + .get(1) + .unwrap() + .as_str(); + + let result = scene.ucmd().arg("-l").arg("test_inode").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(!re_long.is_match(&result.stdout)); + assert!(!result.stdout.contains(inode_long)); + + assert_eq!(inode_short, inode_long) +} + #[cfg(not(any(target_vendor = "apple", target_os = "windows")))] // Truncate not available on mac or win #[test] fn test_ls_human_si() { From f60790dd411fbdc85a77eef24faab5c34422b00b Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Sun, 21 Mar 2021 18:18:47 +0300 Subject: [PATCH 077/104] chroot: move to clap from getopts (#1792) + add tests --- src/uu/chroot/Cargo.toml | 2 +- src/uu/chroot/src/chroot.rs | 122 +++++++++++++++++++++-------------- tests/by-util/test_chroot.rs | 99 +++++++++++++++++++++++++++- 3 files changed, 172 insertions(+), 51 deletions(-) diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index 7ad2f0908..e967d4137 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/chroot.rs" [dependencies] -getopts = "0.2.18" +clap= "2.33" uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index ab654abf8..2c3bcbca4 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -10,60 +10,81 @@ #[macro_use] extern crate uucore; -use uucore::entries; -use uucore::libc::{self, chroot, setgid, setgroups, setuid}; - +use clap::{App, Arg}; use std::ffi::CString; use std::io::Error; use std::path::Path; use std::process::Command; +use uucore::entries; +use uucore::libc::{self, chroot, setgid, setgroups, setuid}; +static VERSION: &str = env!("CARGO_PKG_VERSION"); static NAME: &str = "chroot"; +static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT."; static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]"; -static SUMMARY: &str = "Run COMMAND with root directory set to NEWROOT."; -static LONG_HELP: &str = " - If COMMAND is not specified, it defaults to '$(SHELL) -i'. - If $(SHELL) is not set, /bin/sh is used. -"; + +mod options { + pub const NEWROOT: &str = "newroot"; + pub const USER: &str = "user"; + pub const GROUP: &str = "group"; + pub const GROUPS: &str = "groups"; + pub const USERSPEC: &str = "userspec"; +} pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optopt( - "u", - "user", - "User (ID or name) to switch before running the program", - "USER", + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(SYNTAX) + .arg(Arg::with_name(options::NEWROOT).hidden(true).required(true)) + .arg( + Arg::with_name(options::USER) + .short("u") + .long(options::USER) + .help("User (ID or name) to switch before running the program") + .value_name("USER"), ) - .optopt("g", "group", "Group (ID or name) to switch to", "GROUP") - .optopt( - "G", - "groups", - "Comma-separated list of groups to switch to", - "GROUP1,GROUP2...", + .arg( + Arg::with_name(options::GROUP) + .short("g") + .long(options::GROUP) + .help("Group (ID or name) to switch to") + .value_name("GROUP"), ) - .optopt( - "", - "userspec", - "Colon-separated user and group to switch to. \ + .arg( + Arg::with_name(options::GROUPS) + .short("G") + .long(options::GROUPS) + .help("Comma-separated list of groups to switch to") + .value_name("GROUP1,GROUP2..."), + ) + .arg( + Arg::with_name(options::USERSPEC) + .long(options::USERSPEC) + .help( + "Colon-separated user and group to switch to. \ Same as -u USER -g GROUP. \ Userspec has higher preference than -u and/or -g", - "USER:GROUP", + ) + .value_name("USER:GROUP"), ) - .parse(args); - - if matches.free.is_empty() { - println!("Missing operand: NEWROOT"); - println!("Try `{} --help` for more information.", NAME); - return 1; - } + .get_matches_from(args); let default_shell: &'static str = "/bin/sh"; let default_option: &'static str = "-i"; let user_shell = std::env::var("SHELL"); - let newroot = Path::new(&matches.free[0][..]); + let newroot: &Path = match matches.value_of(options::NEWROOT) { + Some(v) => Path::new(v), + None => crash!( + 1, + "Missing operand: NEWROOT\nTry '{} --help' for more information.", + NAME + ), + }; + if !newroot.is_dir() { crash!( 1, @@ -72,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); } - let command: Vec<&str> = match matches.free.len() { + let command: Vec<&str> = match matches.args.len() { 1 => { let shell: &str = match user_shell { Err(_) => default_shell, @@ -80,7 +101,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; vec![shell, default_option] } - _ => matches.free[1..].iter().map(|x| &x[..]).collect(), + _ => { + let mut vector: Vec<&str> = Vec::new(); + for (&k, v) in matches.args.iter() { + vector.push(k.clone()); + vector.push(&v.vals[0].to_str().unwrap()); + } + vector + } }; set_context(&newroot, &matches); @@ -97,30 +125,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } -fn set_context(root: &Path, options: &getopts::Matches) { - let userspec_str = options.opt_str("userspec"); - let user_str = options.opt_str("user").unwrap_or_default(); - let group_str = options.opt_str("group").unwrap_or_default(); - let groups_str = options.opt_str("groups").unwrap_or_default(); +fn set_context(root: &Path, options: &clap::ArgMatches) { + let userspec_str = options.value_of(options::USERSPEC); + let user_str = options.value_of(options::USER).unwrap_or_default(); + let group_str = options.value_of(options::GROUP).unwrap_or_default(); + let groups_str = options.value_of(options::GROUPS).unwrap_or_default(); let userspec = match userspec_str { Some(ref u) => { let s: Vec<&str> = u.split(':').collect(); - if s.len() != 2 { + if s.len() != 2 || s.iter().any(|&spec| spec == "") { crash!(1, "invalid userspec: `{}`", u) }; s } None => Vec::new(), }; - let user = if userspec.is_empty() { - &user_str[..] + + let (user, group) = if userspec.is_empty() { + (&user_str[..], &group_str[..]) } else { - &userspec[0][..] - }; - let group = if userspec.is_empty() { - &group_str[..] - } else { - &userspec[1][..] + (&userspec[0][..], &userspec[1][..]) }; enter_chroot(root); diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 651491045..9a8fb71dd 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -1 +1,98 @@ -// ToDO: add tests +use crate::common::util::*; + +#[test] +fn test_missing_operand() { + let result = new_ucmd!().run(); + + assert_eq!( + true, + result + .stderr + .starts_with("error: The following required arguments were not provided") + ); + + assert_eq!(true, result.stderr.contains("")); +} + +#[test] +fn test_enter_chroot_fails() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("jail"); + + let result = ucmd.arg("jail").run(); + + assert_eq!( + true, + result.stderr.starts_with( + "chroot: error: cannot chroot to jail: Operation not permitted (os error 1)" + ) + ) +} + +#[test] +fn test_no_such_directory() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch(&at.plus_as_string("a")); + + ucmd.arg("a") + .fails() + .stderr_is("chroot: error: cannot change root directory to `a`: no such directory"); +} + +#[test] +fn test_invalid_user_spec() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("a"); + + let result = ucmd.arg("a").arg("--userspec=ARABA:").run(); + + assert_eq!( + true, + result.stderr.starts_with("chroot: error: invalid userspec") + ); +} + +#[test] +fn test_preference_of_userspec() { + let scene = TestScenario::new(util_name!()); + let result = scene.cmd("whoami").run(); + if is_ci() && result.stderr.contains("No such user/group") { + // In the CI, some server are failing to return whoami. + // As seems to be a configuration issue, ignoring it + return; + } + println!("result.stdout {}", result.stdout); + println!("result.stderr = {}", result.stderr); + let username = result.stdout.trim_end(); + + let ts = TestScenario::new("id"); + let result = ts.cmd("id").arg("-g").arg("-n").run(); + println!("result.stdout {}", result.stdout); + println!("result.stderr = {}", result.stderr); + + if is_ci() && result.stderr.contains("cannot find name for user ID") { + // In the CI, some server are failing to return id. + // As seems to be a configuration issue, ignoring it + return; + } + + let group_name = result.stdout.trim_end(); + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("a"); + + let result = ucmd + .arg("a") + .arg("--user") + .arg("fake") + .arg("-G") + .arg("ABC,DEF") + .arg(format!("--userspec={}:{}", username, group_name)) + .run(); + + println!("result.stdout {}", result.stdout); + println!("result.stderr = {}", result.stderr); +} From ca8fbc37bfe5526af61d210480895069aecc8030 Mon Sep 17 00:00:00 2001 From: pedrohjordao Date: Sun, 21 Mar 2021 15:19:30 +0000 Subject: [PATCH 078/104] od: Changes command line parser to clap (#1849) --- Cargo.lock | 4 +- src/uu/od/Cargo.toml | 2 +- src/uu/od/src/od.rs | 361 ++++++++++++++++++++++++---------- src/uu/od/src/parse_inputs.rs | 40 ++-- 4 files changed, 284 insertions(+), 123 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d4318dec..ad207edd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,3 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. [[package]] name = "advapi32-sys" version = "0.2.0" @@ -1868,7 +1866,7 @@ name = "uu_od" version = "0.0.4" dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "half 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index 14aea59a7..e4db9faf0 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -16,7 +16,7 @@ path = "src/od.rs" [dependencies] byteorder = "1.3.2" -getopts = "0.2.18" +clap = "2.33" half = "1.6" libc = "0.2.42" uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 47d3c29f8..791ddc4fc 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -41,15 +41,18 @@ use crate::parse_nrofbytes::parse_number_of_bytes; use crate::partialreader::*; use crate::peekreader::*; use crate::prn_char::format_ascii_dump; +use clap::{self, AppSettings, Arg, ArgMatches}; static VERSION: &str = env!("CARGO_PKG_VERSION"); const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes +static ABOUT: &str = "dump files in octal and other formats"; -static USAGE: &str = r#"Usage: +static USAGE: &str = r#" od [OPTION]... [--] [FILENAME]... od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]] - od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]] + od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]"#; +static LONG_HELP: &str = r#" Displays data in various human-readable formats. If multiple formats are specified, the output will contain all formats in the order they appear on the command line. Each format will be printed on a new line. Only the line @@ -88,85 +91,18 @@ Any type specification can have a "z" suffix, which will add a ASCII dump at If an error occurred, a diagnostic message will be printed to stderr, and the exitcode will be non-zero."#; -fn create_getopts_options() -> getopts::Options { - let mut opts = getopts::Options::new(); - - opts.optopt( - "A", - "address-radix", - "Select the base in which file offsets are printed.", - "RADIX", - ); - opts.optopt( - "j", - "skip-bytes", - "Skip bytes input bytes before formatting and writing.", - "BYTES", - ); - opts.optopt( - "N", - "read-bytes", - "limit dump to BYTES input bytes", - "BYTES", - ); - opts.optopt( - "", - "endian", - "byte order to use for multi-byte formats", - "big|little", - ); - opts.optopt( - "S", - "strings", - "output strings of at least BYTES graphic chars. 3 is assumed when \ - BYTES is not specified.", - "BYTES", - ); - opts.optflagmulti("a", "", "named characters, ignoring high-order bit"); - opts.optflagmulti("b", "", "octal bytes"); - opts.optflagmulti("c", "", "ASCII characters or backslash escapes"); - opts.optflagmulti("d", "", "unsigned decimal 2-byte units"); - opts.optflagmulti("D", "", "unsigned decimal 4-byte units"); - opts.optflagmulti("o", "", "octal 2-byte units"); - - opts.optflagmulti("I", "", "decimal 8-byte units"); - opts.optflagmulti("L", "", "decimal 8-byte units"); - opts.optflagmulti("i", "", "decimal 4-byte units"); - opts.optflagmulti("l", "", "decimal 8-byte units"); - opts.optflagmulti("x", "", "hexadecimal 2-byte units"); - opts.optflagmulti("h", "", "hexadecimal 2-byte units"); - - opts.optflagmulti("O", "", "octal 4-byte units"); - opts.optflagmulti("s", "", "decimal 2-byte units"); - opts.optflagmulti("X", "", "hexadecimal 4-byte units"); - opts.optflagmulti("H", "", "hexadecimal 4-byte units"); - - opts.optflagmulti("e", "", "floating point double precision (64-bit) units"); - opts.optflagmulti("f", "", "floating point single precision (32-bit) units"); - opts.optflagmulti("F", "", "floating point double precision (64-bit) units"); - - opts.optmulti("t", "format", "select output format or formats", "TYPE"); - opts.optflag( - "v", - "output-duplicates", - "do not use * to mark line suppression", - ); - opts.optflagopt( - "w", - "width", - "output BYTES bytes per output line. 32 is implied when BYTES is not \ - specified.", - "BYTES", - ); - opts.optflag("", "help", "display this help and exit."); - opts.optflag("", "version", "output version information and exit."); - opts.optflag( - "", - "traditional", - "compatibility mode with one input, offset and label.", - ); - - opts +pub(crate) mod options { + pub const ADDRESS_RADIX: &str = "address-radix"; + pub const SKIP_BYTES: &str = "skip-bytes"; + pub const READ_BYTES: &str = "read-bytes"; + pub const ENDIAN: &str = "endian"; + pub const STRINGS: &str = "strings"; + pub const FORMAT: &str = "format"; + pub const OUTPUT_DUPLICATES: &str = "output-duplicates"; + pub const TRADITIONAL: &str = "traditional"; + pub const WIDTH: &str = "width"; + pub const VERSION: &str = "version"; + pub const FILENAME: &str = "FILENAME"; } struct OdOptions { @@ -182,8 +118,8 @@ struct OdOptions { } impl OdOptions { - fn new(matches: getopts::Matches, args: Vec) -> Result { - let byte_order = match matches.opt_str("endian").as_ref().map(String::as_ref) { + fn new<'a>(matches: ArgMatches<'a>, args: Vec) -> Result { + let byte_order = match matches.value_of(options::ENDIAN) { None => ByteOrder::Native, Some("little") => ByteOrder::Little, Some("big") => ByteOrder::Big, @@ -192,7 +128,7 @@ impl OdOptions { } }; - let mut skip_bytes = match matches.opt_default("skip-bytes", "0") { + let mut skip_bytes = match matches.value_of(options::SKIP_BYTES) { None => 0, Some(s) => match parse_number_of_bytes(&s) { Ok(i) => i, @@ -223,8 +159,9 @@ impl OdOptions { } }; - let mut line_bytes = match matches.opt_default("w", "32") { + let mut line_bytes = match matches.value_of(options::WIDTH) { None => 16, + Some(_) if matches.occurrences_of(options::WIDTH) == 0 => 16, Some(s) => s.parse::().unwrap_or(0), }; let min_bytes = formats.iter().fold(1, |max, next| { @@ -235,9 +172,9 @@ impl OdOptions { line_bytes = min_bytes; } - let output_duplicates = matches.opt_present("v"); + let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES); - let read_bytes = match matches.opt_str("read-bytes") { + let read_bytes = match matches.value_of(options::READ_BYTES) { None => None, Some(s) => match parse_number_of_bytes(&s) { Ok(i) => Some(i), @@ -247,10 +184,10 @@ impl OdOptions { }, }; - let radix = match matches.opt_str("A") { + let radix = match matches.value_of(options::ADDRESS_RADIX) { None => Radix::Octal, Some(s) => { - let st = s.into_bytes(); + let st = s.as_bytes(); if st.len() != 1 { return Err("Radix must be one of [d, o, n, x]".to_string()); } else { @@ -286,26 +223,244 @@ impl OdOptions { pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let opts = create_getopts_options(); + let clap_opts = clap::App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(USAGE) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::ADDRESS_RADIX) + .short("A") + .long(options::ADDRESS_RADIX) + .help("Select the base in which file offsets are printed.") + .value_name("RADIX"), + ) + .arg( + Arg::with_name(options::SKIP_BYTES) + .short("j") + .long(options::SKIP_BYTES) + .help("Skip bytes input bytes before formatting and writing.") + .value_name("BYTES"), + ) + .arg( + Arg::with_name(options::READ_BYTES) + .short("N") + .long(options::READ_BYTES) + .help("limit dump to BYTES input bytes") + .value_name("BYTES"), + ) + .arg( + Arg::with_name(options::ENDIAN) + .long(options::ENDIAN) + .help("byte order to use for multi-byte formats") + .possible_values(&["big", "little"]) + .value_name("big|little"), + ) + .arg( + Arg::with_name(options::STRINGS) + .short("S") + .long(options::STRINGS) + .help( + "output strings of at least BYTES graphic chars. 3 is assumed when \ + BYTES is not specified.", + ) + .default_value("3") + .value_name("BYTES"), + ) + .arg( + Arg::with_name("a") + .short("a") + .help("named characters, ignoring high-order bit") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("b") + .short("b") + .help("octal bytes") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("c") + .short("c") + .help("ASCII characters or backslash escapes") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("d") + .short("d") + .help("unsigned decimal 2-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("D") + .short("D") + .help("unsigned decimal 4-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("o") + .short("o") + .help("octal 2-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("I") + .short("I") + .help("decimal 8-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("L") + .short("L") + .help("decimal 8-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("i") + .short("i") + .help("decimal 4-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("l") + .short("l") + .help("decimal 8-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("x") + .short("x") + .help("hexadecimal 2-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("h") + .short("h") + .help("hexadecimal 2-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("O") + .short("O") + .help("octal 4-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("s") + .short("s") + .help("decimal 2-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("X") + .short("X") + .help("hexadecimal 4-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("H") + .short("H") + .help("hexadecimal 4-byte units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("e") + .short("e") + .help("floating point double precision (64-bit) units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("f") + .short("f") + .help("floating point double precision (32-bit) units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name("F") + .short("F") + .help("floating point double precision (64-bit) units") + .multiple(true) + .takes_value(false), + ) + .arg( + Arg::with_name(options::FORMAT) + .short("t") + .long(options::FORMAT) + .help("select output format or formats") + .multiple(true) + .value_name("TYPE"), + ) + .arg( + Arg::with_name(options::OUTPUT_DUPLICATES) + .short("v") + .long(options::OUTPUT_DUPLICATES) + .help("do not use * to mark line suppression") + .takes_value(false) + .possible_values(&["big", "little"]), + ) + .arg( + Arg::with_name(options::WIDTH) + .short("w") + .long(options::WIDTH) + .help( + "output BYTES bytes per output line. 32 is implied when BYTES is not \ + specified.", + ) + .default_value("32") + .value_name("BYTES"), + ) + .arg( + Arg::with_name(options::VERSION) + .long(options::VERSION) + .help("output version information and exit.") + .takes_value(false), + ) + .arg( + Arg::with_name(options::TRADITIONAL) + .long(options::TRADITIONAL) + .help("compatibility mode with one input, offset and label.") + .takes_value(false), + ) + .arg( + Arg::with_name(options::FILENAME) + .hidden(true) + .multiple(true) + ) + .settings(&[ + AppSettings::TrailingVarArg, + AppSettings::DontDelimitTrailingValues, + AppSettings::DisableVersion, + AppSettings::DeriveDisplayOrder, + ]); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => { - show_usage_error!("{}", f); - return 1; - } - }; + let clap_matches = clap_opts + .clone() // Clone to reuse clap_otps to print help + .get_matches_from(args.clone()); - if matches.opt_present("help") { - println!("{}", opts.usage(&USAGE)); - return 0; - } - if matches.opt_present("version") { + if clap_matches.is_present(options::VERSION) { println!("{} {}", executable!(), VERSION); return 0; } - let od_options = match OdOptions::new(matches, args) { + let od_options = match OdOptions::new(clap_matches, args) { Err(s) => { show_usage_error!("{}", s); return 1; diff --git a/src/uu/od/src/parse_inputs.rs b/src/uu/od/src/parse_inputs.rs index 89a833d94..915aa1d92 100644 --- a/src/uu/od/src/parse_inputs.rs +++ b/src/uu/od/src/parse_inputs.rs @@ -1,20 +1,24 @@ -use getopts::Matches; +use super::options; +use clap::ArgMatches; /// Abstraction for getopts pub trait CommandLineOpts { /// returns all command line parameters which do not belong to an option. - fn inputs(&self) -> Vec; + fn inputs(&self) -> Vec<&str>; /// tests if any of the specified options is present. fn opts_present(&self, _: &[&str]) -> bool; } /// Implementation for `getopts` -impl CommandLineOpts for Matches { - fn inputs(&self) -> Vec { - self.free.clone() +impl<'a> CommandLineOpts for ArgMatches<'a> { + fn inputs(&self) -> Vec<&str> { + self.values_of(options::FILENAME) + .map(|values| values.collect()) + .unwrap_or_default() } + fn opts_present(&self, opts: &[&str]) -> bool { - self.opts_present(&opts.iter().map(|s| (*s).to_string()).collect::>()) + opts.iter().any(|opt| self.is_present(opt)) } } @@ -39,7 +43,7 @@ pub enum CommandLineInputs { /// '-' is used as filename if stdin is meant. This is also returned if /// there is no input, as stdin is the default input. pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result { - let mut input_strings: Vec = matches.inputs(); + let mut input_strings = matches.inputs(); if matches.opts_present(&["traditional"]) { return parse_inputs_traditional(input_strings); @@ -59,7 +63,7 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result Result) -> Result { +pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result { match input_strings.len() { 0 => Ok(CommandLineInputs::FileNames(vec!["-".to_string()])), 1 => { let offset0 = parse_offset_operand(&input_strings[0]); Ok(match offset0 { Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)), - _ => CommandLineInputs::FileNames(input_strings), + _ => CommandLineInputs::FileNames( + input_strings.iter().map(|s| s.to_string()).collect(), + ), }) } 2 => { @@ -98,7 +106,7 @@ pub fn parse_inputs_traditional(input_strings: Vec) -> Result Ok(CommandLineInputs::FileAndOffset(( - input_strings[0].clone(), + input_strings[0].clone().to_owned(), m, None, ))), @@ -110,7 +118,7 @@ pub fn parse_inputs_traditional(input_strings: Vec) -> Result Ok(CommandLineInputs::FileAndOffset(( - input_strings[0].clone(), + input_strings[0].clone().to_owned(), n, Some(m), ))), @@ -178,8 +186,8 @@ mod tests { } impl<'a> CommandLineOpts for MockOptions<'a> { - fn inputs(&self) -> Vec { - self.inputs.clone() + fn inputs(&self) -> Vec<&str> { + self.inputs.iter().map(|s| s.as_str()).collect() } fn opts_present(&self, opts: &[&str]) -> bool { for expected in opts.iter() { From 734368bc92e8f4093bf109a60c126b61f8bac499 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 21 Mar 2021 17:03:58 +0100 Subject: [PATCH 079/104] refresh cargo.lock with recent updates --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ad207edd3..e5852987e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1410,7 +1410,7 @@ dependencies = [ name = "uu_chroot" version = "0.0.4" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", ] From 6c9841534020eacc62aee5e450f57d1439db295e Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Sun, 21 Mar 2021 23:27:44 +0300 Subject: [PATCH 080/104] fix(head): check the whether file exists before unwrap (#1858) closes https://github.com/uutils/coreutils/issues/1800 --- src/uu/head/src/head.rs | 7 +++++++ tests/by-util/test_head.rs | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index ae5807c22..0036dbba9 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -149,6 +149,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { first_time = false; let path = Path::new(file); + if path.is_dir() || !path.metadata().is_ok() { + eprintln!( + "cannot open '{}' for reading: No such file or directory", + &path.to_str().unwrap() + ); + continue; + } let reader = File::open(&path).unwrap(); let mut buffer = BufReader::new(reader); if !head(&mut buffer, &settings) { diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index eec82b51f..a1086c004 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -167,3 +167,15 @@ fn test_bug_in_negative_zero_lines() { //GNU Head returns "a\nb\n" .stdout_is(""); } + +#[test] +fn test_no_such_file_or_directory() { + let result = new_ucmd!().arg("no_such_file.toml").run(); + + assert_eq!( + true, + result + .stderr + .contains("cannot open 'no_such_file.toml' for reading: No such file or directory") + ) +} From 40677bdc7a367b85dc0b0a40fb2f52a776ecda6f Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 21 Mar 2021 20:49:15 +0000 Subject: [PATCH 081/104] Fix more problems with utils that aren't being tested --- .github/workflows/GNU.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 59ce82420..5b8742b6b 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -84,14 +84,20 @@ jobs: sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh - sed -i 's|timeout |/usr/bin/timeout |' 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 - sed -i 's| timeout | /usr/bin/timeout |' tests/tail-2/inotify-rotate.sh # Don't break the function called 'grep_timeout' - sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.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 + # Don't break the function called 'grep_timeout' + sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.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 sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh - sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh + sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh tests/cp/fiemap-2.sh tests/init.sh sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh + sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/ls/abmon-align.sh tests/ls/rt-1.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh + sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh + sed -i 's|printf |/usr/bin/printf |' tests/dd/ascii.sh + sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh + sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh + sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh #Add specific timeout to tests that currently hang to limit time spent waiting sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh From 21be280c5a3f9f13b740db9f5f4e48dd0cb7c207 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 21 Mar 2021 22:21:57 +0100 Subject: [PATCH 082/104] rustfmt the od changes --- src/uu/od/src/od.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 791ddc4fc..c3b39fca1 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -442,7 +442,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(options::FILENAME) .hidden(true) - .multiple(true) + .multiple(true), ) .settings(&[ AppSettings::TrailingVarArg, From 027d5e6d9d848a5f58389db79591b63763d9011f Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 21 Mar 2021 22:31:15 +0000 Subject: [PATCH 083/104] Fix yaml syntax --- .github/workflows/GNU.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 5b8742b6b..f89621e3f 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -84,8 +84,7 @@ jobs: sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.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 - # Don't break the function called 'grep_timeout' + 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 # Don't break the function called 'grep_timeout' sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.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 From 27b7552ef4a8986b0e931718563021ee7270fc22 Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Mon, 22 Mar 2021 12:01:54 +0300 Subject: [PATCH 084/104] fix(tail): add support for negative indexing (#1865) closes: https://github.com/uutils/coreutils/issues/1860 --- src/uu/tail/src/tail.rs | 6 ++++-- tests/by-util/test_tail.rs | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index cd391a53e..3a6b04b29 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -80,6 +80,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .short("c") .long(options::BYTES) .takes_value(true) + .allow_hyphen_values(true) .help("Number of bytes to print"), ) .arg( @@ -93,6 +94,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .short("n") .long(options::LINES) .takes_value(true) + .allow_hyphen_values(true) .help("Number of lines to print"), ) .arg( @@ -343,9 +345,9 @@ pub fn parse_size(mut size_slice: &str) -> Result { // sole B is not a valid suffix Err(ParseSizeErr::parse_failure(size_slice)) } else { - let value: Option = size_slice.parse().ok(); + let value: Option = size_slice.parse().ok(); value - .map(|v| Ok(multiplier * v)) + .map(|v| Ok((multiplier as i64 * v.abs()) as u64)) .unwrap_or_else(|| Err(ParseSizeErr::parse_failure(size_slice))) } } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 458fc6aa7..5edff4d55 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -329,3 +329,17 @@ fn test_multiple_input_quiet_flag_overrides_verbose_flag_for_suppressing_headers .run() .stdout_is_fixture("foobar_multiple_quiet.expected"); } + +#[test] +fn test_negative_indexing() { + let positive_lines_index = new_ucmd!().arg("-n").arg("5").arg(FOOBAR_TXT).run(); + + let negative_lines_index = new_ucmd!().arg("-n").arg("-5").arg(FOOBAR_TXT).run(); + + let positive_bytes_index = new_ucmd!().arg("-c").arg("20").arg(FOOBAR_TXT).run(); + + let negative_bytes_index = new_ucmd!().arg("-c").arg("-20").arg(FOOBAR_TXT).run(); + + assert_eq!(positive_lines_index.stdout, negative_lines_index.stdout); + assert_eq!(positive_bytes_index.stdout, negative_bytes_index.stdout); +} From b9662c78a5c520b8661f7a27227b2331e069d725 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 22 Mar 2021 10:14:59 +0100 Subject: [PATCH 085/104] ls: possible fix for access time tests (#1866) --- tests/by-util/test_ls.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index ac5e0c8b3..4fd35a286 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -344,6 +344,7 @@ fn test_ls_order_time() { sleep(Duration::from_millis(100)); at.touch("test-2"); at.append("test-2", "22"); + sleep(Duration::from_millis(100)); at.touch("test-3"); at.append("test-3", "333"); @@ -361,6 +362,7 @@ fn test_ls_order_time() { at.metadata("test-2").permissions(), ) .unwrap(); + let second_access = at.open("test-2").metadata().unwrap().accessed().unwrap(); let result = scene.ucmd().arg("-al").run(); println!("stderr = {:?}", result.stderr); @@ -392,13 +394,23 @@ fn test_ls_order_time() { println!("stderr = {:?}", result.stderr); println!("stdout = {:?}", result.stdout); assert!(result.success); - #[cfg(not(windows))] - assert_eq!(result.stdout, "test-3\ntest-4\ntest-2\ntest-1\n"); - - // Access time does not seem to be set on Windows on read call - // so the order is 4 3 2 1 - #[cfg(windows)] - assert_eq!(result.stdout, "test-4 test-3 test-2 test-1\n"); + let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap(); + let file4_access = at.open("test-4").metadata().unwrap().accessed().unwrap(); + if file3_access > file4_access { + if cfg!(not(windows)) { + assert_eq!(result.stdout, "test-3\ntest-4\ntest-2\ntest-1\n"); + } else { + assert_eq!(result.stdout, "test-3 test-4 test-2 test-1\n"); + } + } else { + // Access time does not seem to be set on Windows and some other + // systems so the order is 4 3 2 1 + if cfg!(not(windows)) { + assert_eq!(result.stdout, "test-4\ntest-3\ntest-2\ntest-1\n"); + } else { + assert_eq!(result.stdout, "test-4 test-3 test-2 test-1\n"); + } + } // test-2 had the last ctime change when the permissions were set // So the order should be 2 4 3 1 From 93c7cbe65e0d4a189960772a4f4f32c718096e59 Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Mon, 22 Mar 2021 12:13:38 +0200 Subject: [PATCH 086/104] documentation for usual macros - removed repeat_str helper as it's now part of std - added docstrings for usual macros and test utils --- src/uucore/src/lib/macros.rs | 16 ++++++++++++++++ tests/by-util/test_pathchk.rs | 2 +- tests/common/macros.rs | 35 +++++++++++++++++++++++++++++++++++ tests/common/util.rs | 24 ++++++++++++++---------- 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 0aa6d8f0a..6836f81aa 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -5,6 +5,9 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +/// Deduce the name of the binary from the current source code filename. +/// +/// e.g.: `src/uu/cp/src/cp.rs` -> `cp` #[macro_export] macro_rules! executable( () => ({ @@ -18,6 +21,7 @@ macro_rules! executable( }) ); +/// Show an error to stderr in a silimar style to GNU coreutils. #[macro_export] macro_rules! show_error( ($($args:tt)+) => ({ @@ -26,6 +30,7 @@ macro_rules! show_error( }) ); +/// Show a warning to stderr in a silimar style to GNU coreutils. #[macro_export] macro_rules! show_warning( ($($args:tt)+) => ({ @@ -34,6 +39,7 @@ macro_rules! show_warning( }) ); +/// Show an info message to stderr in a silimar style to GNU coreutils. #[macro_export] macro_rules! show_info( ($($args:tt)+) => ({ @@ -42,6 +48,7 @@ macro_rules! show_info( }) ); +/// Show a bad inocation help message in a similar style to GNU coreutils. #[macro_export] macro_rules! show_usage_error( ($($args:tt)+) => ({ @@ -51,6 +58,7 @@ macro_rules! show_usage_error( }) ); +/// Display the provided error message, then `exit()` with the provided exit code #[macro_export] macro_rules! crash( ($exit_code:expr, $($args:tt)+) => ({ @@ -59,6 +67,7 @@ macro_rules! crash( }) ); +/// Calls `exit()` with the provided exit code. #[macro_export] macro_rules! exit( ($exit_code:expr) => ({ @@ -66,6 +75,8 @@ macro_rules! exit( }) ); +/// Unwraps the Result. Instead of panicking, it exists the program with the +/// provided exit code. #[macro_export] macro_rules! crash_if_err( ($exit_code:expr, $exp:expr) => ( @@ -76,6 +87,9 @@ macro_rules! crash_if_err( ) ); +/// Unwraps the Result. Instead of panicking, it shows the error and then +/// returns from the function with the provided exit code. +/// Assumes the current function returns an i32 value. #[macro_export] macro_rules! return_if_err( ($exit_code:expr, $exp:expr) => ( @@ -109,6 +123,8 @@ macro_rules! safe_writeln( ) ); +/// Unwraps the Result. Instead of panicking, it exists the program with exit +/// code 1. #[macro_export] macro_rules! safe_unwrap( ($exp:expr) => ( diff --git a/tests/by-util/test_pathchk.rs b/tests/by-util/test_pathchk.rs index bdce377b3..e24a464e0 100644 --- a/tests/by-util/test_pathchk.rs +++ b/tests/by-util/test_pathchk.rs @@ -9,7 +9,7 @@ fn test_default_mode() { // fail on long inputs new_ucmd!() - .args(&[repeat_str("test", 20000)]) + .args(&["test".repeat(20000)]) .fails() .no_stdout(); } diff --git a/tests/common/macros.rs b/tests/common/macros.rs index 645cfcc67..32ff7cbe8 100644 --- a/tests/common/macros.rs +++ b/tests/common/macros.rs @@ -1,3 +1,6 @@ +/// Assertion helper macro for [`CmdResult`] types +/// +/// [`CmdResult`]: crate::tests::common::util::CmdResult #[macro_export] macro_rules! assert_empty_stderr( ($cond:expr) => ( @@ -7,6 +10,9 @@ macro_rules! assert_empty_stderr( ); ); +/// Assertion helper macro for [`CmdResult`] types +/// +/// [`CmdResult`]: crate::tests::common::util::CmdResult #[macro_export] macro_rules! assert_empty_stdout( ($cond:expr) => ( @@ -16,6 +22,9 @@ macro_rules! assert_empty_stdout( ); ); +/// Assertion helper macro for [`CmdResult`] types +/// +/// [`CmdResult`]: crate::tests::common::util::CmdResult #[macro_export] macro_rules! assert_no_error( ($cond:expr) => ( @@ -26,6 +35,7 @@ macro_rules! assert_no_error( ); ); +/// Platform-independent helper for constructing a PathBuf from individual elements #[macro_export] macro_rules! path_concat { ($e:expr, ..$n:expr) => {{ @@ -47,6 +57,9 @@ macro_rules! path_concat { }}; } +/// Deduce the name of the test binary from the test filename. +/// +/// e.g.: `tests/by-util/test_cat.rs` -> `cat` #[macro_export] macro_rules! util_name { () => { @@ -54,6 +67,16 @@ macro_rules! util_name { }; } +/// Convenience macro for acquiring a [`UCommand`] builder. +/// +/// Returns the following: +/// - a [`UCommand`] builder for invoking the binary to be tested +/// +/// This macro is intended for quick, single-call tests. For more complex tests +/// that require multiple invocations of the tested binary, see [`TestScenario`] +/// +/// [`UCommand`]: crate::tests::common::util::UCommand +/// [`TestScenario]: crate::tests::common::util::TestScenario #[macro_export] macro_rules! new_ucmd { () => { @@ -61,6 +84,18 @@ macro_rules! new_ucmd { }; } +/// Convenience macro for acquiring a [`UCommand`] builder and a test path. +/// +/// Returns a tuple containing the following: +/// - an [`AsPath`] that points to a unique temporary test directory +/// - a [`UCommand`] builder for invoking the binary to be tested +/// +/// This macro is intended for quick, single-call tests. For more complex tests +/// that require multiple invocations of the tested binary, see [`TestScenario`] +/// +/// [`UCommand`]: crate::tests::common::util::UCommand +/// [`AsPath`]: crate::tests::common::util::AsPath +/// [`TestScenario]: crate::tests::common::util::TestScenario #[macro_export] macro_rules! at_and_ucmd { () => {{ diff --git a/tests/common/util.rs b/tests/common/util.rs index 0f1acd49a..a2fab66c6 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use std::env; use std::ffi::OsStr; use std::fs::{self, File, OpenOptions}; @@ -27,7 +29,7 @@ static ALREADY_RUN: &str = " you have already run this UCommand, if you want to testing();"; static MULTIPLE_STDIN_MEANINGLESS: &str = "Ucommand is designed around a typical use case of: provide args and input stream -> spawn process -> block until completion -> return output streams. For verifying that a particular section of the input stream is what causes a particular behavior, use the Command type directly."; -/// Test if the program are running under CI +/// Test if the program is running under CI pub fn is_ci() -> bool { std::env::var("CI") .unwrap_or(String::from("false")) @@ -55,14 +57,6 @@ fn read_scenario_fixture>(tmpd: &Option>, file_rel_p AtPath::new(tmpdir_path).read(file_rel_path.as_ref().to_str().unwrap()) } -pub fn repeat_str(s: &str, n: u32) -> String { - let mut repeated = String::new(); - for _ in 0..n { - repeated.push_str(s); - } - repeated -} - /// A command result is the outputs of a command (streams and status code) /// within a struct which has convenience assertion functions about those outputs #[derive(Debug)] @@ -384,8 +378,10 @@ impl AtPath { /// An environment for running a single uutils test case, serves three functions: /// 1. centralizes logic for locating the uutils binary and calling the utility -/// 2. provides a temporary directory for the test case +/// 2. provides a unique temporary directory for the test case /// 3. copies over fixtures for the utility to the temporary directory +/// +/// Fixtures can be found under `tests/fixtures/$util_name/` pub struct TestScenario { bin_path: PathBuf, util_name: String, @@ -420,12 +416,16 @@ impl TestScenario { ts } + /// Returns builder for invoking the target uutils binary. Paths given are + /// treated relative to the environment's unique temporary test directory. pub fn ucmd(&self) -> UCommand { let mut cmd = self.cmd(&self.bin_path); cmd.arg(&self.util_name); cmd } + /// Returns builder for invoking any system command. Paths given are treated + /// relative to the environment's unique temporary test directory. pub fn cmd>(&self, bin: S) -> UCommand { UCommand::new_from_tmp(bin, self.tmpd.clone(), true) } @@ -495,6 +495,8 @@ impl UCommand { ucmd } + /// Add a parameter to the invocation. Path arguments are treated relative + /// to the test environment directory. pub fn arg>(&mut self, arg: S) -> Box<&mut UCommand> { if self.has_run { panic!(ALREADY_RUN); @@ -505,6 +507,8 @@ impl UCommand { Box::new(self) } + /// Add multiple parameters to the invocation. Path arguments are treated relative + /// to the test environment directory. pub fn args>(&mut self, args: &[S]) -> Box<&mut UCommand> { if self.has_run { panic!(MULTIPLE_STDIN_MEANINGLESS); From d86ee34bc60846650aa1ca5e0179e31a8fd923f3 Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Mon, 22 Mar 2021 20:16:28 +0300 Subject: [PATCH 087/104] tsort: move from getopts to clap (#1867) --- src/uu/tsort/Cargo.toml | 2 +- src/uu/tsort/src/tsort.rs | 50 +++++++++++++------------------------ tests/by-util/test_tsort.rs | 33 ++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 33 deletions(-) diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index 10672a9e0..d870e0155 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/tsort.rs" [dependencies] -getopts = "0.2.18" +clap= "2.33" uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 0a0023031..3440972a2 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -9,49 +9,35 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; -static NAME: &str = "tsort"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static SUMMARY: &str = "Topological sort the strings in FILE. +Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). +If FILE is not passed in, stdin is used instead."; +static USAGE: &str = "tsort [OPTIONS] FILE"; + +mod options { + pub const FILE: &str = "file"; +} pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let mut opts = getopts::Options::new(); + let matches = App::new(executable!()) + .version(VERSION) + .usage(USAGE) + .about(SUMMARY) + .arg(Arg::with_name(options::FILE).hidden(true)) + .get_matches_from(args); - opts.optflag("h", "help", "display this help and exit"); - opts.optflag("V", "version", "output version information and exit"); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => crash!(1, "{}", f), - }; - - if matches.opt_present("h") { - println!("{} {}", NAME, VERSION); - println!(); - println!("Usage:"); - println!(" {} [OPTIONS] FILE", NAME); - println!(); - println!("{}", opts.usage("Topological sort the strings in FILE. Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). If FILE is not passed in, stdin is used instead.")); - return 0; - } - - if matches.opt_present("V") { - println!("{} {}", NAME, VERSION); - return 0; - } - - let files = matches.free.clone(); - let input = if files.len() > 1 { - crash!(1, "{}, extra operand '{}'", NAME, matches.free[1]); - } else if files.is_empty() { - "-".to_owned() - } else { - files[0].clone() + let input = match matches.value_of(options::FILE) { + Some(v) => v, + None => "-", }; let mut stdin_buf; diff --git a/tests/by-util/test_tsort.rs b/tests/by-util/test_tsort.rs index c743868ec..159b80025 100644 --- a/tests/by-util/test_tsort.rs +++ b/tests/by-util/test_tsort.rs @@ -15,3 +15,36 @@ fn test_sort_self_loop() { .succeeds() .stdout_only("first\nsecond\n"); } + +#[test] +fn test_no_such_file() { + let result = new_ucmd!().arg("invalid_file_txt").run(); + + assert_eq!(true, result.stderr.contains("No such file or directory")); +} + +#[test] +fn test_version_flag() { + let version_short = new_ucmd!().arg("-V").run(); + let version_long = new_ucmd!().arg("--version").run(); + + assert_eq!(version_short.stdout, version_long.stdout); +} + +#[test] +fn test_help_flag() { + let help_short = new_ucmd!().arg("-h").run(); + let help_long = new_ucmd!().arg("--help").run(); + + assert_eq!(help_short.stdout, help_long.stdout); +} + +#[test] +fn test_multiple_arguments() { + let result = new_ucmd!() + .arg("call_graph.txt") + .arg("invalid_file.txt") + .run(); + + assert_eq!(true, result.stderr.contains("error: Found argument 'invalid_file.txt' which wasn't expected, or isn't valid in this context")) +} From de3f9b8186c2d4cc48c83c94ed06ca53a3950236 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 22 Mar 2021 18:24:23 +0100 Subject: [PATCH 088/104] ls: across & commas formats and width parameter (#1869) --- src/uu/ls/src/ls.rs | 160 +++++++++++++++++++++++++++--------- tests/by-util/test_ls.rs | 172 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 290 insertions(+), 42 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index a935eef54..455b4f7b6 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -15,7 +15,6 @@ extern crate uucore; use clap::{App, Arg}; use number_prefix::NumberPrefix; -use std::cmp::Reverse; #[cfg(unix)] use std::collections::HashMap; use std::fs; @@ -30,6 +29,7 @@ use std::path::{Path, PathBuf}; #[cfg(unix)] use std::time::Duration; use std::time::{SystemTime, UNIX_EPOCH}; +use std::{cmp::Reverse, process::exit}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use time::{strftime, Timespec}; @@ -78,6 +78,8 @@ pub mod options { pub static ONELINE: &str = "1"; pub static LONG: &str = "long"; pub static COLUMNS: &str = "C"; + pub static ACROSS: &str = "x"; + pub static COMMAS: &str = "m"; pub static LONG_NO_OWNER: &str = "g"; pub static LONG_NO_GROUP: &str = "o"; } @@ -98,6 +100,7 @@ pub mod options { pub static HUMAN_READABLE: &str = "human-readable"; pub static SI: &str = "si"; } + pub static WIDTH: &str = "width"; pub static AUTHOR: &str = "author"; pub static NO_GROUP: &str = "no-group"; pub static FORMAT: &str = "format"; @@ -120,6 +123,8 @@ enum Format { Columns, Long, OneLine, + Across, + Commas, } enum Sort { @@ -166,6 +171,7 @@ struct Config { #[cfg(unix)] color: bool, long: LongFormat, + width: Option, } // Fields that can be removed or added to the long format @@ -183,6 +189,8 @@ impl Config { "long" | "verbose" => Format::Long, "single-column" => Format::OneLine, "columns" | "vertical" => Format::Columns, + "across" | "horizontal" => Format::Across, + "commas" => Format::Commas, // below should never happen as clap already restricts the values. _ => unreachable!("Invalid field for --format"), }, @@ -190,6 +198,10 @@ impl Config { ) } else if options.is_present(options::format::LONG) { (Format::Long, options::format::LONG) + } else if options.is_present(options::format::ACROSS) { + (Format::Across, options::format::ACROSS) + } else if options.is_present(options::format::COMMAS) { + (Format::Commas, options::format::COMMAS) } else { (Format::Columns, options::format::COLUMNS) }; @@ -316,6 +328,16 @@ impl Config { } }; + let width = options + .value_of(options::WIDTH) + .map(|x| { + x.parse::().unwrap_or_else(|_e| { + show_error!("invalid line width: ‘{}’", x); + exit(2); + }) + }) + .or_else(|| termsize::get().map(|s| s.cols)); + Config { format, files, @@ -334,6 +356,7 @@ impl Config { #[cfg(unix)] inode: options.is_present(options::INODE), long, + width, } } } @@ -354,13 +377,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::FORMAT) .help("Set the display format.") .takes_value(true) - .possible_values(&["long", "verbose", "single-column", "columns"]) + .possible_values(&["long", "verbose", "single-column", "columns", "vertical", "across", "horizontal", "commas"]) .hide_possible_values(true) .require_equals(true) .overrides_with_all(&[ options::FORMAT, options::format::COLUMNS, options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, ]), ) .arg( @@ -371,6 +396,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::FORMAT, options::format::COLUMNS, options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, ]), ) .arg( @@ -381,8 +408,33 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .overrides_with_all(&[ options::FORMAT, options::format::COLUMNS, - options::format::ONELINE, options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]), + ) + .arg( + Arg::with_name(options::format::ACROSS) + .short(options::format::ACROSS) + .help("List entries in rows instead of in columns.") + .overrides_with_all(&[ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]), + ) + .arg( + Arg::with_name(options::format::COMMAS) + .short(options::format::COMMAS) + .help("List entries separated by commas.") + .overrides_with_all(&[ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, ]), ) // The next three arguments do not override with the other format @@ -601,6 +653,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::RECURSIVE) .help("List the contents of all directories recursively."), ) + .arg( + Arg::with_name(options::WIDTH) + .long(options::WIDTH) + .short("w") + .help("Assume that the terminal is COLS columns wide.") + .value_name("COLS") + .takes_value(true) + ) .arg( Arg::with_name(options::COLOR) .long(options::COLOR) @@ -779,47 +839,71 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) { display_item_long(item, strip, max_links, max_size, config); } } else { - if config.format != Format::OneLine { - let names = items.iter().filter_map(|i| { - let md = get_metadata(i, config); - match md { - Err(e) => { - let filename = get_file_name(i, strip); - show_error!("'{}': {}", filename, e); - None - } - Ok(md) => Some(display_file_name(&i, strip, &md, config)), - } - }); - - if let Some(size) = termsize::get() { - let mut grid = Grid::new(GridOptions { - filling: Filling::Spaces(2), - direction: Direction::TopToBottom, - }); - - for name in names { - grid.add(name); - } - - if let Some(output) = grid.fit_into_width(size.cols as usize) { - print!("{}", output); - return; - } - } - } - - // Couldn't display a grid, either because we don't know - // the terminal width or because fit_into_width failed - for i in items { + let names = items.iter().filter_map(|i| { let md = get_metadata(i, config); - if let Ok(md) = md { - println!("{}", display_file_name(&i, strip, &md, config).contents); + match md { + Err(e) => { + let filename = get_file_name(i, strip); + show_error!("'{}': {}", filename, e); + None + } + Ok(md) => Some(display_file_name(&i, strip, &md, config)), + } + }); + + match (&config.format, config.width) { + (Format::Columns, Some(width)) => display_grid(names, width, Direction::TopToBottom), + (Format::Across, Some(width)) => display_grid(names, width, Direction::LeftToRight), + (Format::Commas, width_opt) => { + let term_width = width_opt.unwrap_or(1); + let mut current_col = 0; + let mut names = names; + if let Some(name) = names.next() { + print!("{}", name.contents); + current_col = name.width as u16 + 2; + } + for name in names { + let name_width = name.width as u16; + if current_col + name_width + 1 > term_width { + current_col = name_width + 2; + print!(",\n{}", name.contents); + } else { + current_col += name_width + 2; + print!(", {}", name.contents); + } + } + // Current col is never zero again if names have been printed. + // So we print a newline. + if current_col > 0 { + println!(); + } + } + _ => { + for name in names { + println!("{}", name.contents); + } } } } } +fn display_grid(names: impl Iterator, width: u16, direction: Direction) { + let mut grid = Grid::new(GridOptions { + filling: Filling::Spaces(2), + direction, + }); + + for name in names { + grid.add(name); + } + + match grid.fit_into_width(width as usize) { + Some(output) => print!("{}", output), + // Width is too small for the grid, so we fit it in one column + None => print!("{}", grid.fit_into_columns(1)), + } +} + use uucore::fs::display_permissions; fn display_item_long( diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 4fd35a286..ecd288735 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -57,12 +57,72 @@ fn test_ls_a() { assert!(!result.stdout.contains("..")); } +#[test] +fn test_ls_width() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch(&at.plus_as_string("test-width-1")); + at.touch(&at.plus_as_string("test-width-2")); + at.touch(&at.plus_as_string("test-width-3")); + at.touch(&at.plus_as_string("test-width-4")); + + for option in &["-w 100", "-w=100", "--width=100", "--width 100"] { + let result = scene + .ucmd() + .args(&option.split(" ").collect::>()) + .run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert_eq!( + result.stdout, + "test-width-1 test-width-2 test-width-3 test-width-4\n", + ) + } + + for option in &["-w 50", "-w=50", "--width=50", "--width 50"] { + let result = scene + .ucmd() + .args(&option.split(" ").collect::>()) + .run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert_eq!( + result.stdout, + "test-width-1 test-width-3\ntest-width-2 test-width-4\n", + ) + } + + for option in &[ + "-w 25", + "-w=25", + "--width=25", + "--width 25", + "-w 0", + "-w=0", + "--width=0", + "--width 0", + ] { + let result = scene + .ucmd() + .args(&option.split(" ").collect::>()) + .run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert_eq!( + result.stdout, + "test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n", + ) + } +} + #[test] fn test_ls_columns() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch(&at.plus_as_string("test-columns-1")); at.touch(&at.plus_as_string("test-columns-2")); + at.touch(&at.plus_as_string("test-columns-3")); + at.touch(&at.plus_as_string("test-columns-4")); // Columns is the default let result = scene.ucmd().run(); @@ -71,9 +131,15 @@ fn test_ls_columns() { assert!(result.success); #[cfg(not(windows))] - assert_eq!(result.stdout, "test-columns-1\ntest-columns-2\n"); + assert_eq!( + result.stdout, + "test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n" + ); #[cfg(windows)] - assert_eq!(result.stdout, "test-columns-1 test-columns-2\n"); + assert_eq!( + result.stdout, + "test-columns-1 test-columns-2 test-columns-3 test-columns-4\n" + ); for option in &["-C", "--format=columns"] { let result = scene.ucmd().arg(option).run(); @@ -81,9 +147,107 @@ fn test_ls_columns() { println!("stdout = {:?}", result.stdout); assert!(result.success); #[cfg(not(windows))] - assert_eq!(result.stdout, "test-columns-1\ntest-columns-2\n"); + assert_eq!( + result.stdout, + "test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n" + ); #[cfg(windows)] - assert_eq!(result.stdout, "test-columns-1 test-columns-2\n"); + assert_eq!( + result.stdout, + "test-columns-1 test-columns-2 test-columns-3 test-columns-4\n" + ); + } + + for option in &["-C", "--format=columns"] { + let result = scene.ucmd().arg("-w=40").arg(option).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert!(result.success); + assert_eq!( + result.stdout, + "test-columns-1 test-columns-3\ntest-columns-2 test-columns-4\n" + ); + } +} + +#[test] +fn test_ls_across() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch(&at.plus_as_string("test-across-1")); + at.touch(&at.plus_as_string("test-across-2")); + at.touch(&at.plus_as_string("test-across-3")); + at.touch(&at.plus_as_string("test-across-4")); + + for option in &["-x", "--format=across"] { + let result = scene.ucmd().arg(option).succeeds(); + // Because the test terminal has width 0, this is the same output as + // the columns option. + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + if cfg!(unix) { + assert_eq!( + result.stdout, + "test-across-1\ntest-across-2\ntest-across-3\ntest-across-4\n" + ); + } else { + assert_eq!( + result.stdout, + "test-across-1 test-across-2 test-across-3 test-across-4\n" + ); + } + } + + for option in &["-x", "--format=across"] { + let result = scene.ucmd().arg("-w=30").arg(option).run(); + // Because the test terminal has width 0, this is the same output as + // the columns option. + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert_eq!( + result.stdout, + "test-across-1 test-across-2\ntest-across-3 test-across-4\n" + ); + } +} + +#[test] +fn test_ls_commas() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch(&at.plus_as_string("test-commas-1")); + at.touch(&at.plus_as_string("test-commas-2")); + at.touch(&at.plus_as_string("test-commas-3")); + at.touch(&at.plus_as_string("test-commas-4")); + + for option in &["-m", "--format=commas"] { + let result = scene.ucmd().arg(option).succeeds(); + if cfg!(unix) { + assert_eq!( + result.stdout, + "test-commas-1,\ntest-commas-2,\ntest-commas-3,\ntest-commas-4\n" + ); + } else { + assert_eq!( + result.stdout, + "test-commas-1, test-commas-2, test-commas-3, test-commas-4\n" + ); + } + } + + for option in &["-m", "--format=commas"] { + let result = scene.ucmd().arg("-w=30").arg(option).succeeds(); + assert_eq!( + result.stdout, + "test-commas-1, test-commas-2,\ntest-commas-3, test-commas-4\n" + ); + } + for option in &["-m", "--format=commas"] { + let result = scene.ucmd().arg("-w=45").arg(option).succeeds(); + assert_eq!( + result.stdout, + "test-commas-1, test-commas-2, test-commas-3,\ntest-commas-4\n" + ); } } From 56da6b4a2c656e387ebd9d98ffae74e82068c7c9 Mon Sep 17 00:00:00 2001 From: James Robson Date: Mon, 22 Mar 2021 17:48:12 +0000 Subject: [PATCH 089/104] Fix test setup --- .github/workflows/GNU.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index f89621e3f..9eb5da2b9 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -81,15 +81,15 @@ jobs: Makefile # 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/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh tests/touch/60-seconds.sh + sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.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 # Don't break the function called 'grep_timeout' - sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh + sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh tests/chgrp/basic.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 sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh - sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh tests/cp/fiemap-2.sh tests/init.sh + sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh tests/cp/fiemap-2.sh init.cfg sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/ls/abmon-align.sh tests/ls/rt-1.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh From 20dec4cbba9ee3153f517d7b444c0fe8ac8aa558 Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Mon, 22 Mar 2021 22:08:07 +0300 Subject: [PATCH 090/104] fix: fix clippy warnings (#1876) --- src/uu/expr/src/syntax_tree.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index d56bab4fc..3381c29bd 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -148,11 +148,11 @@ impl ASTNode { |a: &String, b: &String| Ok(bool_as_string(a >= b)), &operand_values, ), - "|" => infix_operator_or(&operand_values), - "&" => infix_operator_and(&operand_values), + "|" => Ok(infix_operator_or(&operand_values)), + "&" => Ok(infix_operator_and(&operand_values)), ":" | "match" => operator_match(&operand_values), - "length" => prefix_operator_length(&operand_values), - "index" => prefix_operator_index(&operand_values), + "length" => Ok(prefix_operator_length(&operand_values)), + "index" => Ok(prefix_operator_index(&operand_values)), "substr" => prefix_operator_substr(&operand_values), _ => Err(format!("operation not implemented: {}", op_type)), @@ -465,20 +465,20 @@ where } } -fn infix_operator_or(values: &[String]) -> Result { +fn infix_operator_or(values: &[String]) -> String { assert!(values.len() == 2); if value_as_bool(&values[0]) { - Ok(values[0].clone()) + values[0].clone() } else { - Ok(values[1].clone()) + values[1].clone() } } -fn infix_operator_and(values: &[String]) -> Result { +fn infix_operator_and(values: &[String]) -> String { if value_as_bool(&values[0]) && value_as_bool(&values[1]) { - Ok(values[0].clone()) + values[0].clone() } else { - Ok(0.to_string()) + 0.to_string() } } @@ -502,12 +502,12 @@ fn operator_match(values: &[String]) -> Result { } } -fn prefix_operator_length(values: &[String]) -> Result { +fn prefix_operator_length(values: &[String]) -> String { assert!(values.len() == 1); - Ok(values[0].len().to_string()) + values[0].len().to_string() } -fn prefix_operator_index(values: &[String]) -> Result { +fn prefix_operator_index(values: &[String]) -> String { assert!(values.len() == 2); let haystack = &values[0]; let needles = &values[1]; @@ -515,11 +515,11 @@ fn prefix_operator_index(values: &[String]) -> Result { for (current_idx, ch_h) in haystack.chars().enumerate() { for ch_n in needles.chars() { if ch_n == ch_h { - return Ok(current_idx.to_string()); + return current_idx.to_string(); } } } - Ok("0".to_string()) + "0".to_string() } fn prefix_operator_substr(values: &[String]) -> Result { From a1b50ae0f43e78424de6ccdfc545a59aa0156d8c Mon Sep 17 00:00:00 2001 From: Neculai Balaban Date: Mon, 22 Mar 2021 21:09:00 +0200 Subject: [PATCH 091/104] nohup: move from getopts to clap (#1871) - changed some error return codes to match GNU implementation - changed warning/error messages to match GNU nohup - replaced getopts dependency with clap - added a test --- src/uu/nohup/Cargo.toml | 2 +- src/uu/nohup/src/nohup.rs | 134 +++++++++++++++++++----------------- tests/by-util/test_nohup.rs | 20 +++++- 3 files changed, 90 insertions(+), 66 deletions(-) diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 0c5709a65..e9b6f8bd4 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/nohup.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" libc = "0.2.42" uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 5fce208da..afbf2541b 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -10,6 +10,7 @@ #[macro_use] extern crate uucore; +use clap::{App, AppSettings, Arg}; use libc::{c_char, dup2, execvp, signal}; use libc::{SIGHUP, SIG_IGN}; use std::env; @@ -20,50 +21,42 @@ use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive}; -static NAME: &str = "nohup"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Run COMMAND ignoring hangup signals."; +static LONG_HELP: &str = " +If standard input is terminal, it'll be replaced with /dev/null. +If standard output is terminal, it'll be appended to nohup.out instead, +or $HOME/nohup.out, if nohup.out open failed. +If standard error is terminal, it'll be redirected to stdout. +"; +static NOHUP_OUT: &str = "nohup.out"; +// exit codes that match the GNU implementation +static EXIT_CANCELED: i32 = 125; +static EXIT_CANNOT_INVOKE: i32 = 126; +static EXIT_ENOENT: i32 = 127; +static POSIX_NOHUP_FAILURE: i32 = 127; -#[cfg(target_vendor = "apple")] -extern "C" { - fn _vprocmgr_detach_from_console(flags: u32) -> *const libc::c_int; -} - -#[cfg(any(target_os = "linux", target_os = "freebsd"))] -unsafe fn _vprocmgr_detach_from_console(_: u32) -> *const libc::c_int { - std::ptr::null() +mod options { + pub const CMD: &str = "cmd"; } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let usage = get_usage(); - let mut opts = getopts::Options::new(); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::CMD) + .hidden(true) + .required(true) + .multiple(true), + ) + .setting(AppSettings::TrailingVarArg) + .get_matches_from(args); - opts.optflag("h", "help", "Show help and exit"); - opts.optflag("V", "version", "Show version and exit"); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => { - show_error!("{}", f); - show_usage(&opts); - return 1; - } - }; - - if matches.opt_present("V") { - println!("{} {}", NAME, VERSION); - return 0; - } - if matches.opt_present("h") { - show_usage(&opts); - return 0; - } - - if matches.free.is_empty() { - show_error!("Missing operand: COMMAND"); - println!("Try `{} --help` for more information.", NAME); - return 1; - } replace_fds(); unsafe { signal(SIGHUP, SIG_IGN) }; @@ -73,13 +66,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; let cstrs: Vec = matches - .free - .iter() + .values_of(options::CMD) + .unwrap() .map(|x| CString::new(x.as_bytes()).unwrap()) .collect(); let mut args: Vec<*const c_char> = cstrs.iter().map(|s| s.as_ptr()).collect(); args.push(std::ptr::null()); - unsafe { execvp(args[0], args.as_mut_ptr()) } + + let ret = unsafe { execvp(args[0], args.as_mut_ptr()) }; + match ret { + libc::ENOENT => EXIT_ENOENT, + _ => EXIT_CANNOT_INVOKE, + } } fn replace_fds() { @@ -108,23 +106,32 @@ fn replace_fds() { } fn find_stdout() -> File { + let internal_failure_code = match std::env::var("POSIXLY_CORRECT") { + Ok(_) => POSIX_NOHUP_FAILURE, + Err(_) => EXIT_CANCELED, + }; + match OpenOptions::new() .write(true) .create(true) .append(true) - .open(Path::new("nohup.out")) + .open(Path::new(NOHUP_OUT)) { Ok(t) => { - show_warning!("Output is redirected to: nohup.out"); + show_info!("ignoring input and appending output to '{}'", NOHUP_OUT); t } - Err(e) => { + Err(e1) => { let home = match env::var("HOME") { - Err(_) => crash!(2, "Cannot replace STDOUT: {}", e), + Err(_) => { + show_info!("failed to open '{}': {}", NOHUP_OUT, e1); + exit!(internal_failure_code) + } Ok(h) => h, }; let mut homeout = PathBuf::from(home); - homeout.push("nohup.out"); + homeout.push(NOHUP_OUT); + let homeout_str = homeout.to_str().unwrap(); match OpenOptions::new() .write(true) .create(true) @@ -132,30 +139,29 @@ fn find_stdout() -> File { .open(&homeout) { Ok(t) => { - show_warning!("Output is redirected to: {:?}", homeout); + show_info!("ignoring input and appending output to '{}'", homeout_str); t } - Err(e) => crash!(2, "Cannot replace STDOUT: {}", e), + Err(e2) => { + show_info!("failed to open '{}': {}", NOHUP_OUT, e1); + show_info!("failed to open '{}': {}", homeout_str, e2); + exit!(internal_failure_code) + } } } } } -fn show_usage(opts: &getopts::Options) { - let msg = format!( - "{0} {1} - -Usage: - {0} COMMAND [ARG]... - {0} OPTION - -Run COMMAND ignoring hangup signals. -If standard input is terminal, it'll be replaced with /dev/null. -If standard output is terminal, it'll be appended to nohup.out instead, -or $HOME/nohup.out, if nohup.out open failed. -If standard error is terminal, it'll be redirected to stdout.", - NAME, VERSION - ); - - print!("{}", opts.usage(&msg)); +fn get_usage() -> String { + format!("{0} COMMAND [ARG]...\n {0} FLAG", executable!()) +} + +#[cfg(target_vendor = "apple")] +extern "C" { + fn _vprocmgr_detach_from_console(flags: u32) -> *const libc::c_int; +} + +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +unsafe fn _vprocmgr_detach_from_console(_: u32) -> *const libc::c_int { + std::ptr::null() } diff --git a/tests/by-util/test_nohup.rs b/tests/by-util/test_nohup.rs index 651491045..b98ae007c 100644 --- a/tests/by-util/test_nohup.rs +++ b/tests/by-util/test_nohup.rs @@ -1 +1,19 @@ -// ToDO: add tests +use crate::common::util::*; +use std::thread::sleep; + +// General observation: nohup.out will not be created in tests run by cargo test +// because stdin/stdout is not attached to a TTY. +// All that can be tested is the side-effects. + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_vendor = "apple"))] +fn test_nohup_multiple_args_and_flags() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.args(&["touch", "-t", "1006161200", "file1", "file2"]) + .succeeds(); + sleep(std::time::Duration::from_millis(10)); + + assert!(at.file_exists("file1")); + assert!(at.file_exists("file2")); +} From e5ef7486d5be004324dc26239fbf520d4f3bc6b2 Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Tue, 23 Mar 2021 00:42:14 +0300 Subject: [PATCH 092/104] feat: move echo to clap (#1884) --- src/uu/echo/Cargo.toml | 1 + src/uu/echo/src/echo.rs | 50 ++++++++++++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index a8742b68f..7b831fcb8 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/echo.rs" [dependencies] +clap = "2.33" uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 93c395391..7dfdf3113 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -9,10 +9,13 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::io::{self, Write}; use std::iter::Peekable; use std::str::Chars; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +const NAME: &str = "echo"; const SYNTAX: &str = "[OPTIONS]... [STRING]..."; const SUMMARY: &str = "display a line of text"; const HELP: &str = r#" @@ -33,6 +36,13 @@ const HELP: &str = r#" \\xHH byte with hexadecimal value HH (1 to 2 digits) "#; +mod options { + pub const STRING: &str = "string"; + pub const NEWLINE: &str = "n"; + pub const ENABLE_ESCAPE: &str = "e"; + pub const DISABLE_ESCAPE: &str = "E"; +} + fn parse_code( input: &mut Peekable, base: u32, @@ -105,20 +115,38 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let matches = app!(SYNTAX, SUMMARY, HELP) - .optflag("n", "", "do not output the trailing newline") - .optflag("e", "", "enable interpretation of backslash escapes") - .optflag( - "E", - "", - "disable interpretation of backslash escapes (default)", + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .usage(SYNTAX) + .about(SUMMARY) + .help(HELP) + .arg(Arg::with_name(options::STRING).hidden(true).multiple(true)) + .arg( + Arg::with_name(options::NEWLINE) + .short("n") + .help("do not output the trailing newline"), ) - .parse(args); + .arg( + Arg::with_name(options::ENABLE_ESCAPE) + .short("e") + .help("enable interpretation of backslash escapes"), + ) + .arg( + Arg::with_name(options::DISABLE_ESCAPE) + .short("E") + .help("disable interpretation of backslash escapes (default)"), + ) + .get_matches_from(args); - let no_newline = matches.opt_present("n"); - let escaped = matches.opt_present("e"); + let no_newline = matches.is_present("n"); + let escaped = matches.is_present("e"); + let values: Vec = match matches.values_of(options::STRING) { + Some(v) => v.map(|v| v.to_string()).collect(), + None => vec!["".to_string()], + }; - match execute(no_newline, escaped, matches.free) { + match execute(no_newline, escaped, values) { Ok(_) => 0, Err(f) => { show_error!("{}", f); From 5e2e2e8ab6902cde39c0b5d027c83b9cbcd61553 Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Tue, 23 Mar 2021 11:40:05 +0300 Subject: [PATCH 093/104] echo: allow leading hyphens (#1887) * fix: use settings to allow leading hyphen and trailing var arg fixes: https://github.com/uutils/coreutils/issues/1873 * test: add test cases * test: add more test cases with different order in hyphen values * chore: add comment to explain why we need TrailingVarArg --- src/uu/echo/src/echo.rs | 5 ++++ tests/by-util/test_echo.rs | 55 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 7dfdf3113..7c0014229 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -117,6 +117,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = App::new(executable!()) .name(NAME) + // TrailingVarArg specifies the final positional argument is a VarArg + // and it doesn't attempts the parse any further args. + // Final argument must have multiple(true) or the usage string equivalent. + .setting(clap::AppSettings::TrailingVarArg) + .setting(clap::AppSettings::AllowLeadingHyphen) .version(VERSION) .usage(SYNTAX) .about(SUMMARY) diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 2d073d60b..7394ffc1e 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -173,3 +173,58 @@ fn test_disable_escapes() { .succeeds() .stdout_only(format!("{}\n", input_str)); } + +#[test] +fn test_hyphen_value() { + new_ucmd!().arg("-abc").succeeds().stdout_is("-abc\n"); +} + +#[test] +fn test_multiple_hyphen_values() { + new_ucmd!() + .args(&["-abc", "-def", "-edf"]) + .succeeds() + .stdout_is("-abc -def -edf\n"); +} + +#[test] +fn test_hyphen_values_inside_string() { + new_ucmd!() + .arg("'\"\n'CXXFLAGS=-g -O2'\n\"'") + .succeeds() + .stdout + .contains("CXXFLAGS"); +} + +#[test] +fn test_hyphen_values_at_start() { + let result = new_ucmd!() + .arg("-E") + .arg("-test") + .arg("araba") + .arg("-merci") + .run(); + + assert!(result.success); + assert_eq!(false, result.stdout.contains("-E")); + assert_eq!(result.stdout, "-test araba -merci\n"); +} + +#[test] +fn test_hyphen_values_between() { + let result = new_ucmd!().arg("test").arg("-E").arg("araba").run(); + + assert!(result.success); + assert_eq!(result.stdout, "test -E araba\n"); + + let result = new_ucmd!() + .arg("dumdum ") + .arg("dum dum dum") + .arg("-e") + .arg("dum") + .run(); + + assert!(result.success); + assert_eq!(result.stdout, "dumdum dum dum dum -e dum\n"); + assert_eq!(true, result.stdout.contains("-e")); +} From 545fe7d887d5e162610474e07922169f7b78fd2b Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Tue, 23 Mar 2021 11:42:05 +0300 Subject: [PATCH 094/104] feat(unexpand): move from getopts to clap (#1883) * feat: move unexpand to clap * chore: allow muliple files * test: add test fixture, test reading from a file * test: fix typo on file name, add test for multiple inputs * chore: use 'success()' instead of asserting * chore: delete unused variables * chore: use help instead of long_help, break long line --- src/uu/unexpand/Cargo.toml | 2 +- src/uu/unexpand/src/unexpand.rs | 119 +++++++++++------------- tests/by-util/test_unexpand.rs | 19 ++++ tests/fixtures/unexpand/with_spaces.txt | 2 + 4 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 tests/fixtures/unexpand/with_spaces.txt diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index ec6967e21..d66d335bf 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/unexpand.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" unicode-width = "0.1.5" uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 59a2dc6a0..5b08c33cf 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -11,7 +11,7 @@ #[macro_use] extern crate uucore; - +use clap::{App, Arg}; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdout, Write}; use std::str::from_utf8; @@ -19,6 +19,9 @@ use unicode_width::UnicodeWidthChar; static NAME: &str = "unexpand"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static USAGE: &str = "unexpand [OPTION]... [FILE]..."; +static SUMMARY: &str = "Convert blanks in each FILE to tabs, writing to standard output.\n + With no FILE, or when FILE is -, read standard input."; const DEFAULT_TABSTOP: usize = 8; @@ -46,6 +49,14 @@ fn tabstops_parse(s: String) -> Vec { nums } +mod options { + pub const FILE: &str = "file"; + pub const ALL: &str = "all"; + pub const FIRST_ONLY: &str = "first-only"; + pub const TABS: &str = "tabs"; + pub const NO_UTF8: &str = "no-utf8"; +} + struct Options { files: Vec, tabstops: Vec, @@ -54,20 +65,19 @@ struct Options { } impl Options { - fn new(matches: getopts::Matches) -> Options { - let tabstops = match matches.opt_str("t") { + fn new(matches: clap::ArgMatches) -> Options { + let tabstops = match matches.value_of(options::TABS) { None => vec![DEFAULT_TABSTOP], - Some(s) => tabstops_parse(s), + Some(s) => tabstops_parse(s.to_string()), }; - let aflag = (matches.opt_present("all") || matches.opt_present("tabs")) - && !matches.opt_present("first-only"); - let uflag = !matches.opt_present("U"); + let aflag = (matches.is_present(options::ALL) || matches.is_present(options::TABS)) + && !matches.is_present(options::FIRST_ONLY); + let uflag = !matches.is_present(options::NO_UTF8); - let files = if matches.free.is_empty() { - vec!["-".to_owned()] - } else { - matches.free + let files = match matches.value_of(options::FILE) { + Some(v) => vec![v.to_string()], + None => vec!["-".to_owned()], }; Options { @@ -82,60 +92,39 @@ impl Options { pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let mut opts = getopts::Options::new(); - - opts.optflag( - "a", - "all", - "convert all blanks, instead of just initial blanks", - ); - opts.optflag( - "", - "first-only", - "convert only leading sequences of blanks (overrides -a)", - ); - opts.optopt( - "t", - "tabs", - "have tabs N characters apart instead of 8 (enables -a)", - "N", - ); - opts.optopt( - "t", - "tabs", - "use comma separated LIST of tab positions (enables -a)", - "LIST", - ); - opts.optflag( - "U", - "no-utf8", - "interpret input file as 8-bit ASCII rather than UTF-8", - ); - opts.optflag("h", "help", "display this help and exit"); - opts.optflag("V", "version", "output version information and exit"); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => crash!(1, "{}", f), - }; - - if matches.opt_present("help") { - println!("{} {}\n", NAME, VERSION); - println!("Usage: {} [OPTION]... [FILE]...\n", NAME); - println!( - "{}", - opts.usage( - "Convert blanks in each FILE to tabs, writing to standard output.\n\ - With no FILE, or when FILE is -, read standard input." - ) - ); - return 0; - } - - if matches.opt_present("V") { - println!("{} {}", NAME, VERSION); - return 0; - } + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .usage(USAGE) + .about(SUMMARY) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .arg( + Arg::with_name(options::ALL) + .short("a") + .long(options::ALL) + .help("convert all blanks, instead of just initial blanks") + .takes_value(false), + ) + .arg( + Arg::with_name(options::FIRST_ONLY) + .long(options::FIRST_ONLY) + .help("convert only leading sequences of blanks (overrides -a)") + .takes_value(false), + ) + .arg( + Arg::with_name(options::TABS) + .short("t") + .long(options::TABS) + .long_help("use comma separated LIST of tab positions or have tabs N characters apart instead of 8 (enables -a)") + .takes_value(true) + ) + .arg( + Arg::with_name(options::NO_UTF8) + .short("U") + .long(options::NO_UTF8) + .takes_value(false) + .help("interpret input file as 8-bit ASCII rather than UTF-8")) + .get_matches_from(args); unexpand(Options::new(matches)); diff --git a/tests/by-util/test_unexpand.rs b/tests/by-util/test_unexpand.rs index 93cc42d90..e8b880287 100644 --- a/tests/by-util/test_unexpand.rs +++ b/tests/by-util/test_unexpand.rs @@ -136,3 +136,22 @@ fn unexpand_spaces_after_fields() { .run() .stdout_is("\t\tA B C D\t\t A\t\n"); } + +#[test] +fn unexpand_read_from_file() { + new_ucmd!() + .arg("with_spaces.txt") + .arg("-t4") + .run() + .success(); +} + +#[test] +fn unexpand_read_from_two_file() { + new_ucmd!() + .arg("with_spaces.txt") + .arg("with_spaces.txt") + .arg("-t4") + .run() + .success(); +} diff --git a/tests/fixtures/unexpand/with_spaces.txt b/tests/fixtures/unexpand/with_spaces.txt new file mode 100644 index 000000000..3f39671a1 --- /dev/null +++ b/tests/fixtures/unexpand/with_spaces.txt @@ -0,0 +1,2 @@ + abc d e f g \t\t A + \ No newline at end of file From b54f0b1ff20919b42de48f74c0265a889696d726 Mon Sep 17 00:00:00 2001 From: Alessandro Stoltenberg Date: Tue, 23 Mar 2021 11:55:18 +0100 Subject: [PATCH 095/104] echo: Refactored help message. (#1886) --- src/uu/echo/src/echo.rs | 57 ++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 7c0014229..c991f5d3f 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -9,17 +9,17 @@ #[macro_use] extern crate uucore; -use clap::{App, Arg}; +use clap::{crate_version, App, Arg}; use std::io::{self, Write}; use std::iter::Peekable; use std::str::Chars; -static VERSION: &str = env!("CARGO_PKG_VERSION"); -const NAME: &str = "echo"; -const SYNTAX: &str = "[OPTIONS]... [STRING]..."; -const SUMMARY: &str = "display a line of text"; -const HELP: &str = r#" +static NAME: &str = "echo"; +static USAGE: &str = "[OPTIONS]... [STRING]..."; +static SUMMARY: &str = "display a line of text"; +static AFTER_HELP: &str = r#" Echo the STRING(s) to standard output. + If -e is in effect, the following sequences are recognized: \\\\ backslash @@ -37,10 +37,10 @@ const HELP: &str = r#" "#; mod options { - pub const STRING: &str = "string"; - pub const NEWLINE: &str = "n"; - pub const ENABLE_ESCAPE: &str = "e"; - pub const DISABLE_ESCAPE: &str = "E"; + pub static STRING: &str = "STRING"; + pub static NO_NEWLINE: &str = "no_newline"; + pub static ENABLE_BACKSLASH_ESCAPE: &str = "enable_backslash_escape"; + pub static DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape"; } fn parse_code( @@ -122,32 +122,43 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // Final argument must have multiple(true) or the usage string equivalent. .setting(clap::AppSettings::TrailingVarArg) .setting(clap::AppSettings::AllowLeadingHyphen) - .version(VERSION) - .usage(SYNTAX) + .version(crate_version!()) + .usage(USAGE) .about(SUMMARY) - .help(HELP) - .arg(Arg::with_name(options::STRING).hidden(true).multiple(true)) + .after_help(AFTER_HELP) .arg( - Arg::with_name(options::NEWLINE) + Arg::with_name(options::NO_NEWLINE) .short("n") - .help("do not output the trailing newline"), + .help("do not output the trailing newline") + .takes_value(false) + .display_order(1), ) .arg( - Arg::with_name(options::ENABLE_ESCAPE) + Arg::with_name(options::ENABLE_BACKSLASH_ESCAPE) .short("e") - .help("enable interpretation of backslash escapes"), + .help("enable interpretation of backslash escapes") + .takes_value(false) + .display_order(2), ) .arg( - Arg::with_name(options::DISABLE_ESCAPE) + Arg::with_name(options::DISABLE_BACKSLASH_ESCAPE) .short("E") - .help("disable interpretation of backslash escapes (default)"), + .help("disable interpretation of backslash escapes (default)") + .takes_value(false) + .display_order(3), + ) + .arg( + Arg::with_name(options::STRING) + .hidden(true) + .multiple(true) + .allow_hyphen_values(true), ) .get_matches_from(args); - let no_newline = matches.is_present("n"); - let escaped = matches.is_present("e"); + let no_newline = matches.is_present(options::NO_NEWLINE); + let escaped = matches.is_present(options::ENABLE_BACKSLASH_ESCAPE); let values: Vec = match matches.values_of(options::STRING) { - Some(v) => v.map(|v| v.to_string()).collect(), + Some(s) => s.map(|s| s.to_string()).collect(), None => vec!["".to_string()], }; From 4873c8a24b7e00283572f05da18e3b74a6819562 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Tue, 23 Mar 2021 13:49:35 -0700 Subject: [PATCH 096/104] mv: ensure line prints (#1890) Previously this used `print` instead of `println`, and as a result the prompt would never appear and the command would hang. The Rust docs note this about print: > Note that stdout is frequently line-buffered by default so it may be > necessary to use io::stdout().flush() to ensure the output is emitted > immediately. Changing to `println` fixes the issue. Fixes #1889. Co-authored-by: Kevin Burke --- src/uu/mv/src/mv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 6575ad37a..b481aeebc 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -380,7 +380,7 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behavior) -> io::Result<()> { match b.overwrite { OverwriteMode::NoClobber => return Ok(()), OverwriteMode::Interactive => { - print!("{}: overwrite ‘{}’? ", executable!(), to.display()); + println!("{}: overwrite ‘{}’? ", executable!(), to.display()); if !read_yes() { return Ok(()); } From 9b2ee1ce06199be5f7cbf09a5ec39aa7245a1c2d Mon Sep 17 00:00:00 2001 From: Marco Satti Date: Wed, 24 Mar 2021 15:33:11 +0800 Subject: [PATCH 097/104] date: change tests to expect failure (#1895) * date: change tests to expect failure Although these tests contain valid dates, the parsing logic is not implemented yet. It should be changed to expect success when the parsing logic is done. * date: fix test build errors --- tests/by-util/test_date.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 216446266..5619aed94 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -180,39 +180,45 @@ fn test_date_set_mac_unavailable() { #[test] #[cfg(all(unix, not(target_os = "macos")))] +/// TODO: expected to fail currently; change to succeeds() when required. fn test_date_set_valid_2() { if get_effective_uid() == 0 { let (_, mut ucmd) = at_and_ucmd!(); let result = ucmd .arg("--set") .arg("Sat 20 Mar 2021 14:53:01 AWST") - .succeeds(); - result.no_stdout().no_stderr(); + .fails(); + let result = result.no_stdout(); + assert!(result.stderr.starts_with("date: invalid date ")); } } #[test] #[cfg(all(unix, not(target_os = "macos")))] +/// TODO: expected to fail currently; change to succeeds() when required. fn test_date_set_valid_3() { if get_effective_uid() == 0 { let (_, mut ucmd) = at_and_ucmd!(); let result = ucmd .arg("--set") .arg("Sat 20 Mar 2021 14:53:01") // Local timezone - .succeeds(); - result.no_stdout().no_stderr(); + .fails(); + let result = result.no_stdout(); + assert!(result.stderr.starts_with("date: invalid date ")); } } #[test] #[cfg(all(unix, not(target_os = "macos")))] +/// TODO: expected to fail currently; change to succeeds() when required. fn test_date_set_valid_4() { if get_effective_uid() == 0 { let (_, mut ucmd) = at_and_ucmd!(); let result = ucmd .arg("--set") .arg("2020-03-11 21:45:00") // Local timezone - .succeeds(); - result.no_stdout().no_stderr(); + .fails(); + let result = result.no_stdout(); + assert!(result.stderr.starts_with("date: invalid date ")); } } From 365c23049394fd29ff1506827d8eff778b452a2d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 24 Mar 2021 08:56:30 +0100 Subject: [PATCH 098/104] refresh cargo.lock with recent updates (#1896) --- Cargo.lock | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5852987e..aabac3783 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,7 +90,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -295,7 +295,7 @@ dependencies = [ "uu_whoami 0.0.4", "uu_yes 0.0.4", "uucore 0.0.7", - "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -401,12 +401,12 @@ dependencies = [ "plotters 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)", "serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "tinytemplate 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -468,7 +468,7 @@ dependencies = [ "csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1100,7 +1100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1109,12 +1109,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "half 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1129,7 +1129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1286,7 +1286,7 @@ name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1382,7 +1382,7 @@ version = "0.0.4" dependencies = [ "uucore 0.0.7", "uucore_procs 0.0.5", - "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1392,7 +1392,7 @@ dependencies = [ "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", - "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1403,7 +1403,7 @@ dependencies = [ "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", - "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1445,7 +1445,7 @@ dependencies = [ "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", - "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1525,6 +1525,7 @@ dependencies = [ name = "uu_echo" version = "0.0.4" dependencies = [ + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", ] @@ -1835,7 +1836,7 @@ dependencies = [ name = "uu_nohup" version = "0.0.4" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -1977,7 +1978,7 @@ dependencies = [ "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", - "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2196,7 +2197,7 @@ dependencies = [ name = "uu_tsort" version = "0.0.4" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", ] @@ -2225,7 +2226,7 @@ dependencies = [ name = "uu_unexpand" version = "0.0.4" dependencies = [ - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", "uucore_procs 0.0.5", @@ -2344,7 +2345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "walkdir" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2595,9 +2596,9 @@ dependencies = [ "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)" = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f" +"checksum serde 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)" = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" "checksum serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" -"checksum serde_derive 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)" = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" +"checksum serde_derive 1.0.125 (registry+https://github.com/rust-lang/crates.io-index)" = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" "checksum serde_json 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)" = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" "checksum sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d963c78ce367df26d7ea8b8cc655c651b42e8a1e584e869c1e17dae3ccb116a" @@ -2625,7 +2626,7 @@ dependencies = [ "checksum users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486" "checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +"checksum walkdir 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum wasm-bindgen 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe" "checksum wasm-bindgen-backend 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3" From ffcfcfeef75810d8465aabe76bc1a01008ff9a5f Mon Sep 17 00:00:00 2001 From: dkg Date: Wed, 24 Mar 2021 12:56:07 -0400 Subject: [PATCH 099/104] tac is "semi-done" because of unimplemented ---regex (#1901) --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d25a1120..dd60d2126 100644 --- a/README.md +++ b/README.md @@ -301,7 +301,7 @@ Utilities | csplit | date | | | cut | join | | | dircolors | df | | -| dirname | | | +| dirname | tac | | | du | | | | echo | | | | env | | | @@ -354,7 +354,6 @@ Utilities | stdbuf | | | | sum | | | | sync | | | -| tac | | | | tee | | | | timeout | | | | touch | | | From 63317b35292079af1a5f3c3f8c81471dcb83a0b3 Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Wed, 24 Mar 2021 23:46:17 +0300 Subject: [PATCH 100/104] ptx: move from getopts to clap (#1893) * ptx: move from getopts to clap * chore: delete comment * chore: fix some clippy warnings --- src/uu/ptx/Cargo.toml | 1 + src/uu/ptx/src/ptx.rs | 335 ++++++++++++++++++++++++++---------------- 2 files changed, 206 insertions(+), 130 deletions(-) diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index 3a91feeb0..d1e0267b6 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/ptx.rs" [dependencies] +clap = "2.33" aho-corasick = "0.7.3" getopts = "0.2.18" libc = "0.2.42" diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index bfcd6699f..989ab52ef 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -10,7 +10,7 @@ #[macro_use] extern crate uucore; -use getopts::{Matches, Options}; +use clap::{App, Arg}; use regex::Regex; use std::cmp; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -20,6 +20,12 @@ use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; static NAME: &str = "ptx"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static BRIEF: &str = "Usage: ptx [OPTION]... [INPUT]... (without -G) or: \ + ptx -G [OPTION]... [INPUT [OUTPUT]] \n Output a permuted index, \ + including context, of the words in the input files. \n\n Mandatory \ + arguments to long options are mandatory for short options too.\n + With no FILE, or when FILE is -, read standard input. \ + Default is '-F /'."; #[derive(Debug)] enum OutFormat { @@ -61,8 +67,11 @@ impl Default for Config { } } -fn read_word_filter_file(matches: &Matches, option: &str) -> HashSet { - let filename = matches.opt_str(option).expect("parsing options failed!"); +fn read_word_filter_file(matches: &clap::ArgMatches, option: &str) -> HashSet { + let filename = matches + .value_of(option) + .expect("parsing options failed!") + .to_string(); let reader = BufReader::new(crash_if_err!(1, File::open(filename))); let mut words: HashSet = HashSet::new(); for word in reader.lines() { @@ -81,23 +90,29 @@ struct WordFilter { } impl WordFilter { - fn new(matches: &Matches, config: &Config) -> WordFilter { - let (o, oset): (bool, HashSet) = if matches.opt_present("o") { - (true, read_word_filter_file(matches, "o")) + fn new(matches: &clap::ArgMatches, config: &Config) -> WordFilter { + let (o, oset): (bool, HashSet) = if matches.is_present(options::ONLY_FILE) { + (true, read_word_filter_file(matches, options::ONLY_FILE)) } else { (false, HashSet::new()) }; - let (i, iset): (bool, HashSet) = if matches.opt_present("i") { - (true, read_word_filter_file(matches, "i")) + let (i, iset): (bool, HashSet) = if matches.is_present(options::IGNORE_FILE) { + (true, read_word_filter_file(matches, options::IGNORE_FILE)) } else { (false, HashSet::new()) }; - if matches.opt_present("b") { + if matches.is_present(options::BREAK_FILE) { crash!(1, "-b not implemented yet"); } // Ignore empty string regex from cmd-line-args - let arg_reg: Option = if matches.opt_present("W") { - matches.opt_str("W").filter(|reg| !reg.is_empty()) + let arg_reg: Option = if matches.is_present(options::WORD_REGEXP) { + match matches.value_of(options::WORD_REGEXP) { + Some(v) => match v.is_empty() { + true => None, + false => Some(v.to_string()), + }, + None => None, + } } else { None }; @@ -131,55 +146,50 @@ struct WordRef { filename: String, } -fn print_version() { - println!("{} {}", NAME, VERSION); -} - -fn print_usage(opts: &Options) { - let brief = "Usage: ptx [OPTION]... [INPUT]... (without -G) or: \ - ptx -G [OPTION]... [INPUT [OUTPUT]] \n Output a permuted index, \ - including context, of the words in the input files. \n\n Mandatory \ - arguments to long options are mandatory for short options too."; - let explanation = "With no FILE, or when FILE is -, read standard input. \ - Default is '-F /'."; - println!("{}\n{}", opts.usage(&brief), explanation); -} - -fn get_config(matches: &Matches) -> Config { +fn get_config(matches: &clap::ArgMatches) -> Config { let mut config: Config = Default::default(); let err_msg = "parsing options failed"; - if matches.opt_present("G") { + if matches.is_present(options::TRADITIONAL) { config.gnu_ext = false; config.format = OutFormat::Roff; config.context_regex = "[^ \t\n]+".to_owned(); } else { crash!(1, "GNU extensions not implemented yet"); } - if matches.opt_present("S") { + if matches.is_present(options::SENTENCE_REGEXP) { crash!(1, "-S not implemented yet"); } - config.auto_ref = matches.opt_present("A"); - config.input_ref = matches.opt_present("r"); - config.right_ref &= matches.opt_present("R"); - config.ignore_case = matches.opt_present("f"); - if matches.opt_present("M") { - config.macro_name = matches.opt_str("M").expect(err_msg); + config.auto_ref = matches.is_present(options::AUTO_REFERENCE); + config.input_ref = matches.is_present(options::REFERENCES); + config.right_ref &= matches.is_present(options::RIGHT_SIDE_REFS); + config.ignore_case = matches.is_present(options::IGNORE_CASE); + if matches.is_present(options::MACRO_NAME) { + config.macro_name = matches + .value_of(options::MACRO_NAME) + .expect(err_msg) + .to_string(); } - if matches.opt_present("F") { - config.trunc_str = matches.opt_str("F").expect(err_msg); + if matches.is_present(options::IGNORE_CASE) { + config.trunc_str = matches + .value_of(options::IGNORE_CASE) + .expect(err_msg) + .to_string(); } - if matches.opt_present("w") { - let width_str = matches.opt_str("w").expect(err_msg); + if matches.is_present(options::WIDTH) { + let width_str = matches.value_of(options::WIDTH).expect(err_msg).to_string(); config.line_width = crash_if_err!(1, usize::from_str_radix(&width_str, 10)); } - if matches.opt_present("g") { - let gap_str = matches.opt_str("g").expect(err_msg); + if matches.is_present(options::GAP_SIZE) { + let gap_str = matches + .value_of(options::GAP_SIZE) + .expect(err_msg) + .to_string(); config.gap_size = crash_if_err!(1, usize::from_str_radix(&gap_str, 10)); } - if matches.opt_present("O") { + if matches.is_present(options::FORMAT_ROFF) { config.format = OutFormat::Roff; } - if matches.opt_present("T") { + if matches.is_present(options::FORMAT_TEX) { config.format = OutFormat::Tex; } config @@ -494,102 +504,167 @@ fn write_traditional_output( } } +mod options { + pub static FILE: &str = "file"; + pub static AUTO_REFERENCE: &str = "auto-reference"; + pub static TRADITIONAL: &str = "traditional"; + pub static FLAG_TRUNCATION: &str = "flag-truncation"; + pub static MACRO_NAME: &str = "macro-name"; + pub static FORMAT_ROFF: &str = "format=roff"; + pub static RIGHT_SIDE_REFS: &str = "right-side-refs"; + pub static SENTENCE_REGEXP: &str = "sentence-regexp"; + pub static FORMAT_TEX: &str = "format=tex"; + pub static WORD_REGEXP: &str = "word-regexp"; + pub static BREAK_FILE: &str = "break-file"; + pub static IGNORE_CASE: &str = "ignore-case"; + pub static GAP_SIZE: &str = "gap-size"; + pub static IGNORE_FILE: &str = "ignore-file"; + pub static ONLY_FILE: &str = "only-file"; + pub static REFERENCES: &str = "references"; + pub static WIDTH: &str = "width"; +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let mut opts = Options::new(); - opts.optflag( - "A", - "auto-reference", - "output automatically generated references", - ); - opts.optflag("G", "traditional", "behave more like System V 'ptx'"); - opts.optopt( - "F", - "flag-truncation", - "use STRING for flagging line truncations", - "STRING", - ); - opts.optopt( - "M", - "macro-name", - "macro name to use instead of 'xx'", - "STRING", - ); - opts.optflag("O", "format=roff", "generate output as roff directives"); - opts.optflag( - "R", - "right-side-refs", - "put references at right, not counted in -w", - ); - opts.optopt( - "S", - "sentence-regexp", - "for end of lines or end of sentences", - "REGEXP", - ); - opts.optflag("T", "format=tex", "generate output as TeX directives"); - opts.optopt( - "W", - "word-regexp", - "use REGEXP to match each keyword", - "REGEXP", - ); - opts.optopt( - "b", - "break-file", - "word break characters in this FILE", - "FILE", - ); - opts.optflag( - "f", - "ignore-case", - "fold lower case to upper case for sorting", - ); - opts.optopt( - "g", - "gap-size", - "gap size in columns between output fields", - "NUMBER", - ); - opts.optopt( - "i", - "ignore-file", - "read ignore word list from FILE", - "FILE", - ); - opts.optopt( - "o", - "only-file", - "read only word list from this FILE", - "FILE", - ); - opts.optflag("r", "references", "first field of each line is a reference"); - opts.optopt( - "w", - "width", - "output width in columns, reference excluded", - "NUMBER", - ); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + // let mut opts = Options::new(); + let matches = App::new(executable!()) + .name(NAME) + .version(VERSION) + .usage(BRIEF) + .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) + .arg( + Arg::with_name(options::AUTO_REFERENCE) + .short("A") + .long(options::AUTO_REFERENCE) + .help("output automatically generated references") + .takes_value(false), + ) + .arg( + Arg::with_name(options::TRADITIONAL) + .short("G") + .long(options::TRADITIONAL) + .help("behave more like System V 'ptx'"), + ) + .arg( + Arg::with_name(options::FLAG_TRUNCATION) + .short("F") + .long(options::FLAG_TRUNCATION) + .help("use STRING for flagging line truncations") + .value_name("STRING") + .takes_value(true), + ) + .arg( + Arg::with_name(options::MACRO_NAME) + .short("M") + .long(options::MACRO_NAME) + .help("macro name to use instead of 'xx'") + .value_name("STRING") + .takes_value(true), + ) + .arg( + Arg::with_name(options::FORMAT_ROFF) + .short("O") + .long(options::FORMAT_ROFF) + .help("generate output as roff directives"), + ) + .arg( + Arg::with_name(options::RIGHT_SIDE_REFS) + .short("R") + .long(options::RIGHT_SIDE_REFS) + .help("put references at right, not counted in -w") + .takes_value(false), + ) + .arg( + Arg::with_name(options::SENTENCE_REGEXP) + .short("S") + .long(options::SENTENCE_REGEXP) + .help("for end of lines or end of sentences") + .value_name("REGEXP") + .takes_value(true), + ) + .arg( + Arg::with_name(options::FORMAT_TEX) + .short("T") + .long(options::FORMAT_TEX) + .help("generate output as TeX directives"), + ) + .arg( + Arg::with_name(options::WORD_REGEXP) + .short("W") + .long(options::WORD_REGEXP) + .help("use REGEXP to match each keyword") + .value_name("REGEXP") + .takes_value(true), + ) + .arg( + Arg::with_name(options::BREAK_FILE) + .short("b") + .long(options::BREAK_FILE) + .help("word break characters in this FILE") + .value_name("FILE") + .takes_value(true), + ) + .arg( + Arg::with_name(options::IGNORE_CASE) + .short("f") + .long(options::IGNORE_CASE) + .help("fold lower case to upper case for sorting") + .takes_value(false), + ) + .arg( + Arg::with_name(options::GAP_SIZE) + .short("g") + .long(options::GAP_SIZE) + .help("gap size in columns between output fields") + .value_name("NUMBER") + .takes_value(true), + ) + .arg( + Arg::with_name(options::IGNORE_FILE) + .short("i") + .long(options::IGNORE_FILE) + .help("read ignore word list from FILE") + .value_name("FILE") + .takes_value(true), + ) + .arg( + Arg::with_name(options::ONLY_FILE) + .short("o") + .long(options::ONLY_FILE) + .help("read only word list from this FILE") + .value_name("FILE") + .takes_value(true), + ) + .arg( + Arg::with_name(options::REFERENCES) + .short("r") + .long(options::REFERENCES) + .help("first field of each line is a reference") + .value_name("FILE") + .takes_value(false), + ) + .arg( + Arg::with_name(options::WIDTH) + .short("w") + .long(options::WIDTH) + .help("output width in columns, reference excluded") + .value_name("NUMBER") + .takes_value(true), + ) + .get_matches_from(args); - let matches = return_if_err!(1, opts.parse(&args[1..])); + let input_files: Vec = match &matches.values_of(options::FILE) { + Some(v) => v.clone().map(|v| v.to_owned()).collect(), + None => vec!["-".to_string()], + }; - if matches.opt_present("help") { - print_usage(&opts); - return 0; - } - if matches.opt_present("version") { - print_version(); - return 0; - } let config = get_config(&matches); let word_filter = WordFilter::new(&matches, &config); - let file_map = read_input(&matches.free, &config); + let file_map = read_input(&input_files, &config); let word_set = create_word_set(&config, &word_filter, &file_map); - let output_file = if !config.gnu_ext && matches.free.len() == 2 { - matches.free[1].clone() + let output_file = if !config.gnu_ext && matches.args.len() == 2 { + matches.value_of(options::FILE).unwrap_or("-").to_string() } else { "-".to_owned() }; From a9786ce52804384dbf331aec43601c66dabac12f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 25 Mar 2021 08:07:27 +0100 Subject: [PATCH 101/104] Reword the "why" a bit (#1903) --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dd60d2126..da92c80c2 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,8 @@ Why? Many GNU, Linux and other utilities are useful, and obviously [some](http://gnuwin32.sourceforge.net) [effort](http://unxutils.sourceforge.net) has been spent in the past to port them to Windows. However, those projects -are either old and abandoned, are hosted on CVS (which makes it more difficult -for new contributors to contribute to them), are written in platform-specific C, or -suffer from other issues. +are written in platform-specific C, a language considered unsafe compared to Rust, and +have other issues. Rust provides a good, platform-agnostic way of writing systems utilities that are easy to compile anywhere, and this is as good a way as any to try and learn it. From 99da9ea6ec985af34c0f88583465043013d30bd4 Mon Sep 17 00:00:00 2001 From: Mekka Date: Thu, 25 Mar 2021 02:36:48 -0700 Subject: [PATCH 102/104] Cleanup: Fix grammar in "cp" macro comments. (#1905) Replaced "they if" with "if they" in the comments above the "prompt_yes" macro. --- src/uu/cp/src/cp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 01ad4a8aa..569ee78bc 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -112,7 +112,7 @@ macro_rules! or_continue( }) ); -/// Prompts the user yes/no and returns `true` they if successfully +/// Prompts the user yes/no and returns `true` if they successfully /// answered yes. macro_rules! prompt_yes( ($($args:tt)+) => ({ From 52997b63fbdd6ee08c1ad5d32422819389ce5338 Mon Sep 17 00:00:00 2001 From: Sivachandran Date: Thu, 25 Mar 2021 15:57:34 +0530 Subject: [PATCH 103/104] pathchk: move from getopts to clap (#1897) --- src/uu/pathchk/Cargo.toml | 2 +- src/uu/pathchk/src/pathchk.rs | 143 ++++++++++++++++------------------ 2 files changed, 66 insertions(+), 79 deletions(-) diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index 452199f4f..c1d0d0dfa 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/pathchk.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" libc = "0.2.42" uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 745d0d36c..c27e52513 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -12,7 +12,7 @@ #[macro_use] extern crate uucore; -use getopts::Options; +use clap::{App, Arg}; use std::fs; use std::io::{ErrorKind, Write}; @@ -22,107 +22,94 @@ enum Mode { Basic, // check basic compatibility with POSIX Extra, // check for leading dashes and empty names Both, // a combination of `Basic` and `Extra` - Help, // show help - Version, // show version information } static NAME: &str = "pathchk"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Check whether file names are valid or portable"; + +mod options { + pub const POSIX: &str = "posix"; + pub const POSIX_SPECIAL: &str = "posix-special"; + pub const PORTABILITY: &str = "portability"; + pub const PATH: &str = "path"; +} // a few global constants as used in the GNU implementation const POSIX_PATH_MAX: usize = 256; const POSIX_NAME_MAX: usize = 14; -pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); +fn get_usage() -> String { + format!("{0} [OPTION]... NAME...", executable!()) +} - // add options - let mut opts = Options::new(); - opts.optflag("p", "posix", "check for (most) POSIX systems"); - opts.optflag( - "P", - "posix-special", - "check for empty names and leading \"-\"", - ); - opts.optflag( - "", - "portability", - "check for all POSIX systems (equivalent to -p -P)", - ); - opts.optflag("h", "help", "display this help text and exit"); - opts.optflag("V", "version", "output version information and exit"); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(e) => crash!(1, "{}", e), - }; +pub fn uumain(args: impl uucore::Args) -> i32 { + let usage = get_usage(); + + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .arg( + Arg::with_name(options::POSIX) + .short("p") + .help("check for most POSIX systems"), + ) + .arg( + Arg::with_name(options::POSIX_SPECIAL) + .short("P") + .help(r#"check for empty names and leading "-""#), + ) + .arg( + Arg::with_name(options::PORTABILITY) + .long(options::PORTABILITY) + .help("check for all POSIX systems (equivalent to -p -P)"), + ) + .arg(Arg::with_name(options::PATH).hidden(true).multiple(true)) + .get_matches_from(args); // set working mode - let mode = if matches.opt_present("version") { - Mode::Version - } else if matches.opt_present("help") { - Mode::Help - } else if (matches.opt_present("posix") && matches.opt_present("posix-special")) - || matches.opt_present("portability") - { + let is_posix = matches.values_of(options::POSIX).is_some(); + let is_posix_special = matches.values_of(options::POSIX_SPECIAL).is_some(); + let is_portability = matches.values_of(options::PORTABILITY).is_some(); + + let mode = if (is_posix && is_posix_special) || is_portability { Mode::Both - } else if matches.opt_present("posix") { + } else if is_posix { Mode::Basic - } else if matches.opt_present("posix-special") { + } else if is_posix_special { Mode::Extra } else { Mode::Default }; // take necessary actions - match mode { - Mode::Help => { - help(opts); - 0 - } - Mode::Version => { - version(); - 0 - } - _ => { - let mut res = if matches.free.is_empty() { - show_error!("missing operand\nTry {} --help for more information", NAME); - false - } else { - true - }; - // free strings are path operands - // FIXME: TCS, seems inefficient and overly verbose (?) - for p in matches.free { - let mut path = Vec::new(); - for path_segment in p.split('/') { - path.push(path_segment.to_string()); - } - res &= check_path(&mode, &path); - } - // determine error code - if res { - 0 - } else { - 1 + let paths = matches.values_of(options::PATH); + let mut res = if paths.is_none() { + show_error!("missing operand\nTry {} --help for more information", NAME); + false + } else { + true + }; + + if res { + // free strings are path operands + // FIXME: TCS, seems inefficient and overly verbose (?) + for p in paths.unwrap() { + let mut path = Vec::new(); + for path_segment in p.split('/') { + path.push(path_segment.to_string()); } + res &= check_path(&mode, &path); } } -} -// print help -fn help(opts: Options) { - let msg = format!( - "Usage: {} [OPTION]... NAME...\n\n\ - Diagnose invalid or unportable file names.", - NAME - ); - - print!("{}", opts.usage(&msg)); -} - -// print version information -fn version() { - println!("{} {}", NAME, VERSION); + // determine error code + if res { + 0 + } else { + 1 + } } // check a path, given as a slice of it's components and an operating mode From 23b70001a820404b1f50ff9f2dbf5bff7f213bf7 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 25 Mar 2021 20:24:53 +0100 Subject: [PATCH 104/104] ls: version sort (#1898) --- src/uu/ls/src/ls.rs | 26 ++- src/uu/ls/src/version_cmp.rs | 304 +++++++++++++++++++++++++++++++++++ tests/by-util/test_ls.rs | 79 ++++++++- 3 files changed, 407 insertions(+), 2 deletions(-) create mode 100644 src/uu/ls/src/version_cmp.rs diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 455b4f7b6..8714a0fa1 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -13,6 +13,8 @@ extern crate lazy_static; #[macro_use] extern crate uucore; +mod version_cmp; + use clap::{App, Arg}; use number_prefix::NumberPrefix; #[cfg(unix)] @@ -91,6 +93,7 @@ pub mod options { pub static SIZE: &str = "S"; pub static TIME: &str = "t"; pub static NONE: &str = "U"; + pub static VERSION: &str = "v"; } pub mod time { pub static ACCESS: &str = "u"; @@ -132,6 +135,7 @@ enum Sort { Name, Size, Time, + Version, } enum SizeFormat { @@ -270,6 +274,7 @@ impl Config { "name" => Sort::Name, "time" => Sort::Time, "size" => Sort::Size, + "version" => Sort::Version, // below should never happen as clap already restricts the values. _ => unreachable!("Invalid field for --sort"), } @@ -279,6 +284,8 @@ impl Config { Sort::Size } else if options.is_present(options::sort::NONE) { Sort::None + } else if options.is_present(options::sort::VERSION) { + Sort::Version } else { Sort::Name }; @@ -507,13 +514,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("Sort by : name, none (-U), time (-t) or size (-S)") .value_name("field") .takes_value(true) - .possible_values(&["name", "none", "time", "size"]) + .possible_values(&["name", "none", "time", "size", "version"]) .require_equals(true) .overrides_with_all(&[ options::SORT, options::sort::SIZE, options::sort::TIME, options::sort::NONE, + options::sort::VERSION, ]) ) .arg( @@ -525,6 +533,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::SIZE, options::sort::TIME, options::sort::NONE, + options::sort::VERSION, ]) ) .arg( @@ -536,6 +545,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::SIZE, options::sort::TIME, options::sort::NONE, + options::sort::VERSION, + ]) + ) + .arg( + Arg::with_name(options::sort::VERSION) + .short(options::sort::VERSION) + .help("Natural sort of (version) numbers in the filenames.") + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, ]) ) .arg( @@ -549,6 +571,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::SIZE, options::sort::TIME, options::sort::NONE, + options::sort::VERSION, ]) ) @@ -747,6 +770,7 @@ fn sort_entries(entries: &mut Vec, config: &Config) { .sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.len()).unwrap_or(0))), // The default sort in GNU ls is case insensitive Sort::Name => entries.sort_by_key(|k| k.to_string_lossy().to_lowercase()), + Sort::Version => entries.sort_by(version_cmp::version_cmp), Sort::None => {} } diff --git a/src/uu/ls/src/version_cmp.rs b/src/uu/ls/src/version_cmp.rs new file mode 100644 index 000000000..3cd5989f1 --- /dev/null +++ b/src/uu/ls/src/version_cmp.rs @@ -0,0 +1,304 @@ +use std::{cmp::Ordering, path::PathBuf}; + +/// Compare pathbufs in a way that matches the GNU version sort, meaning that +/// numbers get sorted in a natural way. +pub(crate) fn version_cmp(a: &PathBuf, b: &PathBuf) -> Ordering { + let a_string = a.to_string_lossy(); + let b_string = b.to_string_lossy(); + let mut a = a_string.chars().peekable(); + let mut b = b_string.chars().peekable(); + + // The order determined from the number of leading zeroes. + // This is used if the filenames are equivalent up to leading zeroes. + let mut leading_zeroes = Ordering::Equal; + + loop { + match (a.next(), b.next()) { + // If the characters are both numerical. We collect the rest of the number + // and parse them to u64's and compare them. + (Some(a_char @ '0'..='9'), Some(b_char @ '0'..='9')) => { + let mut a_leading_zeroes = 0; + if a_char == '0' { + a_leading_zeroes = 1; + while let Some('0') = a.peek() { + a_leading_zeroes += 1; + a.next(); + } + } + + let mut b_leading_zeroes = 0; + if b_char == '0' { + b_leading_zeroes = 1; + while let Some('0') = b.peek() { + b_leading_zeroes += 1; + b.next(); + } + } + // The first different number of leading zeros determines the order + // so if it's already been determined by a previous number, we leave + // it as that ordering. + // It's b.cmp(&a), because the *largest* number of leading zeros + // should go first + if leading_zeroes == Ordering::Equal { + leading_zeroes = b_leading_zeroes.cmp(&a_leading_zeroes); + } + + let mut a_str = String::new(); + let mut b_str = String::new(); + if a_char != '0' { + a_str.push(a_char); + } + if b_char != '0' { + b_str.push(b_char); + } + + // Unwrapping here is fine because we only call next if peek returns + // Some(_), so next should also return Some(_). + while let Some('0'..='9') = a.peek() { + a_str.push(a.next().unwrap()); + } + + while let Some('0'..='9') = b.peek() { + b_str.push(b.next().unwrap()); + } + + // Since the leading zeroes are stripped, the length can be + // used to compare the numbers. + match a_str.len().cmp(&b_str.len()) { + Ordering::Equal => {} + x => return x, + } + + // At this point, leading zeroes are stripped and the lengths + // are equal, meaning that the strings can be compared using + // the standard compare function. + match a_str.cmp(&b_str) { + Ordering::Equal => {} + x => return x, + } + } + // If there are two characters we just compare the characters + (Some(a_char), Some(b_char)) => match a_char.cmp(&b_char) { + Ordering::Equal => {} + x => return x, + }, + // Otherise, we compare the options (because None < Some(_)) + (a_opt, b_opt) => match a_opt.cmp(&b_opt) { + // If they are completely equal except for leading zeroes, we use the leading zeroes. + Ordering::Equal => return leading_zeroes, + x => return x, + }, + } + } +} + +#[cfg(test)] +mod tests { + use crate::version_cmp::version_cmp; + use std::cmp::Ordering; + use std::path::PathBuf; + #[test] + fn test_version_cmp() { + // Identical strings + assert_eq!( + version_cmp(&PathBuf::from("hello"), &PathBuf::from("hello")), + Ordering::Equal + ); + + assert_eq!( + version_cmp(&PathBuf::from("file12"), &PathBuf::from("file12")), + Ordering::Equal + ); + + assert_eq!( + version_cmp( + &PathBuf::from("file12-suffix"), + &PathBuf::from("file12-suffix") + ), + Ordering::Equal + ); + + assert_eq!( + version_cmp( + &PathBuf::from("file12-suffix24"), + &PathBuf::from("file12-suffix24") + ), + Ordering::Equal + ); + + // Shortened names + assert_eq!( + version_cmp(&PathBuf::from("world"), &PathBuf::from("wo")), + Ordering::Greater, + ); + + assert_eq!( + version_cmp(&PathBuf::from("hello10wo"), &PathBuf::from("hello10world")), + Ordering::Less, + ); + + // Simple names + assert_eq!( + version_cmp(&PathBuf::from("world"), &PathBuf::from("hello")), + Ordering::Greater, + ); + + assert_eq!( + version_cmp(&PathBuf::from("hello"), &PathBuf::from("world")), + Ordering::Less + ); + + assert_eq!( + version_cmp(&PathBuf::from("apple"), &PathBuf::from("ant")), + Ordering::Greater + ); + + assert_eq!( + version_cmp(&PathBuf::from("ant"), &PathBuf::from("apple")), + Ordering::Less + ); + + // Uppercase letters + assert_eq!( + version_cmp(&PathBuf::from("Beef"), &PathBuf::from("apple")), + Ordering::Less, + "Uppercase letters are sorted before all lowercase letters" + ); + + assert_eq!( + version_cmp(&PathBuf::from("Apple"), &PathBuf::from("apple")), + Ordering::Less + ); + + assert_eq!( + version_cmp(&PathBuf::from("apple"), &PathBuf::from("aPple")), + Ordering::Greater + ); + + // Numbers + assert_eq!( + version_cmp(&PathBuf::from("100"), &PathBuf::from("20")), + Ordering::Greater, + "Greater numbers are greater even if they start with a smaller digit", + ); + + assert_eq!( + version_cmp(&PathBuf::from("20"), &PathBuf::from("20")), + Ordering::Equal, + "Equal numbers are equal" + ); + + assert_eq!( + version_cmp(&PathBuf::from("15"), &PathBuf::from("200")), + Ordering::Less, + "Small numbers are smaller" + ); + + // Comparing numbers with other characters + assert_eq!( + version_cmp(&PathBuf::from("1000"), &PathBuf::from("apple")), + Ordering::Less, + "Numbers are sorted before other characters" + ); + + assert_eq!( + version_cmp(&PathBuf::from("file1000"), &PathBuf::from("fileapple")), + Ordering::Less, + "Numbers in the middle of the name are sorted before other characters" + ); + + // Leading zeroes + assert_eq!( + version_cmp(&PathBuf::from("012"), &PathBuf::from("12")), + Ordering::Less, + "A single leading zero can make a difference" + ); + + assert_eq!( + version_cmp(&PathBuf::from("000800"), &PathBuf::from("0000800")), + Ordering::Greater, + "Leading number of zeroes is used even if both non-zero number of zeros" + ); + + // Numbers and other characters combined + assert_eq!( + version_cmp(&PathBuf::from("ab10"), &PathBuf::from("aa11")), + Ordering::Greater + ); + + assert_eq!( + version_cmp(&PathBuf::from("aa10"), &PathBuf::from("aa11")), + Ordering::Less, + "Numbers after other characters are handled correctly." + ); + + assert_eq!( + version_cmp(&PathBuf::from("aa2"), &PathBuf::from("aa100")), + Ordering::Less, + "Numbers after alphabetical characters are handled correctly." + ); + + assert_eq!( + version_cmp(&PathBuf::from("aa10bb"), &PathBuf::from("aa11aa")), + Ordering::Less, + "Number is used even if alphabetical characters after it differ." + ); + + assert_eq!( + version_cmp(&PathBuf::from("aa10aa0010"), &PathBuf::from("aa11aa1")), + Ordering::Less, + "Second number is ignored if the first number differs." + ); + + assert_eq!( + version_cmp(&PathBuf::from("aa10aa0010"), &PathBuf::from("aa10aa1")), + Ordering::Greater, + "Second number is used if the rest is equal." + ); + + assert_eq!( + version_cmp(&PathBuf::from("aa10aa0010"), &PathBuf::from("aa00010aa1")), + Ordering::Greater, + "Second number is used if the rest is equal up to leading zeroes of the first number." + ); + + assert_eq!( + version_cmp(&PathBuf::from("aa10aa0022"), &PathBuf::from("aa010aa022")), + Ordering::Greater, + "The leading zeroes of the first number has priority." + ); + + assert_eq!( + version_cmp(&PathBuf::from("aa10aa0022"), &PathBuf::from("aa10aa022")), + Ordering::Less, + "The leading zeroes of other numbers than the first are used." + ); + + assert_eq!( + version_cmp(&PathBuf::from("file-1.4"), &PathBuf::from("file-1.13")), + Ordering::Less, + "Periods are handled as normal text, not as a decimal point." + ); + + // Greater than u64::Max + // u64 == 18446744073709551615 so this should be plenty: + // 20000000000000000000000 + assert_eq!( + version_cmp( + &PathBuf::from("aa2000000000000000000000bb"), + &PathBuf::from("aa002000000000000000000001bb") + ), + Ordering::Less, + "Numbers larger than u64::MAX are handled correctly without crashing" + ); + + assert_eq!( + version_cmp( + &PathBuf::from("aa2000000000000000000000bb"), + &PathBuf::from("aa002000000000000000000000bb") + ), + Ordering::Greater, + "Leading zeroes for numbers larger than u64::MAX are handled correctly without crashing" + ); + } +} diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index ecd288735..091d47234 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -526,7 +526,6 @@ fn test_ls_order_time() { at.metadata("test-2").permissions(), ) .unwrap(); - let second_access = at.open("test-2").metadata().unwrap().accessed().unwrap(); let result = scene.ucmd().arg("-al").run(); println!("stderr = {:?}", result.stderr); @@ -870,3 +869,81 @@ fn test_ls_hidden_windows() { assert!(result.success); assert!(result.stdout.contains(file)); } + +#[test] +fn test_ls_version_sort() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + for filename in &[ + "a2", + "b1", + "b20", + "a1.4", + "a1.40", + "b3", + "b11", + "b20b", + "b20a", + "a100", + "a1.13", + "aa", + "a1", + "aaa", + "a1.00000040", + "abab", + "ab", + "a01.40", + "a001.001", + "a01.0000001", + "a01.001", + "a001.01", + ] { + at.touch(filename); + } + + let mut expected = vec![ + "a1", + "a001.001", + "a001.01", + "a01.0000001", + "a01.001", + "a1.4", + "a1.13", + "a01.40", + "a1.00000040", + "a1.40", + "a2", + "a100", + "aa", + "aaa", + "ab", + "abab", + "b1", + "b3", + "b11", + "b20", + "b20a", + "b20b", + "", // because of '\n' at the end of the output + ]; + + let result = scene.ucmd().arg("-1v").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + + assert_eq!(result.stdout.split('\n').collect::>(), expected); + + let result = scene.ucmd().arg("-1").arg("--sort=version").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + + assert_eq!(result.stdout.split('\n').collect::>(), expected); + + let result = scene.ucmd().arg("-a1v").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + + expected.insert(0, ".."); + expected.insert(0, "."); + assert_eq!(result.stdout.split('\n').collect::>(), expected,) +}