From a32793c7c07200d3872b04f017dffac379f2be6f Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Mon, 21 Oct 2019 13:35:15 +0000 Subject: [PATCH 01/10] update coreutils from coreutils_sunrise master --- coreutils/.appveyor.yml | 164 ++ coreutils/.busybox-config | 4 + coreutils/.cargo/config | 2 + coreutils/.cirrus.yml | 15 + coreutils/.codecov.yml | 1 + coreutils/.gitignore | 14 + coreutils/.travis.yml | 70 + coreutils/.travis/redox-toolchain.sh | 7 + coreutils/CONTRIBUTING.md | 78 + coreutils/Cargo.lock | 2286 +++++++++++++++++ coreutils/Cargo.toml | 393 +++ coreutils/GNUmakefile | 324 +++ coreutils/LICENSE | 18 + coreutils/Makefile | 5 + coreutils/README.md | 361 +++ coreutils/build.rs | 77 + coreutils/docs/GNUmakefile | 20 + coreutils/docs/Makefile | 5 + coreutils/docs/arch.rst | 28 + coreutils/docs/conf.py | 184 ++ coreutils/docs/index.rst | 21 + coreutils/docs/make.bat | 36 + coreutils/docs/uutils.rst | 24 + coreutils/mkmain.rs | 36 + coreutils/src/arch/Cargo.toml | 17 + coreutils/src/arch/arch.rs | 27 + coreutils/src/base32/Cargo.toml | 21 + coreutils/src/base32/base32.rs | 33 + coreutils/src/base64/Cargo.toml | 17 + coreutils/src/base64/base64.rs | 33 + coreutils/src/base64/base_common.rs | 91 + coreutils/src/basename/Cargo.toml | 16 + coreutils/src/basename/basename.rs | 124 + coreutils/src/cat/Cargo.toml | 23 + coreutils/src/cat/cat.rs | 469 ++++ coreutils/src/chgrp/Cargo.toml | 20 + coreutils/src/chgrp/chgrp.rs | 372 +++ coreutils/src/chmod/Cargo.toml | 21 + coreutils/src/chmod/chmod.rs | 267 ++ coreutils/src/chown/Cargo.toml | 25 + coreutils/src/chown/chown.rs | 456 ++++ coreutils/src/chroot/Cargo.toml | 20 + coreutils/src/chroot/chroot.rs | 208 ++ coreutils/src/cksum/Cargo.toml | 16 + coreutils/src/cksum/build.rs | 52 + coreutils/src/cksum/cksum.rs | 112 + coreutils/src/comm/Cargo.toml | 18 + coreutils/src/comm/comm.rs | 144 ++ coreutils/src/cp/Cargo.toml | 38 + coreutils/src/cp/README.md | 38 + coreutils/src/cp/cp.rs | 1204 +++++++++ coreutils/src/cut/.gitignore | 1 + coreutils/src/cut/Cargo.toml | 16 + coreutils/src/cut/buffer.rs | 153 ++ coreutils/src/cut/cut.rs | 553 ++++ coreutils/src/cut/ranges.rs | 152 ++ coreutils/src/cut/searcher.rs | 54 + coreutils/src/date/Cargo.toml | 18 + coreutils/src/date/date.rs | 272 ++ coreutils/src/date/usage.txt | 72 + coreutils/src/dircolors/Cargo.toml | 17 + coreutils/src/dircolors/colors.rs | 192 ++ coreutils/src/dircolors/dircolors.rs | 311 +++ coreutils/src/dirname/Cargo.toml | 17 + coreutils/src/dirname/dirname.rs | 65 + coreutils/src/du/Cargo.toml | 17 + coreutils/src/du/du.rs | 497 ++++ coreutils/src/echo/Cargo.toml | 16 + coreutils/src/echo/echo.rs | 151 ++ coreutils/src/env/Cargo.toml | 21 + coreutils/src/env/env.rs | 258 ++ coreutils/src/expand/Cargo.toml | 18 + coreutils/src/expand/expand.rs | 252 ++ coreutils/src/expr/Cargo.toml | 18 + coreutils/src/expr/expr.rs | 141 + coreutils/src/expr/syntax_tree.rs | 556 ++++ coreutils/src/expr/tokens.rs | 171 ++ coreutils/src/factor/Cargo.toml | 16 + coreutils/src/factor/build.rs | 153 ++ coreutils/src/factor/factor.rs | 178 ++ coreutils/src/factor/numeric.rs | 132 + coreutils/src/factor/sieve.rs | 225 ++ coreutils/src/false/Cargo.toml | 16 + coreutils/src/false/false.rs | 14 + coreutils/src/fmt/Cargo.toml | 18 + coreutils/src/fmt/fmt.rs | 207 ++ coreutils/src/fmt/linebreak.rs | 507 ++++ coreutils/src/fmt/parasplit.rs | 623 +++++ coreutils/src/fold/Cargo.toml | 16 + coreutils/src/fold/fold.rs | 215 ++ coreutils/src/groups/Cargo.toml | 17 + coreutils/src/groups/groups.rs | 47 + coreutils/src/hashsum/Cargo.toml | 26 + coreutils/src/hashsum/digest.rs | 132 + coreutils/src/hashsum/hashsum.rs | 543 ++++ coreutils/src/head/Cargo.toml | 17 + coreutils/src/head/head.rs | 197 ++ coreutils/src/hostid/Cargo.toml | 17 + coreutils/src/hostid/hostid.rs | 48 + coreutils/src/hostname/Cargo.toml | 19 + coreutils/src/hostname/hostname.rs | 214 ++ coreutils/src/id/Cargo.toml | 17 + coreutils/src/id/id.rs | 323 +++ coreutils/src/install/Cargo.toml | 21 + coreutils/src/install/install.rs | 416 +++ coreutils/src/install/mode.rs | 47 + coreutils/src/join/Cargo.toml | 17 + coreutils/src/join/join.rs | 724 ++++++ coreutils/src/kill/Cargo.toml | 20 + coreutils/src/kill/kill.rs | 171 ++ coreutils/src/link/Cargo.toml | 17 + coreutils/src/link/link.rs | 46 + coreutils/src/ln/Cargo.toml | 17 + coreutils/src/ln/ln.rs | 354 +++ coreutils/src/logname/Cargo.toml | 17 + coreutils/src/logname/logname.rs | 54 + coreutils/src/ls/Cargo.toml | 26 + coreutils/src/ls/ls.rs | 681 +++++ coreutils/src/mkdir/Cargo.toml | 18 + coreutils/src/mkdir/mkdir.rs | 150 ++ coreutils/src/mkfifo/Cargo.toml | 18 + coreutils/src/mkfifo/mkfifo.rs | 95 + coreutils/src/mknod/Cargo.toml | 18 + coreutils/src/mknod/mknod.rs | 200 ++ coreutils/src/mknod/parsemode.rs | 35 + coreutils/src/mktemp/Cargo.toml | 19 + coreutils/src/mktemp/mktemp.rs | 242 ++ coreutils/src/mktemp/tempdir.rs | 50 + coreutils/src/more/Cargo.toml | 24 + coreutils/src/more/more.rs | 168 ++ coreutils/src/mv/Cargo.toml | 17 + coreutils/src/mv/mv.rs | 437 ++++ coreutils/src/nice/Cargo.toml | 18 + coreutils/src/nice/nice.rs | 134 + coreutils/src/nl/Cargo.toml | 22 + coreutils/src/nl/helper.rs | 143 ++ coreutils/src/nl/nl.rs | 384 +++ coreutils/src/nohup/Cargo.toml | 21 + coreutils/src/nohup/nohup.rs | 166 ++ coreutils/src/nproc/Cargo.toml | 19 + coreutils/src/nproc/nproc.rs | 132 + coreutils/src/numfmt/Cargo.toml | 17 + coreutils/src/numfmt/numfmt.rs | 363 +++ coreutils/src/od/Cargo.toml | 20 + coreutils/src/od/byteorder_io.rs | 50 + coreutils/src/od/formatteriteminfo.rs | 56 + coreutils/src/od/inputdecoder.rs | 205 ++ coreutils/src/od/inputoffset.rs | 114 + coreutils/src/od/mockstream.rs | 109 + coreutils/src/od/multifilereader.rs | 209 ++ coreutils/src/od/od.rs | 498 ++++ coreutils/src/od/output_info.rs | 445 ++++ coreutils/src/od/parse_formats.rs | 559 ++++ coreutils/src/od/parse_inputs.rs | 383 +++ coreutils/src/od/parse_nrofbytes.rs | 133 + coreutils/src/od/partialreader.rs | 209 ++ coreutils/src/od/peekreader.rs | 212 ++ coreutils/src/od/prn_char.rs | 154 ++ coreutils/src/od/prn_float.rs | 226 ++ coreutils/src/od/prn_int.rs | 186 ++ coreutils/src/paste/Cargo.toml | 17 + coreutils/src/paste/paste.rs | 144 ++ coreutils/src/pathchk/Cargo.toml | 18 + coreutils/src/pathchk/pathchk.rs | 266 ++ coreutils/src/pinky/Cargo.toml | 17 + coreutils/src/pinky/pinky.rs | 347 +++ coreutils/src/printenv/Cargo.toml | 17 + coreutils/src/printenv/printenv.rs | 77 + coreutils/src/printf/Cargo.toml | 17 + coreutils/src/printf/cli.rs | 34 + coreutils/src/printf/memo.rs | 87 + coreutils/src/printf/mod.rs | 3 + coreutils/src/printf/printf.rs | 296 +++ coreutils/src/printf/tokenize/mod.rs | 4 + .../tokenize/num_format/format_field.rs | 41 + .../printf/tokenize/num_format/formatter.rs | 68 + .../num_format/formatters/base_conv/mod.rs | 330 +++ .../num_format/formatters/base_conv/tests.rs | 55 + .../formatters/cninetyninehexfloatf.rs | 127 + .../tokenize/num_format/formatters/decf.rs | 90 + .../num_format/formatters/float_common.rs | 357 +++ .../tokenize/num_format/formatters/floatf.rs | 36 + .../tokenize/num_format/formatters/intf.rs | 277 ++ .../tokenize/num_format/formatters/mod.rs | 7 + .../tokenize/num_format/formatters/scif.rs | 41 + .../src/printf/tokenize/num_format/mod.rs | 4 + .../printf/tokenize/num_format/num_format.rs | 280 ++ coreutils/src/printf/tokenize/sub.rs | 428 +++ coreutils/src/printf/tokenize/token.rs | 30 + .../src/printf/tokenize/unescaped_text.rs | 253 ++ coreutils/src/ptx/Cargo.toml | 22 + coreutils/src/ptx/ptx.rs | 597 +++++ coreutils/src/pwd/Cargo.toml | 17 + coreutils/src/pwd/pwd.rs | 85 + coreutils/src/readlink/Cargo.toml | 21 + coreutils/src/readlink/readlink.rs | 146 ++ coreutils/src/realpath/Cargo.toml | 20 + coreutils/src/realpath/realpath.rs | 151 ++ coreutils/src/relpath/Cargo.toml | 20 + coreutils/src/relpath/relpath.rs | 124 + coreutils/src/rm/Cargo.toml | 19 + coreutils/src/rm/rm.rs | 326 +++ coreutils/src/rmdir/Cargo.toml | 17 + coreutils/src/rmdir/rmdir.rs | 129 + coreutils/src/seq/Cargo.toml | 17 + coreutils/src/seq/seq.rs | 307 +++ coreutils/src/shred/Cargo.toml | 21 + coreutils/src/shred/shred.rs | 587 +++++ coreutils/src/shuf/Cargo.toml | 18 + coreutils/src/shuf/shuf.rs | 290 +++ coreutils/src/sleep/Cargo.toml | 20 + coreutils/src/sleep/sleep.rs | 74 + coreutils/src/sort/Cargo.toml | 22 + coreutils/src/sort/sort.rs | 547 ++++ coreutils/src/split/Cargo.toml | 17 + coreutils/src/split/README.md | 9 + coreutils/src/split/split.rs | 352 +++ coreutils/src/stat/Cargo.toml | 21 + coreutils/src/stat/fsext.rs | 388 +++ coreutils/src/stat/stat.rs | 999 +++++++ coreutils/src/stat/test_stat.rs | 70 + coreutils/src/stdbuf/Cargo.toml | 21 + coreutils/src/stdbuf/build.rs | 39 + coreutils/src/stdbuf/libstdbuf/Cargo.toml | 18 + coreutils/src/stdbuf/libstdbuf/build.rs | 7 + coreutils/src/stdbuf/libstdbuf/libstdbuf.rs | 69 + coreutils/src/stdbuf/stdbuf.rs | 287 +++ coreutils/src/sum/Cargo.toml | 17 + coreutils/src/sum/sum.rs | 144 ++ coreutils/src/sync/Cargo.toml | 23 + coreutils/src/sync/sync.rs | 170 ++ coreutils/src/tac/Cargo.toml | 17 + coreutils/src/tac/tac.rs | 165 ++ coreutils/src/tail/Cargo.toml | 23 + coreutils/src/tail/README.md | 13 + coreutils/src/tail/platform/mod.rs | 32 + coreutils/src/tail/platform/redox.rs | 25 + coreutils/src/tail/platform/sunrise.rs | 21 + coreutils/src/tail/platform/unix.rs | 42 + coreutils/src/tail/platform/windows.rs | 58 + coreutils/src/tail/tail.rs | 577 +++++ coreutils/src/tee/Cargo.toml | 18 + coreutils/src/tee/tee.rs | 195 ++ coreutils/src/test/Cargo.toml | 21 + coreutils/src/test/test.rs | 444 ++++ coreutils/src/timeout/Cargo.toml | 22 + coreutils/src/timeout/timeout.rs | 174 ++ coreutils/src/touch/Cargo.toml | 22 + coreutils/src/touch/touch.rs | 243 ++ coreutils/src/tr/Cargo.toml | 19 + coreutils/src/tr/expand.rs | 116 + coreutils/src/tr/tr.rs | 259 ++ coreutils/src/true/Cargo.toml | 16 + coreutils/src/true/true.rs | 14 + coreutils/src/truncate/Cargo.toml | 17 + coreutils/src/truncate/truncate.rs | 233 ++ coreutils/src/tsort/Cargo.toml | 17 + coreutils/src/tsort/tsort.rs | 195 ++ coreutils/src/tty/Cargo.toml | 21 + coreutils/src/tty/tty.rs | 82 + coreutils/src/uname/Cargo.toml | 18 + coreutils/src/uname/uname.rs | 130 + coreutils/src/unexpand/Cargo.toml | 18 + coreutils/src/unexpand/unexpand.rs | 330 +++ coreutils/src/uniq/Cargo.toml | 17 + coreutils/src/uniq/uniq.rs | 332 +++ coreutils/src/unlink/Cargo.toml | 18 + coreutils/src/unlink/unlink.rs | 117 + coreutils/src/uptime/Cargo.toml | 21 + coreutils/src/uptime/uptime.rs | 199 ++ coreutils/src/users/Cargo.toml | 20 + coreutils/src/users/users.rs | 78 + coreutils/src/uutils/uutils.rs | 110 + coreutils/src/wc/Cargo.toml | 17 + coreutils/src/wc/wc.rs | 281 ++ coreutils/src/who/Cargo.toml | 21 + coreutils/src/who/who.rs | 541 ++++ coreutils/src/whoami/Cargo.toml | 23 + coreutils/src/whoami/platform/mod.rs | 20 + coreutils/src/whoami/platform/unix.rs | 19 + coreutils/src/whoami/platform/windows.rs | 29 + coreutils/src/whoami/whoami.rs | 54 + coreutils/src/yes/Cargo.toml | 22 + coreutils/src/yes/yes.rs | 93 + coreutils/tests/common/macros.rs | 66 + coreutils/tests/common/mod.rs | 3 + coreutils/tests/common/util.rs | 629 +++++ coreutils/tests/fixtures/.gitattributes | 1 + coreutils/tests/fixtures/cat/256.txt | Bin 0 -> 256 bytes coreutils/tests/fixtures/cat/alpha.txt | 5 + coreutils/tests/fixtures/cat/nonewline.txt | 1 + .../fixtures/cksum/alice_in_wonderland.txt | 5 + .../tests/fixtures/cksum/lorem_ipsum.txt | 13 + .../fixtures/cksum/multiple_files.expected | 2 + .../tests/fixtures/cksum/single_file.expected | 1 + coreutils/tests/fixtures/cksum/stdin.expected | 1 + coreutils/tests/fixtures/comm/a | 2 + coreutils/tests/fixtures/comm/ab.expected | 3 + coreutils/tests/fixtures/comm/ab1.expected | 2 + coreutils/tests/fixtures/comm/ab2.expected | 2 + coreutils/tests/fixtures/comm/ab3.expected | 2 + .../fixtures/comm/ab_delimiter_word.expected | 3 + coreutils/tests/fixtures/comm/aempty.expected | 2 + coreutils/tests/fixtures/comm/b | 2 + .../bad_order11.defaultcheck_order.expected | 4 + .../comm/bad_order12.check_order.expected | 1 + .../comm/bad_order12.nocheck_order.expected | 7 + coreutils/tests/fixtures/comm/bad_order_1 | 4 + coreutils/tests/fixtures/comm/bad_order_2 | 4 + .../comm/defaultcheck_unintuitive.expected | 6 + .../fixtures/comm/defaultcheck_unintuitive_1 | 6 + .../fixtures/comm/defaultcheck_unintuitive_2 | 5 + coreutils/tests/fixtures/comm/empty | 0 .../tests/fixtures/comm/emptyempty.expected | 0 .../tests/fixtures/comm/lowercase_uppercase | 2 + .../comm/lowercase_uppercase.c.expected | 1 + .../comm/lowercase_uppercase.en_us.expected | 2 + coreutils/tests/fixtures/cp/existing_file.txt | 1 + .../tests/fixtures/cp/hello_dir/hello.txt | 0 .../cp/hello_dir_with_file/hello_world.txt | 1 + coreutils/tests/fixtures/cp/hello_world.txt | 1 + coreutils/tests/fixtures/cp/how_are_you.txt | 1 + .../fixtures/cut/delimiter_specified.expected | 5 + coreutils/tests/fixtures/cut/lists.txt | 5 + .../fixtures/cut/output_delimiter.expected | 5 + .../cut/sequences/byte_aggregate.expected | 5 + .../cut/sequences/byte_prefix.expected | 5 + .../cut/sequences/byte_range.expected | 5 + .../cut/sequences/byte_singular.expected | 5 + .../cut/sequences/byte_subsumed.expected | 5 + .../cut/sequences/byte_suffix.expected | 5 + .../cut/sequences/field_aggregate.expected | 5 + .../cut/sequences/field_prefix.expected | 5 + .../cut/sequences/field_range.expected | 5 + .../cut/sequences/field_singular.expected | 5 + .../cut/sequences/field_subsumed.expected | 5 + .../cut/sequences/field_suffix.expected | 5 + .../fixtures/dircolors/bash_def.expected | 2 + .../tests/fixtures/dircolors/csh_def.expected | 1 + .../fixtures/dircolors/internal.expected | 191 ++ .../fixtures/dircolors/keywords.csh.expected | 1 + .../fixtures/dircolors/keywords.sh.expected | 2 + .../tests/fixtures/dircolors/keywords.txt | 23 + .../fixtures/dircolors/test1.csh.expected | 1 + .../fixtures/dircolors/test1.sh.expected | 2 + coreutils/tests/fixtures/dircolors/test1.txt | 24 + .../tests/fixtures/du/subdir/deeper/words.txt | 1 + .../fixtures/du/subdir/links/subwords.txt | Bin 0 -> 5120 bytes .../fixtures/du/subdir/links/subwords2.txt | 1 + coreutils/tests/fixtures/du/words.txt | 1 + coreutils/tests/fixtures/env/vars.conf.txt | 4 + coreutils/tests/fixtures/fold/lorem_ipsum.txt | 1 + .../fold/lorem_ipsum_40_column_hard.expected | 20 + .../fold/lorem_ipsum_40_column_word.expected | 21 + .../fold/lorem_ipsum_80_column.expected | 10 + coreutils/tests/fixtures/hashsum/input.txt | 1 + coreutils/tests/fixtures/hashsum/md5.expected | 1 + .../tests/fixtures/hashsum/sha1.expected | 1 + .../tests/fixtures/hashsum/sha224.expected | 1 + .../tests/fixtures/hashsum/sha256.expected | 1 + .../tests/fixtures/hashsum/sha384.expected | 1 + .../tests/fixtures/hashsum/sha3_224.expected | 1 + .../tests/fixtures/hashsum/sha3_256.expected | 1 + .../tests/fixtures/hashsum/sha3_384.expected | 1 + .../tests/fixtures/hashsum/sha3_512.expected | 1 + .../tests/fixtures/hashsum/sha512.expected | 1 + .../fixtures/hashsum/shake128_256.expected | 1 + .../fixtures/hashsum/shake256_512.expected | 1 + coreutils/tests/fixtures/head/lorem_ipsum.txt | 24 + .../fixtures/head/lorem_ipsum_1_line.expected | 1 + .../head/lorem_ipsum_5_chars.expected | 1 + .../head/lorem_ipsum_default.expected | 10 + .../head/lorem_ipsum_verbose.expected | 11 + .../tests/fixtures/join/autoformat.expected | 5 + coreutils/tests/fixtures/join/capitalized.txt | 4 + .../fixtures/join/case_insensitive.expected | 3 + .../tests/fixtures/join/default.expected | 5 + .../fixtures/join/different_field.expected | 6 + .../fixtures/join/different_fields.expected | 5 + .../tests/fixtures/join/different_lengths.txt | 5 + coreutils/tests/fixtures/join/empty.txt | 0 .../tests/fixtures/join/empty_key.expected | 5 + coreutils/tests/fixtures/join/fields_1.txt | 5 + coreutils/tests/fixtures/join/fields_2.txt | 9 + coreutils/tests/fixtures/join/fields_3.txt | 6 + coreutils/tests/fixtures/join/fields_4.txt | 5 + coreutils/tests/fixtures/join/header.expected | 4 + coreutils/tests/fixtures/join/header_1.txt | 6 + coreutils/tests/fixtures/join/header_2.txt | 5 + .../fixtures/join/header_autoformat.expected | 4 + .../join/missing_format_fields.expected | 5 + .../fixtures/join/semicolon_fields_1.txt | 6 + .../fixtures/join/semicolon_fields_2.txt | 3 + .../join/semicolon_separated.expected | 3 + .../fixtures/join/suppress_joined.expected | 3 + .../fixtures/join/unpaired_lines.expected | 9 + .../join/unpaired_lines_format.expected | 6 + coreutils/tests/fixtures/mv/hello_world.txt | 1 + .../tests/fixtures/nl/joinblanklines.txt | 27 + coreutils/tests/fixtures/nl/section.txt | 18 + coreutils/tests/fixtures/nl/simple.txt | 15 + coreutils/tests/fixtures/od/-f | 1 + coreutils/tests/fixtures/od/0 | 1 + coreutils/tests/fixtures/od/c | 1 + coreutils/tests/fixtures/od/x | 1 + .../tests/fixtures/paste/html_colors.expected | 16 + .../tests/fixtures/paste/html_colors.txt | 32 + ...ext_disabled_ignore_and_only_file.expected | 2 + .../gnu_ext_disabled_roff_auto_ref.expected | 24 + .../gnu_ext_disabled_roff_input_ref.expected | 17 + .../ptx/gnu_ext_disabled_roff_no_ref.expected | 24 + .../gnu_ext_disabled_tex_auto_ref.expected | 24 + .../gnu_ext_disabled_tex_input_ref.expected | 17 + .../ptx/gnu_ext_disabled_tex_no_ref.expected | 24 + coreutils/tests/fixtures/ptx/ignore | 2 + coreutils/tests/fixtures/ptx/input | 7 + coreutils/tests/fixtures/ptx/only | 5 + coreutils/tests/fixtures/sort/check_fail.txt | 8 + .../sort/default_unsorted_ints.expected | 100 + .../fixtures/sort/default_unsorted_ints.txt | 100 + .../fixtures/sort/human_block_sizes.expected | 11 + .../tests/fixtures/sort/human_block_sizes.txt | 11 + .../tests/fixtures/sort/ignore_case.expected | 7 + coreutils/tests/fixtures/sort/ignore_case.txt | 7 + .../sort/merge_ints_interleaved.expected | 9 + .../sort/merge_ints_interleaved_1.txt | 3 + .../sort/merge_ints_interleaved_2.txt | 3 + .../sort/merge_ints_interleaved_3.txt | 3 + .../sort/merge_ints_reversed.expected | 9 + .../fixtures/sort/merge_ints_reversed_1.txt | 3 + .../fixtures/sort/merge_ints_reversed_2.txt | 3 + .../fixtures/sort/merge_ints_reversed_3.txt | 3 + .../fixtures/sort/month_default.expected | 10 + .../tests/fixtures/sort/month_default.txt | 10 + .../tests/fixtures/sort/month_stable.expected | 10 + .../tests/fixtures/sort/month_stable.txt | 10 + .../fixtures/sort/multiple_files.expected | 9 + .../tests/fixtures/sort/multiple_files1.txt | 4 + .../tests/fixtures/sort/multiple_files2.txt | 5 + .../sort/numeric_fixed_floats.expected | 2 + .../fixtures/sort/numeric_fixed_floats.txt | 2 + .../fixtures/sort/numeric_floats.expected | 2 + .../tests/fixtures/sort/numeric_floats.txt | 2 + .../sort/numeric_floats_and_ints.expected | 2 + .../fixtures/sort/numeric_floats_and_ints.txt | 2 + .../sort/numeric_floats_with_nan.expected | 3 + .../fixtures/sort/numeric_floats_with_nan.txt | 3 + .../sort/numeric_unfixed_floats.expected | 2 + .../fixtures/sort/numeric_unfixed_floats.txt | 2 + .../sort/numeric_unsorted_ints.expected | 100 + .../fixtures/sort/numeric_unsorted_ints.txt | 100 + .../numeric_unsorted_ints_unique.expected | 4 + .../sort/numeric_unsorted_ints_unique.txt | 7 + .../tests/fixtures/sort/version.expected | 4 + coreutils/tests/fixtures/sort/version.txt | 4 + .../fixtures/sum/alice_in_wonderland.txt | 5 + .../fixtures/sum/bsd_multiple_files.expected | 2 + .../fixtures/sum/bsd_single_file.expected | 1 + .../tests/fixtures/sum/bsd_stdin.expected | 1 + coreutils/tests/fixtures/sum/lorem_ipsum.txt | 13 + .../fixtures/sum/sysv_multiple_files.expected | 2 + .../fixtures/sum/sysv_single_file.expected | 1 + .../tests/fixtures/sum/sysv_stdin.expected | 1 + .../fixtures/tac/delimited_primes.expected | 1 + .../tests/fixtures/tac/delimited_primes.txt | 1 + .../tac/delimited_primes_before.expected | 1 + .../fixtures/tac/prime_per_line.expected | 25 + .../tests/fixtures/tac/prime_per_line.txt | 25 + .../tests/fixtures/tail/follow_stdin.expected | 10 + coreutils/tests/fixtures/tail/foobar.txt | 11 + coreutils/tests/fixtures/tail/foobar2.txt | 2 + .../tail/foobar_bytes_single.expected | 2 + .../fixtures/tail/foobar_bytes_stdin.expected | 3 + .../tail/foobar_follow_multiple.expected | 15 + .../foobar_follow_multiple_appended.expected | 4 + .../tail/foobar_multiple_quiet.expected | 12 + .../tail/foobar_single_default.expected | 10 + .../tail/foobar_stdin_default.expected | 10 + .../tests/fixtures/tail/foobar_with_null.txt | Bin 0 -> 60 bytes .../tail/foobar_with_null_default.expected | Bin 0 -> 56 bytes .../tests/fixtures/tsort/call_graph.expected | 17 + coreutils/tests/fixtures/tsort/call_graph.txt | 22 + .../tests/fixtures/uniq/skip-1-char.expected | 9 + .../fixtures/uniq/skip-2-fields.expected | 2 + .../uniq/skip-3-check-2-chars.expected | 6 + .../uniq/skip-3-check-5-chars.expected | 7 + .../tests/fixtures/uniq/skip-5-chars.expected | 5 + coreutils/tests/fixtures/uniq/skip-chars.txt | 12 + coreutils/tests/fixtures/uniq/skip-fields.txt | 8 + .../uniq/sorted-all-repeated-prepend.expected | 22 + .../sorted-all-repeated-separate.expected | 21 + .../uniq/sorted-all-repeated.expected | 16 + .../fixtures/uniq/sorted-counts.expected | 8 + .../fixtures/uniq/sorted-ignore-case.expected | 7 + .../uniq/sorted-repeated-only.expected | 6 + .../fixtures/uniq/sorted-simple.expected | 8 + .../fixtures/uniq/sorted-unique-only.expected | 2 + .../uniq/sorted-zero-terminated.expected | Bin 0 -> 120 bytes .../fixtures/uniq/sorted-zero-terminated.txt | Bin 0 -> 270 bytes coreutils/tests/fixtures/uniq/sorted.txt | 18 + .../tests/fixtures/wc/alice_in_wonderland.txt | 5 + coreutils/tests/fixtures/wc/lorem_ipsum.txt | 13 + coreutils/tests/fixtures/wc/moby_dick.txt | 18 + coreutils/tests/test_base32.rs | 88 + coreutils/tests/test_base64.rs | 81 + coreutils/tests/test_basename.rs | 74 + coreutils/tests/test_cat.rs | 167 ++ coreutils/tests/test_chgrp.rs | 148 ++ coreutils/tests/test_chmod.rs | 149 ++ coreutils/tests/test_chown.rs | 51 + coreutils/tests/test_cksum.rs | 23 + coreutils/tests/test_comm.rs | 136 + coreutils/tests/test_cp.rs | 303 +++ coreutils/tests/test_cut.rs | 97 + coreutils/tests/test_dircolors.rs | 101 + coreutils/tests/test_dirname.rs | 29 + coreutils/tests/test_du.rs | 127 + coreutils/tests/test_echo.rs | 114 + coreutils/tests/test_env.rs | 136 + coreutils/tests/test_expr.rs | 43 + coreutils/tests/test_factor.rs | 1049 ++++++++ coreutils/tests/test_false.rs | 7 + coreutils/tests/test_fold.rs | 26 + coreutils/tests/test_hashsum.rs | 46 + coreutils/tests/test_head.rs | 70 + coreutils/tests/test_hostname.rs | 12 + coreutils/tests/test_install.rs | 196 ++ coreutils/tests/test_join.rs | 264 ++ coreutils/tests/test_link.rs | 40 + coreutils/tests/test_ln.rs | 382 +++ coreutils/tests/test_ls.rs | 13 + coreutils/tests/test_mkdir.rs | 59 + coreutils/tests/test_mktemp.rs | 110 + coreutils/tests/test_mv.rs | 438 ++++ coreutils/tests/test_nl.rs | 53 + coreutils/tests/test_numfmt.rs | 147 ++ coreutils/tests/test_od.rs | 662 +++++ coreutils/tests/test_paste.rs | 10 + coreutils/tests/test_pathchk.rs | 13 + coreutils/tests/test_pinky.rs | 52 + coreutils/tests/test_printf.rs | 279 ++ coreutils/tests/test_ptx.rs | 44 + coreutils/tests/test_pwd.rs | 8 + coreutils/tests/test_readlink.rs | 56 + coreutils/tests/test_realpath.rs | 23 + coreutils/tests/test_rm.rs | 175 ++ coreutils/tests/test_rmdir.rs | 103 + coreutils/tests/test_seq.rs | 26 + coreutils/tests/test_sort.rs | 130 + coreutils/tests/test_split.rs | 145 ++ coreutils/tests/test_stat.rs | 240 ++ coreutils/tests/test_stdbuf.rs | 12 + coreutils/tests/test_sum.rs | 48 + coreutils/tests/test_tac.rs | 46 + coreutils/tests/test_tail.rs | 263 ++ coreutils/tests/test_test.rs | 32 + coreutils/tests/test_touch.rs | 286 +++ coreutils/tests/test_tr.rs | 83 + coreutils/tests/test_true.rs | 7 + coreutils/tests/test_truncate.rs | 26 + coreutils/tests/test_tsort.rs | 17 + coreutils/tests/test_unexpand.rs | 112 + coreutils/tests/test_uniq.rs | 105 + coreutils/tests/test_unlink.rs | 49 + coreutils/tests/test_wc.rs | 50 + coreutils/tests/test_who.rs | 72 + coreutils/tests/tests.rs | 98 + coreutils/util/rewrite_rules.rs | 18 + coreutils/uumain.rs | 1 + 569 files changed, 51599 insertions(+) create mode 100644 coreutils/.appveyor.yml create mode 100644 coreutils/.busybox-config create mode 100644 coreutils/.cargo/config create mode 100644 coreutils/.cirrus.yml create mode 100644 coreutils/.codecov.yml create mode 100644 coreutils/.gitignore create mode 100644 coreutils/.travis.yml create mode 100755 coreutils/.travis/redox-toolchain.sh create mode 100644 coreutils/CONTRIBUTING.md create mode 100644 coreutils/Cargo.lock create mode 100644 coreutils/Cargo.toml create mode 100644 coreutils/GNUmakefile create mode 100644 coreutils/LICENSE create mode 100644 coreutils/Makefile create mode 100644 coreutils/README.md create mode 100644 coreutils/build.rs create mode 100644 coreutils/docs/GNUmakefile create mode 100644 coreutils/docs/Makefile create mode 100644 coreutils/docs/arch.rst create mode 100644 coreutils/docs/conf.py create mode 100644 coreutils/docs/index.rst create mode 100644 coreutils/docs/make.bat create mode 100644 coreutils/docs/uutils.rst create mode 100644 coreutils/mkmain.rs create mode 100644 coreutils/src/arch/Cargo.toml create mode 100644 coreutils/src/arch/arch.rs create mode 100644 coreutils/src/base32/Cargo.toml create mode 100644 coreutils/src/base32/base32.rs create mode 100644 coreutils/src/base64/Cargo.toml create mode 100644 coreutils/src/base64/base64.rs create mode 100644 coreutils/src/base64/base_common.rs create mode 100644 coreutils/src/basename/Cargo.toml create mode 100644 coreutils/src/basename/basename.rs create mode 100644 coreutils/src/cat/Cargo.toml create mode 100755 coreutils/src/cat/cat.rs create mode 100644 coreutils/src/chgrp/Cargo.toml create mode 100644 coreutils/src/chgrp/chgrp.rs create mode 100644 coreutils/src/chmod/Cargo.toml create mode 100644 coreutils/src/chmod/chmod.rs create mode 100644 coreutils/src/chown/Cargo.toml create mode 100644 coreutils/src/chown/chown.rs create mode 100644 coreutils/src/chroot/Cargo.toml create mode 100644 coreutils/src/chroot/chroot.rs create mode 100644 coreutils/src/cksum/Cargo.toml create mode 100644 coreutils/src/cksum/build.rs create mode 100644 coreutils/src/cksum/cksum.rs create mode 100644 coreutils/src/comm/Cargo.toml create mode 100644 coreutils/src/comm/comm.rs create mode 100644 coreutils/src/cp/Cargo.toml create mode 100644 coreutils/src/cp/README.md create mode 100644 coreutils/src/cp/cp.rs create mode 100644 coreutils/src/cut/.gitignore create mode 100644 coreutils/src/cut/Cargo.toml create mode 100644 coreutils/src/cut/buffer.rs create mode 100644 coreutils/src/cut/cut.rs create mode 100644 coreutils/src/cut/ranges.rs create mode 100644 coreutils/src/cut/searcher.rs create mode 100644 coreutils/src/date/Cargo.toml create mode 100644 coreutils/src/date/date.rs create mode 100644 coreutils/src/date/usage.txt create mode 100644 coreutils/src/dircolors/Cargo.toml create mode 100644 coreutils/src/dircolors/colors.rs create mode 100644 coreutils/src/dircolors/dircolors.rs create mode 100644 coreutils/src/dirname/Cargo.toml create mode 100644 coreutils/src/dirname/dirname.rs create mode 100644 coreutils/src/du/Cargo.toml create mode 100644 coreutils/src/du/du.rs create mode 100644 coreutils/src/echo/Cargo.toml create mode 100644 coreutils/src/echo/echo.rs create mode 100644 coreutils/src/env/Cargo.toml create mode 100644 coreutils/src/env/env.rs create mode 100644 coreutils/src/expand/Cargo.toml create mode 100644 coreutils/src/expand/expand.rs create mode 100644 coreutils/src/expr/Cargo.toml create mode 100644 coreutils/src/expr/expr.rs create mode 100644 coreutils/src/expr/syntax_tree.rs create mode 100644 coreutils/src/expr/tokens.rs create mode 100644 coreutils/src/factor/Cargo.toml create mode 100644 coreutils/src/factor/build.rs create mode 100644 coreutils/src/factor/factor.rs create mode 100644 coreutils/src/factor/numeric.rs create mode 100644 coreutils/src/factor/sieve.rs create mode 100644 coreutils/src/false/Cargo.toml create mode 100644 coreutils/src/false/false.rs create mode 100644 coreutils/src/fmt/Cargo.toml create mode 100644 coreutils/src/fmt/fmt.rs create mode 100644 coreutils/src/fmt/linebreak.rs create mode 100644 coreutils/src/fmt/parasplit.rs create mode 100644 coreutils/src/fold/Cargo.toml create mode 100644 coreutils/src/fold/fold.rs create mode 100644 coreutils/src/groups/Cargo.toml create mode 100644 coreutils/src/groups/groups.rs create mode 100644 coreutils/src/hashsum/Cargo.toml create mode 100644 coreutils/src/hashsum/digest.rs create mode 100644 coreutils/src/hashsum/hashsum.rs create mode 100644 coreutils/src/head/Cargo.toml create mode 100644 coreutils/src/head/head.rs create mode 100644 coreutils/src/hostid/Cargo.toml create mode 100644 coreutils/src/hostid/hostid.rs create mode 100644 coreutils/src/hostname/Cargo.toml create mode 100644 coreutils/src/hostname/hostname.rs create mode 100644 coreutils/src/id/Cargo.toml create mode 100644 coreutils/src/id/id.rs create mode 100644 coreutils/src/install/Cargo.toml create mode 100644 coreutils/src/install/install.rs create mode 100644 coreutils/src/install/mode.rs create mode 100644 coreutils/src/join/Cargo.toml create mode 100755 coreutils/src/join/join.rs create mode 100644 coreutils/src/kill/Cargo.toml create mode 100644 coreutils/src/kill/kill.rs create mode 100644 coreutils/src/link/Cargo.toml create mode 100644 coreutils/src/link/link.rs create mode 100644 coreutils/src/ln/Cargo.toml create mode 100644 coreutils/src/ln/ln.rs create mode 100644 coreutils/src/logname/Cargo.toml create mode 100644 coreutils/src/logname/logname.rs create mode 100644 coreutils/src/ls/Cargo.toml create mode 100644 coreutils/src/ls/ls.rs create mode 100644 coreutils/src/mkdir/Cargo.toml create mode 100644 coreutils/src/mkdir/mkdir.rs create mode 100644 coreutils/src/mkfifo/Cargo.toml create mode 100644 coreutils/src/mkfifo/mkfifo.rs create mode 100644 coreutils/src/mknod/Cargo.toml create mode 100644 coreutils/src/mknod/mknod.rs create mode 100644 coreutils/src/mknod/parsemode.rs create mode 100644 coreutils/src/mktemp/Cargo.toml create mode 100644 coreutils/src/mktemp/mktemp.rs create mode 100644 coreutils/src/mktemp/tempdir.rs create mode 100644 coreutils/src/more/Cargo.toml create mode 100644 coreutils/src/more/more.rs create mode 100644 coreutils/src/mv/Cargo.toml create mode 100644 coreutils/src/mv/mv.rs create mode 100644 coreutils/src/nice/Cargo.toml create mode 100644 coreutils/src/nice/nice.rs create mode 100644 coreutils/src/nl/Cargo.toml create mode 100644 coreutils/src/nl/helper.rs create mode 100644 coreutils/src/nl/nl.rs create mode 100644 coreutils/src/nohup/Cargo.toml create mode 100644 coreutils/src/nohup/nohup.rs create mode 100644 coreutils/src/nproc/Cargo.toml create mode 100644 coreutils/src/nproc/nproc.rs create mode 100644 coreutils/src/numfmt/Cargo.toml create mode 100644 coreutils/src/numfmt/numfmt.rs create mode 100644 coreutils/src/od/Cargo.toml create mode 100644 coreutils/src/od/byteorder_io.rs create mode 100644 coreutils/src/od/formatteriteminfo.rs create mode 100644 coreutils/src/od/inputdecoder.rs create mode 100644 coreutils/src/od/inputoffset.rs create mode 100644 coreutils/src/od/mockstream.rs create mode 100644 coreutils/src/od/multifilereader.rs create mode 100644 coreutils/src/od/od.rs create mode 100644 coreutils/src/od/output_info.rs create mode 100644 coreutils/src/od/parse_formats.rs create mode 100644 coreutils/src/od/parse_inputs.rs create mode 100644 coreutils/src/od/parse_nrofbytes.rs create mode 100644 coreutils/src/od/partialreader.rs create mode 100644 coreutils/src/od/peekreader.rs create mode 100644 coreutils/src/od/prn_char.rs create mode 100644 coreutils/src/od/prn_float.rs create mode 100644 coreutils/src/od/prn_int.rs create mode 100644 coreutils/src/paste/Cargo.toml create mode 100644 coreutils/src/paste/paste.rs create mode 100644 coreutils/src/pathchk/Cargo.toml create mode 100644 coreutils/src/pathchk/pathchk.rs create mode 100644 coreutils/src/pinky/Cargo.toml create mode 100644 coreutils/src/pinky/pinky.rs create mode 100644 coreutils/src/printenv/Cargo.toml create mode 100644 coreutils/src/printenv/printenv.rs create mode 100644 coreutils/src/printf/Cargo.toml create mode 100644 coreutils/src/printf/cli.rs create mode 100644 coreutils/src/printf/memo.rs create mode 100644 coreutils/src/printf/mod.rs create mode 100644 coreutils/src/printf/printf.rs create mode 100644 coreutils/src/printf/tokenize/mod.rs create mode 100644 coreutils/src/printf/tokenize/num_format/format_field.rs create mode 100644 coreutils/src/printf/tokenize/num_format/formatter.rs create mode 100644 coreutils/src/printf/tokenize/num_format/formatters/base_conv/mod.rs create mode 100644 coreutils/src/printf/tokenize/num_format/formatters/base_conv/tests.rs create mode 100644 coreutils/src/printf/tokenize/num_format/formatters/cninetyninehexfloatf.rs create mode 100644 coreutils/src/printf/tokenize/num_format/formatters/decf.rs create mode 100644 coreutils/src/printf/tokenize/num_format/formatters/float_common.rs create mode 100644 coreutils/src/printf/tokenize/num_format/formatters/floatf.rs create mode 100644 coreutils/src/printf/tokenize/num_format/formatters/intf.rs create mode 100644 coreutils/src/printf/tokenize/num_format/formatters/mod.rs create mode 100644 coreutils/src/printf/tokenize/num_format/formatters/scif.rs create mode 100644 coreutils/src/printf/tokenize/num_format/mod.rs create mode 100644 coreutils/src/printf/tokenize/num_format/num_format.rs create mode 100644 coreutils/src/printf/tokenize/sub.rs create mode 100644 coreutils/src/printf/tokenize/token.rs create mode 100644 coreutils/src/printf/tokenize/unescaped_text.rs create mode 100644 coreutils/src/ptx/Cargo.toml create mode 100644 coreutils/src/ptx/ptx.rs create mode 100644 coreutils/src/pwd/Cargo.toml create mode 100644 coreutils/src/pwd/pwd.rs create mode 100644 coreutils/src/readlink/Cargo.toml create mode 100644 coreutils/src/readlink/readlink.rs create mode 100644 coreutils/src/realpath/Cargo.toml create mode 100644 coreutils/src/realpath/realpath.rs create mode 100644 coreutils/src/relpath/Cargo.toml create mode 100644 coreutils/src/relpath/relpath.rs create mode 100644 coreutils/src/rm/Cargo.toml create mode 100644 coreutils/src/rm/rm.rs create mode 100644 coreutils/src/rmdir/Cargo.toml create mode 100644 coreutils/src/rmdir/rmdir.rs create mode 100644 coreutils/src/seq/Cargo.toml create mode 100644 coreutils/src/seq/seq.rs create mode 100644 coreutils/src/shred/Cargo.toml create mode 100644 coreutils/src/shred/shred.rs create mode 100644 coreutils/src/shuf/Cargo.toml create mode 100644 coreutils/src/shuf/shuf.rs create mode 100644 coreutils/src/sleep/Cargo.toml create mode 100644 coreutils/src/sleep/sleep.rs create mode 100644 coreutils/src/sort/Cargo.toml create mode 100644 coreutils/src/sort/sort.rs create mode 100644 coreutils/src/split/Cargo.toml create mode 100644 coreutils/src/split/README.md create mode 100644 coreutils/src/split/split.rs create mode 100644 coreutils/src/stat/Cargo.toml create mode 100644 coreutils/src/stat/fsext.rs create mode 100644 coreutils/src/stat/stat.rs create mode 100644 coreutils/src/stat/test_stat.rs create mode 100644 coreutils/src/stdbuf/Cargo.toml create mode 100644 coreutils/src/stdbuf/build.rs create mode 100644 coreutils/src/stdbuf/libstdbuf/Cargo.toml create mode 100644 coreutils/src/stdbuf/libstdbuf/build.rs create mode 100644 coreutils/src/stdbuf/libstdbuf/libstdbuf.rs create mode 100644 coreutils/src/stdbuf/stdbuf.rs create mode 100644 coreutils/src/sum/Cargo.toml create mode 100644 coreutils/src/sum/sum.rs create mode 100644 coreutils/src/sync/Cargo.toml create mode 100644 coreutils/src/sync/sync.rs create mode 100644 coreutils/src/tac/Cargo.toml create mode 100644 coreutils/src/tac/tac.rs create mode 100644 coreutils/src/tail/Cargo.toml create mode 100644 coreutils/src/tail/README.md create mode 100644 coreutils/src/tail/platform/mod.rs create mode 100644 coreutils/src/tail/platform/redox.rs create mode 100644 coreutils/src/tail/platform/sunrise.rs create mode 100644 coreutils/src/tail/platform/unix.rs create mode 100644 coreutils/src/tail/platform/windows.rs create mode 100755 coreutils/src/tail/tail.rs create mode 100644 coreutils/src/tee/Cargo.toml create mode 100644 coreutils/src/tee/tee.rs create mode 100644 coreutils/src/test/Cargo.toml create mode 100644 coreutils/src/test/test.rs create mode 100644 coreutils/src/timeout/Cargo.toml create mode 100644 coreutils/src/timeout/timeout.rs create mode 100644 coreutils/src/touch/Cargo.toml create mode 100644 coreutils/src/touch/touch.rs create mode 100644 coreutils/src/tr/Cargo.toml create mode 100644 coreutils/src/tr/expand.rs create mode 100644 coreutils/src/tr/tr.rs create mode 100644 coreutils/src/true/Cargo.toml create mode 100644 coreutils/src/true/true.rs create mode 100644 coreutils/src/truncate/Cargo.toml create mode 100644 coreutils/src/truncate/truncate.rs create mode 100644 coreutils/src/tsort/Cargo.toml create mode 100644 coreutils/src/tsort/tsort.rs create mode 100644 coreutils/src/tty/Cargo.toml create mode 100644 coreutils/src/tty/tty.rs create mode 100644 coreutils/src/uname/Cargo.toml create mode 100644 coreutils/src/uname/uname.rs create mode 100644 coreutils/src/unexpand/Cargo.toml create mode 100644 coreutils/src/unexpand/unexpand.rs create mode 100644 coreutils/src/uniq/Cargo.toml create mode 100644 coreutils/src/uniq/uniq.rs create mode 100644 coreutils/src/unlink/Cargo.toml create mode 100644 coreutils/src/unlink/unlink.rs create mode 100644 coreutils/src/uptime/Cargo.toml create mode 100644 coreutils/src/uptime/uptime.rs create mode 100644 coreutils/src/users/Cargo.toml create mode 100644 coreutils/src/users/users.rs create mode 100644 coreutils/src/uutils/uutils.rs create mode 100644 coreutils/src/wc/Cargo.toml create mode 100644 coreutils/src/wc/wc.rs create mode 100644 coreutils/src/who/Cargo.toml create mode 100644 coreutils/src/who/who.rs create mode 100644 coreutils/src/whoami/Cargo.toml create mode 100644 coreutils/src/whoami/platform/mod.rs create mode 100644 coreutils/src/whoami/platform/unix.rs create mode 100644 coreutils/src/whoami/platform/windows.rs create mode 100644 coreutils/src/whoami/whoami.rs create mode 100644 coreutils/src/yes/Cargo.toml create mode 100644 coreutils/src/yes/yes.rs create mode 100644 coreutils/tests/common/macros.rs create mode 100644 coreutils/tests/common/mod.rs create mode 100644 coreutils/tests/common/util.rs create mode 100644 coreutils/tests/fixtures/.gitattributes create mode 100644 coreutils/tests/fixtures/cat/256.txt create mode 100644 coreutils/tests/fixtures/cat/alpha.txt create mode 100644 coreutils/tests/fixtures/cat/nonewline.txt create mode 100644 coreutils/tests/fixtures/cksum/alice_in_wonderland.txt create mode 100644 coreutils/tests/fixtures/cksum/lorem_ipsum.txt create mode 100644 coreutils/tests/fixtures/cksum/multiple_files.expected create mode 100644 coreutils/tests/fixtures/cksum/single_file.expected create mode 100644 coreutils/tests/fixtures/cksum/stdin.expected create mode 100644 coreutils/tests/fixtures/comm/a create mode 100644 coreutils/tests/fixtures/comm/ab.expected create mode 100644 coreutils/tests/fixtures/comm/ab1.expected create mode 100644 coreutils/tests/fixtures/comm/ab2.expected create mode 100644 coreutils/tests/fixtures/comm/ab3.expected create mode 100644 coreutils/tests/fixtures/comm/ab_delimiter_word.expected create mode 100644 coreutils/tests/fixtures/comm/aempty.expected create mode 100644 coreutils/tests/fixtures/comm/b create mode 100644 coreutils/tests/fixtures/comm/bad_order11.defaultcheck_order.expected create mode 100644 coreutils/tests/fixtures/comm/bad_order12.check_order.expected create mode 100644 coreutils/tests/fixtures/comm/bad_order12.nocheck_order.expected create mode 100644 coreutils/tests/fixtures/comm/bad_order_1 create mode 100644 coreutils/tests/fixtures/comm/bad_order_2 create mode 100644 coreutils/tests/fixtures/comm/defaultcheck_unintuitive.expected create mode 100644 coreutils/tests/fixtures/comm/defaultcheck_unintuitive_1 create mode 100644 coreutils/tests/fixtures/comm/defaultcheck_unintuitive_2 create mode 100644 coreutils/tests/fixtures/comm/empty create mode 100644 coreutils/tests/fixtures/comm/emptyempty.expected create mode 100644 coreutils/tests/fixtures/comm/lowercase_uppercase create mode 100644 coreutils/tests/fixtures/comm/lowercase_uppercase.c.expected create mode 100644 coreutils/tests/fixtures/comm/lowercase_uppercase.en_us.expected create mode 100644 coreutils/tests/fixtures/cp/existing_file.txt create mode 100644 coreutils/tests/fixtures/cp/hello_dir/hello.txt create mode 100644 coreutils/tests/fixtures/cp/hello_dir_with_file/hello_world.txt create mode 100644 coreutils/tests/fixtures/cp/hello_world.txt create mode 100644 coreutils/tests/fixtures/cp/how_are_you.txt create mode 100644 coreutils/tests/fixtures/cut/delimiter_specified.expected create mode 100644 coreutils/tests/fixtures/cut/lists.txt create mode 100644 coreutils/tests/fixtures/cut/output_delimiter.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/byte_aggregate.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/byte_prefix.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/byte_range.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/byte_singular.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/byte_subsumed.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/byte_suffix.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/field_aggregate.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/field_prefix.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/field_range.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/field_singular.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/field_subsumed.expected create mode 100644 coreutils/tests/fixtures/cut/sequences/field_suffix.expected create mode 100644 coreutils/tests/fixtures/dircolors/bash_def.expected create mode 100644 coreutils/tests/fixtures/dircolors/csh_def.expected create mode 100644 coreutils/tests/fixtures/dircolors/internal.expected create mode 100644 coreutils/tests/fixtures/dircolors/keywords.csh.expected create mode 100644 coreutils/tests/fixtures/dircolors/keywords.sh.expected create mode 100644 coreutils/tests/fixtures/dircolors/keywords.txt create mode 100644 coreutils/tests/fixtures/dircolors/test1.csh.expected create mode 100644 coreutils/tests/fixtures/dircolors/test1.sh.expected create mode 100644 coreutils/tests/fixtures/dircolors/test1.txt create mode 100644 coreutils/tests/fixtures/du/subdir/deeper/words.txt create mode 100644 coreutils/tests/fixtures/du/subdir/links/subwords.txt create mode 100644 coreutils/tests/fixtures/du/subdir/links/subwords2.txt create mode 100644 coreutils/tests/fixtures/du/words.txt create mode 100644 coreutils/tests/fixtures/env/vars.conf.txt create mode 100644 coreutils/tests/fixtures/fold/lorem_ipsum.txt create mode 100644 coreutils/tests/fixtures/fold/lorem_ipsum_40_column_hard.expected create mode 100644 coreutils/tests/fixtures/fold/lorem_ipsum_40_column_word.expected create mode 100644 coreutils/tests/fixtures/fold/lorem_ipsum_80_column.expected create mode 100644 coreutils/tests/fixtures/hashsum/input.txt create mode 100644 coreutils/tests/fixtures/hashsum/md5.expected create mode 100644 coreutils/tests/fixtures/hashsum/sha1.expected create mode 100644 coreutils/tests/fixtures/hashsum/sha224.expected create mode 100644 coreutils/tests/fixtures/hashsum/sha256.expected create mode 100644 coreutils/tests/fixtures/hashsum/sha384.expected create mode 100644 coreutils/tests/fixtures/hashsum/sha3_224.expected create mode 100644 coreutils/tests/fixtures/hashsum/sha3_256.expected create mode 100644 coreutils/tests/fixtures/hashsum/sha3_384.expected create mode 100644 coreutils/tests/fixtures/hashsum/sha3_512.expected create mode 100644 coreutils/tests/fixtures/hashsum/sha512.expected create mode 100644 coreutils/tests/fixtures/hashsum/shake128_256.expected create mode 100644 coreutils/tests/fixtures/hashsum/shake256_512.expected create mode 100644 coreutils/tests/fixtures/head/lorem_ipsum.txt create mode 100644 coreutils/tests/fixtures/head/lorem_ipsum_1_line.expected create mode 100644 coreutils/tests/fixtures/head/lorem_ipsum_5_chars.expected create mode 100644 coreutils/tests/fixtures/head/lorem_ipsum_default.expected create mode 100644 coreutils/tests/fixtures/head/lorem_ipsum_verbose.expected create mode 100644 coreutils/tests/fixtures/join/autoformat.expected create mode 100644 coreutils/tests/fixtures/join/capitalized.txt create mode 100644 coreutils/tests/fixtures/join/case_insensitive.expected create mode 100644 coreutils/tests/fixtures/join/default.expected create mode 100644 coreutils/tests/fixtures/join/different_field.expected create mode 100644 coreutils/tests/fixtures/join/different_fields.expected create mode 100644 coreutils/tests/fixtures/join/different_lengths.txt create mode 100644 coreutils/tests/fixtures/join/empty.txt create mode 100644 coreutils/tests/fixtures/join/empty_key.expected create mode 100644 coreutils/tests/fixtures/join/fields_1.txt create mode 100644 coreutils/tests/fixtures/join/fields_2.txt create mode 100644 coreutils/tests/fixtures/join/fields_3.txt create mode 100644 coreutils/tests/fixtures/join/fields_4.txt create mode 100644 coreutils/tests/fixtures/join/header.expected create mode 100644 coreutils/tests/fixtures/join/header_1.txt create mode 100644 coreutils/tests/fixtures/join/header_2.txt create mode 100644 coreutils/tests/fixtures/join/header_autoformat.expected create mode 100644 coreutils/tests/fixtures/join/missing_format_fields.expected create mode 100644 coreutils/tests/fixtures/join/semicolon_fields_1.txt create mode 100644 coreutils/tests/fixtures/join/semicolon_fields_2.txt create mode 100644 coreutils/tests/fixtures/join/semicolon_separated.expected create mode 100644 coreutils/tests/fixtures/join/suppress_joined.expected create mode 100644 coreutils/tests/fixtures/join/unpaired_lines.expected create mode 100644 coreutils/tests/fixtures/join/unpaired_lines_format.expected create mode 100644 coreutils/tests/fixtures/mv/hello_world.txt create mode 100644 coreutils/tests/fixtures/nl/joinblanklines.txt create mode 100644 coreutils/tests/fixtures/nl/section.txt create mode 100644 coreutils/tests/fixtures/nl/simple.txt create mode 100644 coreutils/tests/fixtures/od/-f create mode 100644 coreutils/tests/fixtures/od/0 create mode 100644 coreutils/tests/fixtures/od/c create mode 100644 coreutils/tests/fixtures/od/x create mode 100644 coreutils/tests/fixtures/paste/html_colors.expected create mode 100644 coreutils/tests/fixtures/paste/html_colors.txt create mode 100644 coreutils/tests/fixtures/ptx/gnu_ext_disabled_ignore_and_only_file.expected create mode 100644 coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_auto_ref.expected create mode 100644 coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_input_ref.expected create mode 100644 coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_no_ref.expected create mode 100644 coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_auto_ref.expected create mode 100644 coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_input_ref.expected create mode 100644 coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_no_ref.expected create mode 100644 coreutils/tests/fixtures/ptx/ignore create mode 100644 coreutils/tests/fixtures/ptx/input create mode 100644 coreutils/tests/fixtures/ptx/only create mode 100644 coreutils/tests/fixtures/sort/check_fail.txt create mode 100644 coreutils/tests/fixtures/sort/default_unsorted_ints.expected create mode 100644 coreutils/tests/fixtures/sort/default_unsorted_ints.txt create mode 100644 coreutils/tests/fixtures/sort/human_block_sizes.expected create mode 100644 coreutils/tests/fixtures/sort/human_block_sizes.txt create mode 100644 coreutils/tests/fixtures/sort/ignore_case.expected create mode 100644 coreutils/tests/fixtures/sort/ignore_case.txt create mode 100644 coreutils/tests/fixtures/sort/merge_ints_interleaved.expected create mode 100644 coreutils/tests/fixtures/sort/merge_ints_interleaved_1.txt create mode 100644 coreutils/tests/fixtures/sort/merge_ints_interleaved_2.txt create mode 100644 coreutils/tests/fixtures/sort/merge_ints_interleaved_3.txt create mode 100644 coreutils/tests/fixtures/sort/merge_ints_reversed.expected create mode 100644 coreutils/tests/fixtures/sort/merge_ints_reversed_1.txt create mode 100644 coreutils/tests/fixtures/sort/merge_ints_reversed_2.txt create mode 100644 coreutils/tests/fixtures/sort/merge_ints_reversed_3.txt create mode 100644 coreutils/tests/fixtures/sort/month_default.expected create mode 100644 coreutils/tests/fixtures/sort/month_default.txt create mode 100644 coreutils/tests/fixtures/sort/month_stable.expected create mode 100644 coreutils/tests/fixtures/sort/month_stable.txt create mode 100644 coreutils/tests/fixtures/sort/multiple_files.expected create mode 100644 coreutils/tests/fixtures/sort/multiple_files1.txt create mode 100644 coreutils/tests/fixtures/sort/multiple_files2.txt create mode 100644 coreutils/tests/fixtures/sort/numeric_fixed_floats.expected create mode 100644 coreutils/tests/fixtures/sort/numeric_fixed_floats.txt create mode 100644 coreutils/tests/fixtures/sort/numeric_floats.expected create mode 100644 coreutils/tests/fixtures/sort/numeric_floats.txt create mode 100644 coreutils/tests/fixtures/sort/numeric_floats_and_ints.expected create mode 100644 coreutils/tests/fixtures/sort/numeric_floats_and_ints.txt create mode 100644 coreutils/tests/fixtures/sort/numeric_floats_with_nan.expected create mode 100644 coreutils/tests/fixtures/sort/numeric_floats_with_nan.txt create mode 100644 coreutils/tests/fixtures/sort/numeric_unfixed_floats.expected create mode 100644 coreutils/tests/fixtures/sort/numeric_unfixed_floats.txt create mode 100644 coreutils/tests/fixtures/sort/numeric_unsorted_ints.expected create mode 100644 coreutils/tests/fixtures/sort/numeric_unsorted_ints.txt create mode 100644 coreutils/tests/fixtures/sort/numeric_unsorted_ints_unique.expected create mode 100644 coreutils/tests/fixtures/sort/numeric_unsorted_ints_unique.txt create mode 100644 coreutils/tests/fixtures/sort/version.expected create mode 100644 coreutils/tests/fixtures/sort/version.txt create mode 100644 coreutils/tests/fixtures/sum/alice_in_wonderland.txt create mode 100644 coreutils/tests/fixtures/sum/bsd_multiple_files.expected create mode 100644 coreutils/tests/fixtures/sum/bsd_single_file.expected create mode 100644 coreutils/tests/fixtures/sum/bsd_stdin.expected create mode 100644 coreutils/tests/fixtures/sum/lorem_ipsum.txt create mode 100644 coreutils/tests/fixtures/sum/sysv_multiple_files.expected create mode 100644 coreutils/tests/fixtures/sum/sysv_single_file.expected create mode 100644 coreutils/tests/fixtures/sum/sysv_stdin.expected create mode 100644 coreutils/tests/fixtures/tac/delimited_primes.expected create mode 100644 coreutils/tests/fixtures/tac/delimited_primes.txt create mode 100644 coreutils/tests/fixtures/tac/delimited_primes_before.expected create mode 100644 coreutils/tests/fixtures/tac/prime_per_line.expected create mode 100644 coreutils/tests/fixtures/tac/prime_per_line.txt create mode 100644 coreutils/tests/fixtures/tail/follow_stdin.expected create mode 100644 coreutils/tests/fixtures/tail/foobar.txt create mode 100644 coreutils/tests/fixtures/tail/foobar2.txt create mode 100644 coreutils/tests/fixtures/tail/foobar_bytes_single.expected create mode 100644 coreutils/tests/fixtures/tail/foobar_bytes_stdin.expected create mode 100644 coreutils/tests/fixtures/tail/foobar_follow_multiple.expected create mode 100644 coreutils/tests/fixtures/tail/foobar_follow_multiple_appended.expected create mode 100644 coreutils/tests/fixtures/tail/foobar_multiple_quiet.expected create mode 100644 coreutils/tests/fixtures/tail/foobar_single_default.expected create mode 100644 coreutils/tests/fixtures/tail/foobar_stdin_default.expected create mode 100644 coreutils/tests/fixtures/tail/foobar_with_null.txt create mode 100644 coreutils/tests/fixtures/tail/foobar_with_null_default.expected create mode 100644 coreutils/tests/fixtures/tsort/call_graph.expected create mode 100644 coreutils/tests/fixtures/tsort/call_graph.txt create mode 100644 coreutils/tests/fixtures/uniq/skip-1-char.expected create mode 100644 coreutils/tests/fixtures/uniq/skip-2-fields.expected create mode 100644 coreutils/tests/fixtures/uniq/skip-3-check-2-chars.expected create mode 100644 coreutils/tests/fixtures/uniq/skip-3-check-5-chars.expected create mode 100644 coreutils/tests/fixtures/uniq/skip-5-chars.expected create mode 100644 coreutils/tests/fixtures/uniq/skip-chars.txt create mode 100644 coreutils/tests/fixtures/uniq/skip-fields.txt create mode 100644 coreutils/tests/fixtures/uniq/sorted-all-repeated-prepend.expected create mode 100644 coreutils/tests/fixtures/uniq/sorted-all-repeated-separate.expected create mode 100644 coreutils/tests/fixtures/uniq/sorted-all-repeated.expected create mode 100644 coreutils/tests/fixtures/uniq/sorted-counts.expected create mode 100644 coreutils/tests/fixtures/uniq/sorted-ignore-case.expected create mode 100644 coreutils/tests/fixtures/uniq/sorted-repeated-only.expected create mode 100644 coreutils/tests/fixtures/uniq/sorted-simple.expected create mode 100644 coreutils/tests/fixtures/uniq/sorted-unique-only.expected create mode 100644 coreutils/tests/fixtures/uniq/sorted-zero-terminated.expected create mode 100644 coreutils/tests/fixtures/uniq/sorted-zero-terminated.txt create mode 100644 coreutils/tests/fixtures/uniq/sorted.txt create mode 100644 coreutils/tests/fixtures/wc/alice_in_wonderland.txt create mode 100644 coreutils/tests/fixtures/wc/lorem_ipsum.txt create mode 100644 coreutils/tests/fixtures/wc/moby_dick.txt create mode 100644 coreutils/tests/test_base32.rs create mode 100644 coreutils/tests/test_base64.rs create mode 100644 coreutils/tests/test_basename.rs create mode 100644 coreutils/tests/test_cat.rs create mode 100644 coreutils/tests/test_chgrp.rs create mode 100644 coreutils/tests/test_chmod.rs create mode 100644 coreutils/tests/test_chown.rs create mode 100644 coreutils/tests/test_cksum.rs create mode 100644 coreutils/tests/test_comm.rs create mode 100644 coreutils/tests/test_cp.rs create mode 100644 coreutils/tests/test_cut.rs create mode 100644 coreutils/tests/test_dircolors.rs create mode 100644 coreutils/tests/test_dirname.rs create mode 100644 coreutils/tests/test_du.rs create mode 100644 coreutils/tests/test_echo.rs create mode 100644 coreutils/tests/test_env.rs create mode 100644 coreutils/tests/test_expr.rs create mode 100644 coreutils/tests/test_factor.rs create mode 100644 coreutils/tests/test_false.rs create mode 100644 coreutils/tests/test_fold.rs create mode 100644 coreutils/tests/test_hashsum.rs create mode 100644 coreutils/tests/test_head.rs create mode 100644 coreutils/tests/test_hostname.rs create mode 100644 coreutils/tests/test_install.rs create mode 100644 coreutils/tests/test_join.rs create mode 100644 coreutils/tests/test_link.rs create mode 100644 coreutils/tests/test_ln.rs create mode 100644 coreutils/tests/test_ls.rs create mode 100644 coreutils/tests/test_mkdir.rs create mode 100644 coreutils/tests/test_mktemp.rs create mode 100644 coreutils/tests/test_mv.rs create mode 100644 coreutils/tests/test_nl.rs create mode 100644 coreutils/tests/test_numfmt.rs create mode 100644 coreutils/tests/test_od.rs create mode 100644 coreutils/tests/test_paste.rs create mode 100644 coreutils/tests/test_pathchk.rs create mode 100644 coreutils/tests/test_pinky.rs create mode 100644 coreutils/tests/test_printf.rs create mode 100644 coreutils/tests/test_ptx.rs create mode 100644 coreutils/tests/test_pwd.rs create mode 100644 coreutils/tests/test_readlink.rs create mode 100644 coreutils/tests/test_realpath.rs create mode 100644 coreutils/tests/test_rm.rs create mode 100644 coreutils/tests/test_rmdir.rs create mode 100644 coreutils/tests/test_seq.rs create mode 100644 coreutils/tests/test_sort.rs create mode 100644 coreutils/tests/test_split.rs create mode 100644 coreutils/tests/test_stat.rs create mode 100644 coreutils/tests/test_stdbuf.rs create mode 100644 coreutils/tests/test_sum.rs create mode 100644 coreutils/tests/test_tac.rs create mode 100644 coreutils/tests/test_tail.rs create mode 100644 coreutils/tests/test_test.rs create mode 100644 coreutils/tests/test_touch.rs create mode 100644 coreutils/tests/test_tr.rs create mode 100644 coreutils/tests/test_true.rs create mode 100644 coreutils/tests/test_truncate.rs create mode 100644 coreutils/tests/test_tsort.rs create mode 100644 coreutils/tests/test_unexpand.rs create mode 100644 coreutils/tests/test_uniq.rs create mode 100644 coreutils/tests/test_unlink.rs create mode 100644 coreutils/tests/test_wc.rs create mode 100644 coreutils/tests/test_who.rs create mode 100644 coreutils/tests/tests.rs create mode 100644 coreutils/util/rewrite_rules.rs create mode 100644 coreutils/uumain.rs diff --git a/coreutils/.appveyor.yml b/coreutils/.appveyor.yml new file mode 100644 index 000000000..eb43e1835 --- /dev/null +++ b/coreutils/.appveyor.yml @@ -0,0 +1,164 @@ +# spell-checker:words POSIX repo SDK SDKs toolchain toolchains +# spell-checker:ignore uutils ARCH ABI BACKTRACE BINDIR cl COMNTOOLS dllcrt findstr maint MINGW MINGWDIR mkdir MSVC MSYS rustc rustlib rustup USERPROFILE vcvarsall + +version: "{build} ~ {branch}" + +branches: + except: + - gh-pages + +os: Visual Studio 2015 + +artifacts: + - path: target\%TARGET%\debug\uutils.exe + name: uutils.exe + +matrix: + allow_failures: + - CHANNEL: nightly +# - ABI: gnu + +environment: + global: + FEATURES: "windows" + BUILD_OPTIONS: "--no-default-features" + TEST_OPTIONS: "--no-default-features --no-fail-fast" + + matrix: + # minimum version + - CHANNEL: 1.31.0 + ARCH: i686 + ABI: msvc + # "msvc" ABI + - CHANNEL: stable + ARCH: i686 + ABI: msvc + - CHANNEL: stable + ARCH: x86_64 + ABI: msvc +# - CHANNEL: beta +# ARCH: i686 +# ABI: msvc +# - CHANNEL: beta +# ARCH: x86_64 +# ABI: msvc + - CHANNEL: nightly + ARCH: i686 + ABI: msvc + FEATURES: "windows nightly" + - CHANNEL: nightly + ARCH: x86_64 + ABI: msvc + FEATURES: "windows nightly" + # "gnu" ABI + - CHANNEL: stable + ARCH: i686 + ABI: gnu + - CHANNEL: stable + ARCH: x86_64 + ABI: gnu +# - CHANNEL: beta +# ARCH: i686 +# ABI: gnu +# - CHANNEL: beta +# ARCH: x86_64 +# ABI: gnu +# - CHANNEL: nightly +# ARCH: i686 +# ABI: gnu +# - CHANNEL: nightly +# ARCH: x86_64 +# ABI: gnu + # * specific gnu compilers + - CHANNEL: stable + ARCH: i686 + ABI: gnu + MINGW_URL: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/4.9.2/threads-win32/dwarf/i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z/download + MINGW_ARCHIVE: i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z + - CHANNEL: stable + ARCH: x86_64 + ABI: gnu + MINGW_URL: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.3.0/threads-posix/seh/x86_64-7.3.0-release-posix-seh-rt_v5-rev0.7z/download#mingw-w64-x86_64-7.3.0-posix-seh.7z + +install: + # force branch checkout (if knowable), then reset to the specific commit ## (can be needed for accurate code coverage info) + # * this allows later apps to see the branch name using standard `git branch` operations, yet always builds the correct specific commit + # * ref: [`@`](https://archive.is/RVpnF) + - if DEFINED APPVEYOR_REPO_BRANCH if /I "%APPVEYOR_REPO_SCM%"=="git" ( git checkout "%APPVEYOR_REPO_BRANCH%" 2>NUL & git reset --hard "%APPVEYOR_REPO_COMMIT%" ) + # ensure CWD is project main directory + - cd "%APPVEYOR_BUILD_FOLDER%" + # create a working area + - ps: if ( ! $env:CI_TEMP_DIR ) { $env:CI_TEMP_DIR = "${env:TEMP}\${env:APPVEYOR_JOB_ID}" ; mkdir -force $env:CI_TEMP_DIR | out-null } + + # define the TARGET host + - set "TARGET=%ARCH%-pc-windows-%ABI%" + + # show relevant environment settings + - ps: ('CHANNEL', 'ARCH', 'ABI', 'FEATURES', 'BUILD_OPTIONS', 'TEST_OPTIONS', 'TARGET') |% { write-host -f yellow "$_=$(get-content -ea silent env:/$_)" } + + # rust installation + # * install `rust` via `rustup` + - appveyor DownloadFile "https://win.rustup.rs/" -FileName "%CI_TEMP_DIR%\rustup-init.exe" + - call "%CI_TEMP_DIR%\rustup-init.exe" -y --default-toolchain %CHANNEL% --default-host %TARGET% --no-modify-path >NUL + - set "PATH=%PATH%;%USERPROFILE%\.cargo\bin" + - ps: $env:TOOLCHAIN = $("$(rustup show active-toolchain)" -split '\s+')[0] + # * set RUST_BACKTRACE for enhanced error messages + - set RUST_BACKTRACE=1 + # * show rust versions + - rustc -vV + - cargo -vV + + # "gnu" ABI setup + # * use the system MinGW/MSYS if we can + - if /i "%ABI%"=="gnu" set MSYS_BINDIR=C:\msys64\usr\bin + - if /i "%ABI%"=="gnu" if /i "%ARCH%"=="i686" set "MSYS_BITS=32" + - if /i "%ABI%"=="gnu" if /i "%ARCH%"=="x86_64" set "MSYS_BITS=64" + - if defined MSYS_BITS set "MSYS_MINGWDIR=C:\msys64\mingw%MSYS_BITS%" + - if defined MSYS_MINGWDIR set "MSYS_BINDIR=C:\msys64\usr\bin" + ## * workaround for rust-lang/rust#47048 / rust-lang/rust#53454 ## !maint: remove when resolved + - if /i "%ABI%"=="gnu" if /i "%ARCH%"=="i686" if not DEFINED MINGW_URL set "MINGW_URL=https://sourceforge.net/projects/mingw-w64/files/Toolchains targetting Win32/Personal Builds/mingw-builds/8.1.0/threads-posix/dwarf/i686-8.1.0-release-posix-dwarf-rt_v6-rev0.7z" + - if /i "%ABI%"=="gnu" if /i "%ARCH%"=="x86_64" if not DEFINED MINGW_URL set "MINGW_URL=https://sourceforge.net/projects/mingw-w64/files/Toolchains targetting Win64/Personal Builds/mingw-builds/8.1.0/threads-posix/seh/x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z" + ## (end workaround) + # * specific MinGW, if specified + - ps: if ( ! $env:MINGW_ARCHIVE -and $env:MINGW_URL ) { $env:MINGW_ARCHIVE = $($([URI]$env:MINGW_URL).fragment).TrimStart('#') } + - ps: if ( ! $env:MINGW_ARCHIVE -and $env:MINGW_URL ) { $env:MINGW_ARCHIVE = $([URI]$env:MINGW_URL).segments[-1] } + - if defined MINGW_ARCHIVE curl --insecure -fsSL "%MINGW_URL%" -o "%CI_TEMP_DIR%\%MINGW_ARCHIVE%" + - if defined MINGW_ARCHIVE mkdir "%CI_TEMP_DIR%\MinGW" >NUL + - if defined MINGW_ARCHIVE 7z x -y "%CI_TEMP_DIR%\%MINGW_ARCHIVE%" -o"%CI_TEMP_DIR%\MinGW" >NUL + - if defined MINGW_ARCHIVE set "MSYS_MINGWDIR=%CI_TEMP_DIR%\MinGW\mingw%MSYS_BITS%" + - if defined MINGW_ARCHIVE set "MSYS_BINDIR=%MSYS_MINGWDIR%\bin" + # * MinGW/MSYS PATH setup + - if defined MSYS_MINGWDIR set PATH=%MSYS_MINGWDIR%\%ARCH%-w64-mingw32\bin;%MSYS_BINDIR%;%PATH% + ## * workaround for rust-lang/rust#47048 / rust-lang/rust#53454 ## !maint: remove when resolved + # ** ref: , + # ** egs: , + - if /i "%ABI%"=="gnu" rustup install %CHANNEL%-%ARCH%-pc-windows-msvc + - if /i "%ABI%"=="gnu" rustup default %CHANNEL%-%ARCH%-pc-windows-msvc + - if /i "%ABI%"=="gnu" rustup target add %TARGET% + - ps: $env:TOOLCHAIN = $("$(rustup show active-toolchain)" -split '\s+')[0] + - if /i "%ABI%"=="gnu" rustup show + - if /i "%ABI%"=="gnu" rustc -vV + # ** copy libs from gcc toolchain to rust toolchain (more specifically, "crt2.o" and "dllcrt2.o" are needed) + - if defined MSYS_MINGWDIR copy /y "%MSYS_MINGWDIR%\%ARCH%-w64-mingw32\lib\*.o" "%USERPROFILE%\.rustup\toolchains\%TOOLCHAIN%\lib\rustlib\%TARGET%\lib" >NUL + ## (end workaround) + # * show `gcc` info + - if /i "%ABI%"=="gnu" ( where gcc && gcc --version ) + + # "msvc" ABI setup + - if /i "%ABI%" == "msvc" if /i "%ARCH%" == "i686" call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" + - if /i "%ABI%" == "msvc" if /i "%ARCH%" == "x86_64" call "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 + - if /i "%ABI%" == "msvc" if /i "%ARCH%" == "x86_64" call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" x86_amd64 + # * show `cl` info + - if /i "%ABI%"=="msvc" ( where cl && cl 2>&1 | findstr /i /c:"version" ) + + # finalize options + - ps: if ("$env:FEATURES".length) { $env:BUILD_OPTIONS = $(($env:BUILD_OPTIONS, "--features `"${env:FEATURES}`"")|?{$_}) -join ' ' } + - ps: if ("$env:FEATURES".length) { $env:TEST_OPTIONS = $(($env:TEST_OPTIONS, "--features `"${env:FEATURES}`"")|?{$_}) -join ' ' } + +build_script: + - ps: $env:BUILD_CMD = $(("cargo +${env:TOOLCHAIN} build --target=${env:TARGET}", $env:BUILD_OPTIONS)|?{$_}) -join ' ' + - echo [ `%BUILD_CMD%` ] & %BUILD_CMD% + +test_script: + - ps: $env:TEST_CMD = $(("cargo +${env:TOOLCHAIN} test --target=${env:TARGET}", $env:TEST_OPTIONS)|?{$_}) -join ' ' + - echo [ `%TEST_CMD%` ] & %TEST_CMD% diff --git a/coreutils/.busybox-config b/coreutils/.busybox-config new file mode 100644 index 000000000..e6921536f --- /dev/null +++ b/coreutils/.busybox-config @@ -0,0 +1,4 @@ +CONFIG_FEATURE_FANCY_HEAD=y +CONFIG_UNICODE_SUPPORT=y +CONFIG_DESKTOP=y +CONFIG_LONG_OPTS=y diff --git a/coreutils/.cargo/config b/coreutils/.cargo/config new file mode 100644 index 000000000..58e1381b1 --- /dev/null +++ b/coreutils/.cargo/config @@ -0,0 +1,2 @@ +[target.x86_64-unknown-redox] +linker = "x86_64-unknown-redox-gcc" diff --git a/coreutils/.cirrus.yml b/coreutils/.cirrus.yml new file mode 100644 index 000000000..fb7d7d09b --- /dev/null +++ b/coreutils/.cirrus.yml @@ -0,0 +1,15 @@ +freebsd_instance: + image: freebsd-12-0-release-amd64 + +task: + name: stable x86_64-unknown-freebsd + setup_script: + - pkg install -y curl gmake + - curl https://sh.rustup.rs -sSf --output rustup.sh + - sh rustup.sh -y + build_script: + - . $HOME/.cargo/env + - cargo build + test_script: + - . $HOME/.cargo/env + - cargo test diff --git a/coreutils/.codecov.yml b/coreutils/.codecov.yml new file mode 100644 index 000000000..69cb76019 --- /dev/null +++ b/coreutils/.codecov.yml @@ -0,0 +1 @@ +comment: false diff --git a/coreutils/.gitignore b/coreutils/.gitignore new file mode 100644 index 000000000..3eabba702 --- /dev/null +++ b/coreutils/.gitignore @@ -0,0 +1,14 @@ +target/ +/src/*/gen_table +/build/ +/tmp/ +/busybox/ +/.vscode/ +/.vs/ +*~ +.*.swp +.*.swo +.idea +Cargo.lock +lib*.a +/docs/_build \ No newline at end of file diff --git a/coreutils/.travis.yml b/coreutils/.travis.yml new file mode 100644 index 000000000..32929a629 --- /dev/null +++ b/coreutils/.travis.yml @@ -0,0 +1,70 @@ +language: rust + +rust: + - stable + - beta + +os: + - linux + - osx + +env: + # sphinx v1.8.0 is bugged & fails for linux builds; so, force specfic `sphinx` version + global: FEATURES='' TEST_INSTALL='' SPHINX_VERSIONED='sphinx==1.7.8' + +matrix: + allow_failures: + - rust: nightly + fast_finish: true + include: + - rust: 1.31.0 + - rust: stable + os: linux + env: TEST_INSTALL=true + - rust: stable + os: osx + env: TEST_INSTALL=true + - rust: nightly + os: linux + env: FEATURES=nightly + - rust: nightly + os: osx + env: FEATURES=nightly + - rust: nightly + os: linux + env: FEATURES=nightly,redox CC=x86_64-unknown-redox-gcc CARGO_ARGS='--no-default-features --target=x86_64-unknown-redox' REDOX=1 + +cache: + directories: + - $HOME/.cargo + +sudo: true + +before_install: + - if [ $REDOX ]; then ./.travis/redox-toolchain.sh; fi + +install: + - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python-pip && sudo pip install $SPHINX_VERSIONED; fi + - | + if [ $TRAVIS_OS_NAME = osx ]; then + brew update + brew upgrade python + pip3 install $SPHINX_VERSIONED + fi + +script: + - cargo build $CARGO_ARGS --features "$FEATURES" + - if [ ! $REDOX ]; then cargo test $CARGO_ARGS --features "$FEATURES" --no-fail-fast; fi + - if [ -n "$TEST_INSTALL" ]; then mkdir installdir_test; DESTDIR=installdir_test make install; [ `ls installdir_test/usr/local/bin | wc -l` -gt 0 ]; fi + +addons: + apt: + packages: + - libssl-dev + +after_success: | + if [ "$TRAVIS_OS_NAME" = linux -a "$TRAVIS_RUST_VERSION" = stable ]; then + bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) + cargo tarpaulin --out Xml + bash <(curl -s https://codecov.io/bash) + fi diff --git a/coreutils/.travis/redox-toolchain.sh b/coreutils/.travis/redox-toolchain.sh new file mode 100755 index 000000000..83bc8fc45 --- /dev/null +++ b/coreutils/.travis/redox-toolchain.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +rustup target add x86_64-unknown-redox +sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys AA12E97F0881517F +sudo add-apt-repository 'deb https://static.redox-os.org/toolchain/apt /' +sudo apt-get update -qq +sudo apt-get install -y x86-64-unknown-redox-gcc diff --git a/coreutils/CONTRIBUTING.md b/coreutils/CONTRIBUTING.md new file mode 100644 index 000000000..11ccca62a --- /dev/null +++ b/coreutils/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# Contributing to coreutils + +Contributions are very welcome, and should target Rust's master branch until the +standard libraries are stabilized. You may *claim* an item on the to-do list by +following these steps: + +1. Open an issue named "Implement [the utility of your choice]", e.g. "Implement ls" +2. State that you are working on this utility. +3. Develop the utility. +4. Add integration tests. +5. Add the reference to your utility into Cargo.toml and Makefile. +6. Remove utility from the to-do list in the README. +7. Submit a pull request and close the issue. + +The steps above imply that, before starting to work on a utility, you should +search the issues to make sure no one else is working on it. + +## Commit messages + +To help the project maintainers review pull requests from contributors across +numerous utilites, the team has settled on conventions for commit messages. + +From http://git-scm.com/book/ch5-2.html: + +``` +Short (50 chars or less) summary of changes + +More detailed explanatory text, if necessary. Wrap it to about 72 +characters or so. In some contexts, the first line is treated as the +subject of an email and the rest of the text as the body. The blank +line separating the summary from the body is critical (unless you omit +the body entirely); tools like rebase can get confused if you run the +two together. + +Further paragraphs come after blank lines. + + - Bullet points are okay, too + + - Typically a hyphen or asterisk is used for the bullet, preceded by a + single space, with blank lines in between, but conventions vary here +``` + +Furthermore, here are a few examples for a summary line: + +* commit for a single utility + +``` +nohup: cleanup and refactor +``` + +* commit for a utility's tests + +``` +tests/rm: test new feature +``` + +Beyond changes to an individual utility or its tests, other summary +lines for non-utility modules include: + +``` +README: add help +``` + +``` +travis: fix build +``` + +``` +uucore: add new modules +``` + +``` +uutils: add new utility +``` + +``` +gitignore: add temporary files +``` diff --git a/coreutils/Cargo.lock b/coreutils/Cargo.lock new file mode 100644 index 000000000..ec52f0ec3 --- /dev/null +++ b/coreutils/Cargo.lock @@ -0,0 +1,2286 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "advapi32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aho-corasick" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aho-corasick" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arch" +version = "0.0.1" +dependencies = [ + "platform-info 0.0.1 (git+https://github.com/sunriseos/platform-info.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "atty" +version = "0.2.13" +source = "git+https://github.com/sunriseos/atty.git#2ac1c868a2fc532fde6cbe228c161d27ba0fd231" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "backtrace" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", +] + +[[package]] +name = "base32" +version = "0.0.1" +dependencies = [ + "clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "base64" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "basename" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "bit-set" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit-vec" +version = "0.5.1" +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 = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "block-buffer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byte-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cargo_metadata" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cat" +version = "0.0.1" +dependencies = [ + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "cc" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chgrp" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "chmod" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "walker 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "chown" +version = "0.0.1" +dependencies = [ + "clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "chrono" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", +] + +[[package]] +name = "chroot" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "cksum" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "clap" +version = "2.33.0" +source = "git+https://github.com/sunriseos/clap.git#6dd48d847dd9149a34a9c8f55001ea3492e24950" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.13 (git+https://github.com/sunriseos/atty.git)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clippy" +version = "0.0.212" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy_lints 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clippy_lints" +version = "0.0.212" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cargo_metadata 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", + "if_chain 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "comm" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "cp" +version = "0.0.1" +dependencies = [ + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "filetime 0.2.7 (git+https://github.com/sunriseos/filetime.git)", + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "ioctl-sys 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cpp_macros 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_build" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_common 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_common" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_common 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_syn" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_synmap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_synom" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cut" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "data-encoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "date" +version = "0.0.1" +dependencies = [ + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "digest" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dircolors" +version = "0.0.1" +dependencies = [ + "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "dirname" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "du" +version = "0.0.1" +dependencies = [ + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "echo" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "either" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "env" +version = "0.0.1" +dependencies = [ + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "error-chain" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "expand" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "expr" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "onig 4.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "factor" +version = "0.0.1" +dependencies = [ + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "failure" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "false" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "filetime" +version = "0.2.7" +source = "git+https://github.com/sunriseos/filetime.git#9e9e419fccc29ee6e2f06b4bbe03926b184a1f9b" +dependencies = [ + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fmt" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fold" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "generic-array" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getopts" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glob" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "groups" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "half" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hashsum" +version = "0.0.1" +dependencies = [ + "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.7 (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)", + "sha3 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "head" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "hex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hostid" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "hostname" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "id" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "if_chain" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "install" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "ioctl-sys" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itertools" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itertools" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "join" +version = "0.0.1" +dependencies = [ + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kill" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "lazy_static" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.62" +source = "git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62#98b318f4a1f741230c27ca22d022d4369d0f3694" + +[[package]] +name = "libstdbuf" +version = "0.0.1" +dependencies = [ + "cpp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_build 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "link" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "ln" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "logname" +version = "0.0.1" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "ls" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "termsize 0.1.6 (git+https://github.com/sunriseos/termsize.git)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "md5" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", +] + +[[package]] +name = "memchr" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mkdir" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "mkfifo" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "mknod" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "mktemp" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "more" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "mv" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "nice" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[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.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nl" +version = "0.0.1" +dependencies = [ + "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "nodrop" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "nohup" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "nproc" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "num-integer" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num_cpus" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", +] + +[[package]] +name = "number_prefix" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "numfmt" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "od" +version = "0.0.1" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "half 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "onig" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "onig_sys" +version = "69.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "paste" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "pathchk" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pinky" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "pkg-config" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "platform-info" +version = "0.0.1" +source = "git+https://github.com/sunriseos/platform-info.git#6f38ff0cf0e4c872567b8bdf20317a4836607461" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "printenv" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "printf" +version = "0.0.1" +dependencies = [ + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ptx" +version = "0.0.1" +dependencies = [ + "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "pulldown-cmark" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pwd" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quine-mc_cluskey" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "readlink" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "realpath" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "relpath" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rm" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rmdir" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rust-users" +version = "0.6.0" +source = "git+https://github.com/uutils/rust-users#e64253f2b995e7f1a458c68a7eca66e0171d183a" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "same-file" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "seq" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "serde" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "sha2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha3" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "shred" +version = "0.0.1" +dependencies = [ + "filetime 0.2.7 (git+https://github.com/sunriseos/filetime.git)", + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "shuf" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "sleep" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "smallvec" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "sort" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "split" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "stat" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "stdbuf" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libstdbuf 0.0.1", + "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "sum" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "syn" +version = "0.15.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sync" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tac" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "tail" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tee" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tempfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "term_grid" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termsize" +version = "0.1.6" +source = "git+https://github.com/sunriseos/termsize.git#07ebe43eb0f57e60e89e00383f0557bb1d1e0693" +dependencies = [ + "atty 0.2.13 (git+https://github.com/sunriseos/atty.git)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "test" +version = "0.0.1" +dependencies = [ + "atty 0.2.13 (git+https://github.com/sunriseos/atty.git)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.42" +source = "git+https://github.com/sunriseos/time.git?branch=v0.1#79790b6c4e937c4591706e68143926f05696923d" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "timeout" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "toml" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "touch" +version = "0.0.1" +dependencies = [ + "filetime 0.2.7 (git+https://github.com/sunriseos/filetime.git)", + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "tr" +version = "0.0.1" +dependencies = [ + "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "true" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "truncate" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "tsort" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "tty" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "typenum" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ucd-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "uname" +version = "0.0.1" +dependencies = [ + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "platform-info 0.0.1 (git+https://github.com/sunriseos/platform-info.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "unexpand" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unindent" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "uniq" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "unix_socket" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", +] + +[[package]] +name = "unlink" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "uptime" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "users" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "utf8-ranges" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "uucore" +version = "0.0.1" +source = "git+https://github.com/sunriseos/uucore.git#a60175ba8d841347c44c9117eaab432e1c7c0144" +dependencies = [ + "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "platform-info 0.0.1 (git+https://github.com/sunriseos/platform-info.git)", + "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "wild 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "uutils" +version = "0.0.1" +dependencies = [ + "arch 0.0.1", + "base32 0.0.1", + "base64 0.0.1", + "basename 0.0.1", + "cat 0.0.1", + "chgrp 0.0.1", + "chmod 0.0.1", + "chown 0.0.1", + "chroot 0.0.1", + "cksum 0.0.1", + "comm 0.0.1", + "cp 0.0.1", + "cut 0.0.1", + "date 0.0.1", + "dircolors 0.0.1", + "dirname 0.0.1", + "du 0.0.1", + "echo 0.0.1", + "env 0.0.1", + "expand 0.0.1", + "expr 0.0.1", + "factor 0.0.1", + "false 0.0.1", + "filetime 0.2.7 (git+https://github.com/sunriseos/filetime.git)", + "fmt 0.0.1", + "fold 0.0.1", + "groups 0.0.1", + "hashsum 0.0.1", + "head 0.0.1", + "hostid 0.0.1", + "hostname 0.0.1", + "id 0.0.1", + "install 0.0.1", + "join 0.0.1", + "kill 0.0.1", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", + "link 0.0.1", + "ln 0.0.1", + "logname 0.0.1", + "ls 0.0.1", + "mkdir 0.0.1", + "mkfifo 0.0.1", + "mknod 0.0.1", + "mktemp 0.0.1", + "more 0.0.1", + "mv 0.0.1", + "nice 0.0.1", + "nl 0.0.1", + "nohup 0.0.1", + "nproc 0.0.1", + "numfmt 0.0.1", + "od 0.0.1", + "paste 0.0.1", + "pathchk 0.0.1", + "pinky 0.0.1", + "printenv 0.0.1", + "printf 0.0.1", + "ptx 0.0.1", + "pwd 0.0.1", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "readlink 0.0.1", + "realpath 0.0.1", + "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "relpath 0.0.1", + "rm 0.0.1", + "rmdir 0.0.1", + "rust-users 0.6.0 (git+https://github.com/uutils/rust-users)", + "seq 0.0.1", + "shred 0.0.1", + "shuf 0.0.1", + "sleep 0.0.1", + "sort 0.0.1", + "split 0.0.1", + "stat 0.0.1", + "stdbuf 0.0.1", + "sum 0.0.1", + "sync 0.0.1", + "tac 0.0.1", + "tail 0.0.1", + "tee 0.0.1", + "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "test 0.0.1", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "timeout 0.0.1", + "touch 0.0.1", + "tr 0.0.1", + "true 0.0.1", + "truncate 0.0.1", + "tsort 0.0.1", + "tty 0.0.1", + "uname 0.0.1", + "unexpand 0.0.1", + "unindent 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "uniq 0.0.1", + "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unlink 0.0.1", + "uptime 0.0.1", + "users 0.0.1", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "wc 0.0.1", + "who 0.0.1", + "whoami 0.0.1", + "yes 0.0.1", +] + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "walkdir" +version = "2.2.8" +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)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "walker" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wc" +version = "0.0.1" +dependencies = [ + "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "who" +version = "0.0.1" +dependencies = [ + "clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "whoami" +version = "0.0.1" +dependencies = [ + "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wild" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)", +] + +[[package]] +name = "yes" +version = "0.0.1" +dependencies = [ + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[metadata] +"checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" +"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" +"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.13 (git+https://github.com/sunriseos/atty.git)" = "" +"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" +"checksum backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f106c02a3604afcdc0df5d36cc47b44b55917dbaf3d808f71c163a0ddba64637" +"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" +"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" +"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814" +"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum cargo_metadata 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1efca0b863ca03ed4c109fb1c55e0bc4bbeb221d3e103d86251046b06a526bd0" +"checksum cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a0c56216487bb80eec9c4516337b2588a4f2a2290d72a1416d930e4dcdb0c90d" +"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" +"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" +"checksum clap 2.33.0 (git+https://github.com/sunriseos/clap.git)" = "" +"checksum clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)" = "7e253af13a0cc39c7f22cf16f1be49d593dedc5895fe2fbb15f14d66ead00533" +"checksum clippy_lints 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)" = "bd2326065405649672adbd5cb30dad2fad3a470935653d51c70591d47d3a8512" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum cpp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d1cd8699ffa1b18fd388183f7762e0545eddbd5c6ec95e9e3b42a4a71a507ff" +"checksum cpp_build 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c47531e7e09532ad4827098729794f5e1a5b1c2ccbb5e295498d2e7ab451c445" +"checksum cpp_common 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "79e39149a7943affa02f5b6e347ca2840a129cc78d5883ee229f0f1c4027d628" +"checksum cpp_macros 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6bba562eb4d65561efb6cef4e5f0de5936edfee7c6af7a4dfc323f6f2c997e40" +"checksum cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8cd649bf5b3804d92fe12a60c7698f5a538a6033ed8a668bf5241d4d4f1644e" +"checksum cpp_synmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "897e4f9cdbe2874edd3ffe53718ee5d8b89e2a970057b2c93d3214104f2e90b6" +"checksum cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fc8da5694233b646150c785118f77835ad0a49680c7f312a10ef30957c67b6d" +"checksum data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" +"checksum digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" +"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum filetime 0.2.7 (git+https://github.com/sunriseos/filetime.git)" = "" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fceb69994e330afed50c93524be68c42fa898c2d9fd4ee8da03bd7363acd26f2" +"checksum getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = "72327b15c228bfe31f1390f93dd5e9279587f0463836393c9df719ce62a3e450" +"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +"checksum half 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9353c2a89d550b58fa0061d8ed8d002a7d8cdf2494eb0e432859bd3a9e543836" +"checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum if_chain 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4bac95d9aa0624e7b78187d6fb8ab012b41d9f6f54b1bcb61e61c4845f8357ec" +"checksum ioctl-sys 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" +"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum libc 0.2.62 (git+https://github.com/sunriseos/libc.git?branch=sunrise-0.2.62)" = "" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" +"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" +"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b" +"checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" +"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" +"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dbf9993e59c894e3c08aa1c2712914e9e6bf1fcbfc6bef283e2183df345a4fee" +"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +"checksum onig 4.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a646989adad8a19f49be2090374712931c3a59835cb5277b4530f48b417f26e7" +"checksum onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +"checksum platform-info 0.0.1 (git+https://github.com/sunriseos/platform-info.git)" = "" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fdf85cda6cadfae5428a54661d431330b312bc767ddbc57adbedc24da66e32" +"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" +"checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58" +"checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48" +"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" +"checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +"checksum rust-users 0.6.0 (git+https://github.com/uutils/rust-users)" = "" +"checksum rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ccc78bfd5acd7bf3e89cffcf899e5cb1a52d6fafa8dec2739ad70c9577a57288" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" +"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" +"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.91 (registry+https://github.com/rust-lang/crates.io-index)" = "a72e9b96fa45ce22a4bc23da3858dfccfd60acd28a25bcd328a98fdd6bea43fd" +"checksum serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "101b495b109a3e3ca8c4cbe44cf62391527cdfb6ba15821c5ce80bcd5ea23f9f" +"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" +"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" +"checksum sha3 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26405905b6a56a94c60109cfda62610507ac14a65be531f5767dec5c5a8dd6a0" +"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" +"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" +"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +"checksum tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0" +"checksum term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "230d3e804faaed5a39b08319efb797783df2fd9671b39b7596490cb486d702cf" +"checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" +"checksum termsize 0.1.6 (git+https://github.com/sunriseos/termsize.git)" = "" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)" = "" +"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" +"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unindent 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "834b4441326c660336850c5c0926cc20548e848967a5f57bc20c2b741c8d41f4" +"checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" +"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" +"checksum uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)" = "" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c7904a7e2bb3cdf0cf5e783f44204a85a37a93151738fa349f06680f59a98b45" +"checksum walker 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44971d5e5ae4f7904dffb6260ebd3910e7bcae104a94730e04a24cb6af40646b" +"checksum wild 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "690e5dbd46cfaf2f3bd09875ad94e92cc56459fce505807d6ce5332671aa93ae" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" diff --git a/coreutils/Cargo.toml b/coreutils/Cargo.toml new file mode 100644 index 000000000..551278865 --- /dev/null +++ b/coreutils/Cargo.toml @@ -0,0 +1,393 @@ +[package] +name = "uutils" +version = "0.0.1" +authors = [] +build = "build.rs" +autotests = false + +[features] +unix = [ + "chgrp", + "chmod", + "chown", + "chroot", + "du", + "groups", + "hostid", + "id", + "install", + "kill", + "logname", + "mkfifo", + "mknod", + "nice", + "numfmt", + "nohup", + "pathchk", + "pinky", + "stat", + "stdbuf", + "timeout", + "touch", + "tty", + "uname", + "unlink", + "uptime", + "users", + "who", + + "generic" +] +windows = ["generic"] +# Feature "fuchsia" contains the exclusive list of utilities +# that can be compiled and run on Fuchsia. Should be built +# with --no-default-features when selecting this feature. +# TODO: merge with "unix" to avoid duplication once we support +# all utilities in that feature. +fuchsia = [ + # unix utilities + "chgrp", + "chmod", + "chown", + "du", + "groups", + "hostid", + "install", + "logname", + "mkfifo", + "mknod", + "nice", + "pathchk", + "stdbuf", + "tty", + "uname", + "unlink", + + # All generic utilities + "generic" +] +generic = [ + "arch", + "hostname", + "nproc", + "sync", + "touch", + "whoami", + "redox_generic" +] +# Feature "redox"/"redox_generic" contains the exclusive list of utilities +# that can be compiled and run on redox. Should be built +# with --no-default-features when selecting this feature. +# TODO: merge with "generic" to avoid duplication once we support +# all utilities in that feature. +redox_generic = [ + + # And maybe all generic utilities + "base32", + "base64", + "basename", + "cat", + "cksum", + "comm", + "cp", + "cut", + "date", + "dircolors", + "dirname", + "echo", + "env", + "expand", + "expr", + "factor", + "false", + "fmt", + "fold", + "hashsum", + "head", + "join", + "link", + "ln", + "ls", + "mkdir", + "mktemp", + "more", + "mv", + "nl", + "od", + "paste", + "printenv", + "printf", + "ptx", + "pwd", + "readlink", + "realpath", + "relpath", + "rm", + "rmdir", + "seq", + "shred", + "shuf", + "sleep", + "sort", + "split", + "sum", + "tac", + "tail", + "tee", + "test", + "tr", + "true", + "truncate", + "tsort", + "unexpand", + "uniq", + "wc", + "yes", +] +redox = [ + "uname", + "chmod", + "install", + "redox_generic" +] + +# Feature "sunrise" contains the exclusive list of utilities +# that can be compiled and run on Sunrise. Should be built +# with --no-default-features when selecting this feature. +sunrise = [ + # Base generic + "uname", + #"chmod", + "arch", + #"hostname", + "nproc", + #"sync", + "touch", + #"whoami", + + "install", + + # Most other generic supported by sunrise + "sunrise_generic" +] + +sunrise_generic = [ + + # And maybe all generic utilities + "base32", + "base64", + "basename", + "cat", + "cksum", + "comm", + "cp", + "cut", + "date", + #"dircolors", + "dirname", + "echo", + "env", + "expand", + #"expr", + "factor", + "false", + "fmt", + "fold", + "hashsum", + "head", + "join", + #"link", + #"ln", + "ls", + "mkdir", + #"mktemp", + "more", + "mv", + "nl", + "od", + "paste", + "printenv", + "printf", + "ptx", + "pwd", + #"readlink", + "realpath", + "relpath", + "rm", + "rmdir", + "seq", + "shred", + "shuf", + "sleep", + "sort", + "split", + "sum", + "tac", + "tail", + "tee", + "test", + "tr", + "true", + "truncate", + "tsort", + "unexpand", + "uniq", + "wc", + #"yes", +] + +test_unimplemented = [] +nightly = [] +default = ["unix"] + +[workspace] + +[dependencies] +uucore = "0.0.1" +arch = { optional=true, path="src/arch" } +base32 = { optional=true, path="src/base32" } +base64 = { optional=true, path="src/base64" } +basename = { optional=true, path="src/basename" } +cat = { optional=true, path="src/cat" } +chgrp = { optional=true, path="src/chgrp" } +chmod = { optional=true, path="src/chmod" } +chown = { optional=true, path="src/chown" } +chroot = { optional=true, path="src/chroot" } +cksum = { optional=true, path="src/cksum" } +comm = { optional=true, path="src/comm" } +cp = { optional=true, path="src/cp" } +cut = { optional=true, path="src/cut" } +date = { optional=true, path="src/date" } +dircolors= { optional=true, path="src/dircolors" } +dirname = { optional=true, path="src/dirname" } +du = { optional=true, path="src/du" } +echo = { optional=true, path="src/echo" } +env = { optional=true, path="src/env" } +expand = { optional=true, path="src/expand" } +expr = { optional=true, path="src/expr" } +factor = { optional=true, path="src/factor" } +false = { optional=true, path="src/false" } +fmt = { optional=true, path="src/fmt" } +fold = { optional=true, path="src/fold" } +groups = { optional=true, path="src/groups" } +hashsum = { optional=true, path="src/hashsum" } +head = { optional=true, path="src/head" } +hostid = { optional=true, path="src/hostid" } +hostname = { optional=true, path="src/hostname" } +id = { optional=true, path="src/id" } +install = { optional=true, path="src/install" } +join = { optional=true, path="src/join" } +kill = { optional=true, path="src/kill" } +link = { optional=true, path="src/link" } +ln = { optional=true, path="src/ln" } +ls = { optional=true, path="src/ls" } +logname = { optional=true, path="src/logname" } +mkdir = { optional=true, path="src/mkdir" } +mkfifo = { optional=true, path="src/mkfifo" } +mknod = { optional=true, path="src/mknod" } +mktemp = { optional=true, path="src/mktemp" } +more = { optional=true, path="src/more" } +mv = { optional=true, path="src/mv" } +nice = { optional=true, path="src/nice" } +nl = { optional=true, path="src/nl" } +nohup = { optional=true, path="src/nohup" } +nproc = { optional=true, path="src/nproc" } +numfmt = { optional=true, path="src/numfmt" } +od = { optional=true, path="src/od" } +paste = { optional=true, path="src/paste" } +pathchk = { optional=true, path="src/pathchk" } +pinky = { optional=true, path="src/pinky" } +printenv = { optional=true, path="src/printenv" } +printf = { optional=true, path="src/printf" } +ptx = { optional=true, path="src/ptx" } +pwd = { optional=true, path="src/pwd" } +readlink = { optional=true, path="src/readlink" } +realpath = { optional=true, path="src/realpath" } +relpath = { optional=true, path="src/relpath" } +rm = { optional=true, path="src/rm" } +rmdir = { optional=true, path="src/rmdir" } +seq = { optional=true, path="src/seq" } +shred = { optional=true, path="src/shred" } +shuf = { optional=true, path="src/shuf" } +sleep = { optional=true, path="src/sleep" } +sort = { optional=true, path="src/sort" } +split = { optional=true, path="src/split" } +stat = { optional=true, path="src/stat" } +stdbuf = { optional=true, path="src/stdbuf" } +sum = { optional=true, path="src/sum" } +sync = { optional=true, path="src/sync" } +tac = { optional=true, path="src/tac" } +tail = { optional=true, path="src/tail" } +tee = { optional=true, path="src/tee" } +test = { optional=true, path="src/test" } +timeout = { optional=true, path="src/timeout" } +touch = { optional=true, path="src/touch" } +tr = { optional=true, path="src/tr" } +true = { optional=true, path="src/true" } +truncate = { optional=true, path="src/truncate" } +tsort = { optional=true, path="src/tsort" } +tty = { optional=true, path="src/tty" } +uname = { optional=true, path="src/uname" } +unexpand = { optional=true, path="src/unexpand" } +uniq = { optional=true, path="src/uniq" } +unlink = { optional=true, path="src/unlink" } +uptime = { optional=true, path="src/uptime" } +users = { optional=true, path="src/users" } +wc = { optional=true, path="src/wc" } +who = { optional=true, path="src/who" } +whoami = { optional=true, path="src/whoami" } +yes = { optional=true, path="src/yes" } + +[dev-dependencies] +time = "0.1.42" +filetime = "0.2.5" +libc = "0.2.62" +regex = "1.0.3" +rand = "0.6.5" +tempdir = "0.3.7" +unindent = "0.1.3" +lazy_static = "1.3.0" + +[target.'cfg(unix)'.dev-dependencies] +# FIXME: this should use the normal users crate, but it conflicts with the users utility +rust-users = { git = "https://github.com/uutils/rust-users" } +unix_socket = "0.5.0" + +[[bin]] +name = "uutils" +path = "src/uutils/uutils.rs" + +[[test]] +name = "tests" + +[patch.crates-io.libc] +git = "https://github.com/sunriseos/libc.git" +branch = "sunrise-0.2.62" + +[patch.crates-io.uucore] +git = "https://github.com/sunriseos/uucore.git" +branch = "master" + +[patch.crates-io.atty] +git = "https://github.com/sunriseos/atty.git" +branch = "master" + +[patch.crates-io.clap] +git = "https://github.com/sunriseos/clap.git" +branch = "master" + +[patch.crates-io.filetime] +git = "https://github.com/sunriseos/filetime.git" +branch = "master" + +[patch.crates-io.platform-info] +git = "https://github.com/sunriseos/platform-info.git" +branch = "master" + +[patch.crates-io.time] +git = "https://github.com/sunriseos/time.git" +branch = "v0.1" + +[patch.crates-io.termsize] +git = "https://github.com/sunriseos/termsize.git" +branch = "master" diff --git a/coreutils/GNUmakefile b/coreutils/GNUmakefile new file mode 100644 index 000000000..3e90613e3 --- /dev/null +++ b/coreutils/GNUmakefile @@ -0,0 +1,324 @@ +# Config options +PROFILE ?= debug +MULTICALL ?= n +INSTALL ?= install +ifneq (,$(filter install, $(MAKECMDGOALS))) +override PROFILE:=release +endif + +PROFILE_CMD := +ifeq ($(PROFILE),release) + PROFILE_CMD = --release +endif + +RM := rm -rf + +# Binaries +CARGO ?= cargo +CARGOFLAGS ?= + +# Install directories +PREFIX ?= /usr/local +DESTDIR ?= +BINDIR ?= /bin +MANDIR ?= /man/man1 + +INSTALLDIR_BIN=$(DESTDIR)$(PREFIX)$(BINDIR) +INSTALLDIR_MAN=$(DESTDIR)$(PREFIX)/share/$(MANDIR) +$(shell test -d $(INSTALLDIR_MAN)) +ifneq ($(.SHELLSTATUS),0) +override INSTALLDIR_MAN=$(DESTDIR)$(PREFIX)$(MANDIR) +endif + +#prefix to apply to uutils binary and all tool binaries +PROG_PREFIX ?= + +# This won't support any directory with spaces in its name, but you can just +# make a symlink without spaces that points to the directory. +BASEDIR ?= $(shell pwd) +BUILDDIR := $(BASEDIR)/target/${PROFILE} +PKG_BUILDDIR := $(BUILDDIR)/deps +DOCSDIR := $(BASEDIR)/docs + +BUSYBOX_ROOT := $(BASEDIR)/tmp +BUSYBOX_VER := 1.24.1 +BUSYBOX_SRC := $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER) + +# Possible programs +PROGS := \ + base32 \ + base64 \ + basename \ + cat \ + cksum \ + comm \ + cp \ + cut \ + dircolors \ + dirname \ + echo \ + env \ + expand \ + expr \ + factor \ + false \ + fmt \ + fold \ + hashsum \ + head \ + join \ + link \ + ln \ + ls \ + mkdir \ + mktemp \ + more \ + mv \ + nl \ + numfmt \ + nproc \ + od \ + paste \ + printenv \ + printf \ + ptx \ + pwd \ + readlink \ + realpath \ + relpath \ + rm \ + rmdir \ + seq \ + shred \ + shuf \ + sleep \ + sort \ + split \ + sum \ + sync \ + tac \ + tail \ + tee \ + test \ + tr \ + true \ + truncate \ + tsort \ + unexpand \ + uniq \ + wc \ + whoami \ + yes + +UNIX_PROGS := \ + arch \ + chgrp \ + chmod \ + chown \ + chroot \ + du \ + groups \ + hostid \ + hostname \ + id \ + install \ + kill \ + logname \ + mkfifo \ + mknod \ + nice \ + nohup \ + pathchk \ + pinky \ + stat \ + stdbuf \ + timeout \ + touch \ + tty \ + uname \ + unlink \ + uptime \ + users \ + who + +ifneq ($(OS),Windows_NT) + PROGS := $(PROGS) $(UNIX_PROGS) +endif + +UTILS ?= $(PROGS) + +# Programs with usable tests +TEST_PROGS := \ + base32 \ + base64 \ + basename \ + cat \ + chgrp \ + chmod \ + chown \ + cksum \ + comm \ + cp \ + cut \ + dircolors \ + dirname \ + echo \ + env \ + expr \ + factor \ + false \ + fold \ + hashsum \ + head \ + install \ + link \ + ln \ + ls \ + mkdir \ + mktemp \ + mv \ + nl \ + numfmt \ + od \ + paste \ + pathchk \ + pinky \ + printf \ + ptx \ + pwd \ + readlink \ + realpath \ + rm \ + rmdir \ + seq \ + sort \ + split \ + stat \ + stdbuf \ + sum \ + tac \ + tail \ + test \ + touch \ + tr \ + true \ + truncate \ + tsort \ + unexpand \ + uniq \ + unlink \ + wc \ + who + +TESTS := \ + $(sort $(filter $(UTILS),$(filter-out $(SKIP_UTILS),$(TEST_PROGS)))) + +TEST_NO_FAIL_FAST := +TEST_SPEC_FEATURE := +ifneq ($(SPEC),) +TEST_NO_FAIL_FAST :=--no-fail-fast +TEST_SPEC_FEATURE := test_unimplemented +endif + +define TEST_BUSYBOX +test_busybox_$(1): + (cd $(BUSYBOX_SRC)/testsuite && bindir=$(BUILDDIR) ./runtest $(RUNTEST_ARGS) $(1) ) +endef + +# Output names +EXES := \ + $(sort $(filter $(UTILS),$(filter-out $(SKIP_UTILS),$(PROGS)))) + +INSTALLEES := ${EXES} +ifeq (${MULTICALL}, y) +INSTALLEES := ${INSTALLEES} uutils +endif + +# Shared library extension +SYSTEM := $(shell uname) +DYLIB_EXT := +ifeq ($(SYSTEM),Linux) + DYLIB_EXT := so + DYLIB_FLAGS := -shared +endif +ifeq ($(SYSTEM),Darwin) + DYLIB_EXT := dylib + DYLIB_FLAGS := -dynamiclib -undefined dynamic_lookup +endif + +all: build + +do_install = $(INSTALL) ${1} +use_default := 1 + +build-pkgs: +ifneq (${MULTICALL}, y) + ${CARGO} build ${CARGOFLAGS} ${PROFILE_CMD} $(foreach pkg,$(EXES),-p $(pkg)) +endif + +build-uutils: + ${CARGO} build ${CARGOFLAGS} --features "${EXES}" ${PROFILE_CMD} --no-default-features + +build-manpages: + cd $(DOCSDIR) && make man + +build: build-uutils build-pkgs build-manpages + +$(foreach test,$(filter-out $(SKIP_UTILS),$(PROGS)),$(eval $(call TEST_BUSYBOX,$(test)))) + +test: + ${CARGO} test ${CARGOFLAGS} --features "$(TESTS) $(TEST_SPEC_FEATURE)" --no-default-features $(TEST_NO_FAIL_FAST) + +busybox-src: + if [ ! -e $(BUSYBOX_SRC) ]; then \ + mkdir -p $(BUSYBOX_ROOT); \ + wget https://busybox.net/downloads/busybox-$(BUSYBOX_VER).tar.bz2 -P $(BUSYBOX_ROOT); \ + tar -C $(BUSYBOX_ROOT) -xf $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER).tar.bz2; \ + fi; \ + +# This is a busybox-specific config file their test suite wants to parse. +$(BUILDDIR)/.config: $(BASEDIR)/.busybox-config + cp $< $@ + +# Test under the busybox testsuite +$(BUILDDIR)/busybox: busybox-src build-uutils $(BUILDDIR)/.config + cp $(BUILDDIR)/uutils $(BUILDDIR)/busybox; \ + chmod +x $@; + +ifeq ($(EXES),) +busytest: +else +busytest: $(BUILDDIR)/busybox $(addprefix test_busybox_,$(filter-out $(SKIP_UTILS),$(EXES))) +endif + +clean: + $(RM) $(BUILDDIR) + cd $(DOCSDIR) && make clean + +distclean: clean + $(CARGO) clean $(CARGOFLAGS) && $(CARGO) update $(CARGOFLAGS) + +install: build + mkdir -p $(INSTALLDIR_BIN) + mkdir -p $(INSTALLDIR_MAN) +ifeq (${MULTICALL}, y) + $(INSTALL) $(BUILDDIR)/uutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)uutils + cd $(INSTALLDIR_BIN) && $(foreach prog, $(filter-out uutils, $(INSTALLEES)), \ + ln -fs $(PROG_PREFIX)uutils $(PROG_PREFIX)$(prog) &&) : + cat $(DOCSDIR)/_build/man/uutils.1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)uutils.1.gz +else + $(foreach prog, $(INSTALLEES), \ + $(INSTALL) $(BUILDDIR)/$(prog) $(INSTALLDIR_BIN)/$(PROG_PREFIX)$(prog);) +endif + $(foreach man, $(filter $(INSTALLEES), $(basename $(notdir $(wildcard $(DOCSDIR)/_build/man/*)))), \ + cat $(DOCSDIR)/_build/man/$(man).1 | gzip > $(INSTALLDIR_MAN)/$(PROG_PREFIX)$(man).1.gz &&) : + +uninstall: +ifeq (${MULTICALL}, y) + rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)uutils) +endif + rm -f $(addprefix $(INSTALLDIR_MAN)/,$(PROG_PREFIX)uutils.1.gz) + rm -f $(addprefix $(INSTALLDIR_BIN)/$(PROG_PREFIX),$(PROGS)) + rm -f $(addprefix $(INSTALLDIR_MAN)/$(PROG_PREFIX),$(addsuffix .1.gz,$(PROGS))) + +.PHONY: all build build-uutils build-pkgs build-docs test distclean clean busytest install uninstall diff --git a/coreutils/LICENSE b/coreutils/LICENSE new file mode 100644 index 000000000..0177c4ab7 --- /dev/null +++ b/coreutils/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/coreutils/Makefile b/coreutils/Makefile new file mode 100644 index 000000000..044aaa770 --- /dev/null +++ b/coreutils/Makefile @@ -0,0 +1,5 @@ +USEGNU=gmake $* +all: + @$(USEGNU) +.DEFAULT: + @$(USEGNU) diff --git a/coreutils/README.md b/coreutils/README.md new file mode 100644 index 000000000..8fc611d20 --- /dev/null +++ b/coreutils/README.md @@ -0,0 +1,361 @@ +uutils coreutils +================ + +[![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ) +[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/coreutils/blob/master/LICENSE) +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils?ref=badge_shield) +[![LOC](https://tokei.rs/b1/github/uutils/coreutils?category=code)](https://github.com/Aaronepower/tokei) +[![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils) + +[![Build Status](https://api.travis-ci.org/uutils/coreutils.svg?branch=master)](https://travis-ci.org/uutils/coreutils) +[![Build Status (Windows)](https://ci.appveyor.com/api/projects/status/787ltcxgy86r20le?svg=true)](https://ci.appveyor.com/project/Arcterus/coreutils) +[![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master) + +----------------------------------------------- + +uutils is an attempt at writing universal (as in cross-platform) CLI +utils in [Rust](http://www.rust-lang.org). This repo is to aggregate the GNU +coreutils rewrites. + +Why? +---- + +Many GNU, Linux and other utils are pretty awesome, 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, abandoned, hosted on CVS, written in platform-specific C, etc. + +Rust provides a good, platform-agnostic way of writing systems utils that are easy +to compile anywhere, and this is as good a way as any to try and learn it. + +Requirements +------------ + +* Rust (`cargo`, `rustc`) +* GNU Make (required to build documentation) +* [Sphinx](http://www.sphinx-doc.org/) (for documentation) +* gzip (for installing documentation) + +### Rust Version ### + +uutils follows Rust's release channels and is tested against stable, beta and nightly. +The current oldest supported version of the Rust compiler is `1.31.0`. + +On both Windows and Redox, only the nightly version is tested currently. + +Build Instructions +------------------ + +There are currently two methods to build uutils: GNU Make and Cargo. However, +while there may be two methods, both systems are required to build on Unix +(only Cargo is required on Windows). + +First, for both methods, we need to fetch the repository: +```bash +$ git clone https://github.com/uutils/coreutils +$ cd coreutils +``` + +### Cargo ### + +Building uutils using Cargo is easy because the process is the same as for +every other Rust program: +```bash +# to keep debug information, compile without --release +$ cargo build --release +``` + +Because the above command attempts to build utilities that only work on +Unix-like platforms at the moment, to build on Windows, you must do the +following: +```bash +# to keep debug information, compile without --release +$ cargo build --release --no-default-features --features windows +``` + +If you don't want to build every utility available on your platform into the +multicall binary (the Busybox-esque binary), you can also specify which ones +you want to build manually. For example: +```bash +$ cargo build --features "base32 cat echo rm" --no-default-features +``` + +If you don't even want to build the multicall binary and would prefer to just +build the utilities as individual binaries, that is possible too. For example: +```bash +$ cargo build -p base32 -p cat -p echo -p rm +``` + +### GNU Make ### + +Building using `make` is a simple process as well. + +To simply build all available utilities: +```bash +$ make +``` + +To build all but a few of the available utilities: +```bash +$ make SKIP_UTILS='UTILITY_1 UTILITY_2' +``` + +To build only a few of the available utilities: +```bash +$ make UTILS='UTILITY_1 UTILITY_2' +``` + +Installation Instructions +------------------------- + +### Cargo ### + +Likewise, installing can simply be done using: +```bash +$ cargo install +``` + +This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`). + +### GNU Make ### + +To install all available utilities: +```bash +$ make install +``` + +To install all but a few of the available utilities: +```bash +$ make SKIP_UTILS='UTILITY_1 UTILITY_2' install +``` + +To install only a few of the available utilities: +```bash +$ make UTILS='UTILITY_1 UTILITY_2' install +``` + +To install every program with a prefix (e.g. uu-echo uu-cat): +```bash +$ make PROG_PREFIX=PREFIX_GOES_HERE install +``` + +To install the multicall binary: +```bash +$ make MULTICALL=y install +``` + +Set install parent directory (default value is /usr/local): +```bash +# DESTDIR is also supported +$ make PREFIX=/my/path install +``` + +### NixOS ### + +The [standard package set](https://nixos.org/nixpkgs/manual/) of [NixOS](https://nixos.org/) +provides this package out of the box since 18.03: + +``` +nix-env -iA nixos.uutils-coreutils +``` + +Uninstallation Instructions +--------------------------- + +Uninstallation differs depending on how you have installed uutils. If you used +Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use +Make to uninstall. + +### Cargo ### + +To uninstall uutils: +```bash +$ cargo uninstall uutils +``` + +### GNU Make ### + +To uninstall all utilities: +```bash +$ make uninstall +``` + +To uninstall every program with a set prefix: +```bash +$ make PROG_PREFIX=PREFIX_GOES_HERE uninstall +``` + +To uninstall the multicall binary: +```bash +$ make MULTICALL=y uninstall +``` + +To uninstall from a custom parent directory: +```bash +# DESTDIR is also supported +$ make PREFIX=/my/path uninstall +``` + +Test Instructions +----------------- + +Testing can be done using either Cargo or `make`. + +### Cargo ### + +Just like with building, we follow the standard procedure for testing using +Cargo: +```bash +$ cargo test +``` + +If you would prefer to test a select few utilities: +```bash +$ cargo test --features "chmod mv tail" --no-default-features +``` + +### GNU Make ### + +To simply test all available utilities: +```bash +$ make test +``` + +To test all but a few of the available utilities: +```bash +$ make SKIP_UTILS='UTILITY_1 UTILITY_2' test +``` + +To test only a few of the available utilities: +```bash +$ make UTILS='UTILITY_1 UTILITY_2' test +``` + +To include tests for unimplemented behavior: +```bash +$ make UTILS='UTILITY_1 UTILITY_2' SPEC=y test +``` + +Run Busybox Tests +----------------- + +This testing functionality is only available on *nix operating systems and +requires `make`. + +To run busybox's tests for all utilities for which busybox has tests +```bash +$ make busytest +``` + +To run busybox's tests for a few of the available utilities +```bash +$ make UTILS='UTILITY_1 UTILITY_2' busytest +``` + +To pass an argument like "-v" to the busybox test runtime +```bash +$ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest +``` + +Contribute +---------- + +To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). + +Utilities +--------- + +| Done | Semi-Done | To Do | +|-----------|-----------|--------| +| arch | cp | chcon | +| base32 | expr | csplit | +| base64 | install | dd | +| basename | ls | df | +| cat | more | numfmt | +| chgrp | od (`--strings` and 128-bit data types missing) | pr | +| chmod | printf | runcon | +| chown | sort | stty | +| chroot | split | | +| cksum | tail | | +| comm | test | | +| cut | date | | +| dircolors | join | | +| dirname | | | +| du | | | +| echo | | | +| env | | | +| expand | | | +| factor | | | +| false | | | +| fmt | | | +| fold | | | +| groups | | | +| hashsum | | | +| head | | | +| hostid | | | +| hostname | | | +| id | | | +| kill | | | +| link | | | +| ln | | | +| logname | | | +| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/hashsum/hashsum.rs)) | | +| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/hashsum/hashsum.rs)) | | +| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/hashsum/hashsum.rs)) | | +| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/hashsum/hashsum.rs)) | | +| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/hashsum/hashsum.rs)) | | +| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/hashsum/hashsum.rs)) | | +| mkdir | | | +| mkfifo | | | +| mknod | | | +| mktemp | | | +| mv | | | +| nice | | | +| nl | | | +| nohup | | | +| nproc | | | +| paste | | | +| pathchk | | | +| pinky | | | +| printenv | | | +| ptx | | | +| pwd | | | +| readlink | | | +| realpath | | | +| relpath | | | +| rm | | | +| rmdir | | | +| seq | | | +| shred | | | +| shuf | | | +| sleep | | | +| stat | | | +| stdbuf | | | +| sum | | | +| sync | | | +| tac | | | +| tee | | | +| timeout | | | +| touch | | | +| tr | | | +| true | | | +| truncate | | | +| tsort | | | +| tty | | | +| uname | | | +| unexpand | | | +| uniq | | | +| unlink | | | +| uptime | | | +| users | | | +| wc | | | +| who | | | +| whoami | | | +| yes | | | + +License +------- + +uutils is licensed under the MIT License - see the `LICENSE` file for details + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fuutils%2Fcoreutils?ref=badge_large) diff --git a/coreutils/build.rs b/coreutils/build.rs new file mode 100644 index 000000000..04f576696 --- /dev/null +++ b/coreutils/build.rs @@ -0,0 +1,77 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +pub fn main() { + if let Ok(profile) = env::var("PROFILE") { + println!("cargo:rustc-cfg=build={:?}", profile); + } + + let feature_prefix = "CARGO_FEATURE_"; + let out_dir = env::var("OUT_DIR").unwrap(); + + let mut crates = Vec::new(); + for (key, val) in env::vars() { + if val == "1" && key.starts_with(feature_prefix) { + let krate = key[feature_prefix.len()..].to_lowercase(); + match krate.as_ref() { + "default" | "unix" | "redox" | "redox_generic" | "fuchsia" | "generic" | "windows" + | "sunrise" | "sunrise_generic" | "nightly" | "test_unimplemented" => continue, + _ => {} + } + crates.push(krate.to_string()); + } + } + crates.sort(); + + let mut cf = File::create(Path::new(&out_dir).join("uutils_crates.rs")).unwrap(); + let mut mf = File::create(Path::new(&out_dir).join("uutils_map.rs")).unwrap(); + + mf.write_all( + " + type UtilityMap = HashMap<&'static str, fn(Vec) -> i32>; + + fn util_map() -> UtilityMap { + let mut map: UtilityMap = HashMap::new();\n" + .as_bytes(), + ).unwrap(); + + for krate in crates { + cf.write_all(format!("extern crate uu_{krate};\n", krate = krate).as_bytes()) + .unwrap(); + + match krate.as_ref() { + "hashsum" => { + mf.write_all( + "map.insert(\"hashsum\", uu_hashsum::uumain); + map.insert(\"md5sum\", uu_hashsum::uumain); + map.insert(\"sha1sum\", uu_hashsum::uumain); + map.insert(\"sha224sum\", uu_hashsum::uumain); + map.insert(\"sha256sum\", uu_hashsum::uumain); + map.insert(\"sha384sum\", uu_hashsum::uumain); + map.insert(\"sha512sum\", uu_hashsum::uumain); + map.insert(\"sha3sum\", uu_hashsum::uumain); + map.insert(\"sha3-224sum\", uu_hashsum::uumain); + map.insert(\"sha3-256sum\", uu_hashsum::uumain); + map.insert(\"sha3-384sum\", uu_hashsum::uumain); + map.insert(\"sha3-512sum\", uu_hashsum::uumain); + map.insert(\"shake128sum\", uu_hashsum::uumain); + map.insert(\"shake256sum\", uu_hashsum::uumain);\n" + .as_bytes(), + ).unwrap(); + } + _ => mf.write_all( + format!( + "map.insert(\"{krate}\", uu_{krate}::uumain);\n", + krate = krate + ).as_bytes(), + ).unwrap(), + } + } + + mf.write_all("map\n}\n".as_bytes()).unwrap(); + + cf.flush().unwrap(); + mf.flush().unwrap(); +} diff --git a/coreutils/docs/GNUmakefile b/coreutils/docs/GNUmakefile new file mode 100644 index 000000000..05cef70c1 --- /dev/null +++ b/coreutils/docs/GNUmakefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = uutils +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help GNUmakefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: GNUmakefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/coreutils/docs/Makefile b/coreutils/docs/Makefile new file mode 100644 index 000000000..044aaa770 --- /dev/null +++ b/coreutils/docs/Makefile @@ -0,0 +1,5 @@ +USEGNU=gmake $* +all: + @$(USEGNU) +.DEFAULT: + @$(USEGNU) diff --git a/coreutils/docs/arch.rst b/coreutils/docs/arch.rst new file mode 100644 index 000000000..66b516159 --- /dev/null +++ b/coreutils/docs/arch.rst @@ -0,0 +1,28 @@ +.. print machine hardware name + +==== +arch +==== + +.. FIXME: this needs to be autogenerated somehow + +-------- +Synopsis +-------- + +``arch`` [OPTION]... + +----------- +Description +----------- + +``arch`` is an alias for ``uname -m``. They both print the machine hardware +name. + +An exit code of zero indicates success, whereas anything else means failure. +For this program, a non-zero exit code generally means the user provided +invalid options. + +-h, --help print a help menu for this program displaying accepted + options and arguments +-v, --version print the version number of this program diff --git a/coreutils/docs/conf.py b/coreutils/docs/conf.py new file mode 100644 index 000000000..b12a8be62 --- /dev/null +++ b/coreutils/docs/conf.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# uutils documentation build configuration file, created by +# sphinx-quickstart on Tue Dec 5 23:20:18 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +import glob +import os + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.imgmath'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'uutils' +copyright = '2017, uutils developers' +author = 'uutils developers' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.0.1' +# The full version, including alpha/beta/rc tags. +release = '0.0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'uutilsdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'uutils.tex', 'uutils Documentation', + 'uutils developers', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [] +for name in glob.glob('*.rst'): + if name != 'index.rst': + desc = '' + with open(name) as f: + desc = f.readline().strip() + if desc.startswith('..'): + desc = desc[2:].strip() + else: + desc = '' + man_pages.append(( + name[:-4], # source file without extension + name[:-4].replace('/', '-'), # output file + desc, + [author], + 1 + )) + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'uutils', 'uutils Documentation', + author, 'uutils', 'A cross-platform reimplementation of GNU coreutils in Rust.', + 'Miscellaneous'), +] + + + diff --git a/coreutils/docs/index.rst b/coreutils/docs/index.rst new file mode 100644 index 000000000..4d70d4368 --- /dev/null +++ b/coreutils/docs/index.rst @@ -0,0 +1,21 @@ +.. uutils documentation master file, created by + sphinx-quickstart on Tue Dec 5 23:20:18 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to uutils's documentation! +================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + arch + uutils + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/coreutils/docs/make.bat b/coreutils/docs/make.bat new file mode 100644 index 000000000..0fb8a16c1 --- /dev/null +++ b/coreutils/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build +set SPHINXPROJ=uutils + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/coreutils/docs/uutils.rst b/coreutils/docs/uutils.rst new file mode 100644 index 000000000..19af87fef --- /dev/null +++ b/coreutils/docs/uutils.rst @@ -0,0 +1,24 @@ +.. run core utilities + +====== +uutils +====== + +.. FIXME: this needs to be autogenerated somehow + +-------- +Synopsis +-------- + +``uutils`` [OPTION]... [PROGRAM] [OPTION]... [ARGUMENTS]... + +----------- +Description +----------- + +``uutils`` is a program that contains that other coreutils commands, somewhat +similar to Busybox. + +--help, -h print a help menu for PROGRAM displaying accepted options and + arguments; if PROGRAM was not given, do the same but for this + program diff --git a/coreutils/mkmain.rs b/coreutils/mkmain.rs new file mode 100644 index 000000000..f065ea8b2 --- /dev/null +++ b/coreutils/mkmain.rs @@ -0,0 +1,36 @@ +use std::env; +use std::io::Write; +use std::fs::File; +use std::path::Path; + +static TEMPLATE: &'static str = "\ +extern crate uu_@UTIL_CRATE@; +extern crate uucore; + +use std::io::Write; +use uu_@UTIL_CRATE@::uumain; + +fn main() { + uucore::panic::install_sigpipe_hook(); + + let code = uumain(uucore::args().collect()); + // Since stdout is line-buffered by default, we need to ensure any pending + // writes are flushed before exiting. Ideally, this should be enforced by + // each utility. + // + // See: https://github.com/rust-lang/rust/issues/23818 + // + std::io::stdout().flush().expect(\"could not flush stdout\"); + std::process::exit(code); +} +"; + +pub fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + let pkgname = env::var("CARGO_PKG_NAME").unwrap(); + + let main = TEMPLATE.replace("@UTIL_CRATE@", &pkgname); + let mut file = File::create(&Path::new(&out_dir).join("main.rs")).unwrap(); + + write!(file, "{}", main).unwrap(); +} diff --git a/coreutils/src/arch/Cargo.toml b/coreutils/src/arch/Cargo.toml new file mode 100644 index 000000000..d009a09c4 --- /dev/null +++ b/coreutils/src/arch/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "arch" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_arch" +path = "arch.rs" + +[dependencies] +platform-info = "0.0.1" +uucore = "0.0.1" + +[[bin]] +name = "arch" +path = "../../uumain.rs" diff --git a/coreutils/src/arch/arch.rs b/coreutils/src/arch/arch.rs new file mode 100644 index 000000000..d27b5d216 --- /dev/null +++ b/coreutils/src/arch/arch.rs @@ -0,0 +1,27 @@ +#![crate_name = "uu_arch"] + +// This file is part of the uutils coreutils package. +// +// (c) Smigle00 +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// + +extern crate platform_info; +#[macro_use] +extern crate uucore; + +use platform_info::*; + +static SYNTAX: &str = ""; +static SUMMARY: &str = "Determine architecture name for current machine."; +static LONG_HELP: &str = ""; + +pub fn uumain(args: Vec) -> i32 { + new_coreopts!(SYNTAX, SUMMARY, LONG_HELP).parse(args); + let uts = return_if_err!(1, PlatformInfo::new()); + println!("{}", uts.machine().trim()); + 0 +} diff --git a/coreutils/src/base32/Cargo.toml b/coreutils/src/base32/Cargo.toml new file mode 100644 index 000000000..75b25454b --- /dev/null +++ b/coreutils/src/base32/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "base32" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_base32" +path = "base32.rs" + +[dependencies.uucore] +version = "0.0.1" +features = ["encoding"] + +[dependencies.clippy] +version = "0.0.212" +optional = true + +[[bin]] +name = "base32" +path = "../../uumain.rs" diff --git a/coreutils/src/base32/base32.rs b/coreutils/src/base32/base32.rs new file mode 100644 index 000000000..548784298 --- /dev/null +++ b/coreutils/src/base32/base32.rs @@ -0,0 +1,33 @@ +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +#![crate_name = "uu_base32"] + +#[macro_use] +extern crate uucore; +use uucore::encoding::Format; + +#[path = "../base64/base_common.rs"] +mod base_common; + +static SYNTAX: &str = "[OPTION]... [FILE]"; +static SUMMARY: &str = + "Base32 encode or decode FILE, or standard input, to standard output."; +static LONG_HELP: &str = " + With no FILE, or when FILE is -, read standard input. + + The data are encoded as described for the base32 alphabet in RFC + 4648. When decoding, the input may contain newlines in addition + to the bytes of the formal base32 alphabet. Use --ignore-garbage + to attempt to recover from any other non-alphabet bytes in the + encoded stream. +"; + +pub fn uumain(args: Vec) -> i32 { + base_common::execute(args, SYNTAX, SUMMARY, LONG_HELP, Format::Base32) +} diff --git a/coreutils/src/base64/Cargo.toml b/coreutils/src/base64/Cargo.toml new file mode 100644 index 000000000..63b82b4ec --- /dev/null +++ b/coreutils/src/base64/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "base64" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_base64" +path = "base64.rs" + +[dependencies.uucore] +version = "0.0.1" +features = ["encoding"] + +[[bin]] +name = "base64" +path = "../../uumain.rs" diff --git a/coreutils/src/base64/base64.rs b/coreutils/src/base64/base64.rs new file mode 100644 index 000000000..9072fecb0 --- /dev/null +++ b/coreutils/src/base64/base64.rs @@ -0,0 +1,33 @@ +#![crate_name = "uu_base64"] + +// This file is part of the uutils coreutils package. +// +// (c) Jordy Dickinson +// (c) Jian Zeng +// +// 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 uucore::encoding::Format; + +mod base_common; + +static SYNTAX: &str = "[OPTION]... [FILE]"; +static SUMMARY: &str = + "Base64 encode or decode FILE, or standard input, to standard output."; +static LONG_HELP: &str = " + With no FILE, or when FILE is -, read standard input. + + The data are encoded as described for the base64 alphabet in RFC + 3548. When decoding, the input may contain newlines in addition + to the bytes of the formal base64 alphabet. Use --ignore-garbage + to attempt to recover from any other non-alphabet bytes in the + encoded stream. +"; + +pub fn uumain(args: Vec) -> i32 { + base_common::execute(args, SYNTAX, SUMMARY, LONG_HELP, Format::Base64) +} diff --git a/coreutils/src/base64/base_common.rs b/coreutils/src/base64/base_common.rs new file mode 100644 index 000000000..040b91608 --- /dev/null +++ b/coreutils/src/base64/base_common.rs @@ -0,0 +1,91 @@ +// This file is part of the uutils coreutils package. +// +// (c) Jordy Dickinson +// (c) Jian Zeng +// (c) Alex Lyon +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +use uucore; +use uucore::encoding::{wrap_print, Data, Format}; + +use std::fs::File; +use std::io::{stdin, BufReader, Read}; +use std::path::Path; + +pub fn execute( + args: Vec, + syntax: &str, + summary: &str, + long_help: &str, + format: Format, +) -> i32 { + let matches = new_coreopts!(syntax, summary, long_help) + .optflag("d", "decode", "decode data") + .optflag( + "i", + "ignore-garbage", + "when decoding, ignore non-alphabetic characters", + ) + .optopt( + "w", + "wrap", + "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", + "COLS", + ) + .parse(args); + + let line_wrap = matches.opt_str("wrap").map(|s| { + match s.parse() { + Ok(n) => n, + Err(e) => { + crash!(1, "invalid wrap size: ‘{}’: {}", s, e); + } + } + }); + let ignore_garbage = matches.opt_present("ignore-garbage"); + let decode = matches.opt_present("decode"); + + if matches.free.len() > 1 { + disp_err!("extra operand ‘{}’", matches.free[0]); + return 1; + } + + if matches.free.is_empty() || &matches.free[0][..] == "-" { + let stdin_raw = stdin(); + handle_input(&mut stdin_raw.lock(), format, line_wrap, ignore_garbage, decode); + } else { + let path = Path::new(matches.free[0].as_str()); + let file_buf = safe_unwrap!(File::open(&path)); + let mut input = BufReader::new(file_buf); + handle_input(&mut input, format, line_wrap, ignore_garbage, decode); + }; + + 0 +} + +fn handle_input( + input: &mut R, + format: Format, + line_wrap: Option, + ignore_garbage: bool, + decode: bool, +) { + let mut data = Data::new(input, format) + .ignore_garbage(ignore_garbage); + if let Some(wrap) = line_wrap { + data = data.line_wrap(wrap); + } + + if !decode { + let encoded = data.encode(); + wrap_print(&data, encoded); + } else { + match data.decode() { + Ok(s) => print!("{}", String::from_utf8(s).unwrap()), + Err(_) => crash!(1, "invalid input"), + } + } +} \ No newline at end of file diff --git a/coreutils/src/basename/Cargo.toml b/coreutils/src/basename/Cargo.toml new file mode 100644 index 000000000..1e3cb7adc --- /dev/null +++ b/coreutils/src/basename/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "basename" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_basename" +path = "basename.rs" + +[dependencies] +uucore = "0.0.1" + +[[bin]] +name = "basename" +path = "../../uumain.rs" diff --git a/coreutils/src/basename/basename.rs b/coreutils/src/basename/basename.rs new file mode 100644 index 000000000..9711efea3 --- /dev/null +++ b/coreutils/src/basename/basename.rs @@ -0,0 +1,124 @@ +#![crate_name = "uu_basename"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jimmy Lu + * + * 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 std::path::{is_separator, PathBuf}; + +static NAME: &str = "basename"; +static SYNTAX: &str = "NAME [SUFFIX]"; +static SUMMARY: &str = "Print NAME with any leading directory components removed + If specified, also remove a trailing SUFFIX"; +static LONG_HELP: &str = ""; + +pub fn uumain(args: Vec) -> i32 { + // + // Argument parsing + // + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optflag( + "a", + "multiple", + "Support more than one argument. Treat every argument as a name.", + ) + .optopt( + "s", + "suffix", + "Remove a trailing suffix. This option implies the -a option.", + "SUFFIX", + ) + .optflag( + "z", + "zero", + "Output a zero byte (ASCII NUL) at the end of each line, rather than a newline.", + ) + .parse(args); + + // too few arguments + if matches.free.is_empty() { + crash!( + 1, + "{0}: {1}\nTry '{0} --help' for more information.", + NAME, + "missing operand" + ); + } + let opt_s = matches.opt_present("s"); + let opt_a = matches.opt_present("a"); + let opt_z = matches.opt_present("z"); + let multiple_paths = opt_s || opt_a; + // too many arguments + if !multiple_paths && matches.free.len() > 2 { + crash!( + 1, + "{0}: extra operand '{1}'\nTry '{0} --help' for more information.", + NAME, + matches.free[2] + ); + } + + let suffix = if opt_s { + matches.opt_str("s").unwrap() + } else if !opt_a && matches.free.len() > 1 { + matches.free[1].clone() + } else { + "".to_owned() + }; + + // + // Main Program Processing + // + + let paths = if multiple_paths { + &matches.free[..] + } else { + &matches.free[0..1] + }; + + let line_ending = if opt_z { "\0" } else { "\n" }; + for path in paths { + print!("{}{}", basename(&path, &suffix), line_ending); + } + + 0 +} + +fn basename(fullname: &str, suffix: &str) -> String { + // Remove all platform-specific path separators from the end + let mut path: String = fullname + .chars() + .rev() + .skip_while(|&ch| is_separator(ch)) + .collect(); + + // Undo reverse + path = path.chars().rev().collect(); + + // Convert to path buffer and get last path component + let pb = PathBuf::from(path); + match pb.components().last() { + Some(c) => strip_suffix(c.as_os_str().to_str().unwrap(), suffix), + None => "".to_owned(), + } +} + +fn strip_suffix(name: &str, suffix: &str) -> String { + if name == suffix { + return name.to_owned(); + } + + if name.ends_with(suffix) { + return name[..name.len() - suffix.len()].to_owned(); + } + + name.to_owned() +} diff --git a/coreutils/src/cat/Cargo.toml b/coreutils/src/cat/Cargo.toml new file mode 100644 index 000000000..e4a37d1f7 --- /dev/null +++ b/coreutils/src/cat/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "cat" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_cat" +path = "cat.rs" + +[dependencies] +quick-error = "1.2.2" + +[dependencies.uucore] +version = "0.0.1" +features = ["fs"] + +[target.'cfg(unix)'.dependencies] +unix_socket = "0.5.0" + +[[bin]] +name = "cat" +path = "../../uumain.rs" diff --git a/coreutils/src/cat/cat.rs b/coreutils/src/cat/cat.rs new file mode 100755 index 000000000..37762fb8e --- /dev/null +++ b/coreutils/src/cat/cat.rs @@ -0,0 +1,469 @@ +#![crate_name = "uu_cat"] + +// This file is part of the uutils coreutils package. +// +// (c) Jordi Boggiano +// (c) Evgeniy Klyuchikov +// (c) Joshua S. Miller +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// + +#[macro_use] +extern crate quick_error; +#[cfg(unix)] +extern crate unix_socket; +#[macro_use] +extern crate uucore; + +// last synced with: cat (GNU coreutils) 8.13 +use quick_error::ResultExt; +use std::fs::{metadata, File}; +use std::io::{self, stderr, stdin, stdout, BufWriter, Read, Write}; +use uucore::fs::is_stdin_interactive; + +/// Unix domain socket support +#[cfg(unix)] +use std::net::Shutdown; +#[cfg(unix)] +use std::os::unix::fs::FileTypeExt; +#[cfg(unix)] +use unix_socket::UnixStream; + +static SYNTAX: &str = "[OPTION]... [FILE]..."; +static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output + With no FILE, or when FILE is -, read standard input."; +static LONG_HELP: &str = ""; + +#[derive(PartialEq)] +enum NumberingMode { + NumberNone, + NumberNonEmpty, + NumberAll, +} + +quick_error! { + #[derive(Debug)] + enum CatError { + /// Wrapper for io::Error with path context + Input(err: io::Error, path: String) { + display("cat: {0}: {1}", path, err) + context(path: &'a str, err: io::Error) -> (err, path.to_owned()) + cause(err) + } + + /// Wrapper for io::Error with no context + Output(err: io::Error) { + display("cat: {0}", err) from() + cause(err) + } + + /// Uknown Filetype classification + UnknownFiletype(path: String) { + display("cat: {0}: unknown filetype", path) + } + + /// At least one error was encountered in reading or writing + EncounteredErrors(count: usize) { + display("cat: encountered {0} errors", count) + } + + /// Denotes an error caused by trying to `cat` a directory + IsDirectory(path: String) { + display("cat: {0}: Is a directory", path) + } + } +} + +struct OutputOptions { + /// Line numbering mode + number: NumberingMode, + + /// Suppress repeated empty output lines + squeeze_blank: bool, + + /// display TAB characters as `tab` + show_tabs: bool, + + /// If `show_tabs == true`, this string will be printed in the + /// place of tabs + tab: String, + + /// Can be set to show characters other than '\n' a the end of + /// each line, e.g. $ + end_of_line: String, + + /// use ^ and M- notation, except for LF (\\n) and TAB (\\t) + show_nonprint: bool, +} + +/// Represents an open file handle, stream, or other device +struct InputHandle { + reader: Box, + is_interactive: bool, +} + +/// Concrete enum of recognized file types. +/// +/// *Note*: `cat`-ing a directory should result in an +/// CatError::IsDirectory +enum InputType { + Directory, + File, + StdIn, + SymLink, + #[cfg(unix)] + BlockDevice, + #[cfg(unix)] + CharacterDevice, + #[cfg(unix)] + Fifo, + #[cfg(unix)] + Socket, +} + +type CatResult = Result; + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optflag("A", "show-all", "equivalent to -vET") + .optflag( + "b", + "number-nonblank", + "number nonempty output lines, overrides -n", + ) + .optflag("e", "", "equivalent to -vE") + .optflag("E", "show-ends", "display $ at end of each line") + .optflag("n", "number", "number all output lines") + .optflag("s", "squeeze-blank", "suppress repeated empty output lines") + .optflag("t", "", "equivalent to -vT") + .optflag("T", "show-tabs", "display TAB characters as ^I") + .optflag( + "v", + "show-nonprinting", + "use ^ and M- notation, except for LF (\\n) and TAB (\\t)", + ) + .parse(args); + + let number_mode = if matches.opt_present("b") { + NumberingMode::NumberNonEmpty + } else if matches.opt_present("n") { + NumberingMode::NumberAll + } else { + NumberingMode::NumberNone + }; + + let show_nonprint = matches.opts_present(&[ + "A".to_owned(), + "e".to_owned(), + "t".to_owned(), + "v".to_owned(), + ]); + let show_ends = matches.opts_present(&["E".to_owned(), "A".to_owned(), "e".to_owned()]); + let show_tabs = matches.opts_present(&["A".to_owned(), "T".to_owned(), "t".to_owned()]); + let squeeze_blank = matches.opt_present("s"); + let mut files = matches.free; + if files.is_empty() { + files.push("-".to_owned()); + } + + let can_write_fast = !(show_tabs || show_nonprint || show_ends || squeeze_blank + || number_mode != NumberingMode::NumberNone); + + let success = if can_write_fast { + write_fast(files).is_ok() + } else { + let tab = if show_tabs { "^I" } else { "\t" }.to_owned(); + + let end_of_line = if show_ends { "$\n" } else { "\n" }.to_owned(); + + let options = OutputOptions { + end_of_line, + number: number_mode, + show_nonprint, + show_tabs, + squeeze_blank, + tab, + }; + + write_lines(files, &options).is_ok() + }; + + if success { 0 } else { 1 } +} + +/// Classifies the `InputType` of file at `path` if possible +/// +/// # Arguments +/// +/// * `path` - Path on a file system to classify metadata +fn get_input_type(path: &str) -> CatResult { + if path == "-" { + return Ok(InputType::StdIn); + } + + match metadata(path).context(path)?.file_type() { + #[cfg(unix)] + ft if ft.is_block_device() => + { + Ok(InputType::BlockDevice) + } + #[cfg(unix)] + ft if ft.is_char_device() => + { + Ok(InputType::CharacterDevice) + } + #[cfg(unix)] + ft if ft.is_fifo() => + { + Ok(InputType::Fifo) + } + #[cfg(unix)] + ft if ft.is_socket() => + { + Ok(InputType::Socket) + } + ft if ft.is_dir() => Ok(InputType::Directory), + ft if ft.is_file() => Ok(InputType::File), + ft if ft.is_symlink() => Ok(InputType::SymLink), + _ => Err(CatError::UnknownFiletype(path.to_owned())), + } +} + +/// Returns an InputHandle from which a Reader can be accessed or an +/// error +/// +/// # Arguments +/// +/// * `path` - `InputHandler` will wrap a reader from this file path +fn open(path: &str) -> CatResult { + if path == "-" { + let stdin = stdin(); + return Ok(InputHandle { + reader: Box::new(stdin) as Box, + is_interactive: is_stdin_interactive(), + }); + } + + match get_input_type(path)? { + InputType::Directory => Err(CatError::IsDirectory(path.to_owned())), + #[cfg(unix)] + InputType::Socket => { + let socket = UnixStream::connect(path).context(path)?; + socket.shutdown(Shutdown::Write).context(path)?; + Ok(InputHandle { + reader: Box::new(socket) as Box, + is_interactive: false, + }) + } + _ => { + let file = File::open(path).context(path)?; + Ok(InputHandle { + reader: Box::new(file) as Box, + is_interactive: false, + }) + } + } +} + +/// Writes files to stdout with no configuration. This allows a +/// simple memory copy. Returns `Ok(())` if no errors were +/// encountered, or an error with the number of errors encountered. +/// +/// # Arguments +/// +/// * `files` - There is no short circuit when encountiner an error +/// reading a file in this vector +fn write_fast(files: Vec) -> CatResult<()> { + let mut writer = stdout(); + let mut in_buf = [0; 1024 * 64]; + let mut error_count = 0; + + for file in files { + match open(&file[..]) { + Ok(mut handle) => while let Ok(n) = handle.reader.read(&mut in_buf) { + if n == 0 { + break; + } + writer.write_all(&in_buf[..n]).context(&file[..])?; + }, + Err(error) => { + writeln!(&mut stderr(), "{}", error)?; + error_count += 1; + } + } + } + + match error_count { + 0 => Ok(()), + _ => Err(CatError::EncounteredErrors(error_count)), + } +} + +/// State that persists between output of each file +struct OutputState { + /// The current line number + line_number: usize, + + /// Whether the output cursor is at the beginning of a new line + at_line_start: bool, +} + +/// Writes files to stdout with `options` as configuration. Returns +/// `Ok(())` if no errors were encountered, or an error with the +/// number of errors encountered. +/// +/// # Arguments +/// +/// * `files` - There is no short circuit when encountiner an error +/// reading a file in this vector +fn write_lines(files: Vec, options: &OutputOptions) -> CatResult<()> { + let mut error_count = 0; + let mut state = OutputState { + line_number: 1, + at_line_start: true, + }; + + for file in files { + if let Err(error) = write_file_lines(&file, options, &mut state) { + writeln!(&mut stderr(), "{}", error).context(&file[..])?; + error_count += 1; + } + } + + match error_count { + 0 => Ok(()), + _ => Err(CatError::EncounteredErrors(error_count)), + } +} + +/// Outputs file contents to stdout in a linewise fashion, +/// propagating any errors that might occur. +fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> { + let mut handle = open(file)?; + let mut in_buf = [0; 1024 * 31]; + let mut writer = BufWriter::with_capacity(1024 * 64, stdout()); + let mut one_blank_kept = false; + + while let Ok(n) = handle.reader.read(&mut in_buf) { + if n == 0 { + break; + } + let in_buf = &in_buf[..n]; + let mut pos = 0; + while pos < n { + // skip empty line_number enumerating them if needed + if in_buf[pos] == b'\n' { + if !state.at_line_start || !options.squeeze_blank || !one_blank_kept { + one_blank_kept = true; + if state.at_line_start && options.number == NumberingMode::NumberAll { + write!(&mut writer, "{0:6}\t", state.line_number)?; + state.line_number += 1; + } + writer.write_all(options.end_of_line.as_bytes())?; + if handle.is_interactive { + writer.flush().context(&file[..])?; + } + } + state.at_line_start = true; + pos += 1; + continue; + } + one_blank_kept = false; + if state.at_line_start && options.number != NumberingMode::NumberNone { + write!(&mut writer, "{0:6}\t", state.line_number)?; + state.line_number += 1; + } + + // print to end of line or end of buffer + let offset = if options.show_nonprint { + write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab.as_bytes()) + } else if options.show_tabs { + write_tab_to_end(&in_buf[pos..], &mut writer) + } else { + write_to_end(&in_buf[pos..], &mut writer) + }; + // end of buffer? + if offset == 0 { + state.at_line_start = false; + break; + } + // print suitable end of line + writer.write_all(options.end_of_line.as_bytes())?; + if handle.is_interactive { + writer.flush()?; + } + state.at_line_start = true; + pos += offset; + } + } + + Ok(()) +} + +// write***_to_end methods +// Write all symbols till end of line or end of buffer is reached +// Return the (number of written symbols + 1) or 0 if the end of buffer is reached +fn write_to_end(in_buf: &[u8], writer: &mut W) -> usize { + match in_buf.iter().position(|c| *c == b'\n') { + Some(p) => { + writer.write_all(&in_buf[..p]).unwrap(); + p + 1 + } + None => { + writer.write_all(in_buf).unwrap(); + 0 + } + } +} + +fn write_tab_to_end(mut in_buf: &[u8], writer: &mut W) -> usize { + let mut count = 0; + loop { + match in_buf + .iter() + .position(|c| *c == b'\n' || *c == b'\t') + { + Some(p) => { + writer.write_all(&in_buf[..p]).unwrap(); + if in_buf[p] == b'\n' { + return count + p + 1; + } else { + writer.write_all(b"^I").unwrap(); + in_buf = &in_buf[p + 1..]; + count += p + 1; + } + } + None => { + writer.write_all(in_buf).unwrap(); + return 0; + } + }; + } +} + +fn write_nonprint_to_end(in_buf: &[u8], writer: &mut W, tab: &[u8]) -> usize { + let mut count = 0; + + for byte in in_buf.iter().map(|c| *c) { + if byte == b'\n' { + break; + } + match byte { + 9 => writer.write_all(tab), + 0...8 | 10...31 => writer.write_all(&[b'^', byte + 64]), + 32...126 => writer.write_all(&[byte]), + 127 => writer.write_all(&[b'^', byte - 64]), + 128...159 => writer.write_all(&[b'M', b'-', b'^', byte - 64]), + 160...254 => writer.write_all(&[b'M', b'-', byte - 128]), + _ => writer.write_all(&[b'M', b'-', b'^', 63]), + }.unwrap(); + count += 1; + } + if count != in_buf.len() { + count + 1 + } else { + 0 + } +} diff --git a/coreutils/src/chgrp/Cargo.toml b/coreutils/src/chgrp/Cargo.toml new file mode 100644 index 000000000..caea10ccf --- /dev/null +++ b/coreutils/src/chgrp/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "chgrp" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_chgrp" +path = "chgrp.rs" + +[dependencies] +walkdir = "2.2.8" + +[dependencies.uucore] +version = "0.0.1" +features = ["entries", "fs"] + +[[bin]] +name = "chgrp" +path = "../../uumain.rs" diff --git a/coreutils/src/chgrp/chgrp.rs b/coreutils/src/chgrp/chgrp.rs new file mode 100644 index 000000000..ed50432d3 --- /dev/null +++ b/coreutils/src/chgrp/chgrp.rs @@ -0,0 +1,372 @@ +#![crate_name = "uu_chgrp"] + +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// 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 uucore::libc::{self, gid_t, lchown}; +pub use uucore::entries; +use uucore::fs::resolve_relative_path; + +extern crate walkdir; +use walkdir::WalkDir; + +use std::io::Result as IOResult; +use std::io::Error as IOError; + +use std::fs; +use std::fs::Metadata; +use std::os::unix::fs::MetadataExt; + +use std::path::Path; + +use std::ffi::CString; +use std::os::unix::ffi::OsStrExt; + +static SYNTAX: &str = + "chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE..."; +static SUMMARY: &str = "Change the group of each FILE to GROUP."; + +const FTS_COMFOLLOW: u8 = 1; +const FTS_PHYSICAL: u8 = 1 << 1; +const FTS_LOGICAL: u8 = 1 << 2; + +pub fn uumain(args: Vec) -> i32 { + let mut opts = new_coreopts!(SYNTAX, SUMMARY, ""); + opts.optflag("c", + "changes", + "like verbose but report only when a change is made") + .optflag("f", "silent", "") + .optflag("", "quiet", "suppress most error messages") + .optflag("v", + "verbose", + "output a diagnostic for every file processed") + .optflag("", "dereference", "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself") + .optflag("h", "no-dereference", "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)") + .optflag("", + "no-preserve-root", + "do not treat '/' specially (the default)") + .optflag("", "preserve-root", "fail to operate recursively on '/'") + .optopt("", + "reference", + "use RFILE's owner and group rather than specifying OWNER:GROUP values", + "RFILE") + .optflag("R", + "recursive", + "operate on files and directories recursively") + .optflag("H", + "", + "if a command line argument is a symbolic link to a directory, traverse it") + .optflag("L", + "", + "traverse every symbolic link to a directory encountered") + .optflag("P", "", "do not traverse any symbolic links (default)"); + + let mut bit_flag = FTS_PHYSICAL; + let mut preserve_root = false; + let mut derefer = -1; + let flags: &[char] = &['H', 'L', 'P']; + for opt in &args { + match opt.as_str() { + // If more than one is specified, only the final one takes effect. + s if s.contains(flags) => { + if let Some(idx) = s.rfind(flags) { + match s.chars().nth(idx).unwrap() { + 'H' => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL, + 'L' => bit_flag = FTS_LOGICAL, + 'P' => bit_flag = FTS_PHYSICAL, + _ => (), + } + } + } + "--no-preserve-root" => preserve_root = false, + "--preserve-root" => preserve_root = true, + "--dereference" => derefer = 1, + "--no-dereference" => derefer = 0, + _ => (), + } + } + + let matches = opts.parse(args); + let recursive = matches.opt_present("recursive"); + if recursive { + if bit_flag == FTS_PHYSICAL { + if derefer == 1 { + show_info!("-R --dereference requires -H or -L"); + return 1; + } + derefer = 0; + } + } else { + bit_flag = FTS_PHYSICAL; + } + + let verbosity = if matches.opt_present("changes") { + Verbosity::Changes + } else if matches.opt_present("silent") || matches.opt_present("quiet") { + Verbosity::Silent + } else if matches.opt_present("verbose") { + Verbosity::Verbose + } else { + Verbosity::Normal + }; + + if matches.free.is_empty() { + disp_err!("missing operand"); + return 1; + } else if matches.free.len() < 2 && !matches.opt_present("reference") { + disp_err!("missing operand after ‘{}’", matches.free[0]); + return 1; + } + + let dest_gid: gid_t; + let mut files; + if let Some(file) = matches.opt_str("reference") { + match fs::metadata(&file) { + Ok(meta) => { + dest_gid = meta.gid(); + } + Err(e) => { + show_info!("failed to get attributes of '{}': {}", file, e); + return 1; + } + } + files = matches.free; + } else { + match entries::grp2gid(&matches.free[0]) { + Ok(g) => { + dest_gid = g; + } + _ => { + show_info!("invalid group: {}", matches.free[0].as_str()); + return 1; + } + } + files = matches.free; + files.remove(0); + } + + let executor = Chgrper { + bit_flag, + dest_gid, + verbosity, + recursive, + dereference: derefer != 0, + preserve_root, + files, + }; + executor.exec() +} + +#[derive(PartialEq, Debug)] +enum Verbosity { + Silent, + Changes, + Verbose, + Normal, +} + +struct Chgrper { + dest_gid: gid_t, + bit_flag: u8, + verbosity: Verbosity, + files: Vec, + recursive: bool, + preserve_root: bool, + dereference: bool, +} + +macro_rules! unwrap { + ($m:expr, $e:ident, $err:block) => ( + match $m { + Ok(meta) => meta, + Err($e) => $err, + } + ) +} + +impl Chgrper { + fn exec(&self) -> i32 { + let mut ret = 0; + for f in &self.files { + ret |= self.traverse(f); + } + ret + } + + fn chgrp>(&self, path: P, dgid: gid_t, follow: bool) -> IOResult<()> { + let path = path.as_ref(); + let s = CString::new(path.as_os_str().as_bytes()).unwrap(); + let ret = unsafe { + if follow { + libc::chown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid) + } else { + lchown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid) + } + }; + if ret == 0 { + Ok(()) + } else { + Err(IOError::last_os_error()) + } + } + + #[cfg(windows)] + fn is_bind_root>(&self, root: P) -> bool { + // TODO: is there an equivalent on Windows? + false + } + + #[cfg(unix)] + fn is_bind_root>(&self, path: P) -> bool { + if let (Ok(given), Ok(root)) = (fs::metadata(path), fs::metadata("/")) { + given.dev() == root.dev() && given.ino() == root.ino() + } else { + // FIXME: not totally sure if it's okay to just ignore an error here + false + } + } + + fn traverse>(&self, root: P) -> i32 { + let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL; + let path = root.as_ref(); + let meta = match self.obtain_meta(path, follow_arg) { + Some(m) => m, + _ => return 1, + }; + + // Prohibit only if: + // (--preserve-root and -R present) && + // ( + // (argument is not symlink && resolved to be '/') || + // (argument is symlink && should follow argument && resolved to be '/') + // ) + if self.recursive && self.preserve_root { + let may_exist = if follow_arg { + path.canonicalize().ok() + } else { + let real = resolve_relative_path(path); + if real.is_dir() { + Some(real.canonicalize().expect("failed to get real path")) + } else { + Some(real.into_owned()) + } + }; + + if let Some(p) = may_exist { + if p.parent().is_none() || self.is_bind_root(p) { + show_info!("it is dangerous to operate recursively on '/'"); + show_info!("use --no-preserve-root to override this failsafe"); + return 1; + } + } + } + + let ret = self.wrap_chgrp(path, &meta, follow_arg); + + if !self.recursive { + ret + } else { + ret | self.dive_into(&root) + } + } + + fn dive_into>(&self, root: P) -> i32 { + let mut ret = 0; + let root = root.as_ref(); + let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0; + for entry in WalkDir::new(root).follow_links(follow).min_depth(1) { + let entry = unwrap!(entry, e, { + ret = 1; + show_info!("{}", e); + continue; + }); + let path = entry.path(); + let meta = match self.obtain_meta(path, follow) { + Some(m) => m, + _ => { + ret = 1; + continue; + } + }; + + ret = self.wrap_chgrp(path, &meta, follow); + } + ret + } + + fn obtain_meta>(&self, path: P, follow: bool) -> Option { + use self::Verbosity::*; + let path = path.as_ref(); + let meta = if follow { + unwrap!(path.metadata(), e, { + match self.verbosity { + Silent => (), + _ => show_info!("cannot access '{}': {}", path.display(), e), + } + return None; + }) + } else { + unwrap!(path.symlink_metadata(), e, { + match self.verbosity { + Silent => (), + _ => show_info!("cannot dereference '{}': {}", path.display(), e), + } + return None; + }) + }; + Some(meta) + } + + fn wrap_chgrp>(&self, path: P, meta: &Metadata, follow: bool) -> i32 { + use self::Verbosity::*; + let mut ret = 0; + let dest_gid = self.dest_gid; + let path = path.as_ref(); + if let Err(e) = self.chgrp(path, dest_gid, follow) { + match self.verbosity { + Silent => (), + _ => { + show_info!("changing group of '{}': {}", path.display(), e); + if self.verbosity == Verbose { + println!( + "failed to change group of {} from {} to {}", + path.display(), + entries::gid2grp(meta.gid()).unwrap(), + entries::gid2grp(dest_gid).unwrap() + ); + }; + } + } + ret = 1; + } else { + let changed = dest_gid != meta.gid(); + if changed { + match self.verbosity { + Changes | Verbose => { + println!( + "changed group of {} from {} to {}", + path.display(), + entries::gid2grp(meta.gid()).unwrap(), + entries::gid2grp(dest_gid).unwrap() + ); + } + _ => (), + }; + } else if self.verbosity == Verbose { + println!( + "group of {} retained as {}", + path.display(), + entries::gid2grp(dest_gid).unwrap() + ); + } + } + ret + } +} diff --git a/coreutils/src/chmod/Cargo.toml b/coreutils/src/chmod/Cargo.toml new file mode 100644 index 000000000..0df9d54a8 --- /dev/null +++ b/coreutils/src/chmod/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "chmod" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_chmod" +path = "chmod.rs" + +[dependencies] +libc = "0.2.42" +walker = "1.0.0" + +[dependencies.uucore] +version = "0.0.1" +features = ["mode"] + +[[bin]] +name = "chmod" +path = "../../uumain.rs" diff --git a/coreutils/src/chmod/chmod.rs b/coreutils/src/chmod/chmod.rs new file mode 100644 index 000000000..b428285bd --- /dev/null +++ b/coreutils/src/chmod/chmod.rs @@ -0,0 +1,267 @@ +#![crate_name = "uu_chmod"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[cfg(unix)] +extern crate libc; +extern crate walker; + +#[macro_use] +extern crate uucore; + +use std::fs; +use std::os::unix::fs::{MetadataExt, PermissionsExt}; +use std::path::Path; +use walker::Walker; +#[cfg(not(windows))] +use uucore::mode; +use uucore::fs::display_permissions_unix; + +const NAME: &str = "chmod"; +static SUMMARY: &str = "Change the mode of each FILE to MODE. + With --reference, change the mode of each FILE to that of RFILE."; +static LONG_HELP: &str = " + Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'. +"; + +pub fn uumain(mut args: Vec) -> i32 { + let syntax = format!( + "[OPTION]... MODE[,MODE]... FILE... + {0} [OPTION]... OCTAL-MODE FILE... + {0} [OPTION]... --reference=RFILE FILE...", + NAME + ); + let mut opts = new_coreopts!(&syntax, SUMMARY, LONG_HELP); + opts.optflag("c", "changes", "like verbose but report only when a change is made") + // TODO: support --silent (can be done using clap) + .optflag("f", "quiet", "suppress most error messages") + .optflag("v", "verbose", "output a diagnostic for every file processed") + .optflag("", "no-preserve-root", "do not treat '/' specially (the default)") + .optflag("", "preserve-root", "fail to operate recursively on '/'") + .optopt("", "reference", "use RFILE's mode instead of MODE values", "RFILE") + .optflag("R", "recursive", "change files and directories recursively"); + + // sanitize input for - at beginning (e.g. chmod -x testfile). Remove + // the option and save it for later, after parsing is finished. + let negative_option = sanitize_input(&mut args); + + let mut matches = opts.parse(args); + if matches.free.is_empty() { + show_error!("missing an argument"); + show_error!("for help, try '{} --help'", NAME); + return 1; + } else { + let changes = matches.opt_present("changes"); + let quiet = matches.opt_present("quiet"); + let verbose = matches.opt_present("verbose"); + let preserve_root = matches.opt_present("preserve-root"); + let recursive = matches.opt_present("recursive"); + let fmode = matches + .opt_str("reference") + .and_then(|ref fref| match fs::metadata(fref) { + Ok(meta) => Some(meta.mode()), + Err(err) => crash!(1, "cannot stat attribues of '{}': {}", fref, err), + }); + let cmode = if fmode.is_none() { + // If there was a negative option, now it's a good time to + // use it. + if negative_option.is_some() { + negative_option + } else { + Some(matches.free.remove(0)) + } + } else { + None + }; + let chmoder = Chmoder { + changes, + quiet, + verbose, + preserve_root, + recursive, + fmode, + cmode, + }; + match chmoder.chmod(matches.free) { + Ok(()) => {} + Err(e) => return e, + } + } + + 0 +} + +fn sanitize_input(args: &mut Vec) -> Option { + for i in 0..args.len() { + let first = args[i].chars().nth(0).unwrap(); + if first != '-' { + continue; + } + if let Some(second) = args[i].chars().nth(1) { + match second { + 'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'...'7' => { + return Some(args.remove(i)); + } + _ => {} + } + } + } + None +} + +struct Chmoder { + changes: bool, + quiet: bool, + verbose: bool, + preserve_root: bool, + recursive: bool, + fmode: Option, + cmode: Option, +} + +impl Chmoder { + fn chmod(&self, files: Vec) -> Result<(), i32> { + let mut r = Ok(()); + + for filename in &files { + let filename = &filename[..]; + let file = Path::new(filename); + if file.exists() { + if file.is_dir() { + if !self.preserve_root || filename != "/" { + if self.recursive { + let walk_dir = match Walker::new(&file) { + Ok(m) => m, + Err(f) => { + crash!(1, "{}", f.to_string()); + } + }; + // XXX: here (and elsewhere) we see that this impl will have issues + // with non-UTF-8 filenames. Using OsString won't fix this because + // on Windows OsStrings cannot be built out of non-UTF-8 chars. One + // possible fix is to use CStrings rather than Strings in the args + // to chmod() and chmod_file(). + r = self.chmod( + walk_dir + .filter_map(|x| match x { + Ok(o) => match o.path().into_os_string().to_str() { + Some(s) => Some(s.to_owned()), + None => None, + }, + Err(_) => None, + }) + .collect(), + ).and(r); + r = self.chmod_file(&file, filename).and(r); + } + } else { + show_error!("could not change permissions of directory '{}'", filename); + r = Err(1); + } + } else { + r = self.chmod_file(&file, filename).and(r); + } + } else { + show_error!("no such file or directory '{}'", filename); + r = Err(1); + } + } + + r + } + + #[cfg(windows)] + fn chmod_file(&self, file: &Path, name: &str) -> Result<(), i32> { + // chmod is useless on Windows + // it doesn't set any permissions at all + // instead it just sets the readonly attribute on the file + Err(0) + } + #[cfg(any(unix, target_os = "redox"))] + fn chmod_file(&self, file: &Path, name: &str) -> Result<(), i32> { + let mut fperm = match fs::metadata(name) { + Ok(meta) => meta.mode() & 0o7777, + Err(err) => { + if !self.quiet { + show_error!("{}", err); + } + return Err(1); + } + }; + match self.fmode { + Some(mode) => self.change_file(fperm, mode, file, name)?, + None => { + let cmode_unwrapped = self.cmode.clone().unwrap(); + for mode in cmode_unwrapped.split(',') { + // cmode is guaranteed to be Some in this case + let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + let result = if mode.contains(arr) { + mode::parse_numeric(fperm, mode) + } else { + mode::parse_symbolic(fperm, mode, file.is_dir()) + }; + match result { + Ok(mode) => { + self.change_file(fperm, mode, file, name)?; + fperm = mode; + } + Err(f) => { + if !self.quiet { + show_error!("{}", f); + } + return Err(1); + } + } + } + } + } + + Ok(()) + } + + #[cfg(unix)] + fn change_file(&self, fperm: u32, mode: u32, file: &Path, path: &str) -> Result<(), i32> { + if fperm == mode { + if self.verbose && !self.changes { + show_info!("mode of '{}' retained as {:o} ({})", file.display(), fperm, display_permissions_unix(fperm)); + } + Ok(()) + } else if let Err(err) = + fs::set_permissions(Path::new(path), fs::Permissions::from_mode(mode)) + { + if !self.quiet { + show_error!("{}", err); + } + if self.verbose { + show_info!( + "failed to change mode of file '{}' from {:o} ({}) to {:o} ({})", + file.display(), + fperm, + display_permissions_unix(fperm), + mode, + display_permissions_unix(mode) + ); + } + Err(1) + } else { + if self.verbose || self.changes { + show_info!( + "mode of '{}' changed from {:o} ({}) to {:o} ({})", + file.display(), + fperm, + display_permissions_unix(fperm), + mode, + display_permissions_unix(mode) + ); + } + Ok(()) + } + } +} diff --git a/coreutils/src/chown/Cargo.toml b/coreutils/src/chown/Cargo.toml new file mode 100644 index 000000000..1e6ec9e36 --- /dev/null +++ b/coreutils/src/chown/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "chown" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_chown" +path = "chown.rs" + +[dependencies] +glob = "0.3.0" +walkdir = "2.2" + +[dependencies.uucore] +version = "0.0.1" +features = ["entries", "fs"] + +[dependencies.clippy] +version = "0.0.212" +optional = true + +[[bin]] +name = "chown" +path = "../../uumain.rs" diff --git a/coreutils/src/chown/chown.rs b/coreutils/src/chown/chown.rs new file mode 100644 index 000000000..ef6086c2c --- /dev/null +++ b/coreutils/src/chown/chown.rs @@ -0,0 +1,456 @@ +#![crate_name = "uu_chown"] +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// +#![cfg_attr(feature = "clippy", feature(plugin))] +#![cfg_attr(feature = "clippy", plugin(clippy))] + +#[macro_use] +extern crate uucore; +use uucore::libc::{self, gid_t, lchown, uid_t}; +pub use uucore::entries::{self, Group, Locate, Passwd}; +use uucore::fs::resolve_relative_path; + +extern crate walkdir; +use walkdir::WalkDir; + +use std::fs::{self, Metadata}; +use std::os::unix::fs::MetadataExt; + +use std::io; +use std::io::Result as IOResult; + +use std::path::Path; +use std::convert::AsRef; + +use std::ffi::CString; +use std::os::unix::ffi::OsStrExt; + +static SYNTAX: &str = + "[OPTION]... [OWNER][:[GROUP]] FILE...\n chown [OPTION]... --reference=RFILE FILE..."; +static SUMMARY: &str = "change file owner and group"; + +const FTS_COMFOLLOW: u8 = 1; +const FTS_PHYSICAL: u8 = 1 << 1; +const FTS_LOGICAL: u8 = 1 << 2; + +pub fn uumain(args: Vec) -> i32 { + let mut opts = new_coreopts!(SYNTAX, SUMMARY, ""); + opts.optflag("c", + "changes", + "like verbose but report only when a change is made") + .optflag("f", "silent", "") + .optflag("", "quiet", "suppress most error messages") + .optflag("v", + "verbose", + "output a diagnostic for every file processed") + .optflag("", "dereference", "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself") + .optflag("h", "no-dereference", "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)") + + .optopt("", "from", "change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute", "CURRENT_OWNER:CURRENT_GROUP") + .optopt("", + "reference", + "use RFILE's owner and group rather than specifying OWNER:GROUP values", + "RFILE") + .optflag("", + "no-preserve-root", + "do not treat '/' specially (the default)") + .optflag("", "preserve-root", "fail to operate recursively on '/'") + + .optflag("R", + "recursive", + "operate on files and directories recursively") + .optflag("H", + "", + "if a command line argument is a symbolic link to a directory, traverse it") + .optflag("L", + "", + "traverse every symbolic link to a directory encountered") + .optflag("P", "", "do not traverse any symbolic links (default)"); + + let mut bit_flag = FTS_PHYSICAL; + let mut preserve_root = false; + let mut derefer = -1; + let flags: &[char] = &['H', 'L', 'P']; + for opt in &args { + match opt.as_str() { + // If more than one is specified, only the final one takes effect. + s if s.contains(flags) => { + if let Some(idx) = s.rfind(flags) { + match s.chars().nth(idx).unwrap() { + 'H' => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL, + 'L' => bit_flag = FTS_LOGICAL, + 'P' => bit_flag = FTS_PHYSICAL, + _ => (), + } + } + } + "--no-preserve-root" => preserve_root = false, + "--preserve-root" => preserve_root = true, + "--dereference" => derefer = 1, + "--no-dereference" => derefer = 0, + _ => (), + } + } + + let matches = opts.parse(args); + let recursive = matches.opt_present("recursive"); + if recursive { + if bit_flag == FTS_PHYSICAL { + if derefer == 1 { + show_info!("-R --dereference requires -H or -L"); + return 1; + } + derefer = 0; + } + } else { + bit_flag = FTS_PHYSICAL; + } + + let verbosity = if matches.opt_present("changes") { + Verbosity::Changes + } else if matches.opt_present("silent") || matches.opt_present("quiet") { + Verbosity::Silent + } else if matches.opt_present("verbose") { + Verbosity::Verbose + } else { + Verbosity::Normal + }; + + let filter = if let Some(spec) = matches.opt_str("from") { + match parse_spec(&spec) { + Ok((Some(uid), None)) => IfFrom::User(uid), + Ok((None, Some(gid))) => IfFrom::Group(gid), + Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid), + Ok((None, None)) => IfFrom::All, + Err(e) => { + show_info!("{}", e); + return 1; + } + } + } else { + IfFrom::All + }; + + if matches.free.is_empty() { + disp_err!("missing operand"); + return 1; + } else if matches.free.len() < 2 && !matches.opt_present("reference") { + disp_err!("missing operand after ‘{}’", matches.free[0]); + return 1; + } + + let mut files; + let dest_uid: Option; + let dest_gid: Option; + if let Some(file) = matches.opt_str("reference") { + match fs::metadata(&file) { + Ok(meta) => { + dest_gid = Some(meta.gid()); + dest_uid = Some(meta.uid()); + } + Err(e) => { + show_info!("failed to get attributes of '{}': {}", file, e); + return 1; + } + } + files = matches.free; + } else { + match parse_spec(&matches.free[0]) { + Ok((u, g)) => { + dest_uid = u; + dest_gid = g; + } + Err(e) => { + show_info!("{}", e); + return 1; + } + } + files = matches.free; + files.remove(0); + } + let executor = Chowner { + bit_flag, + dest_uid, + dest_gid, + verbosity, + recursive, + dereference: derefer != 0, + filter, + preserve_root, + files, + }; + executor.exec() +} + +fn parse_spec(spec: &str) -> Result<(Option, Option), String> { + let args = spec.split(':').collect::>(); + let usr_only = args.len() == 1; + let grp_only = args.len() == 2 && args[0].is_empty() && !args[1].is_empty(); + let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty(); + + if usr_only { + Ok(( + Some(match Passwd::locate(args[0]) { + Ok(v) => v.uid(), + _ => return Err(format!("invalid user: ‘{}’", spec)), + }), + None, + )) + } else if grp_only { + Ok(( + None, + Some(match Group::locate(args[1]) { + Ok(v) => v.gid(), + _ => return Err(format!("invalid group: ‘{}’", spec)), + }), + )) + } else if usr_grp { + Ok(( + Some(match Passwd::locate(args[0]) { + Ok(v) => v.uid(), + _ => return Err(format!("invalid user: ‘{}’", spec)), + }), + Some(match Group::locate(args[1]) { + Ok(v) => v.gid(), + _ => return Err(format!("invalid group: ‘{}’", spec)), + }), + )) + } else { + Ok((None, None)) + } +} + +#[derive(PartialEq, Debug)] +enum Verbosity { + Silent, + Changes, + Verbose, + Normal, +} + +enum IfFrom { + All, + User(u32), + Group(u32), + UserGroup(u32, u32), +} + +struct Chowner { + dest_uid: Option, + dest_gid: Option, + bit_flag: u8, + verbosity: Verbosity, + filter: IfFrom, + files: Vec, + recursive: bool, + preserve_root: bool, + dereference: bool, +} + +macro_rules! unwrap { + ($m:expr, $e:ident, $err:block) => ( + match $m { + Ok(meta) => meta, + Err($e) => $err, + } + ) +} + +impl Chowner { + fn exec(&self) -> i32 { + let mut ret = 0; + for f in &self.files { + ret |= self.traverse(f); + } + ret + } + + fn chown>( + &self, + path: P, + duid: uid_t, + dgid: gid_t, + follow: bool, + ) -> IOResult<()> { + let path = path.as_ref(); + let s = CString::new(path.as_os_str().as_bytes()).unwrap(); + let ret = unsafe { + if follow { + libc::chown(s.as_ptr(), duid, dgid) + } else { + lchown(s.as_ptr(), duid, dgid) + } + }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + + fn traverse>(&self, root: P) -> i32 { + let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL; + let path = root.as_ref(); + let meta = match self.obtain_meta(path, follow_arg) { + Some(m) => m, + _ => return 1, + }; + + // Prohibit only if: + // (--preserve-root and -R present) && + // ( + // (argument is not symlink && resolved to be '/') || + // (argument is symlink && should follow argument && resolved to be '/') + // ) + if self.recursive && self.preserve_root { + let may_exist = if follow_arg { + path.canonicalize().ok() + } else { + let real = resolve_relative_path(path); + if real.is_dir() { + Some(real.canonicalize().expect("failed to get real path")) + } else { + Some(real.into_owned()) + } + }; + + if let Some(p) = may_exist { + if p.parent().is_none() { + show_info!("it is dangerous to operate recursively on '/'"); + show_info!("use --no-preserve-root to override this failsafe"); + return 1; + } + } + } + + let ret = if self.matched(meta.uid(), meta.gid()) { + self.wrap_chown(path, &meta, follow_arg) + } else { + 0 + }; + + if !self.recursive { + ret + } else { + ret | self.dive_into(&root) + } + } + + fn dive_into>(&self, root: P) -> i32 { + let mut ret = 0; + let root = root.as_ref(); + let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0; + for entry in WalkDir::new(root).follow_links(follow).min_depth(1) { + let entry = unwrap!(entry, e, { + ret = 1; + show_info!("{}", e); + continue; + }); + let path = entry.path(); + let meta = match self.obtain_meta(path, follow) { + Some(m) => m, + _ => { + ret = 1; + continue; + } + }; + + if !self.matched(meta.uid(), meta.gid()) { + continue; + } + + ret = self.wrap_chown(path, &meta, follow); + } + ret + } + + fn obtain_meta>(&self, path: P, follow: bool) -> Option { + use self::Verbosity::*; + let path = path.as_ref(); + let meta = if follow { + unwrap!(path.metadata(), e, { + match self.verbosity { + Silent => (), + _ => show_info!("cannot access '{}': {}", path.display(), e), + } + return None; + }) + } else { + unwrap!(path.symlink_metadata(), e, { + match self.verbosity { + Silent => (), + _ => show_info!("cannot dereference '{}': {}", path.display(), e), + } + return None; + }) + }; + Some(meta) + } + + fn wrap_chown>(&self, path: P, meta: &Metadata, follow: bool) -> i32 { + use self::Verbosity::*; + let mut ret = 0; + let dest_uid = self.dest_uid.unwrap_or(meta.uid()); + let dest_gid = self.dest_gid.unwrap_or(meta.gid()); + let path = path.as_ref(); + if let Err(e) = self.chown(path, dest_uid, dest_gid, follow) { + match self.verbosity { + Silent => (), + _ => { + show_info!("changing ownership of '{}': {}", path.display(), e); + if self.verbosity == Verbose { + println!( + "failed to change ownership of {} from {}:{} to {}:{}", + path.display(), + entries::uid2usr(meta.uid()).unwrap(), + entries::gid2grp(meta.gid()).unwrap(), + entries::uid2usr(dest_uid).unwrap(), + entries::gid2grp(dest_gid).unwrap() + ); + }; + } + } + ret = 1; + } else { + let changed = dest_uid != meta.uid() || dest_gid != meta.gid(); + if changed { + match self.verbosity { + Changes | Verbose => { + println!( + "changed ownership of {} from {}:{} to {}:{}", + path.display(), + entries::uid2usr(meta.uid()).unwrap(), + entries::gid2grp(meta.gid()).unwrap(), + entries::uid2usr(dest_uid).unwrap(), + entries::gid2grp(dest_gid).unwrap() + ); + } + _ => (), + }; + } else if self.verbosity == Verbose { + println!( + "ownership of {} retained as {}:{}", + path.display(), + entries::uid2usr(dest_uid).unwrap(), + entries::gid2grp(dest_gid).unwrap() + ); + } + } + ret + } + + #[inline] + fn matched(&self, uid: uid_t, gid: gid_t) -> bool { + match self.filter { + IfFrom::All => true, + IfFrom::User(u) => u == uid, + IfFrom::Group(g) => g == gid, + IfFrom::UserGroup(u, g) => u == uid && g == gid, + } + } +} diff --git a/coreutils/src/chroot/Cargo.toml b/coreutils/src/chroot/Cargo.toml new file mode 100644 index 000000000..6fd893277 --- /dev/null +++ b/coreutils/src/chroot/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "chroot" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_chroot" +path = "chroot.rs" + +[dependencies] +getopts = "0.2.18" + +[dependencies.uucore] +version = "0.0.1" +features = ["entries"] + +[[bin]] +name = "chroot" +path = "../../uumain.rs" diff --git a/coreutils/src/chroot/chroot.rs b/coreutils/src/chroot/chroot.rs new file mode 100644 index 000000000..a7973e8fe --- /dev/null +++ b/coreutils/src/chroot/chroot.rs @@ -0,0 +1,208 @@ +#![crate_name = "uu_chroot"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Vsevolod Velichko + * (c) Jian Zeng + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; +use uucore::libc::{self, chroot, setgid, setgroups, setuid}; +use uucore::entries; + +use std::ffi::CString; +use std::io::Error; +use std::iter::FromIterator; +use std::path::Path; +use std::process::Command; + +static NAME: &str = "chroot"; +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. +"; + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optopt( + "u", + "user", + "User (ID or name) to switch before running the program", + "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...", + ) + .optopt( + "", + "userspec", + "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", + ) + .parse(args); + + if matches.free.is_empty() { + println!("Missing operand: NEWROOT"); + println!("Try `{} --help` for more information.", NAME); + return 1; + } + + 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][..]); + if !newroot.is_dir() { + crash!( + 1, + "cannot change root directory to `{}`: no such directory", + newroot.display() + ); + } + + let command: Vec<&str> = match matches.free.len() { + 1 => { + let shell: &str = match user_shell { + Err(_) => default_shell, + Ok(ref s) => s.as_ref(), + }; + vec![shell, default_option] + } + _ => matches.free[1..].iter().map(|x| &x[..]).collect(), + }; + + set_context(&newroot, &matches); + + let pstatus = Command::new(command[0]) + .args(&command[1..]) + .status() + .unwrap_or_else(|e| crash!(1, "Cannot exec: {}", e)); + + if pstatus.success() { + 0 + } else { + match pstatus.code() { + Some(i) => i, + None => -1, + } + } +} + +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(); + let userspec = match userspec_str { + Some(ref u) => { + let s: Vec<&str> = u.split(':').collect(); + if s.len() != 2 { + crash!(1, "invalid userspec: `{}`", u) + }; + s + } + None => Vec::new(), + }; + let user = if userspec.is_empty() { + &user_str[..] + } else { + &userspec[0][..] + }; + let group = if userspec.is_empty() { + &group_str[..] + } else { + &userspec[1][..] + }; + + enter_chroot(root); + + set_groups_from_str(&groups_str[..]); + set_main_group(&group[..]); + set_user(&user[..]); +} + +fn enter_chroot(root: &Path) { + let root_str = root.display(); + std::env::set_current_dir(root).unwrap(); + let err = unsafe { + chroot(CString::new(".") + .unwrap() + .as_bytes_with_nul() + .as_ptr() as *const libc::c_char) + }; + if err != 0 { + crash!( + 1, + "cannot chroot to {}: {}", + root_str, + Error::last_os_error() + ) + }; +} + +fn set_main_group(group: &str) { + if !group.is_empty() { + let group_id = match entries::grp2gid(group) { + Ok(g) => g, + _ => crash!(1, "no such group: {}", group), + }; + let err = unsafe { setgid(group_id) }; + if err != 0 { + crash!( + 1, + "cannot set gid to {}: {}", + group_id, + Error::last_os_error() + ) + } + } +} + +#[cfg(any(target_os = "macos", target_os = "freebsd"))] +fn set_groups(groups: Vec) -> libc::c_int { + unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) } +} + +#[cfg(target_os = "linux")] +fn set_groups(groups: Vec) -> libc::c_int { + unsafe { setgroups(groups.len() as libc::size_t, groups.as_ptr()) } +} + +fn set_groups_from_str(groups: &str) { + if !groups.is_empty() { + let groups_vec: Vec = + FromIterator::from_iter(groups.split(',').map(|x| match entries::grp2gid(x) { + Ok(g) => g, + _ => crash!(1, "no such group: {}", x), + })); + let err = set_groups(groups_vec); + if err != 0 { + crash!(1, "cannot set groups: {}", Error::last_os_error()) + } + } +} + +fn set_user(user: &str) { + if !user.is_empty() { + let user_id = entries::usr2uid(user).unwrap(); + let err = unsafe { setuid(user_id as libc::uid_t) }; + if err != 0 { + crash!(1, "cannot set user to {}: {}", user, Error::last_os_error()) + } + } +} diff --git a/coreutils/src/cksum/Cargo.toml b/coreutils/src/cksum/Cargo.toml new file mode 100644 index 000000000..f608e4396 --- /dev/null +++ b/coreutils/src/cksum/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cksum" +version = "0.0.1" +authors = [] + +[lib] +name = "uu_cksum" +path = "cksum.rs" + +[dependencies] +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "cksum" +path = "../../uumain.rs" diff --git a/coreutils/src/cksum/build.rs b/coreutils/src/cksum/build.rs new file mode 100644 index 000000000..37c48db5c --- /dev/null +++ b/coreutils/src/cksum/build.rs @@ -0,0 +1,52 @@ +/* +* 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; + +#[path = "../../mkmain.rs"] +mod mkmain; + +fn main() { + mkmain::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, + "const 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 & 0x80000000 != 0 { + crc <<= 1; + crc ^= 0x04c11db7; + } else { + crc <<= 1; + } + } + + crc +} diff --git a/coreutils/src/cksum/cksum.rs b/coreutils/src/cksum/cksum.rs new file mode 100644 index 000000000..08b56b7d3 --- /dev/null +++ b/coreutils/src/cksum/cksum.rs @@ -0,0 +1,112 @@ +#![crate_name = "uu_cksum"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Michael Gehring + * + * 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 std::fs::File; +use std::io::{self, stdin, BufReader, Read}; +#[cfg(not(windows))] +use std::mem; +use std::path::Path; + +include!(concat!(env!("OUT_DIR"), "/crc_table.rs")); + +static SYNTAX: &str = "[OPTIONS] [FILE]..."; +static SUMMARY: &str = "Print CRC and size for each file"; +static LONG_HELP: &str = ""; + +#[inline] +fn crc_update(crc: u32, input: u8) -> u32 { + (crc << 8) ^ CRC_TABLE[((crc >> 24) as usize ^ input as usize) & 0xFF] +} + +#[inline] +fn crc_final(mut crc: u32, mut length: usize) -> u32 { + while length != 0 { + crc = crc_update(crc, length as u8); + length >>= 8; + } + + !crc +} + +#[cfg(windows)] +fn init_byte_array() -> Vec { + vec![0; 1024 * 1024] +} + +#[cfg(not(windows))] +fn init_byte_array() -> [u8; 1024 * 1024] { + unsafe { mem::uninitialized() } +} + +#[inline] +fn cksum(fname: &str) -> io::Result<(u32, usize)> { + let mut crc = 0u32; + let mut size = 0usize; + + let file; + let mut rd: Box = match fname { + "-" => Box::new(stdin()), + _ => { + file = File::open(&Path::new(fname))?; + Box::new(BufReader::new(file)) + } + }; + + let mut bytes = init_byte_array(); + loop { + match rd.read(&mut bytes) { + Ok(num_bytes) => { + if num_bytes == 0 { + return Ok((crc_final(crc, size), size)); + } + for &b in bytes[..num_bytes].iter() { + crc = crc_update(crc, b); + } + size += num_bytes; + } + Err(err) => return Err(err), + } + } + //Ok((0 as u32,0 as usize)) +} + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP).parse(args); + + let files = matches.free; + + if files.is_empty() { + match cksum("-") { + Ok((crc, size)) => println!("{} {}", crc, size), + Err(err) => { + show_error!("{}", err); + return 2; + } + } + return 0; + } + + let mut exit_code = 0; + for fname in &files { + match cksum(fname.as_ref()) { + Ok((crc, size)) => println!("{} {} {}", crc, size, fname), + Err(err) => { + show_error!("'{}' {}", fname, err); + exit_code = 2; + } + } + } + + exit_code +} diff --git a/coreutils/src/comm/Cargo.toml b/coreutils/src/comm/Cargo.toml new file mode 100644 index 000000000..6ddb78f3e --- /dev/null +++ b/coreutils/src/comm/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "comm" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_comm" +path = "comm.rs" + +[dependencies] +libc = "0.2.42" +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "comm" +path = "../../uumain.rs" diff --git a/coreutils/src/comm/comm.rs b/coreutils/src/comm/comm.rs new file mode 100644 index 000000000..f9381826b --- /dev/null +++ b/coreutils/src/comm/comm.rs @@ -0,0 +1,144 @@ +#![crate_name = "uu_comm"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Michael Gehring + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::cmp::Ordering; +use std::fs::File; +use std::io::{self, stdin, BufRead, BufReader, Stdin}; +use std::path::Path; + +static SYNTAX: &str = "[OPTIONS] FILE1 FILE2"; +static SUMMARY: &str = "Compare sorted files line by line"; +static LONG_HELP: &str = ""; + +fn mkdelim(col: usize, opts: &getopts::Matches) -> String { + let mut s = String::new(); + let delim = match opts.opt_str("output-delimiter") { + Some(d) => d.clone(), + None => "\t".to_owned(), + }; + + if col > 1 && !opts.opt_present("1") { + s.push_str(delim.as_ref()); + } + if col > 2 && !opts.opt_present("2") { + s.push_str(delim.as_ref()); + } + + s +} + +fn ensure_nl(line: &mut String) { + match line.chars().last() { + Some('\n') => (), + _ => line.push_str("\n"), + } +} + +enum LineReader { + Stdin(Stdin), + FileIn(BufReader), +} + +impl LineReader { + fn read_line(&mut self, buf: &mut String) -> io::Result { + match *self { + LineReader::Stdin(ref mut r) => r.read_line(buf), + LineReader::FileIn(ref mut r) => r.read_line(buf), + } + } +} + +fn comm(a: &mut LineReader, b: &mut LineReader, opts: &getopts::Matches) { + let delim: Vec = (0..4).map(|col| mkdelim(col, opts)).collect(); + + let ra = &mut String::new(); + let mut na = a.read_line(ra); + let rb = &mut String::new(); + let mut nb = b.read_line(rb); + + while na.is_ok() || nb.is_ok() { + let ord = match (na.is_ok(), nb.is_ok()) { + (false, true) => Ordering::Greater, + (true, false) => Ordering::Less, + (true, true) => match (&na, &nb) { + (&Ok(0), &Ok(0)) => break, + (&Ok(0), _) => Ordering::Greater, + (_, &Ok(0)) => Ordering::Less, + _ => ra.cmp(&rb), + }, + _ => unreachable!(), + }; + + match ord { + Ordering::Less => { + if !opts.opt_present("1") { + ensure_nl(ra); + print!("{}{}", delim[1], ra); + } + ra.clear(); + na = a.read_line(ra); + } + Ordering::Greater => { + if !opts.opt_present("2") { + ensure_nl(rb); + print!("{}{}", delim[2], rb); + } + rb.clear(); + nb = b.read_line(rb); + } + Ordering::Equal => { + if !opts.opt_present("3") { + ensure_nl(ra); + print!("{}{}", delim[3], ra); + } + ra.clear(); + rb.clear(); + na = a.read_line(ra); + nb = b.read_line(rb); + } + } + } +} + +fn open_file(name: &str) -> io::Result { + match name { + "-" => Ok(LineReader::Stdin(stdin())), + _ => { + let f = File::open(&Path::new(name))?; + Ok(LineReader::FileIn(BufReader::new(f))) + } + } +} + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optflag("1", "", "suppress column 1 (lines uniq to FILE1)") + .optflag("2", "", "suppress column 2 (lines uniq to FILE2)") + .optflag( + "3", + "", + "suppress column 3 (lines that appear in both files)", + ) + .optopt("", "output-delimiter", "separate columns with STR", "STR") + .parse(args); + + let mut f1 = open_file(matches.free[0].as_ref()).unwrap(); + let mut f2 = open_file(matches.free[1].as_ref()).unwrap(); + + comm(&mut f1, &mut f2, &matches); + + 0 +} diff --git a/coreutils/src/cp/Cargo.toml b/coreutils/src/cp/Cargo.toml new file mode 100644 index 000000000..cabe80bcd --- /dev/null +++ b/coreutils/src/cp/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "cp" +version = "0.0.1" +authors = [ + "Jordy Dickinson ", + "Joshua S. Miller ", +] +build = "../../mkmain.rs" + +[lib] +name = "uu_cp" +path = "cp.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +walkdir = "2.2.8" +clap = "2.32.0" +quick-error = "1.2.2" +filetime = "0.2" + +[dependencies.uucore] +version = "0.0.1" +features = ["fs"] + +[target.'cfg(target_os = "linux")'.dependencies] +ioctl-sys = "0.5.2" + +[target.'cfg(target_os = "windows")'.dependencies] +kernel32-sys = "0.2.2" +winapi = "0.3" + +[target.'cfg(unix)'.dependencies] +xattr="0.2.1" + +[[bin]] +name = "cp" +path = "../../uumain.rs" diff --git a/coreutils/src/cp/README.md b/coreutils/src/cp/README.md new file mode 100644 index 000000000..d536e5c47 --- /dev/null +++ b/coreutils/src/cp/README.md @@ -0,0 +1,38 @@ +## Feature list + +### To Do + +- [ ] archive +- [ ] attributes-only +- [ ] copy-contents +- [ ] no-dereference-preserve-linkgs +- [ ] dereference +- [ ] no-dereference +- [ ] preserve-default-attributes +- [ ] preserve +- [ ] no-preserve +- [ ] parents +- [ ] reflink +- [ ] sparse +- [ ] strip-trailing-slashes +- [ ] update +- [ ] one-file-system +- [ ] context +- [ ] cli-symbolic-links + +### Completed + +- [x] backup +- [x] force (Not implemented on Windows) +- [x] interactive +- [x] link +- [x] no-clobber +- [x] no-target-directory +- [x] paths +- [x] recursive +- [x] remove-destination (On Windows, current only works for writeable files) +- [x] suffix +- [x] symbolic-link +- [x] target-directory +- [x] verbose +- [x] version diff --git a/coreutils/src/cp/cp.rs b/coreutils/src/cp/cp.rs new file mode 100644 index 000000000..b116d7148 --- /dev/null +++ b/coreutils/src/cp/cp.rs @@ -0,0 +1,1204 @@ +#![crate_name = "uu_cp"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordy Dickinson + * (c) Joshua S. Miller + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +extern crate clap; +extern crate filetime; +#[cfg(target_os = "linux")] +#[macro_use] +extern crate ioctl_sys; +extern crate libc; +#[macro_use] +extern crate quick_error; +#[macro_use] +extern crate uucore; +extern crate walkdir; +#[cfg(unix)] +extern crate xattr; + +#[cfg(windows)] +extern crate kernel32; +#[cfg(windows)] +use kernel32::GetFileInformationByHandle; +#[cfg(windows)] +use kernel32::CreateFile2; +#[cfg(windows)] +extern crate winapi; + +use std::mem; +use std::ffi::CString; +use clap::{App, Arg, ArgMatches}; +use quick_error::ResultExt; +use std::collections::HashSet; +use std::fs; +use std::io::{stdin, stdout, Write}; +use std::io; +use std::path::{Path, PathBuf, StripPrefixError}; +use std::str::FromStr; +use uucore::fs::{canonicalize, CanonicalizeMode}; +use walkdir::WalkDir; +#[cfg(target_os = "linux")] +use std::os::unix::io::IntoRawFd; +#[cfg(target_os = "linux")] +use std::fs::File; +use std::fs::OpenOptions; +use filetime::FileTime; + +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; + +#[cfg(target_os = "linux")] +ioctl!(write ficlone with 0x94, 9; std::os::raw::c_int); + +quick_error! { + #[derive(Debug)] + pub enum Error { + /// Simple io::Error wrapper + IoErr(err: io::Error) { from() cause(err) display("{}", err) } + + /// Wrapper for io::Error with path context + IoErrContext(err: io::Error, path: String) { + display("{}: {}", path, err) + context(path: &'a str, err: io::Error) -> (err, path.to_owned()) + cause(err) + } + + /// General copy error + Error(err: String) { + display("{}", err) + from(err: String) -> (err) + from(err: &'static str) -> (err.to_string()) + } + + /// Represents the state when a non-fatal error has occured + /// and not all files were copied. + NotAllFilesCopied {} + + /// Simple walkdir::Error wrapper + WalkDirErr(err: walkdir::Error) { from() display("{}", err) cause(err) } + + /// Simple std::path::StripPrefixError wrapper + StripPrefixError(err: StripPrefixError) { from() } + + /// Result of a skipped file + Skipped(reason: String) { display("{}", reason) } + + /// Result of a skipped file + InvalidArgument(description: String) { display("{}", description) } + + /// All standard options are included as an an implementation + /// path, but those that are not implemented yet should return + /// a NotImplemented error. + NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) } + } +} + +/// Continue next iteration of loop if result of expression is error +macro_rules! or_continue( + ($expr:expr) => (match $expr { + Ok(temp) => temp, + Err(error) => { + show_error!("{}", error); + continue + }, + }) +); + +/// Prompts the user yes/no and returns `true` they if successfully +/// answered yes. +macro_rules! prompt_yes( + ($($args:tt)+) => ({ + print!($($args)+); + print!(" [y/N]: "); + crash_if_err!(1, stdout().flush()); + let mut s = String::new(); + match stdin().read_line(&mut s) { + Ok(_) => match s.char_indices().nth(0) { + Some((_, x)) => x == 'y' || x == 'Y', + _ => false + }, + _ => false + } + }) +); + +pub type CopyResult = Result; +pub type Source = PathBuf; +pub type Target = PathBuf; + +/// Specifies whether when overwrite files +#[derive(Clone, Eq, PartialEq)] +pub enum ClobberMode { + Force, + RemoveDestination, + Standard, +} + +/// Specifies whether when overwrite files +#[derive(Clone, Eq, PartialEq)] +pub enum OverwriteMode { + /// [Default] Always overwrite existing files + Clobber(ClobberMode), + /// Prompt before overwriting a file + Interactive(ClobberMode), + /// Never overwrite a file + NoClobber, +} + +#[derive(Clone, Eq, PartialEq)] +pub enum ReflinkMode { + Always, + Auto, + Never, +} + +/// Specifies the expected file type of copy target +pub enum TargetType { + Directory, + File, +} + +#[derive(Clone, Eq, PartialEq)] +pub enum BackupMode { + ExistingBackup, + NoBackup, + NumberedBackup, + SimpleBackup, +} + +pub enum CopyMode { + Link, + SymLink, + Sparse, + Copy, + Update, + AttrOnly, +} + +#[derive(Clone, Eq, PartialEq)] +pub enum Attribute { + #[cfg(unix)] + Mode, + Ownership, + Timestamps, + Context, + Links, + Xattr, +} + +/// Re-usable, extensible copy options +#[allow(dead_code)] +pub struct Options { + attributes_only: bool, + backup: bool, + copy_contents: bool, + copy_mode: CopyMode, + dereference: bool, + no_target_dir: bool, + one_file_system: bool, + overwrite: OverwriteMode, + parents: bool, + reflink: bool, + reflink_mode: ReflinkMode, + preserve_attributes: Vec, + recursive: bool, + backup_suffix: String, + target_dir: Option, + update: bool, + verbose: bool, +} + +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; +static EXIT_OK: i32 = 0; +static EXIT_ERR: i32 = 1; + +/// Prints the version +fn print_version() { + println!("{} {}", executable!(), VERSION); +} + +fn get_usage() -> String { + format!( + "{0} [OPTION]... [-T] SOURCE DEST + {0} [OPTION]... SOURCE... DIRECTORY + {0} [OPTION]... -t DIRECTORY SOURCE...", + executable!() + ) +} + +// Argument constants +static OPT_ARCHIVE: &str = "archive"; +static OPT_ATTRIBUTES_ONLY: &str = "attributes-only"; +static OPT_BACKUP: &str = "backup"; +static OPT_CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links"; +static OPT_CONTEXT: &str = "context"; +static OPT_COPY_CONTENTS: &str = "copy-contents"; +static OPT_DEREFERENCE: &str = "dereference"; +static OPT_FORCE: &str = "force"; +static OPT_INTERACTIVE: &str = "interactive"; +static OPT_LINK: &str = "link"; +static OPT_NO_CLOBBER: &str = "no-clobber"; +static OPT_NO_DEREFERENCE: &str = "no-dereference"; +static OPT_NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs"; +static OPT_NO_PRESERVE: &str = "no-preserve"; +static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; +static OPT_ONE_FILE_SYSTEM: &str = "one-file-system"; +static OPT_PARENTS: &str = "parents"; +static OPT_PATHS: &str = "paths"; +static OPT_PRESERVE: &str = "preserve"; +static OPT_PRESERVE_DEFAULT_ATTRIBUTES: &str = "preserve-default-attributes"; +static OPT_RECURSIVE: &str = "recursive"; +static OPT_RECURSIVE_ALIAS: &str = "recursive_alias"; +static OPT_REFLINK: &str = "reflink"; +static OPT_REMOVE_DESTINATION: &str = "remove-destination"; +static OPT_SPARSE: &str = "sparse"; +static OPT_STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; +static OPT_SUFFIX: &str = "suffix"; +static OPT_SYMBOLIC_LINK: &str = "symbolic-link"; +static OPT_TARGET_DIRECTORY: &str = "target-directory"; +static OPT_UPDATE: &str = "update"; +static OPT_VERBOSE: &str = "verbose"; +static OPT_VERSION: &str = "version"; + +#[cfg(unix)] +static PRESERVABLE_ATTRIBUTES: &[&str] = &[ + "mode", + "ownership", + "timestamps", + "context", + "links", + "xattr", + "all", +]; + +#[cfg(not(unix))] +static PRESERVABLE_ATTRIBUTES: &[&str] = &[ + "ownership", + "timestamps", + "context", + "links", + "xattr", + "all", +]; + +static DEFAULT_ATTRIBUTES: &[Attribute] = &[ + #[cfg(unix)] + Attribute::Mode, + Attribute::Ownership, + Attribute::Timestamps, +]; + +pub fn uumain(args: Vec) -> i32 { + let usage = get_usage(); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .arg(Arg::with_name(OPT_TARGET_DIRECTORY) + .short("t") + .conflicts_with(OPT_NO_TARGET_DIRECTORY) + .long(OPT_TARGET_DIRECTORY) + .value_name(OPT_TARGET_DIRECTORY) + .takes_value(true) + .help("copy all SOURCE arguments into target-directory")) + .arg(Arg::with_name(OPT_NO_TARGET_DIRECTORY) + .short("T") + .long(OPT_NO_TARGET_DIRECTORY) + .conflicts_with(OPT_TARGET_DIRECTORY) + .help("Treat DEST as a regular file and not a directory")) + .arg(Arg::with_name(OPT_VERSION) + .short("V") + .long(OPT_VERSION) + .help("output version information and exit")) + .arg(Arg::with_name(OPT_INTERACTIVE) + .short("i") + .long(OPT_INTERACTIVE) + .conflicts_with(OPT_NO_CLOBBER) + .help("ask before overwriting files")) + .arg(Arg::with_name(OPT_LINK) + .short("l") + .long(OPT_LINK) + .overrides_with(OPT_REFLINK) + .help("hard-link files instead of copying")) + .arg(Arg::with_name(OPT_NO_CLOBBER) + .short("n") + .long(OPT_NO_CLOBBER) + .conflicts_with(OPT_INTERACTIVE) + .help("don't overwrite a file that already exists")) + .arg(Arg::with_name(OPT_RECURSIVE) + .short("r") + .long(OPT_RECURSIVE) + .help("copy directories recursively")) + .arg(Arg::with_name(OPT_RECURSIVE_ALIAS) + .short("R") + .help("same as -r")) + .arg(Arg::with_name(OPT_VERBOSE) + .short("v") + .long(OPT_VERBOSE) + .help("explicitly state what is being done")) + .arg(Arg::with_name(OPT_SYMBOLIC_LINK) + .short("s") + .long(OPT_SYMBOLIC_LINK) + .conflicts_with(OPT_LINK) + .overrides_with(OPT_REFLINK) + .help("make symbolic links instead of copying")) + .arg(Arg::with_name(OPT_FORCE) + .short("f") + .long(OPT_FORCE) + .help("if an existing destination file cannot be opened, remove it and \ + try again (this option is ignored when the -n option is also used). \ + Currently not implemented for Windows.")) + .arg(Arg::with_name(OPT_REMOVE_DESTINATION) + .long(OPT_REMOVE_DESTINATION) + .conflicts_with(OPT_FORCE) + .help("remove each existing destination file before attempting to open it \ + (contrast with --force). On Windows, current only works for writeable files.")) + .arg(Arg::with_name(OPT_BACKUP) + .short("b") + .long(OPT_BACKUP) + .help("make a backup of each existing destination file")) + .arg(Arg::with_name(OPT_SUFFIX) + .short("S") + .long(OPT_SUFFIX) + .takes_value(true) + .default_value("~") + .value_name("SUFFIX") + .help("override the usual backup suffix")) + .arg(Arg::with_name(OPT_UPDATE) + .short("u") + .long(OPT_UPDATE) + .help("copy only when the SOURCE file is newer than the destination file\ + or when the destination file is missing")) + .arg(Arg::with_name(OPT_REFLINK) + .long(OPT_REFLINK) + .takes_value(true) + .value_name("WHEN") + .help("control clone/CoW copies. See below")) + .arg(Arg::with_name(OPT_ATTRIBUTES_ONLY) + .long(OPT_ATTRIBUTES_ONLY) + .conflicts_with(OPT_COPY_CONTENTS) + .overrides_with(OPT_REFLINK) + .help("Don't copy the file data, just the attributes")) + .arg(Arg::with_name(OPT_PRESERVE) + .long(OPT_PRESERVE) + .takes_value(true) + .multiple(true) + .use_delimiter(true) + .possible_values(PRESERVABLE_ATTRIBUTES) + .value_name("ATTR_LIST") + .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE, OPT_ARCHIVE]) + .help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\ + if possible additional attributes: context, links, xattr, all")) + .arg(Arg::with_name(OPT_PRESERVE_DEFAULT_ATTRIBUTES) + .short("-p") + .long(OPT_PRESERVE_DEFAULT_ATTRIBUTES) + .conflicts_with_all(&[OPT_PRESERVE, OPT_NO_PRESERVE, OPT_ARCHIVE]) + .help("same as --preserve=mode(unix only),ownership,timestamps")) + .arg(Arg::with_name(OPT_NO_PRESERVE) + .long(OPT_NO_PRESERVE) + .takes_value(true) + .value_name("ATTR_LIST") + .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_ARCHIVE]) + .help("don't preserve the specified attributes")) + + // TODO: implement the following args + .arg(Arg::with_name(OPT_ARCHIVE) + .short("a") + .long(OPT_ARCHIVE) + .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE]) + .help("NotImplemented: same as -dR --preserve=all")) + .arg(Arg::with_name(OPT_COPY_CONTENTS) + .long(OPT_COPY_CONTENTS) + .conflicts_with(OPT_ATTRIBUTES_ONLY) + .help("NotImplemented: copy contents of special files when recursive")) + .arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS) + .short("d") + .help("NotImplemented: same as --no-dereference --preserve=links")) + .arg(Arg::with_name(OPT_DEREFERENCE) + .short("L") + .long(OPT_DEREFERENCE) + .conflicts_with(OPT_NO_DEREFERENCE) + .help("NotImplemented: always follow symbolic links in SOURCE")) + .arg(Arg::with_name(OPT_NO_DEREFERENCE) + .short("-P") + .long(OPT_NO_DEREFERENCE) + .conflicts_with(OPT_DEREFERENCE) + .help("NotImplemented: never follow symbolic links in SOURCE")) + .arg(Arg::with_name(OPT_PARENTS) + .long(OPT_PARENTS) + .help("NotImplemented: use full source file name under DIRECTORY")) + .arg(Arg::with_name(OPT_SPARSE) + .long(OPT_SPARSE) + .takes_value(true) + .value_name("WHEN") + .help("NotImplemented: control creation of sparse files. See below")) + .arg(Arg::with_name(OPT_STRIP_TRAILING_SLASHES) + .long(OPT_STRIP_TRAILING_SLASHES) + .help("NotImplemented: remove any trailing slashes from each SOURCE argument")) + .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) + .value_name("CTX") + .help("NotImplemented: set SELinux security context of destination file to default type")) + .arg(Arg::with_name(OPT_CLI_SYMBOLIC_LINKS) + .short("H") + .help("NotImplemented: follow command-line symbolic links in SOURCE")) + // END TODO + + .arg(Arg::with_name(OPT_PATHS) + .multiple(true)) + .get_matches_from(&args); + + if matches.is_present(OPT_VERSION) { + print_version(); + return EXIT_OK; + } + + let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches)); + let paths: Vec = matches + .values_of("paths") + .map(|v| v.map(|p| p.to_string()).collect()) + .unwrap_or_default(); + + let (sources, target) = crash_if_err!(EXIT_ERR, parse_path_args(&paths, &options)); + + if let Err(error) = copy(&sources, &target, &options) { + match error { + // Error::NotAllFilesCopied is non-fatal, but the error + // code should still be EXIT_ERR as does GNU cp + Error::NotAllFilesCopied => {} + // Else we caught a fatal bubbled-up error, log it to stderr + _ => show_error!("{}", error), + }; + return EXIT_ERR; + } + + EXIT_OK +} + +impl ClobberMode { + fn from_matches(matches: &ArgMatches) -> ClobberMode { + if matches.is_present(OPT_FORCE) { + ClobberMode::Force + } else if matches.is_present(OPT_REMOVE_DESTINATION) { + ClobberMode::RemoveDestination + } else { + ClobberMode::Standard + } + } +} + +impl OverwriteMode { + fn from_matches(matches: &ArgMatches) -> OverwriteMode { + if matches.is_present(OPT_INTERACTIVE) { + OverwriteMode::Interactive(ClobberMode::from_matches(matches)) + } else if matches.is_present(OPT_NO_CLOBBER) { + OverwriteMode::NoClobber + } else { + OverwriteMode::Clobber(ClobberMode::from_matches(matches)) + } + } +} + +impl CopyMode { + fn from_matches(matches: &ArgMatches) -> CopyMode { + if matches.is_present(OPT_LINK) { + CopyMode::Link + } else if matches.is_present(OPT_SYMBOLIC_LINK) { + CopyMode::SymLink + } else if matches.is_present(OPT_SPARSE) { + CopyMode::Sparse + } else if matches.is_present(OPT_UPDATE) { + CopyMode::Update + } else if matches.is_present(OPT_ATTRIBUTES_ONLY) { + CopyMode::AttrOnly + } else { + CopyMode::Copy + } + } +} + +impl FromStr for Attribute { + type Err = Error; + + fn from_str(value: &str) -> CopyResult { + Ok(match &*value.to_lowercase() { + #[cfg(unix)] + "mode" => Attribute::Mode, + "ownership" => Attribute::Ownership, + "timestamps" => Attribute::Timestamps, + "context" => Attribute::Context, + "links" => Attribute::Links, + "xattr" => Attribute::Xattr, + _ => { + return Err(Error::InvalidArgument(format!( + "invalid attribute '{}'", + value + ))) + } + }) + } +} + +impl Options { + fn from_matches(matches: &ArgMatches) -> CopyResult { + let not_implemented_opts = vec![ + OPT_ARCHIVE, + OPT_COPY_CONTENTS, + OPT_NO_DEREFERENCE_PRESERVE_LINKS, + OPT_DEREFERENCE, + OPT_NO_DEREFERENCE, + OPT_PARENTS, + OPT_SPARSE, + OPT_STRIP_TRAILING_SLASHES, + OPT_ONE_FILE_SYSTEM, + OPT_CONTEXT, + #[cfg(windows)] + OPT_FORCE, + #[cfg(not(any(windows, unix)))] + OPT_SYMBOLIC_LINK, + ]; + + for not_implemented_opt in not_implemented_opts { + if matches.is_present(not_implemented_opt) { + return Err(Error::NotImplemented(not_implemented_opt.to_string())); + } + } + + let recursive = matches.is_present(OPT_RECURSIVE) || matches.is_present(OPT_RECURSIVE_ALIAS) + || matches.is_present(OPT_ARCHIVE); + + let backup = matches.is_present(OPT_BACKUP) || (matches.occurrences_of(OPT_SUFFIX) > 0); + + // Parse target directory options + let no_target_dir = matches.is_present(OPT_NO_TARGET_DIRECTORY); + let target_dir = matches + .value_of(OPT_TARGET_DIRECTORY) + .map(|v| v.to_string()); + + // Parse attributes to preserve + let preserve_attributes: Vec = if matches.is_present(OPT_PRESERVE) { + match matches.values_of(OPT_PRESERVE) { + None => DEFAULT_ATTRIBUTES.to_vec(), + Some(attribute_strs) => { + let mut attributes = Vec::new(); + for attribute_str in attribute_strs { + if attribute_str == "all" { + #[cfg(unix)] + attributes.push(Attribute::Mode); + attributes.push(Attribute::Ownership); + attributes.push(Attribute::Timestamps); + attributes.push(Attribute::Context); + attributes.push(Attribute::Xattr); + attributes.push(Attribute::Links); + break; + } else { + attributes.push(Attribute::from_str(attribute_str)?); + } + } + attributes + } + } + } else if matches.is_present(OPT_PRESERVE_DEFAULT_ATTRIBUTES) { + DEFAULT_ATTRIBUTES.to_vec() + } else { + vec![] + }; + + let options = Options { + attributes_only: matches.is_present(OPT_ATTRIBUTES_ONLY), + copy_contents: matches.is_present(OPT_COPY_CONTENTS), + copy_mode: CopyMode::from_matches(matches), + dereference: matches.is_present(OPT_DEREFERENCE), + one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM), + overwrite: OverwriteMode::from_matches(matches), + parents: matches.is_present(OPT_PARENTS), + backup_suffix: matches.value_of(OPT_SUFFIX).unwrap().to_string(), + update: matches.is_present(OPT_UPDATE), + verbose: matches.is_present(OPT_VERBOSE), + reflink: matches.is_present(OPT_REFLINK), + reflink_mode: { + if let Some(reflink) = matches.value_of(OPT_REFLINK) { + match reflink { + "always" => ReflinkMode::Always, + "auto" => ReflinkMode::Auto, + value => { + return Err(Error::InvalidArgument(format!( + "invalid argument '{}' for \'reflink\'", + value + ))) + } + } + } else { + ReflinkMode::Never + } + }, + backup, + no_target_dir, + preserve_attributes, + recursive, + target_dir, + }; + + Ok(options) + } +} + +impl TargetType { + /// Return TargetType required for `target`. + /// + /// Treat target as a dir if we have multiple sources or the target + /// exists and already is a directory + fn determine(sources: &[Source], target: &Target) -> TargetType { + if sources.len() > 1 || target.is_dir() { + TargetType::Directory + } else { + TargetType::File + } + } +} + +/// Returns tuple of (Source paths, Target) +fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec, Target)> { + let mut paths = path_args.iter().map(PathBuf::from).collect::>(); + + if paths.is_empty() { + // No files specified + return Err("missing file operand".into()); + } + + // Return an error if the user requested to copy more than one + // file source to a file target + if options.no_target_dir && options.target_dir.is_none() && paths.len() > 2 { + return Err(format!("extra operand {:?}", paths[2]).into()); + } + + let (sources, target) = match options.target_dir { + Some(ref target) => { + // All path arges are sources, and the target dir was + // specified separately + (paths, PathBuf::from(target)) + } + None => { + // If there was no explicit target-dir, then use the last + // path_arg + let target = paths.pop().unwrap(); + (paths, target) + } + }; + + Ok((sources, target)) +} + +fn preserve_hardlinks( + hard_links: &mut Vec<(String, u64)>, + source: &std::path::PathBuf, + dest: std::path::PathBuf, + found_hard_link: &mut bool, +) -> CopyResult<()> { + // Redox does not currently support hard links + #[cfg(not(any(target_os = "redox", target_os = "sunrise")))] + { + if !source.is_dir() { + unsafe { + let src_path = CString::new(source.as_os_str().to_str().unwrap()).unwrap(); + let inode: u64; + let nlinks: u64; + #[cfg(unix)] + { + let mut stat = mem::zeroed(); + if libc::lstat(src_path.as_ptr(), &mut stat) < 0 { + return Err(format!( + "cannot stat {:?}: {}", + src_path, + std::io::Error::last_os_error() + ).into()); + } + inode = stat.st_ino as u64; + nlinks = stat.st_nlink as u64; + } + #[cfg(windows)] + { + let stat = mem::uninitialized(); + let handle = CreateFile2( + src_path.as_ptr() as *const u16, + winapi::um::winnt::GENERIC_READ, + winapi::um::winnt::FILE_SHARE_READ, + 0, + std::ptr::null_mut(), + ); + if GetFileInformationByHandle(handle, stat) != 0 { + return Err(format!( + "cannot get file information {:?}: {}", + source, + std::io::Error::last_os_error() + ).into()); + } + inode = ((*stat).nFileIndexHigh as u64) << 32 | (*stat).nFileIndexLow as u64; + nlinks = (*stat).nNumberOfLinks as u64; + } + + for hard_link in hard_links.iter() { + if hard_link.1 == inode { + std::fs::hard_link(hard_link.0.clone(), dest.clone()).unwrap(); + *found_hard_link = true; + } + } + if !(*found_hard_link) && nlinks > 1 { + hard_links.push((dest.clone().to_str().unwrap().to_string(), inode)); + } + } + } + } + Ok(()) +} + +/// Copy all `sources` to `target`. Returns an +/// `Err(Error::NotAllFilesCopied)` if at least one non-fatal error was +/// encountered. +/// +/// Behavior depends on `options`, see [`Options`] for details. +/// +/// [`Options`]: ./struct.Options.html +fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()> { + let target_type = TargetType::determine(sources, target); + verify_target_type(target, &target_type)?; + + let mut preserve_hard_links = false; + for attribute in &options.preserve_attributes { + if *attribute == Attribute::Links { + preserve_hard_links = true; + } + } + + let mut hard_links: Vec<(String, u64)> = vec![]; + + let mut non_fatal_errors = false; + let mut seen_sources = HashSet::with_capacity(sources.len()); + for source in sources { + if seen_sources.contains(source) { + show_warning!("source '{}' specified more than once", source.display()); + } else { + let mut found_hard_link = false; + if preserve_hard_links { + let dest = construct_dest_path(source, target, &target_type, options)?; + preserve_hardlinks(&mut hard_links, source, dest, &mut found_hard_link).unwrap(); + } + if !found_hard_link { + if let Err(error) = copy_source(source, target, &target_type, options) { + show_error!("{}", error); + match error { + Error::Skipped(_) => (), + _ => non_fatal_errors = true, + } + } + } + seen_sources.insert(source); + } + } + if non_fatal_errors { + Err(Error::NotAllFilesCopied) + } else { + Ok(()) + } +} + +fn construct_dest_path( + source_path: &Path, + target: &Target, + target_type: &TargetType, + options: &Options, +) -> CopyResult { + if options.no_target_dir && target.is_dir() { + return Err(format!( + "cannot overwrite directory '{}' with non-directory", + target.display() + ).into()); + } + + Ok(match *target_type { + TargetType::Directory => { + let root = source_path.parent().unwrap_or(source_path); + localize_to_target(root, source_path, target)? + } + TargetType::File => target.to_path_buf(), + }) +} + +fn copy_source( + source: &Source, + target: &Target, + target_type: &TargetType, + options: &Options, +) -> CopyResult<()> { + let source_path = Path::new(&source); + if source_path.is_dir() { + // Copy as directory + copy_directory(source, target, options) + } else { + // Copy as file + let dest = construct_dest_path(source_path, target, target_type, options)?; + copy_file(source_path, dest.as_path(), options) + } +} + +/// Read the contents of the directory `root` and recursively copy the +/// contents to `target`. +/// +/// Any errors encounted copying files in the tree will be logged but +/// will not cause a short-circuit. +fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult<()> { + if !options.recursive { + return Err(format!("omitting directory '{}'", root.display()).into()); + } + + let root_path = Path::new(&root).canonicalize()?; + + let root_parent = if target.exists() { + root_path.parent() + } else { + Some(root_path.as_path()) + }; + + #[cfg(unix)] + let mut hard_links: Vec<(String, u64)> = vec![]; + let mut preserve_hard_links = false; + for attribute in &options.preserve_attributes { + if *attribute == Attribute::Links { + preserve_hard_links = true; + } + } + + // This should be changed once Redox supports hardlinks + #[cfg(any(windows, target_os = "redox", target_os = "sunrise"))] + let mut hard_links: Vec<(String, u64)> = vec![]; + + for path in WalkDir::new(root) { + let path = or_continue!(or_continue!(path).path().canonicalize()); + let local_to_root_parent = match root_parent { + Some(parent) => or_continue!(path.strip_prefix(&parent)).to_path_buf(), + None => path.clone(), + }; + + let local_to_target = target.join(&local_to_root_parent); + + if path.is_dir() && !local_to_target.exists() { + or_continue!(fs::create_dir_all(local_to_target.clone())); + } else if !path.is_dir() { + if preserve_hard_links { + let mut found_hard_link = false; + let source = path.to_path_buf(); + let dest = local_to_target.as_path().to_path_buf(); + preserve_hardlinks(&mut hard_links, &source, dest, &mut found_hard_link).unwrap(); + if !found_hard_link { + copy_file(path.as_path(), local_to_target.as_path(), options)?; + } + } else { + copy_file(path.as_path(), local_to_target.as_path(), options)?; + } + } + } + + Ok(()) +} + +impl OverwriteMode { + fn verify(&self, path: &Path) -> CopyResult<()> { + match *self { + OverwriteMode::NoClobber => Err(Error::Skipped(format!( + "Not overwriting {} because of option '{}'", + path.display(), + OPT_NO_CLOBBER + ))), + OverwriteMode::Interactive(_) => { + if prompt_yes!("{}: overwrite {}? ", executable!(), path.display()) { + Ok(()) + } else { + Err(Error::Skipped(format!( + "Not overwriting {} at user request", + path.display() + ))) + } + } + OverwriteMode::Clobber(_) => Ok(()), + } + } +} + +fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResult<()> { + let context = &*format!("'{}' -> '{}'", source.display().to_string(), dest.display()); + Ok(match *attribute { + #[cfg(unix)] + Attribute::Mode => { + let mode = fs::metadata(source).context(context)?.permissions().mode(); + let mut dest_metadata = fs::metadata(source).context(context)?.permissions(); + dest_metadata.set_mode(mode); + } + Attribute::Ownership => { + let metadata = fs::metadata(source).context(context)?; + fs::set_permissions(dest, metadata.permissions()).context(context)?; + } + Attribute::Timestamps => { + let metadata = fs::metadata(source)?; + filetime::set_file_times( + Path::new(dest), + FileTime::from_last_access_time(&metadata), + FileTime::from_last_modification_time(&metadata), + )?; + } + Attribute::Context => {} + Attribute::Links => {} + Attribute::Xattr => { + #[cfg(unix)] + { + let xattrs = xattr::list(source)?; + for attr in xattrs { + if let Some(attr_value) = xattr::get(source, attr.clone())? { + crash_if_err!(EXIT_ERR, xattr::set(dest, attr, &attr_value[..])); + } + } + } + #[cfg(not(unix))] + { + return Err(format!("XAttrs are only supported on unix.").into()); + } + } + }) +} + +#[cfg(unix)] +fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { + Ok(std::os::unix::fs::symlink(source, dest).context(context)?) +} + +#[cfg(not(any(unix, windows)))] +fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { + Err(Error::NotImplemented(OPT_SPARSE.to_string())) +} + +#[cfg(windows)] +fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { + Ok(std::os::windows::fs::symlink_file(source, dest).context(context)?) +} + +fn context_for(src: &Path, dest: &Path) -> String { + format!("'{}' -> '{}'", src.display(), dest.display()) +} + +/// Implements a relatively naive backup that is not as full featured +/// as GNU cp. No CONTROL version control method argument is taken +/// for backups. +/// TODO: Add version control methods +fn backup_file(path: &Path, suffix: &str) -> CopyResult { + let mut backup_path = path.to_path_buf().into_os_string(); + backup_path.push(suffix); + fs::copy(path, &backup_path)?; + Ok(backup_path.into()) +} + +fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { + if paths_refer_to_same_file(source, dest)? { + return Err(format!("{}: same file", context_for(source, dest)).into()); + } + + options.overwrite.verify(dest)?; + + if options.backup { + backup_file(dest, &options.backup_suffix)?; + } + + match options.overwrite { + OverwriteMode::Clobber(ClobberMode::Force) => { + if fs::metadata(dest)?.permissions().readonly() { + fs::remove_file(dest)?; + } + } + OverwriteMode::Clobber(ClobberMode::RemoveDestination) => { + fs::remove_file(dest)?; + } + _ => (), + }; + + Ok(()) +} + +/// Copy the a file from `source` to `dest`. No path manipulation is +/// done on either `source` or `dest`, the are used as provieded. +/// +/// Behavior when copying to existing files is contingent on the +/// `options.overwrite` mode. If a file is skipped, the return type +/// should be `Error:Skipped` +/// +/// The original permissions of `source` will be copied to `dest` +/// after a successful copy. +fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { + if dest.exists() { + handle_existing_dest(source, dest, options)?; + } + + if options.verbose { + println!("{}", context_for(source, dest)); + } + + #[allow(unused)] + { + // TODO: implement --preserve flag + let mut preserve_context = false; + for attribute in &options.preserve_attributes { + if *attribute == Attribute::Context { + preserve_context = true; + } + } + } + + match options.copy_mode { + CopyMode::Link => { + fs::hard_link(source, dest).context(&*context_for(source, dest))?; + } + CopyMode::Copy => { + copy_helper(source, dest, options)?; + } + CopyMode::SymLink => { + symlink_file(source, dest, &*context_for(source, dest))?; + } + CopyMode::Sparse => return Err(Error::NotImplemented(OPT_SPARSE.to_string())), + CopyMode::Update => { + if dest.exists() { + let src_metadata = fs::metadata(source.clone())?; + let dest_metadata = fs::metadata(dest.clone())?; + + let src_time = src_metadata.modified()?; + let dest_time = dest_metadata.modified()?; + if src_time <= dest_time { + return Ok(()); + } else { + copy_helper(source, dest, options)?; + } + } else { + copy_helper(source, dest, options)?; + } + } + CopyMode::AttrOnly => { + OpenOptions::new() + .write(true) + .truncate(false) + .create(true) + .open(dest) + .unwrap(); + } + }; + + for attribute in &options.preserve_attributes { + copy_attribute(source, dest, attribute)?; + } + + Ok(()) +} + +///Copy the file from `source` to `dest` either using the normal `fs::copy` or the +///`FICLONE` ioctl if --reflink is specified and the filesystem supports it. +fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { + if options.reflink { + #[cfg(not(target_os = "linux"))] + return Err(format!("--reflink is only supported on linux").into()); + + #[cfg(target_os = "linux")] + { + let src_file = File::open(source).unwrap().into_raw_fd(); + let dst_file = OpenOptions::new() + .write(true) + .truncate(false) + .create(true) + .open(dest) + .unwrap() + .into_raw_fd(); + match options.reflink_mode { + ReflinkMode::Always => unsafe { + let result = ficlone(dst_file, src_file as *const i32); + if result != 0 { + return Err(format!( + "failed to clone {:?} from {:?}: {}", + source, + dest, + std::io::Error::last_os_error() + ).into()); + } else { + return Ok(()); + } + }, + ReflinkMode::Auto => unsafe { + let result = ficlone(dst_file, src_file as *const i32); + if result != 0 { + fs::copy(source, dest).context(&*context_for(source, dest))?; + } + }, + ReflinkMode::Never => {} + } + } + } else { + fs::copy(source, dest).context(&*context_for(source, dest))?; + } + Ok(()) +} + +/// Generate an error message if `target` is not the correct `target_type` +pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { + match (target_type, target.is_dir()) { + (&TargetType::Directory, false) => { + Err(format!("target: '{}' is not a directory", target.display()).into()) + } + (&TargetType::File, true) => Err(format!( + "cannot overwrite directory '{}' with non-directory", + target.display() + ).into()), + _ => Ok(()), + } +} + +/// Remove the `root` prefix from `source` and prefix it with `target` +/// to create a file that is local to `target` +/// # Examples +/// +/// ```ignore +/// assert!(uu_cp::localize_to_target( +/// &Path::new("a/source/"), +/// &Path::new("a/source/c.txt"), +/// &Path::new("target/"), +/// ).unwrap() == Path::new("target/c.txt")) +/// ``` +pub fn localize_to_target(root: &Path, source: &Path, target: &Path) -> CopyResult { + let local_to_root = source.strip_prefix(&root)?; + Ok(target.join(&local_to_root)) +} + +pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result { + // We have to take symlinks and relative paths into account. + let pathbuf1 = canonicalize(p1, CanonicalizeMode::Normal)?; + let pathbuf2 = canonicalize(p2, CanonicalizeMode::Normal)?; + + Ok(pathbuf1 == pathbuf2) +} + +#[test] +fn test_cp_localize_to_target() { + assert!( + localize_to_target( + &Path::new("a/source/"), + &Path::new("a/source/c.txt"), + &Path::new("target/") + ).unwrap() == Path::new("target/c.txt") + ) +} diff --git a/coreutils/src/cut/.gitignore b/coreutils/src/cut/.gitignore new file mode 100644 index 000000000..1de565933 --- /dev/null +++ b/coreutils/src/cut/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/coreutils/src/cut/Cargo.toml b/coreutils/src/cut/Cargo.toml new file mode 100644 index 000000000..97d4ade2b --- /dev/null +++ b/coreutils/src/cut/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cut" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_cut" +path = "cut.rs" + +[dependencies] +uucore = "0.0.1" + +[[bin]] +name = "cut" +path = "../../uumain.rs" diff --git a/coreutils/src/cut/buffer.rs b/coreutils/src/cut/buffer.rs new file mode 100644 index 000000000..11f48bccb --- /dev/null +++ b/coreutils/src/cut/buffer.rs @@ -0,0 +1,153 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Rolf Morel + * (c) kwantam + * substantially rewritten to use the stdlib BufReader trait + * rather than re-implementing it here. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use std::io::{BufRead, BufReader, Read, Write}; +use std::io::Result as IoResult; + +#[allow(non_snake_case)] +pub mod Bytes { + use std::io::Write; + + pub trait Select { + fn select(&mut self, bytes: usize, out: Option<&mut W>) -> Selected; + } + + #[derive(PartialEq, Eq, Debug)] + pub enum Selected { + NewlineFound, + Complete(usize), + Partial(usize), + EndOfFile, + } +} + +#[derive(Debug)] +pub struct ByteReader +where + R: Read, +{ + inner: BufReader, + newline_char: u8, +} + +impl ByteReader { + pub fn new(read: R, newline_char: u8) -> ByteReader { + ByteReader { + inner: BufReader::with_capacity(4096, read), + newline_char, + } + } +} + +impl Read for ByteReader { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + self.inner.read(buf) + } +} + +impl BufRead for ByteReader { + fn fill_buf(&mut self) -> IoResult<&[u8]> { + self.inner.fill_buf() + } + + fn consume(&mut self, amt: usize) { + self.inner.consume(amt) + } +} + +impl ByteReader { + pub fn consume_line(&mut self) -> usize { + let mut bytes_consumed = 0; + let mut consume_val; + let newline_char = self.newline_char; + + loop { + { + // need filled_buf to go out of scope + let filled_buf = match self.fill_buf() { + Ok(b) => { + if b.is_empty() { + return bytes_consumed; + } else { + b + } + } + Err(e) => crash!(1, "read error: {}", e), + }; + + if let Some(idx) = filled_buf.iter().position(|byte| *byte == newline_char) { + consume_val = idx + 1; + bytes_consumed += consume_val; + break; + } + + consume_val = filled_buf.len(); + } + + bytes_consumed += consume_val; + self.consume(consume_val); + } + + self.consume(consume_val); + bytes_consumed + } +} + +impl self::Bytes::Select for ByteReader { + fn select(&mut self, bytes: usize, out: Option<&mut W>) -> Bytes::Selected { + enum SRes { + Comp, + Part, + Newl, + }; + + use self::Bytes::Selected::*; + + let newline_char = self.newline_char; + let (res, consume_val) = { + let buffer = match self.fill_buf() { + Err(e) => crash!(1, "read error: {}", e), + Ok(b) => b, + }; + + let (res, consume_val) = match buffer.len() { + 0 => return EndOfFile, + buf_used if bytes < buf_used => { + // because the output delimiter should only be placed between + // segments check if the byte after bytes is a newline + let buf_slice = &buffer[0..bytes + 1]; + + match buf_slice.iter().position(|byte| *byte == newline_char) { + Some(idx) => (SRes::Newl, idx + 1), + None => (SRes::Comp, bytes), + } + } + _ => match buffer.iter().position(|byte| *byte == newline_char) { + Some(idx) => (SRes::Newl, idx + 1), + None => (SRes::Part, buffer.len()), + }, + }; + + if let Some(out) = out { + crash_if_err!(1, out.write_all(&buffer[0..consume_val])); + } + (res, consume_val) + }; + + self.consume(consume_val); + match res { + SRes::Comp => Complete(consume_val), + SRes::Part => Partial(consume_val), + SRes::Newl => NewlineFound, + } + } +} diff --git a/coreutils/src/cut/cut.rs b/coreutils/src/cut/cut.rs new file mode 100644 index 000000000..c6efea47e --- /dev/null +++ b/coreutils/src/cut/cut.rs @@ -0,0 +1,553 @@ +#![crate_name = "uu_cut"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Rolf Morel + * + * 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 std::fs::File; +use std::io::{stdin, stdout, BufRead, BufReader, Read, Stdout, Write}; +use std::path::Path; + +use ranges::Range; +use searcher::Searcher; + +mod buffer; +mod ranges; +mod searcher; + +static SYNTAX: &str = + "[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+"; +static SUMMARY: &str = + "Prints specified byte or field columns from each line of stdin or the input files"; +static LONG_HELP: &str = " + Each call must specify a mode (what to use for columns), + a sequence (which columns to print), and provide a data source + + Specifying a mode + + Use --bytes (-b) or --characters (-c) to specify byte mode + + Use --fields (-f) to specify field mode, where each line is broken into + fields identified by a delimiter character. For example for a typical CSV + you could use this in combination with setting comma as the delimiter + + Specifying a sequence + + A sequence is a group of 1 or more numbers or inclusive ranges separated + by a commas. + + cut -f 2,5-7 some_file.txt + will display the 2nd, 5th, 6th, and 7th field for each source line + + Ranges can extend to the end of the row by excluding the the second number + + cut -f 3- some_file.txt + will display the 3rd field and all fields after for each source line + + The first number of a range can be excluded, and this is effectively the + same as using 1 as the first number: it causes the range to begin at the + first column. Ranges can also display a single column + + cut -f 1,3-5 some_file.txt + will display the 1st, 3rd, 4th, and 5th field for each source line + + The --complement option, when used, inverts the effect of the sequence + + cut --complement -f 4-6 some_file.txt + will display the every field but the 4th, 5th, and 6th + + Specifying a data source + + If no sourcefile arguments are specified, stdin is used as the source of + lines to print + + If sourcefile arguments are specified, stdin is ignored and all files are + read in consecutively if a sourcefile is not successfully read, a warning + will print to stderr, and the eventual status code will be 1, but cut + will continue to read through proceeding sourcefiles + + To print columns from both STDIN and a file argument, use - (dash) as a + sourcefile argument to represent stdin. + + Field Mode options + + The fields in each line are identified by a delimiter (separator) + + Set the delimiter + Set the delimiter which separates fields in the file using the + --delimiter (-d) option. Setting the delimiter is optional. + If not set, a default delimiter of Tab will be used. + + Optionally Filter based on delimiter + If the --only-delimited (-s) flag is provided, only lines which + contain the delimiter will be printed + + Replace the delimiter + If the --output-delimiter option is provided, the argument used for + it will replace the delimiter character in each line printed. This is + useful for transforming tabular data - e.g. to convert a CSV to a + TSV (tab-separated file) + + Line endings + + When the --zero-terminated (-z) option is used, cut sees \\0 (null) as the + 'line ending' character (both for the purposes of reading lines and + separating printed lines) instead of \\n (newline). This is useful for + tabular data where some of the cells may contain newlines + + echo 'ab\\0cd' | cut -z -c 1 + will result in 'a\\0c\\0' +"; + +struct Options { + out_delim: Option, + zero_terminated: bool, +} + +struct FieldOptions { + delimiter: String, // one char long, String because of UTF8 representation + out_delimeter: Option, + only_delimited: bool, + zero_terminated: bool, +} + +enum Mode { + Bytes(Vec, Options), + Characters(Vec, Options), + Fields(Vec, FieldOptions), +} + +fn list_to_ranges(list: &str, complement: bool) -> Result, String> { + if complement { + Range::from_list(list).map(|r| ranges::complement(&r)) + } else { + Range::from_list(list) + } +} + +fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> i32 { + use buffer::Bytes::Select; + use buffer::Bytes::Selected::*; + + let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; + let mut buf_read = buffer::ByteReader::new(reader, newline_char); + let mut out = stdout(); + + 'newline: loop { + let mut cur_pos = 1; + let mut print_delim = false; + + for &Range { low, high } in ranges.iter() { + // skip upto low + let orig_pos = cur_pos; + loop { + match buf_read.select(low - cur_pos, None::<&mut Stdout>) { + NewlineFound => { + crash_if_err!(1, out.write_all(&[newline_char])); + continue 'newline; + } + Complete(len) => { + cur_pos += len; + break; + } + Partial(len) => cur_pos += len, + EndOfFile => { + if orig_pos != cur_pos { + crash_if_err!(1, out.write_all(&[newline_char])); + } + + break 'newline; + } + } + } + + if let Some(ref delim) = opts.out_delim { + if print_delim { + crash_if_err!(1, out.write_all(delim.as_bytes())); + } + print_delim = true; + } + + // write out from low to high + loop { + match buf_read.select(high - cur_pos + 1, Some(&mut out)) { + NewlineFound => continue 'newline, + Partial(len) => cur_pos += len, + Complete(_) => { + cur_pos = high + 1; + break; + } + EndOfFile => { + if cur_pos != low || low == high { + crash_if_err!(1, out.write_all(&[newline_char])); + } + + break 'newline; + } + } + } + } + + buf_read.consume_line(); + crash_if_err!(1, out.write_all(&[newline_char])); + } + + 0 +} + +fn cut_fields_delimiter( + reader: R, + ranges: &[Range], + delim: &str, + only_delimited: bool, + newline_char: u8, + out_delim: &str, +) -> i32 { + let mut buf_in = BufReader::new(reader); + let mut out = stdout(); + let mut buffer = Vec::new(); + + 'newline: loop { + buffer.clear(); + match buf_in.read_until(newline_char, &mut buffer) { + Ok(n) if n == 0 => break, + Err(e) => { + if buffer.is_empty() { + crash!(1, "read error: {}", e); + } + } + _ => (), + } + + let line = &buffer[..]; + let mut fields_pos = 1; + let mut low_idx = 0; + let mut delim_search = Searcher::new(line, delim.as_bytes()).peekable(); + let mut print_delim = false; + + if delim_search.peek().is_none() { + if !only_delimited { + crash_if_err!(1, out.write_all(line)); + if line[line.len() - 1] != newline_char { + crash_if_err!(1, out.write_all(&[newline_char])); + } + } + + continue; + } + + for &Range { low, high } in ranges.iter() { + if low - fields_pos > 0 { + low_idx = match delim_search.nth(low - fields_pos - 1) { + Some((_, beyond_delim)) => beyond_delim, + None => break, + }; + } + + for _ in 0..high - low + 1 { + if print_delim { + crash_if_err!(1, out.write_all(out_delim.as_bytes())); + } + + match delim_search.next() { + Some((high_idx, next_low_idx)) => { + let segment = &line[low_idx..high_idx]; + + crash_if_err!(1, out.write_all(segment)); + + print_delim = true; + + low_idx = next_low_idx; + fields_pos = high + 1; + } + None => { + let segment = &line[low_idx..]; + + crash_if_err!(1, out.write_all(segment)); + + if line[line.len() - 1] == newline_char { + continue 'newline; + } + break; + } + } + } + } + + crash_if_err!(1, out.write_all(&[newline_char])); + } + + 0 +} + +fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32 { + let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; + if let Some(ref o_delim) = opts.out_delimeter { + return cut_fields_delimiter( + reader, + ranges, + &opts.delimiter, + opts.only_delimited, + newline_char, + o_delim, + ); + } + + let mut buf_in = BufReader::new(reader); + let mut out = stdout(); + let mut buffer = Vec::new(); + + 'newline: loop { + buffer.clear(); + match buf_in.read_until(newline_char, &mut buffer) { + Ok(n) if n == 0 => break, + Err(e) => { + if buffer.is_empty() { + crash!(1, "read error: {}", e); + } + } + _ => (), + } + + let line = &buffer[..]; + let mut fields_pos = 1; + let mut low_idx = 0; + let mut delim_search = Searcher::new(line, opts.delimiter.as_bytes()).peekable(); + let mut print_delim = false; + + if delim_search.peek().is_none() { + if !opts.only_delimited { + crash_if_err!(1, out.write_all(line)); + if line[line.len() - 1] != newline_char { + crash_if_err!(1, out.write_all(&[newline_char])); + } + } + + continue; + } + + for &Range { low, high } in ranges.iter() { + if low - fields_pos > 0 { + low_idx = match delim_search.nth(low - fields_pos - 1) { + Some((_, beyond_delim)) => beyond_delim, + None => break, + }; + } + + if print_delim && low_idx >= opts.delimiter.as_bytes().len() { + low_idx -= opts.delimiter.as_bytes().len(); + } + + match delim_search.nth(high - low) { + Some((high_idx, next_low_idx)) => { + let segment = &line[low_idx..high_idx]; + + crash_if_err!(1, out.write_all(segment)); + + print_delim = true; + low_idx = next_low_idx; + fields_pos = high + 1; + } + None => { + let segment = &line[low_idx..line.len()]; + + crash_if_err!(1, out.write_all(segment)); + + if line[line.len() - 1] == newline_char { + continue 'newline; + } + break; + } + } + } + + crash_if_err!(1, out.write_all(&[newline_char])); + } + + 0 +} + +fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { + let mut stdin_read = false; + let mut exit_code = 0; + + if filenames.is_empty() { + filenames.push("-".to_owned()); + } + + for filename in &filenames { + if filename == "-" { + if stdin_read { + continue; + } + + exit_code |= match mode { + Mode::Bytes(ref ranges, ref opts) => cut_bytes(stdin(), ranges, opts), + Mode::Characters(ref ranges, ref opts) => cut_bytes(stdin(), ranges, opts), + Mode::Fields(ref ranges, ref opts) => cut_fields(stdin(), ranges, opts), + }; + + stdin_read = true; + } else { + let path = Path::new(&filename[..]); + + if !path.exists() { + show_error!("{}", msg_args_nonexistent_file!(filename)); + continue; + } + + let file = match File::open(&path) { + Ok(f) => f, + Err(e) => { + show_error!("opening '{}': {}", &filename[..], e); + continue; + } + }; + + exit_code |= match mode { + Mode::Bytes(ref ranges, ref opts) => cut_bytes(file, ranges, opts), + Mode::Characters(ref ranges, ref opts) => cut_bytes(file, ranges, opts), + Mode::Fields(ref ranges, ref opts) => cut_fields(file, ranges, opts), + }; + } + } + + exit_code +} + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optopt("b", "bytes", "filter byte columns from the input source", "sequence") + .optopt("c", "characters", "alias for character mode", "sequence") + .optopt("d", "delimiter", "specify the delimiter character that separates fields in the input source. Defaults to Tab.", "delimiter") + .optopt("f", "fields", "filter field columns from the input source", "sequence") + .optflag("n", "", "legacy option - has no effect.") + .optflag("", "complement", "invert the filter - instead of displaying only the filtered columns, display all but those columns") + .optflag("s", "only-delimited", "in field mode, only print lines which contain the delimiter") + .optflag("z", "zero-terminated", "instead of filtering columns based on line, filter columns based on \\0 (NULL character)") + .optopt("", "output-delimiter", "in field mode, replace the delimiter in output lines with this option's argument", "new delimiter") + .parse(args); + let complement = matches.opt_present("complement"); + + let mode_parse = match ( + matches.opt_str("bytes"), + matches.opt_str("characters"), + matches.opt_str("fields"), + ) { + (Some(byte_ranges), None, None) => { + list_to_ranges(&byte_ranges[..], complement).map(|ranges| { + Mode::Bytes( + ranges, + Options { + out_delim: matches.opt_str("output-delimiter"), + zero_terminated: matches.opt_present("zero-terminated"), + }, + ) + }) + } + (None, Some(char_ranges), None) => { + list_to_ranges(&char_ranges[..], complement).map(|ranges| { + Mode::Characters( + ranges, + Options { + out_delim: matches.opt_str("output-delimiter"), + zero_terminated: matches.opt_present("zero-terminated"), + }, + ) + }) + } + (None, None, Some(field_ranges)) => list_to_ranges(&field_ranges[..], complement) + .and_then(|ranges| { + let out_delim = match matches.opt_str("output-delimiter") { + Some(s) => { + if s.is_empty() { + Some("\0".to_owned()) + } else { + Some(s) + } + } + None => None, + }; + + let only_delimited = matches.opt_present("only-delimited"); + let zero_terminated = matches.opt_present("zero-terminated"); + + match matches.opt_str("delimiter") { + Some(delim) => { + if delim.chars().count() > 1 { + Err(msg_opt_invalid_should_be!( + "empty or 1 character long", + "a value 2 characters or longer", + "--delimiter", + "-d" + ).to_owned()) + } else { + let delim = if delim.is_empty() { + "\0".to_owned() + } else { + delim + }; + + Ok(Mode::Fields( + ranges, + FieldOptions { + delimiter: delim, + out_delimeter: out_delim, + only_delimited, + zero_terminated, + }, + )) + } + } + None => Ok(Mode::Fields( + ranges, + FieldOptions { + delimiter: "\t".to_owned(), + out_delimeter: out_delim, + only_delimited, + zero_terminated, + }, + )), + } + }), + (ref b, ref c, ref f) if b.is_some() || c.is_some() || f.is_some() => Err( + msg_expects_no_more_than_one_of!("--fields (-f)", "--chars (-c)", "--bytes (-b)") + .to_owned(), + ), + _ => Err(msg_expects_one_of!("--fields (-f)", "--chars (-c)", "--bytes (-b)").to_owned()), + }; + + let mode_parse = match mode_parse { + Err(_) => mode_parse, + Ok(mode) => match mode { + Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("delimiter") => Err( + msg_opt_only_usable_if!("printing a sequence of fields", "--delimiter", "-d") + .to_owned(), + ), + Mode::Bytes(_, _) | Mode::Characters(_, _) if matches.opt_present("only-delimited") => { + Err(msg_opt_only_usable_if!( + "printing a sequence of fields", + "--only-delimited", + "-s" + ).to_owned()) + } + _ => Ok(mode), + }, + }; + + match mode_parse { + Ok(mode) => cut_files(matches.free, mode), + Err(err_msg) => { + show_error!("{}", err_msg); + 1 + } + } +} diff --git a/coreutils/src/cut/ranges.rs b/coreutils/src/cut/ranges.rs new file mode 100644 index 000000000..44edcb943 --- /dev/null +++ b/coreutils/src/cut/ranges.rs @@ -0,0 +1,152 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Rolf Morel + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use std::str::FromStr; + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Range { + pub low: usize, + pub high: usize, +} + +impl FromStr for Range { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + use std::usize::MAX; + + let mut parts = s.splitn(2, '-'); + + let field = "fields and positions are numbered from 1"; + let order = "high end of range less than low end"; + let inval = "failed to parse range"; + + match (parts.next(), parts.next()) { + (Some(nm), None) => { + if let Ok(nm) = nm.parse::() { + if nm > 0 { + Ok(Range { low: nm, high: nm }) + } else { + Err(field) + } + } else { + Err(inval) + } + } + (Some(n), Some(m)) if m.is_empty() => { + if let Ok(low) = n.parse::() { + if low > 0 { + Ok(Range { + low, + high: MAX - 1, + }) + } else { + Err(field) + } + } else { + Err(inval) + } + } + (Some(n), Some(m)) if n.len() == 0 => { + if let Ok(high) = m.parse::() { + if high > 0 { + Ok(Range { low: 1, high }) + } else { + Err(field) + } + } else { + Err(inval) + } + } + (Some(n), Some(m)) => match (n.parse::(), m.parse::()) { + (Ok(low), Ok(high)) => { + if low > 0 && low <= high { + Ok(Range { + low, + high, + }) + } else if low == 0 { + Err(field) + } else { + Err(order) + } + } + _ => Err(inval), + }, + _ => unreachable!(), + } + } +} + +impl Range { + pub fn from_list(list: &str) -> Result, String> { + use std::cmp::max; + + let mut ranges: Vec = vec![]; + + for item in list.split(',') { + match FromStr::from_str(item) { + Ok(range_item) => ranges.push(range_item), + Err(e) => return Err(format!("range '{}' was invalid: {}", item, e)), + } + } + + ranges.sort(); + + // merge overlapping ranges + for i in 0..ranges.len() { + let j = i + 1; + + while j < ranges.len() && ranges[j].low <= ranges[i].high { + let j_high = ranges.remove(j).high; + ranges[i].high = max(ranges[i].high, j_high); + } + } + + Ok(ranges) + } +} + +pub fn complement(ranges: &[Range]) -> Vec { + use std::usize; + + let mut complements = Vec::with_capacity(ranges.len() + 1); + + if !ranges.is_empty() && ranges[0].low > 1 { + complements.push(Range { + low: 1, + high: ranges[0].low - 1, + }); + } + + let mut ranges_iter = ranges.iter().peekable(); + loop { + match (ranges_iter.next(), ranges_iter.peek()) { + (Some(left), Some(right)) => { + if left.high + 1 != right.low { + complements.push(Range { + low: left.high + 1, + high: right.low - 1, + }); + } + } + (Some(last), None) => { + if last.high < usize::MAX - 1 { + complements.push(Range { + low: last.high + 1, + high: usize::MAX - 1, + }); + } + } + _ => break, + } + } + + complements +} diff --git a/coreutils/src/cut/searcher.rs b/coreutils/src/cut/searcher.rs new file mode 100644 index 000000000..2394ca539 --- /dev/null +++ b/coreutils/src/cut/searcher.rs @@ -0,0 +1,54 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Rolf Morel + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[derive(Clone)] +pub struct Searcher<'a> { + haystack: &'a [u8], + needle: &'a [u8], + position: usize, +} + +impl<'a> Searcher<'a> { + pub fn new(haystack: &'a [u8], needle: &'a [u8]) -> Searcher<'a> { + Searcher { + haystack, + needle, + position: 0, + } + } +} + +impl<'a> Iterator for Searcher<'a> { + type Item = (usize, usize); + + fn next(&mut self) -> Option<(usize, usize)> { + if self.needle.len() == 1 { + for offset in self.position..self.haystack.len() { + if self.haystack[offset] == self.needle[0] { + self.position = offset + 1; + return Some((offset, offset + 1)); + } + } + + self.position = self.haystack.len(); + return None; + } + + while self.position + self.needle.len() <= self.haystack.len() { + if &self.haystack[self.position..self.position + self.needle.len()] == self.needle { + let match_pos = self.position; + self.position += self.needle.len(); + return Some((match_pos, match_pos + self.needle.len())); + } else { + self.position += 1; + } + } + None + } +} diff --git a/coreutils/src/date/Cargo.toml b/coreutils/src/date/Cargo.toml new file mode 100644 index 000000000..29b66ded3 --- /dev/null +++ b/coreutils/src/date/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "date" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_date" +path = "date.rs" + +[dependencies] +chrono = "0.4.4" +clap = "2.32.0" +uucore = "0.0.1" + +[[bin]] +name = "date" +path = "../../uumain.rs" diff --git a/coreutils/src/date/date.rs b/coreutils/src/date/date.rs new file mode 100644 index 000000000..db81f2fd6 --- /dev/null +++ b/coreutils/src/date/date.rs @@ -0,0 +1,272 @@ +#![crate_name = "uu_date"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Anthony Deschamps + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate chrono; +#[macro_use] +extern crate clap; +extern crate uucore; + +use chrono::{DateTime, FixedOffset, Local, Offset}; +use chrono::offset::Utc; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; + +// Options +const DATE: &str = "date"; +const HOURS: &str = "hours"; +const MINUTES: &str = "minutes"; +const SECONDS: &str = "seconds"; +const NS: &str = "ns"; + +// Help strings + +static ISO_8601_HELP_STRING: &str = "output date/time in ISO 8601 format. + FMT='date' for date only (the default), + 'hours', 'minutes', 'seconds', or 'ns' + for date and time to the indicated precision. + Example: 2006-08-14T02:34:56-06:00"; + +static RFC_2822_HELP_STRING: &str = "output date and time in RFC 2822 format. + Example: Mon, 14 Aug 2006 02:34:56 -0600"; + +static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format. + FMT='date', 'seconds', or 'ns' + for date and time to the indicated precision. + Example: 2006-08-14 02:34:56-06:00"; + +/// Settings for this program, parsed from the command line +struct Settings { + utc: bool, + format: Format, + date_source: DateSource, + set_to: Option>, +} + +/// Various ways of displaying the date +enum Format { + Iso8601(Iso8601Format), + Rfc2822, + Rfc3339(Rfc3339Format), + Custom(String), + Default, +} + +/// Various places that dates can come from +enum DateSource { + Now, + Custom(String), + File(PathBuf), +} + +enum Iso8601Format { + Date, + Hours, + Minutes, + Seconds, + Ns, +} + +impl<'a> From<&'a str> for Iso8601Format { + fn from(s: &str) -> Self { + match s { + HOURS => Iso8601Format::Hours, + MINUTES => Iso8601Format::Minutes, + SECONDS => Iso8601Format::Seconds, + NS => Iso8601Format::Ns, + DATE => Iso8601Format::Date, + // Should be caught by clap + _ => panic!("Invalid format: {}", s), + } + } +} + +enum Rfc3339Format { + Date, + Seconds, + Ns, +} + +impl<'a> From<&'a str> for Rfc3339Format { + fn from(s: &str) -> Self { + match s { + DATE => Rfc3339Format::Date, + SECONDS => Rfc3339Format::Seconds, + NS => Rfc3339Format::Ns, + // Should be caught by clap + _ => panic!("Invalid format: {}", s), + } + } +} + +pub fn uumain(args: Vec) -> i32 { + let settings = parse_cli(args); + + 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 + } else { + // Declare a file here because it needs to outlive the `dates` iterator. + let file: File; + + // Get the current time, either in the local time zone or UTC. + let now: DateTime = if settings.utc { + let now = Utc::now(); + now.with_timezone(&now.offset().fix()) + } else { + let now = Local::now(); + 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) => { + let date = parse_date(input.clone()); + let iter = std::iter::once(date); + Box::new(iter) + } + DateSource::File(ref path) => { + file = File::open(path).unwrap(); + let lines = BufReader::new(file).lines(); + let iter = lines.filter_map(Result::ok).map(parse_date); + Box::new(iter) + } + DateSource::Now => { + let iter = std::iter::once(Ok(now)); + Box::new(iter) + } + }; + + let format_string = make_format_string(&settings); + + // Format all the dates + for date in dates { + match date { + Ok(date) => { + let formatted = date.format(format_string); + println!("{}", formatted); + } + Err((input, _err)) => { + println!("date: invalid date '{}'", input); + } + } + } + } + + 0 +} + +/// Handle command line arguments. +fn parse_cli(args: Vec) -> Settings { + let matches = clap_app!( + date => + (@group dates => + (@arg date: -d --date [STRING] + "display time described by STRING, not 'now'") + (@arg file: -f --file [DATEFILE] + "like --date; once for each line of DATEFILE")) + + (@group format => + (@arg iso_8601: -I --("iso-8601") + possible_value[date hours minutes seconds ns] + #{0, 1} + ISO_8601_HELP_STRING) + (@arg rfc_2822: -R --("rfc-2822") + RFC_2822_HELP_STRING) + (@arg rfc_3339: --("rfc-3339") + possible_value[date seconds ns] + RFC_3339_HELP_STRING) + (@arg custom_format: +takes_value { + |s| if s.starts_with('+') { + Ok(()) + } else { + Err(String::from("Date formats must start with a '+' character")) + } + })) + + (@arg debug: --debug + "annotate the parsed date, and warn about questionable usage to stderr") + (@arg reference: -r --reference [FILE] + "display the last modification time of FILE") + (@arg set: -s --set [STRING] + "set time described by STRING") + (@arg utc: -u --utc --universal + "print or set Coordinated Universal Time (UTC)")) + + // TODO: Decide whether this is appropriate. + // The GNU date command has an explanation of all formatting options, + // but the `chrono` crate has a few differences (most notably, the %Z option) + // (after_help: include_str!("usage.txt"))) + .get_matches_from(args); + + let format = if let Some(form) = matches.value_of("custom_format") { + let form = form[1..].into(); + Format::Custom(form) + } else if let Some(fmt) = matches + .values_of("iso_8601") + .map(|mut iter| iter.next().unwrap_or(DATE).into()) + { + Format::Iso8601(fmt) + } else if matches.is_present("rfc_2822") { + Format::Rfc2822 + } else if let Some(fmt) = matches.value_of("rfc_3339").map(Into::into) { + Format::Rfc3339(fmt) + } else { + Format::Default + }; + + let date_source = if let Some(date) = matches.value_of("date") { + DateSource::Custom(date.into()) + } else if let Some(file) = matches.value_of("file") { + DateSource::File(file.into()) + } else { + DateSource::Now + }; + + Settings { + utc: matches.is_present("utc"), + format, + date_source, + // TODO: Handle this option: + set_to: None, + } +} + +/// Return the appropriate format string for the given settings. +fn make_format_string(settings: &Settings) -> &str { + match settings.format { + Format::Iso8601(ref fmt) => match *fmt { + Iso8601Format::Date => "%F", + Iso8601Format::Hours => "%FT%H%:z", + Iso8601Format::Minutes => "%FT%H:%M%:z", + Iso8601Format::Seconds => "%FT%T%:z", + Iso8601Format::Ns => "%FT%T,%f%:z", + }, + Format::Rfc2822 => "%a, %d %h %Y %T %z", + Format::Rfc3339(ref fmt) => match *fmt { + Rfc3339Format::Date => "%F", + Rfc3339Format::Seconds => "%F %T%:z", + Rfc3339Format::Ns => "%F %T.%f%:z", + }, + Format::Custom(ref fmt) => fmt, + Format::Default => "%c", + } +} diff --git a/coreutils/src/date/usage.txt b/coreutils/src/date/usage.txt new file mode 100644 index 000000000..12df1a03c --- /dev/null +++ b/coreutils/src/date/usage.txt @@ -0,0 +1,72 @@ +FORMAT controls the output. Interpreted sequences are: + + %% a literal % + %a locale's abbreviated weekday name (e.g., Sun) + %A locale's full weekday name (e.g., Sunday) + %b locale's abbreviated month name (e.g., Jan) + %B locale's full month name (e.g., January) + %c locale's date and time (e.g., Thu Mar 3 23:05:25 2005) + %C century; like %Y, except omit last two digits (e.g., 20) + %d day of month (e.g., 01) + %D date; same as %m/%d/%y + %e day of month, space padded; same as %_d + %F full date; same as %Y-%m-%d + %g last two digits of year of ISO week number (see %G) + %G year of ISO week number (see %V); normally useful only with %V + %h same as %b + %H hour (00..23) + %I hour (01..12) + %j day of year (001..366) + %k hour, space padded ( 0..23); same as %_H + %l hour, space padded ( 1..12); same as %_I + %m month (01..12) + %M minute (00..59) + %n a newline + %N nanoseconds (000000000..999999999) + %p locale's equivalent of either AM or PM; blank if not known + %P like %p, but lower case + %q quarter of year (1..4) + %r locale's 12-hour clock time (e.g., 11:11:04 PM) + %R 24-hour hour and minute; same as %H:%M + %s seconds since 1970-01-01 00:00:00 UTC + %S second (00..60) + %t a tab + %T time; same as %H:%M:%S + %u day of week (1..7); 1 is Monday + %U week number of year, with Sunday as first day of week (00..53) + %V ISO week number, with Monday as first day of week (01..53) + %w day of week (0..6); 0 is Sunday + %W week number of year, with Monday as first day of week (00..53) + %x locale's date representation (e.g., 12/31/99) + %X locale's time representation (e.g., 23:13:48) + %y last two digits of year (00..99) + %Y year + %z +hhmm numeric time zone (e.g., -0400) + %:z +hh:mm numeric time zone (e.g., -04:00) + %::z +hh:mm:ss numeric time zone (e.g., -04:00:00) + %:::z numeric time zone with : to necessary precision (e.g., -04, +05:30) + %Z alphabetic time zone abbreviation (e.g., EDT) + +By default, date pads numeric fields with zeroes. +The following optional flags may follow '%': + + - (hyphen) do not pad the field + _ (underscore) pad with spaces + 0 (zero) pad with zeros + ^ use upper case if possible + # use opposite case if possible + +After any flags comes an optional field width, as a decimal number; +then an optional modifier, which is either +E to use the locale's alternate representations if available, or +O to use the locale's alternate numeric symbols if available. + +Examples: +Convert seconds since the epoch (1970-01-01 UTC) to a date + $ date --date='@2147483647' + +Show the time on the west coast of the US (use tzselect(1) to find TZ) + $ TZ='America/Los_Angeles' date + +Show the local time for 9AM next Friday on the west coast of the US + $ date --date='TZ="America/Los_Angeles" 09:00 next Fri' diff --git a/coreutils/src/dircolors/Cargo.toml b/coreutils/src/dircolors/Cargo.toml new file mode 100644 index 000000000..f80e8c941 --- /dev/null +++ b/coreutils/src/dircolors/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dircolors" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_dircolors" +path = "dircolors.rs" + +[dependencies] +glob = "0.3.0" +uucore = "0.0.1" + +[[bin]] +name = "dircolors" +path = "../../uumain.rs" diff --git a/coreutils/src/dircolors/colors.rs b/coreutils/src/dircolors/colors.rs new file mode 100644 index 000000000..c19920508 --- /dev/null +++ b/coreutils/src/dircolors/colors.rs @@ -0,0 +1,192 @@ +pub const INTERNAL_DB: &str = + r#"# Configuration file for dircolors, a utility to help you set the +# LS_COLORS environment variable used by GNU ls with the --color option. +# Copyright (C) 1996-2016 Free Software Foundation, Inc. +# Copying and distribution of this file, with or without modification, +# are permitted provided the copyright notice and this notice are preserved. +# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the +# slackware version of dircolors) are recognized but ignored. +# Below are TERM entries, which can be a glob patterns, to match +# against the TERM environment variable to determine if it is colorizable. +TERM Eterm +TERM ansi +TERM color-xterm +TERM con[0-9]*x[0-9]* +TERM cons25 +TERM console +TERM cygwin +TERM dtterm +TERM eterm-color +TERM gnome +TERM gnome-256color +TERM hurd +TERM jfbterm +TERM konsole +TERM kterm +TERM linux +TERM linux-c +TERM mach-color +TERM mach-gnu-color +TERM mlterm +TERM putty +TERM putty-256color +TERM rxvt* +TERM screen* +TERM st +TERM st-256color +TERM terminator +TERM tmux* +TERM vt100 +TERM xterm* +# Below are the color init strings for the basic file types. A color init +# string consists of one or more of the following numeric codes: +# Attribute codes: +# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed +# Text color codes: +# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white +# Background color codes: +# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white +#NORMAL 00 # no color code at all +#FILE 00 # regular file: use no color at all +RESET 0 # reset to "normal" color +DIR 01;34 # directory +LINK 01;36 # symbolic link. (If you set this to 'target' instead of a + # numerical value, the color is as for the file pointed to.) +MULTIHARDLINK 00 # regular file with more than one link +FIFO 40;33 # pipe +SOCK 01;35 # socket +DOOR 01;35 # door +BLK 40;33;01 # block device driver +CHR 40;33;01 # character device driver +ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ... +MISSING 00 # ... and the files they point to +SETUID 37;41 # file that is setuid (u+s) +SETGID 30;43 # file that is setgid (g+s) +CAPABILITY 30;41 # file with capability +STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w) +OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky +STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable +# This is for files with execute permission: +EXEC 01;32 +# List any file extensions like '.gz' or '.tar' that you would like ls +# to colorize below. Put the extension, a space, and the color init string. +# (and any comments you want to add after a '#') +# If you use DOS-style suffixes, you may want to uncomment the following: +#.cmd 01;32 # executables (bright green) +#.exe 01;32 +#.com 01;32 +#.btm 01;32 +#.bat 01;32 +# Or if you want to colorize scripts even if they do not have the +# executable bit actually set. +#.sh 01;32 +#.csh 01;32 + # archives or compressed (bright red) +.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 +# image formats +.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 +# http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions +.ogv 01;35 +.ogx 01;35 +# audio formats +.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 +# http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions +.oga 00;36 +.opus 00;36 +.spx 00;36 +.xspf 00;36"#; diff --git a/coreutils/src/dircolors/dircolors.rs b/coreutils/src/dircolors/dircolors.rs new file mode 100644 index 000000000..864438500 --- /dev/null +++ b/coreutils/src/dircolors/dircolors.rs @@ -0,0 +1,311 @@ +#![crate_name = "uu_dircolors"] + +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// + +extern crate glob; + +#[macro_use] +extern crate uucore; + +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::borrow::Borrow; +use std::env; + +static SYNTAX: &str = "[OPTION]... [FILE]"; +static SUMMARY: &str = "Output commands to set the LS_COLORS environment variable."; +static LONG_HELP: &str = " + If FILE is specified, read it to determine which colors to use for which + file types and extensions. Otherwise, a precompiled database is used. + For details on the format of these files, run 'dircolors --print-database' +"; + +mod colors; +use colors::INTERNAL_DB; + +#[derive(PartialEq, Debug)] +pub enum OutputFmt { + Shell, + CShell, + Unknown, +} + +pub fn guess_syntax() -> OutputFmt { + use std::path::Path; + match env::var("SHELL") { + Ok(ref s) if !s.is_empty() => { + let shell_path: &Path = s.as_ref(); + if let Some(name) = shell_path.file_name() { + if name == "csh" || name == "tcsh" { + OutputFmt::CShell + } else { + OutputFmt::Shell + } + } else { + OutputFmt::Shell + } + } + _ => OutputFmt::Unknown, + } +} + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optflag("b", "sh", "output Bourne shell code to set LS_COLORS") + .optflag( + "", + "bourne-shell", + "output Bourne shell code to set LS_COLORS", + ) + .optflag("c", "csh", "output C shell code to set LS_COLORS") + .optflag("", "c-shell", "output C shell code to set LS_COLORS") + .optflag("p", "print-database", "print the byte counts") + .parse(args); + + if (matches.opt_present("csh") || matches.opt_present("c-shell") || matches.opt_present("sh") + || matches.opt_present("bourne-shell")) && matches.opt_present("print-database") + { + disp_err!( + "the options to output dircolors' internal database and\nto select a shell \ + syntax are mutually exclusive" + ); + return 1; + } + + if matches.opt_present("print-database") { + if !matches.free.is_empty() { + disp_err!( + "extra operand ‘{}’\nfile operands cannot be combined with \ + --print-database (-p)", + matches.free[0] + ); + return 1; + } + println!("{}", INTERNAL_DB); + return 0; + } + + let mut out_format = OutputFmt::Unknown; + if matches.opt_present("csh") || matches.opt_present("c-shell") { + out_format = OutputFmt::CShell; + } else if matches.opt_present("sh") || matches.opt_present("bourne-shell") { + out_format = OutputFmt::Shell; + } + + if out_format == OutputFmt::Unknown { + match guess_syntax() { + OutputFmt::Unknown => { + show_info!("no SHELL environment variable, and no shell type option given"); + return 1; + } + fmt => out_format = fmt, + } + } + + let result; + if matches.free.is_empty() { + result = parse(INTERNAL_DB.lines(), out_format, "") + } else { + if matches.free.len() > 1 { + disp_err!("extra operand ‘{}’", matches.free[1]); + return 1; + } + match File::open(matches.free[0].as_str()) { + Ok(f) => { + let fin = BufReader::new(f); + result = parse( + fin.lines().filter_map(|l| l.ok()), + out_format, + matches.free[0].as_str(), + ) + } + Err(e) => { + show_info!("{}: {}", matches.free[0], e); + return 1; + } + } + } + match result { + Ok(s) => { + println!("{}", s); + 0 + } + Err(s) => { + show_info!("{}", s); + 1 + } + } +} + +pub trait StrUtils { + /// Remove comments and trim whitespace + fn purify(&self) -> &Self; + /// Like split_whitespace() but only produce 2 components + fn split_two(&self) -> (&str, &str); + fn fnmatch(&self, pattern: &str) -> bool; +} + +impl StrUtils for str { + fn purify(&self) -> &Self { + let mut line = self; + for (n, c) in self.chars().enumerate() { + if c != '#' { + continue; + } + + // Ignore if '#' is at the beginning of line + if n == 0 { + line = &self[..0]; + break; + } + + // Ignore the content after '#' + // only if it is preceded by at least one whitespace + if self.chars().nth(n - 1).unwrap().is_whitespace() { + line = &self[..n]; + } + } + line.trim() + } + + fn split_two(&self) -> (&str, &str) { + if let Some(b) = self.find(char::is_whitespace) { + let key = &self[..b]; + if let Some(e) = self[b..].find(|c: char| !c.is_whitespace()) { + (key, &self[b + e..]) + } else { + (key, "") + } + } else { + ("", "") + } + } + + fn fnmatch(&self, pat: &str) -> bool { + pat.parse::().unwrap().matches(self) + } +} + +#[derive(PartialEq)] +enum ParseState { + Global, + Matched, + Continue, + Pass, +} +use std::collections::HashMap; +fn parse(lines: T, fmt: OutputFmt, fp: &str) -> Result +where + T: IntoIterator, + T::Item: Borrow, +{ + // 1440 > $(dircolors | wc -m) + let mut result = String::with_capacity(1440); + match fmt { + OutputFmt::Shell => result.push_str("LS_COLORS='"), + OutputFmt::CShell => result.push_str("setenv LS_COLORS '"), + _ => unreachable!(), + } + + let mut table: HashMap<&str, &str> = HashMap::with_capacity(48); + table.insert("normal", "no"); + table.insert("norm", "no"); + table.insert("file", "fi"); + table.insert("reset", "rs"); + table.insert("dir", "di"); + table.insert("lnk", "ln"); + table.insert("link", "ln"); + table.insert("symlink", "ln"); + table.insert("orphan", "or"); + table.insert("missing", "mi"); + table.insert("fifo", "pi"); + table.insert("pipe", "pi"); + table.insert("sock", "so"); + table.insert("blk", "bd"); + table.insert("block", "bd"); + table.insert("chr", "cd"); + table.insert("char", "cd"); + table.insert("door", "do"); + table.insert("exec", "ex"); + table.insert("left", "lc"); + table.insert("leftcode", "lc"); + table.insert("right", "rc"); + table.insert("rightcode", "rc"); + table.insert("end", "ec"); + table.insert("endcode", "ec"); + table.insert("suid", "su"); + table.insert("setuid", "su"); + table.insert("sgid", "sg"); + table.insert("setgid", "sg"); + table.insert("sticky", "st"); + table.insert("other_writable", "ow"); + table.insert("owr", "ow"); + table.insert("sticky_other_writable", "tw"); + table.insert("owt", "tw"); + table.insert("capability", "ca"); + table.insert("multihardlink", "mh"); + table.insert("clrtoeol", "cl"); + + let term = env::var("TERM").unwrap_or_else(|_| "none".to_owned()); + let term = term.as_str(); + + let mut state = ParseState::Global; + + for (num, line) in lines.into_iter().enumerate() { + let num = num + 1; + let line = line.borrow().purify(); + if line.is_empty() { + continue; + } + + let (key, val) = line.split_two(); + if val.is_empty() { + return Err(format!( + "{}:{}: invalid line; missing second token", + fp, num + )); + } + let lower = key.to_lowercase(); + + if lower == "term" { + if term.fnmatch(val) { + state = ParseState::Matched; + } else if state != ParseState::Matched { + state = ParseState::Pass; + } + } else { + if state == ParseState::Matched { + // prevent subsequent mismatched TERM from + // cancelling the input + state = ParseState::Continue; + } + if state != ParseState::Pass { + if key.starts_with('.') { + result.push_str(format!("*{}={}:", key, val).as_str()); + } else if key.starts_with('*') { + result.push_str(format!("{}={}:", key, val).as_str()); + } else if lower == "options" || lower == "color" || lower == "eightbit" { + // Slackware only. Ignore + } else if let Some(s) = table.get(lower.as_str()) { + result.push_str(format!("{}={}:", s, val).as_str()); + } else { + return Err(format!("{}:{}: unrecognized keyword {}", fp, num, key)); + } + } + } + } + + match fmt { + OutputFmt::Shell => result.push_str("';\nexport LS_COLORS"), + OutputFmt::CShell => result.push('\''), + _ => unreachable!(), + } + + Ok(result) +} diff --git a/coreutils/src/dirname/Cargo.toml b/coreutils/src/dirname/Cargo.toml new file mode 100644 index 000000000..746757b48 --- /dev/null +++ b/coreutils/src/dirname/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dirname" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_dirname" +path = "dirname.rs" + +[dependencies] +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "dirname" +path = "../../uumain.rs" diff --git a/coreutils/src/dirname/dirname.rs b/coreutils/src/dirname/dirname.rs new file mode 100644 index 000000000..d56c9e464 --- /dev/null +++ b/coreutils/src/dirname/dirname.rs @@ -0,0 +1,65 @@ +#![crate_name = "uu_dirname"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Derek Chiang + * + * 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 std::path::Path; + +static NAME: &str = "dirname"; +static SYNTAX: &str = "[OPTION] NAME..."; +static SUMMARY: &str = "strip last component from file name"; +static LONG_HELP: &str = " + Output each NAME with its last non-slash component and trailing slashes + removed; if NAME contains no /'s, output '.' (meaning the current + directory). +"; + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optflag("z", "zero", "separate output with NUL rather than newline") + .parse(args); + + let separator = if matches.opt_present("zero") { + "\0" + } else { + "\n" + }; + + if !matches.free.is_empty() { + for path in &matches.free { + let p = Path::new(path); + match p.parent() { + Some(d) => { + if d.components().next() == None { + print!(".") + } else { + print!("{}", d.to_string_lossy()); + } + } + None => { + if p.is_absolute() || path == "/" { + print!("/"); + } else { + print!("."); + } + } + } + print!("{}", separator); + } + } else { + println!("{0}: missing operand", NAME); + println!("Try '{0} --help' for more information.", NAME); + return 1; + } + + 0 +} diff --git a/coreutils/src/du/Cargo.toml b/coreutils/src/du/Cargo.toml new file mode 100644 index 000000000..8bbbfc869 --- /dev/null +++ b/coreutils/src/du/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "du" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_du" +path = "du.rs" + +[dependencies] +time = "0.1.40" +uucore = "0.0.1" + +[[bin]] +name = "du" +path = "../../uumain.rs" diff --git a/coreutils/src/du/du.rs b/coreutils/src/du/du.rs new file mode 100644 index 000000000..a3fc1dd1e --- /dev/null +++ b/coreutils/src/du/du.rs @@ -0,0 +1,497 @@ +#![crate_name = "uu_du"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Derek Chiang + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate time; + +#[macro_use] +extern crate uucore; + +use std::collections::HashSet; +use std::env; +use std::fs; +use std::io::{stderr, Result, Write}; +use std::iter; +use std::os::unix::fs::MetadataExt; +use std::path::PathBuf; +use time::Timespec; + +const NAME: &str = "du"; +const SUMMARY: &str = "estimate file space usage"; +const LONG_HELP: &str = " + Display values are in units of the first available SIZE from + --block-size, and the DU_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environ‐ + ment variables. Otherwise, units default to 1024 bytes (or 512 if + POSIXLY_CORRECT is set). + + SIZE is an integer and optional unit (example: 10M is 10*1024*1024). + Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB, ... (pow‐ + ers of 1000). +"; + +// TODO: Suport Z & Y (currently limited by size of u64) +const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)]; + +struct Options { + all: bool, + program_name: String, + max_depth: Option, + total: bool, + separate_dirs: bool, +} + +struct Stat { + path: PathBuf, + is_dir: bool, + size: u64, + blocks: u64, + inode: u64, + created: u64, + accessed: u64, + modified: u64, +} + +impl Stat { + fn new(path: PathBuf) -> Result { + let metadata = fs::symlink_metadata(&path)?; + Ok(Stat { + path, + is_dir: metadata.is_dir(), + size: metadata.len(), + blocks: metadata.blocks() as u64, + inode: metadata.ino() as u64, + created: metadata.mtime() as u64, + accessed: metadata.atime() as u64, + modified: metadata.mtime() as u64, + }) + } +} + +fn unit_string_to_number(s: &str) -> Option { + let mut offset = 0; + let mut s_chars = s.chars().rev(); + + let (mut ch, multiple) = match s_chars.next() { + Some('B') | Some('b') => ('B', 1000u64), + Some(ch) => (ch, 1024u64), + None => return None, + }; + if ch == 'B' { + ch = s_chars.next()?; + offset += 1; + } + ch = ch.to_ascii_uppercase(); + + let unit = UNITS + .iter() + .rev() + .find(|&&(unit_ch, _)| unit_ch == ch) + .map(|&(_, val)| { + // we found a match, so increment offset + offset += 1; + val + }) + .or_else(|| if multiple == 1024 { Some(0) } else { None })?; + + let number = s[..s.len() - offset].parse::().ok()?; + + Some(number * multiple.pow(unit)) +} + +fn translate_to_pure_number(s: &Option) -> Option { + match *s { + Some(ref s) => unit_string_to_number(s), + None => None, + } +} + +fn read_block_size(s: Option) -> u64 { + match translate_to_pure_number(&s) { + Some(v) => v, + None => { + if let Some(value) = s { + show_error!("invalid --block-size argument '{}'", value); + }; + + for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { + if let Some(quantity) = translate_to_pure_number(&env::var(env_var).ok()) { + return quantity; + } + } + + if env::var("POSIXLY_CORRECT").is_ok() { + 512 + } else { + 1024 + } + } + } +} + +// this takes `my_stat` to avoid having to stat files multiple times. +// XXX: this should use the impl Trait return type when it is stabilized +fn du( + mut my_stat: Stat, + options: &Options, + depth: usize, + inodes: &mut HashSet, +) -> Box> { + let mut stats = vec![]; + let mut futures = vec![]; + + if my_stat.is_dir { + let read = match fs::read_dir(&my_stat.path) { + Ok(read) => read, + Err(e) => { + safe_writeln!( + stderr(), + "{}: cannot read directory ‘{}‘: {}", + options.program_name, + my_stat.path.display(), + e + ); + return Box::new(iter::once(my_stat)); + } + }; + + for f in read { + match f { + Ok(entry) => { + match Stat::new(entry.path()) { + Ok(this_stat) => { + if this_stat.is_dir { + futures.push(du(this_stat, options, depth + 1, inodes)); + } else { + if inodes.contains(&this_stat.inode) { + continue; + } + inodes.insert(this_stat.inode); + my_stat.size += this_stat.size; + my_stat.blocks += this_stat.blocks; + if options.all { + stats.push(this_stat); + } + } + } + Err(error) => show_error!("{}", error), + } + } + Err(error) => show_error!("{}", error), + } + } + } + + stats.extend(futures.into_iter().flat_map(|val| val).rev().filter_map( + |stat| { + if !options.separate_dirs && stat.path.parent().unwrap() == my_stat.path { + my_stat.size += stat.size; + my_stat.blocks += stat.blocks; + } + if options.max_depth == None || depth < options.max_depth.unwrap() { + Some(stat) + } else { + None + } + }, + )); + stats.push(my_stat); + Box::new(stats.into_iter()) +} + +fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String { + for &(unit, power) in &UNITS { + let limit = multiplier.pow(power); + if size >= limit { + return format!("{:.1}{}", (size as f64) / (limit as f64), unit); + } + } + format!("{}B", size) +} + +fn convert_size_b(size: u64, _multiplier: u64, _block_size: u64) -> String { + format!("{}", ((size as f64) / (1 as f64)).ceil()) +} + +fn convert_size_k(size: u64, multiplier: u64, _block_size: u64) -> String { + format!("{}", ((size as f64) / (multiplier as f64)).ceil()) +} + +fn convert_size_m(size: u64, multiplier: u64, _block_size: u64) -> String { + format!("{}", ((size as f64) / ((multiplier * multiplier) as f64)).ceil()) +} + +fn convert_size_other(size: u64, _multiplier: u64, block_size: u64) -> String { + format!("{}", ((size as f64) / (block_size as f64)).ceil()) +} + +pub fn uumain(args: Vec) -> i32 { + let syntax = format!( + "[OPTION]... [FILE]... + {0} [OPTION]... --files0-from=F", + NAME + ); + let matches = new_coreopts!(&syntax, SUMMARY, LONG_HELP) + // In task + .optflag("a", "all", " write counts for all files, not just directories") + // In main + .optflag("", "apparent-size", "print apparent sizes, rather than disk usage + although the apparent size is usually smaller, it may be larger due to holes + in ('sparse') files, internal fragmentation, indirect blocks, and the like") + // In main + .optopt("B", "block-size", "scale sizes by SIZE before printing them. + E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below.", + "SIZE") + // In main + .optflag("b", "bytes", "equivalent to '--apparent-size --block-size=1'") + // In main + .optflag("c", "total", "produce a grand total") + // In task + // opts.optflag("D", "dereference-args", "dereference only symlinks that are listed + // on the command line"), + // In main + // opts.optopt("", "files0-from", "summarize disk usage of the NUL-terminated file + // names specified in file F; + // If F is - then read names from standard input", "F"), + // // In task + // opts.optflag("H", "", "equivalent to --dereference-args (-D)"), + // In main + .optflag("h", "human-readable", "print sizes in human readable format (e.g., 1K 234M 2G)") + // In main + .optflag("", "si", "like -h, but use powers of 1000 not 1024") + // In main + .optflag("k", "", "like --block-size=1K") + // In task + .optflag("l", "count-links", "count sizes many times if hard linked") + // // In main + .optflag("m", "", "like --block-size=1M") + // // In task + // opts.optflag("L", "dereference", "dereference all symbolic links"), + // // In task + // opts.optflag("P", "no-dereference", "don't follow any symbolic links (this is the default)"), + // // In main + .optflag("0", "null", "end each output line with 0 byte rather than newline") + // In main + .optflag("S", "separate-dirs", "do not include size of subdirectories") + // In main + .optflag("s", "summarize", "display only a total for each argument") + // // In task + // opts.optflag("x", "one-file-system", "skip directories on different file systems"), + // // In task + // opts.optopt("X", "exclude-from", "exclude files that match any pattern in FILE", "FILE"), + // // In task + // opts.optopt("", "exclude", "exclude files that match PATTERN", "PATTERN"), + // In main + .optopt("d", "max-depth", "print the total for a directory (or file, with --all) + only if it is N or fewer levels below the command + line argument; --max-depth=0 is the same as --summarize", "N") + // In main + .optflagopt("", "time", "show time of the last modification of any file in the + directory, or any of its subdirectories. If WORD is given, show time as WORD instead + of modification time: atime, access, use, ctime or status", "WORD") + // In main + .optopt("", "time-style", "show times using style STYLE: + full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'", "STYLE") + .parse(args); + + let summarize = matches.opt_present("summarize"); + + let max_depth_str = matches.opt_str("max-depth"); + let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::().ok()); + match (max_depth_str, max_depth) { + (Some(ref s), _) if summarize => { + show_error!("summarizing conflicts with --max-depth={}", *s); + return 1; + } + (Some(ref s), None) => { + show_error!("invalid maximum depth '{}'", *s); + return 1; + } + (Some(_), Some(_)) | (None, _) => { /* valid */ } + } + + let options = Options { + all: matches.opt_present("all"), + program_name: NAME.to_owned(), + max_depth, + total: matches.opt_present("total"), + separate_dirs: matches.opt_present("S"), + }; + + let strs = if matches.free.is_empty() { + vec!["./".to_owned()] + } else { + matches.free.clone() + }; + + let block_size = read_block_size(matches.opt_str("block-size")); + + let multiplier: u64 = if matches.opt_present("si") { + 1000 + } else { + 1024 + }; + let convert_size_fn = { + if matches.opt_present("human-readable") || matches.opt_present("si") { + convert_size_human + } else if matches.opt_present("b") { + convert_size_b + } else if matches.opt_present("k") { + convert_size_k + } else if matches.opt_present("m") { + convert_size_m + } else { + convert_size_other + } + }; + let convert_size = |size| convert_size_fn(size, multiplier, block_size); + + let time_format_str = match matches.opt_str("time-style") { + Some(s) => { + match &s[..] { + "full-iso" => "%Y-%m-%d %H:%M:%S.%f %z", + "long-iso" => "%Y-%m-%d %H:%M", + "iso" => "%Y-%m-%d", + _ => { + show_error!( + "invalid argument '{}' for 'time style' +Valid arguments are: +- 'full-iso' +- 'long-iso' +- 'iso' +Try '{} --help' for more information.", + s, + NAME + ); + return 1; + } + } + } + None => "%Y-%m-%d %H:%M", + }; + + let line_separator = if matches.opt_present("0") { "\0" } else { "\n" }; + + let mut grand_total = 0; + for path_str in strs { + let path = PathBuf::from(&path_str); + match Stat::new(path) { + Ok(stat) => { + let mut inodes: HashSet = HashSet::new(); + + let iter = du(stat, &options, 0, &mut inodes); + let (_, len) = iter.size_hint(); + let len = len.unwrap(); + for (index, stat) in iter.enumerate() { + let size = if matches.opt_present("apparent-size") { + stat.size + } else if matches.opt_present("b") { + stat.size + } else { + // C's stat is such that each block is assume to be 512 bytes + // See: http://linux.die.net/man/2/stat + stat.blocks * 512 + }; + if matches.opt_present("time") { + let tm = { + let (secs, nsecs) = { + let time = match matches.opt_str("time") { + Some(s) => { + match &s[..] { + "accessed" => stat.accessed, + "created" => stat.created, + "modified" => stat.modified, + _ => { + show_error!( + "invalid argument 'modified' for '--time' + Valid arguments are: + - 'accessed', 'created', 'modified' + Try '{} --help' for more information.", + NAME + ); + return 1; + } + } + } + None => stat.modified, + }; + ((time / 1000) as i64, (time % 1000 * 1_000_000) as i32) + }; + time::at(Timespec::new(secs, nsecs)) + }; + if !summarize || index == len - 1 { + let time_str = tm.strftime(time_format_str).unwrap(); + print!( + "{}\t{}\t{}{}", + convert_size(size), + time_str, + stat.path.display(), + line_separator + ); + } + } else if !summarize || index == len - 1 { + print!( + "{}\t{}{}", + convert_size(size), + stat.path.display(), + line_separator + ); + } + if options.total && index == (len - 1) { + // The last element will be the total size of the the path under + // path_str. We add it to the grand total. + grand_total += size; + } + } + } + Err(_) => { + show_error!("{}: {}", path_str, "No such file or directory"); + } + } + } + + if options.total { + print!("{}\ttotal", convert_size(grand_total)); + print!("{}", line_separator); + } + + 0 +} + +#[cfg(test)] +mod test_du { + #[allow(unused_imports)] + use super::*; + + #[test] + fn test_translate_to_pure_number() { + let test_data = [ + (Some("10".to_string()), Some(10)), + (Some("10K".to_string()), Some(10 * 1024)), + (Some("5M".to_string()), Some(5 * 1024 * 1024)), + (Some("900KB".to_string()), Some(900 * 1000)), + (Some("BAD_STRING".to_string()), None), + ]; + for it in test_data.into_iter() { + assert_eq!(translate_to_pure_number(&it.0), it.1); + } + } + + #[test] + fn test_read_block_size() { + let test_data = [ + (Some("10".to_string()), 10), + (None, 1024), + (Some("BAD_STRING".to_string()), 1024), + ]; + for it in test_data.into_iter() { + assert_eq!(read_block_size(it.0.clone()), it.1); + } + } +} diff --git a/coreutils/src/echo/Cargo.toml b/coreutils/src/echo/Cargo.toml new file mode 100644 index 000000000..2c63c3427 --- /dev/null +++ b/coreutils/src/echo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "echo" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_echo" +path = "echo.rs" + +[dependencies] +uucore = "0.0.1" + +[[bin]] +name = "echo" +path = "../../uumain.rs" diff --git a/coreutils/src/echo/echo.rs b/coreutils/src/echo/echo.rs new file mode 100644 index 000000000..1178652e1 --- /dev/null +++ b/coreutils/src/echo/echo.rs @@ -0,0 +1,151 @@ +#![crate_name = "uu_echo"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Derek Chiang + * (c) Christopher Brown + * + * 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 std::io::{self, Write}; +use std::iter::Peekable; +use std::str::Chars; + +const SYNTAX: &str = "[OPTIONS]... [STRING]..."; +const SUMMARY: &str = "display a line of text"; +const HELP: &str = r#" + Echo the STRING(s) to standard output. + If -e is in effect, the following sequences are recognized: + + \\\\ backslash + \\a alert (BEL) + \\b backspace + \\c produce no further output + \\e escape + \\f form feed + \\n new line + \\r carriage return + \\t horizontal tab + \\v vertical tab + \\0NNN byte with octal value NNN (1 to 3 digits) + \\xHH byte with hexadecimal value HH (1 to 2 digits) +"#; + +fn parse_code( + input: &mut Peekable, + base: u32, + max_digits: u32, + bits_per_digit: u32, +) -> Option { + let mut ret = 0x80000000; + for _ in 0..max_digits { + match input.peek().and_then(|c| c.to_digit(base)) { + Some(n) => ret = (ret << bits_per_digit) | n, + None => break, + } + input.next(); + } + std::char::from_u32(ret) +} + +fn print_escaped(input: &str, mut output: impl Write) -> io::Result { + let mut should_stop = false; + + let mut buffer = ['\\'; 2]; + + let mut iter = input.chars().peekable(); + while let Some(mut c) = iter.next() { + let mut start = 1; + + if c == '\\' { + if let Some(next) = iter.next() { + c = match next { + '\\' => '\\', + 'a' => '\x07', + 'b' => '\x08', + 'c' => { + should_stop = true; + break + }, + 'e' => '\x1b', + 'f' => '\x0c', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + 'v' => '\x0b', + 'x' => parse_code(&mut iter, 16, 2, 4).unwrap_or_else(|| { + start = 0; + next + }), + '0' => parse_code(&mut iter, 8, 3, 3).unwrap_or_else(|| { + start = 0; + next + }), + _ => { + start = 0; + next + }, + }; + } + } + + buffer[1] = c; + + // because printing char slices is apparently not available in the standard library + for ch in &buffer[start..] { + write!(output, "{}", ch)?; + } + } + + Ok(should_stop) +} + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(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)") + .parse(args); + + let no_newline = matches.opt_present("n"); + let escaped = matches.opt_present("e"); + + match execute(no_newline, escaped, matches.free) { + Ok(_) => 0, + Err(f) => { + show_error!("{}", f); + 1 + } + } +} + +fn execute(no_newline: bool, escaped: bool, free: Vec) -> io::Result<()> { + let stdout = io::stdout(); + let mut output = stdout.lock(); + + for (i, input) in free.iter().enumerate() { + if i > 0 { + write!(output, " ")?; + } + if escaped { + let should_stop = print_escaped(&input, &mut output)?; + if should_stop { + break; + } + } else { + write!(output, "{}", input)?; + } + } + + if !no_newline { + writeln!(output)?; + } + + Ok(()) +} diff --git a/coreutils/src/env/Cargo.toml b/coreutils/src/env/Cargo.toml new file mode 100644 index 000000000..5f8527b24 --- /dev/null +++ b/coreutils/src/env/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "env" +version = "0.0.1" +authors = ["uutils developers"] +description = "Set each NAME to VALUE in the environment and run COMMAND" +build = "../../mkmain.rs" +edition = "2018" + +[lib] +name = "uu_env" +path = "env.rs" + +[dependencies] +clap = "2.33" +libc = "0.2.42" +uucore = "0.0.1" +rust-ini = "0.13.0" + +[[bin]] +name = "env" +path = "../../uumain.rs" diff --git a/coreutils/src/env/env.rs b/coreutils/src/env/env.rs new file mode 100644 index 000000000..1d03137a4 --- /dev/null +++ b/coreutils/src/env/env.rs @@ -0,0 +1,258 @@ +#![crate_name = "uu_env"] +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* last synced with: env (GNU coreutils) 8.13 */ + +#[macro_use] +extern crate clap; + +use clap::{App, AppSettings, Arg}; +use ini::Ini; +use std::borrow::Cow; +use std::env; +use std::io::{self, Write}; +use std::process::Command; + +const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]"; +const AFTER_HELP: &str = "\ +A mere - implies -i. If no COMMAND, print the resulting environment. +"; + +struct Options<'a> { + ignore_env: bool, + null: bool, + files: Vec<&'a str>, + unsets: Vec<&'a str>, + sets: Vec<(&'a str, &'a str)>, + program: Vec<&'a str>, +} + +// print name=value env pairs on screen +// if null is true, separate pairs with a \0, \n otherwise +fn print_env(null: bool) { + let stdout_raw = io::stdout(); + let mut stdout = stdout_raw.lock(); + for (n, v) in env::vars() { + write!(stdout, "{}={}{}", n, v, if null { '\0' } else { '\n' }).unwrap(); + } +} + +fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result { + // is it a NAME=VALUE like opt ? + if let Some(idx) = opt.find('=') { + // yes, so push name, value pair + let (name, value) = opt.split_at(idx); + opts.sets.push((name, &value['='.len_utf8()..])); + + Ok(false) + } else { + // no, it's a program-like opt + parse_program_opt(opts, opt).map(|_| true) + } +} + +fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32> { + if opts.null { + eprintln!("{}: cannot specify --null (-0) with command", crate_name!()); + eprintln!("Type \"{} --help\" for detailed information", crate_name!()); + Err(1) + } else { + opts.program.push(opt); + Ok(()) + } +} + +fn load_config_file(opts: &mut Options) -> Result<(), i32> { + // NOTE: config files are parsed using an INI parser b/c it's available and compatible with ".env"-style files + // ... * but support for actual INI files, although working, is not intended, nor claimed + for &file in &opts.files { + let conf = if file == "-" { + let stdin = io::stdin(); + let mut stdin_locked = stdin.lock(); + Ini::read_from(&mut stdin_locked) + } else { + Ini::load_from_file(file) + }; + + let conf = match conf { + Ok(config) => config, + Err(error) => { + eprintln!("env: error: \"{}\": {}", file, error); + return Err(1); + } + }; + + for (_, prop) in &conf { // ignore all INI section lines (treat them as comments) + for (key, value) in prop { + env::set_var(key, value); + } + } + } + + Ok(()) +} + +#[cfg(not(windows))] +fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) { + let progname = Cow::from(args[0]); + (progname, &args[1..]) +} + +#[cfg(windows)] +fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) { + args.insert(0, "/d/c"); + let progname = env::var("ComSpec") + .map(Cow::from) + .unwrap_or_else(|_| Cow::from("cmd")); + + (progname, &args[..]) +} + +fn create_app() -> App<'static, 'static> { + App::new(crate_name!()) + .version(crate_version!()) + .author(crate_authors!()) + .about(crate_description!()) + .usage(USAGE) + .after_help(AFTER_HELP) + .setting(AppSettings::AllowExternalSubcommands) + .arg(Arg::with_name("ignore-environment") + .short("i") + .long("ignore-environment") + .help("start with an empty environment")) + .arg(Arg::with_name("null") + .short("0") + .long("null") + .help("end each output line with a 0 byte rather than a newline (only valid when \ + printing the environment)")) + .arg(Arg::with_name("file") + .short("f") + .long("file") + .takes_value(true) + .number_of_values(1) + .value_name("PATH") + .multiple(true) + .help("read and set variables from a \".env\"-style configuration file (prior to any \ + unset and/or set)")) + .arg(Arg::with_name("unset") + .short("u") + .long("unset") + .takes_value(true) + .number_of_values(1) + .value_name("NAME") + .multiple(true) + .help("remove variable from the environment")) +} + +fn run_env(args: Vec) -> Result<(), i32> { + let app = create_app(); + let matches = app.get_matches_from(args); + + let ignore_env = matches.is_present("ignore-environment"); + let null = matches.is_present("null"); + let files = matches + .values_of("file") + .map(|v| v.collect()) + .unwrap_or_else(|| Vec::with_capacity(0)); + let unsets = matches + .values_of("unset") + .map(|v| v.collect()) + .unwrap_or_else(|| Vec::with_capacity(0)); + + let mut opts = Options { + ignore_env, + null, + files, + unsets, + sets: vec![], + program: vec![], + }; + + // we handle the name, value pairs and the program to be executed by treating them as external + // subcommands in clap + if let (external, Some(matches)) = matches.subcommand() { + let mut begin_prog_opts = false; + + if external == "-" { + // "-" implies -i and stop parsing opts + opts.ignore_env = true; + } else { + begin_prog_opts = parse_name_value_opt(&mut opts, external)?; + } + + if let Some(mut iter) = matches.values_of("") { + // read NAME=VALUE arguments (and up to a single program argument) + while !begin_prog_opts { + if let Some(opt) = iter.next() { + begin_prog_opts = parse_name_value_opt(&mut opts, opt)?; + } else { + break; + } + } + + // read any leftover program arguments + for opt in iter { + parse_program_opt(&mut opts, opt)?; + } + } + } + + // NOTE: we manually set and unset the env vars below rather than using Command::env() to more + // easily handle the case where no command is given + + // remove all env vars if told to ignore presets + if opts.ignore_env { + for (ref name, _) in env::vars() { + env::remove_var(name); + } + } + + // load .env-style config file prior to those given on the command-line + load_config_file(&mut opts)?; + + // unset specified env vars + for name in &opts.unsets { + env::remove_var(name); + } + + // set specified env vars + for &(ref name, ref val) in &opts.sets { + // FIXME: set_var() panics if name is an empty string + env::set_var(name, val); + } + + if !opts.program.is_empty() { + // we need to execute a command + let (prog, args) = build_command(&mut opts.program); + + // FIXME: this should just use execvp() (no fork()) on Unix-like systems + match Command::new(&*prog).args(args).status() { + Ok(exit) => { + if !exit.success() { + return Err(exit.code().unwrap()); + } + } + Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127), + Err(_) => return Err(126), + } + } else { + // no program provided, so just dump all env vars to stdout + print_env(opts.null); + } + + Ok(()) +} + +pub fn uumain(args: Vec) -> i32 { + match run_env(args) { + Ok(()) => 0, + Err(code) => code, + } +} diff --git a/coreutils/src/expand/Cargo.toml b/coreutils/src/expand/Cargo.toml new file mode 100644 index 000000000..ba8f38c59 --- /dev/null +++ b/coreutils/src/expand/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "expand" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_expand" +path = "expand.rs" + +[dependencies] +unicode-width = "0.1.5" +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "expand" +path = "../../uumain.rs" diff --git a/coreutils/src/expand/expand.rs b/coreutils/src/expand/expand.rs new file mode 100644 index 000000000..7430aa1ea --- /dev/null +++ b/coreutils/src/expand/expand.rs @@ -0,0 +1,252 @@ +#![crate_name = "uu_expand"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Virgile Andreani + * (c) kwantam + * 20150428 updated to work with both UTF-8 and non-UTF-8 encodings + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate unicode_width; + +#[macro_use] +extern crate uucore; + +use std::fs::File; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; +use std::iter::repeat; +use std::str::from_utf8; +use unicode_width::UnicodeWidthChar; + +static SYNTAX: &str = "[OPTION]... [FILE]..."; +static SUMMARY: &str = "Convert tabs in each FILE to spaces, writing to standard output. + With no FILE, or when FILE is -, read standard input."; +static LONG_HELP: &str = ""; + +static DEFAULT_TABSTOP: usize = 8; + +fn tabstops_parse(s: String) -> Vec { + let words = s.split(',').collect::>(); + + let nums = words + .into_iter() + .map(|sn| { + sn.parse::() + .unwrap_or_else(|_| crash!(1, "{}\n", "tab size contains invalid character(s)")) + }) + .collect::>(); + + if nums.iter().any(|&n| n == 0) { + crash!(1, "{}\n", "tab size cannot be 0"); + } + + if let (false, _) = nums.iter() + .fold((true, 0), |(acc, last), &n| (acc && last <= n, n)) + { + crash!(1, "{}\n", "tab sizes must be ascending"); + } + + nums +} + +struct Options { + files: Vec, + tabstops: Vec, + tspaces: String, + iflag: bool, + uflag: bool, +} + +impl Options { + fn new(matches: getopts::Matches) -> Options { + let tabstops = match matches.opt_str("t") { + None => vec![DEFAULT_TABSTOP], + Some(s) => tabstops_parse(s), + }; + + let iflag = matches.opt_present("i"); + let uflag = !matches.opt_present("U"); + + // avoid allocations when dumping out long sequences of spaces + // by precomputing the longest string of spaces we will ever need + let nspaces = tabstops + .iter() + .scan(0, |pr, &it| { + let ret = Some(it - *pr); + *pr = it; + ret + }) + .max() + .unwrap(); // length of tabstops is guaranteed >= 1 + let tspaces = repeat(' ').take(nspaces).collect(); + + let files = if matches.free.is_empty() { + vec!["-".to_owned()] + } else { + matches.free + }; + + Options { + files, + tabstops, + tspaces, + iflag, + uflag, + } + } +} + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optflag("i", "initial", "do not convert tabs after non blanks") + .optopt( + "t", + "tabs", + "have tabs NUMBER characters apart, not 8", + "NUMBER", + ) + .optopt( + "t", + "tabs", + "use comma separated list of explicit tab positions", + "LIST", + ) + .optflag( + "U", + "no-utf8", + "interpret input file as 8-bit ASCII rather than UTF-8", + ) + .parse(args); + + expand(Options::new(matches)); + + 0 +} + +fn open(path: String) -> BufReader> { + let file_buf; + if path == "-" { + BufReader::new(Box::new(stdin()) as Box) + } else { + file_buf = match File::open(&path[..]) { + Ok(a) => a, + Err(e) => crash!(1, "{}: {}\n", &path[..], e), + }; + BufReader::new(Box::new(file_buf) as Box) + } +} + +fn next_tabstop(tabstops: &[usize], col: usize) -> usize { + if tabstops.len() == 1 { + tabstops[0] - col % tabstops[0] + } else { + match tabstops.iter().skip_while(|&&t| t <= col).next() { + Some(t) => t - col, + None => 1, + } + } +} + +#[derive(PartialEq, Eq, Debug)] +enum CharType { + Backspace, + Tab, + Other, +} + +fn expand(options: Options) { + use self::CharType::*; + + let mut output = BufWriter::new(stdout()); + let ts = options.tabstops.as_ref(); + let mut buf = Vec::new(); + + for file in options.files.into_iter() { + let mut fh = open(file); + + while match fh.read_until('\n' as u8, &mut buf) { + Ok(s) => s > 0, + Err(_) => buf.is_empty(), + } { + let mut col = 0; + let mut byte = 0; + let mut init = true; + + while byte < buf.len() { + let (ctype, cwidth, nbytes) = if options.uflag { + let nbytes = char::from(buf[byte]).len_utf8(); + + if byte + nbytes > buf.len() { + // don't overrun buffer because of invalid UTF-8 + (Other, 1, 1) + } else if let Ok(t) = from_utf8(&buf[byte..byte + nbytes]) { + match t.chars().next() { + Some('\t') => (Tab, 0, nbytes), + Some('\x08') => (Backspace, 0, nbytes), + Some(c) => (Other, UnicodeWidthChar::width(c).unwrap_or(0), nbytes), + None => { + // no valid char at start of t, so take 1 byte + (Other, 1, 1) + } + } + } else { + (Other, 1, 1) // implicit assumption: non-UTF-8 char is 1 col wide + } + } else { + ( + match buf[byte] { + // always take exactly 1 byte in strict ASCII mode + 0x09 => Tab, + 0x08 => Backspace, + _ => Other, + }, + 1, + 1, + ) + }; + + // figure out how many columns this char takes up + match ctype { + Tab => { + // figure out how many spaces to the next tabstop + let nts = next_tabstop(ts, col); + col += nts; + + // now dump out either spaces if we're expanding, or a literal tab if we're not + if init || !options.iflag { + safe_unwrap!(output.write_all(&options.tspaces[..nts].as_bytes())); + } else { + safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); + } + } + _ => { + col = if ctype == Other { + col + cwidth + } else if col > 0 { + col - 1 + } else { + 0 + }; + + // if we're writing anything other than a space, then we're + // done with the line's leading spaces + if buf[byte] != 0x20 { + init = false; + } + + safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); + } + } + + byte += nbytes; // advance the pointer + } + + buf.truncate(0); // clear the buffer + } + } +} diff --git a/coreutils/src/expr/Cargo.toml b/coreutils/src/expr/Cargo.toml new file mode 100644 index 000000000..36529665f --- /dev/null +++ b/coreutils/src/expr/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "expr" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_expr" +path = "expr.rs" + +[dependencies] +libc = "0.2.42" +onig = "~4.3.2" +uucore = "0.0.1" + +[[bin]] +name = "expr" +path = "../../uumain.rs" diff --git a/coreutils/src/expr/expr.rs b/coreutils/src/expr/expr.rs new file mode 100644 index 000000000..f0e124d42 --- /dev/null +++ b/coreutils/src/expr/expr.rs @@ -0,0 +1,141 @@ +#![crate_name = "uu_expr"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Roman Gafiyatullin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate onig; +#[macro_use] +extern crate uucore; + +mod tokens; +mod syntax_tree; + +static NAME: &str = "expr"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + // For expr utility we do not want getopts. + // The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)` + + if maybe_handle_help_or_version(&args) { + 0 + } else { + let token_strings = args[1..].to_vec(); + + match process_expr(&token_strings) { + Ok(expr_result) => print_expr_ok(&expr_result), + Err(expr_error) => print_expr_error(&expr_error), + } + } +} + +fn process_expr(token_strings: &[String]) -> Result { + let maybe_tokens = tokens::strings_to_tokens(&token_strings); + let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens); + evaluate_ast(maybe_ast) +} + +fn print_expr_ok(expr_result: &str) -> i32 { + println!("{}", expr_result); + if expr_result == "0" || expr_result == "" { + 1 + } else { + 0 + } +} + +fn print_expr_error(expr_error: &str) -> ! { + crash!(2, "{}", expr_error) +} + +fn evaluate_ast(maybe_ast: Result, String>) -> Result { + if maybe_ast.is_err() { + Err(maybe_ast.err().unwrap()) + } else { + maybe_ast.ok().unwrap().evaluate() + } +} + +fn maybe_handle_help_or_version(args: &[String]) -> bool { + if args.len() == 2 { + if args[1] == "--help" { + print_help(); + true + } else if args[1] == "--version" { + print_version(); + true + } else { + false + } + } else { + false + } +} + +fn print_help() { + //! The following is taken from GNU coreutils' "expr --help" output. + print!( + r#"Usage: expr EXPRESSION + or: expr OPTION + + --help display this help and exit + --version output version information and exit + +Print the value of EXPRESSION to standard output. A blank line below +separates increasing precedence groups. EXPRESSION may be: + + ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2 + + ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0 + + ARG1 < ARG2 ARG1 is less than ARG2 + ARG1 <= ARG2 ARG1 is less than or equal to ARG2 + ARG1 = ARG2 ARG1 is equal to ARG2 + ARG1 != ARG2 ARG1 is unequal to ARG2 + ARG1 >= ARG2 ARG1 is greater than or equal to ARG2 + ARG1 > ARG2 ARG1 is greater than ARG2 + + ARG1 + ARG2 arithmetic sum of ARG1 and ARG2 + ARG1 - ARG2 arithmetic difference of ARG1 and ARG2 + + ARG1 * ARG2 arithmetic product of ARG1 and ARG2 + ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2 + ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2 + + STRING : REGEXP anchored pattern match of REGEXP in STRING + + match STRING REGEXP same as STRING : REGEXP + substr STRING POS LENGTH substring of STRING, POS counted from 1 + index STRING CHARS index in STRING where any CHARS is found, or 0 + length STRING length of STRING + + TOKEN interpret TOKEN as a string, even if it is a + keyword like 'match' or an operator like '/' + + ( EXPRESSION ) value of EXPRESSION + +Beware that many operators need to be escaped or quoted for shells. +Comparisons are arithmetic if both ARGs are numbers, else lexicographical. +Pattern matches return the string matched between \( and \) or null; if +\( and \) are not used, they return the number of characters matched or 0. + +Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null +or 0, 2 if EXPRESSION is syntactically invalid, and 3 if an error occurred. + +Environment variables: + * EXPR_DEBUG_TOKENS=1 dump expression's tokens + * EXPR_DEBUG_RPN=1 dump expression represented in reverse polish notation + * EXPR_DEBUG_SYA_STEP=1 dump each parser step + * EXPR_DEBUG_AST=1 dump expression represented abstract syntax tree +"# + ); +} + +fn print_version() { + println!("{} {}", NAME, VERSION); +} diff --git a/coreutils/src/expr/syntax_tree.rs b/coreutils/src/expr/syntax_tree.rs new file mode 100644 index 000000000..66b467bf6 --- /dev/null +++ b/coreutils/src/expr/syntax_tree.rs @@ -0,0 +1,556 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Roman Gafiyatullin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +//! +//! Here we employ shunting-yard algorithm for building AST from tokens according to operators' precedence and associativeness. +//! * https://en.wikipedia.org/wiki/Shunting-yard_algorithm +//! + +use tokens::Token; +use onig::{Regex, RegexOptions, Syntax}; + +type TokenStack = Vec<(usize, Token)>; +pub type OperandsList = Vec>; + +#[derive(Debug)] +pub enum ASTNode { + Leaf { + token_idx: usize, + value: String, + }, + Node { + token_idx: usize, + op_type: String, + operands: OperandsList, + }, +} +impl ASTNode { + fn debug_dump(&self) { + self.debug_dump_impl(1); + } + fn debug_dump_impl(&self, depth: usize) { + for _ in 0..depth { + print!("\t",); + } + match *self { + ASTNode::Leaf { + ref token_idx, + ref value, + } => println!( + "Leaf( {} ) at #{} ( evaluate -> {:?} )", + value, + token_idx, + self.evaluate() + ), + ASTNode::Node { + ref token_idx, + ref op_type, + ref operands, + } => { + println!( + "Node( {} ) at #{} (evaluate -> {:?})", + op_type, + token_idx, + self.evaluate() + ); + for operand in operands { + operand.debug_dump_impl(depth + 1); + } + } + } + } + + fn new_node(token_idx: usize, op_type: &str, operands: OperandsList) -> Box { + Box::new(ASTNode::Node { + token_idx: token_idx, + op_type: op_type.into(), + operands: operands, + }) + } + fn new_leaf(token_idx: usize, value: &str) -> Box { + Box::new(ASTNode::Leaf { + token_idx, + value: value.into(), + }) + } + pub fn evaluate(&self) -> Result { + match *self { + ASTNode::Leaf { ref value, .. } => Ok(value.clone()), + ASTNode::Node { ref op_type, .. } => match self.operand_values() { + Err(reason) => Err(reason), + Ok(operand_values) => match op_type.as_ref() { + "+" => infix_operator_two_ints(|a: i64, b: i64| { + checked_binop(|| a.checked_add(b), "+") + }, &operand_values + ), + "-" => infix_operator_two_ints(|a: i64, b: i64| { + checked_binop(|| a.checked_sub(b), "-") + }, &operand_values + ), + "*" => infix_operator_two_ints(|a: i64, b: i64| { + checked_binop(|| a.checked_mul(b), "*") + }, &operand_values + ), + "/" => infix_operator_two_ints(|a: i64, b: i64| { + if b == 0 { + Err("division by zero".to_owned()) + } else { + checked_binop(|| a.checked_div(b), "/") + } + }, &operand_values + ), + "%" => infix_operator_two_ints( + |a: i64, b: i64| { + if b == 0 { + Err("division by zero".to_owned()) + } else { + Ok(a % b) + } + }, + &operand_values, + ), + "=" => infix_operator_two_ints_or_two_strings( + |a: i64, b: i64| Ok(bool_as_int(a == b)), + |a: &String, b: &String| Ok(bool_as_string(a == b)), + &operand_values, + ), + "!=" => infix_operator_two_ints_or_two_strings( + |a: i64, b: i64| Ok(bool_as_int(a != b)), + |a: &String, b: &String| Ok(bool_as_string(a != b)), + &operand_values, + ), + "<" => infix_operator_two_ints_or_two_strings( + |a: i64, b: i64| Ok(bool_as_int(a < b)), + |a: &String, b: &String| Ok(bool_as_string(a < b)), + &operand_values, + ), + ">" => infix_operator_two_ints_or_two_strings( + |a: i64, b: i64| Ok(bool_as_int(a > b)), + |a: &String, b: &String| Ok(bool_as_string(a > b)), + &operand_values, + ), + "<=" => infix_operator_two_ints_or_two_strings( + |a: i64, b: i64| Ok(bool_as_int(a <= b)), + |a: &String, b: &String| Ok(bool_as_string(a <= b)), + &operand_values, + ), + ">=" => infix_operator_two_ints_or_two_strings( + |a: i64, b: i64| Ok(bool_as_int(a >= b)), + |a: &String, b: &String| Ok(bool_as_string(a >= b)), + &operand_values, + ), + "|" => infix_operator_or(&operand_values), + "&" => infix_operator_and(&operand_values), + ":" | "match" => operator_match(&operand_values), + "length" => prefix_operator_length(&operand_values), + "index" => prefix_operator_index(&operand_values), + "substr" => prefix_operator_substr(&operand_values), + + _ => Err(format!("operation not implemented: {}", op_type)), + }, + }, + } + } + pub fn operand_values(&self) -> Result, String> { + if let &ASTNode::Node { ref operands, .. } = self { + let mut out = Vec::with_capacity(operands.len()); + for operand in operands { + match operand.evaluate() { + Ok(value) => out.push(value), + Err(reason) => return Err(reason), + } + } + Ok(out) + } else { + panic!("Invoked .operand_values(&self) not with ASTNode::Node") + } + } +} + +pub fn tokens_to_ast( + maybe_tokens: Result, String>, +) -> Result, String> { + if maybe_tokens.is_err() { + Err(maybe_tokens.err().unwrap()) + } else { + let tokens = maybe_tokens.ok().unwrap(); + let mut out_stack: TokenStack = Vec::new(); + let mut op_stack: TokenStack = Vec::new(); + + for (token_idx, token) in tokens { + if let Err(reason) = + push_token_to_either_stack(token_idx, &token, &mut out_stack, &mut op_stack) + { + return Err(reason); + } + } + if let Err(reason) = move_rest_of_ops_to_out(&mut out_stack, &mut op_stack) { + return Err(reason); + } + assert!(op_stack.is_empty()); + + maybe_dump_rpn(&out_stack); + let result = ast_from_rpn(&mut out_stack); + if !out_stack.is_empty() { + Err("syntax error (fist RPN token does not represent expression AST's root)".to_owned()) + } else { + maybe_dump_ast(&result); + result + } + } +} + +fn maybe_dump_ast(result: &Result, String>) { + use std::env; + if let Ok(debug_var) = env::var("EXPR_DEBUG_AST") { + if debug_var == "1" { + println!("EXPR_DEBUG_AST"); + match *result { + Ok(ref ast) => ast.debug_dump(), + Err(ref reason) => println!("\terr: {:?}", reason), + } + } + } +} + +fn maybe_dump_rpn(rpn: &TokenStack) { + use std::env; + if let Ok(debug_var) = env::var("EXPR_DEBUG_RPN") { + if debug_var == "1" { + println!("EXPR_DEBUG_RPN"); + for token in rpn { + println!("\t{:?}", token); + } + } + } +} + +fn ast_from_rpn(rpn: &mut TokenStack) -> Result, String> { + match rpn.pop() { + None => Err("syntax error (premature end of expression)".to_owned()), + + Some((token_idx, Token::Value { value })) => Ok(ASTNode::new_leaf(token_idx, &value)), + + Some((token_idx, Token::InfixOp { value, .. })) => { + maybe_ast_node(token_idx, &value, 2, rpn) + } + + Some((token_idx, Token::PrefixOp { value, arity })) => { + maybe_ast_node(token_idx, &value, arity, rpn) + } + + Some((token_idx, unexpected_token)) => { + panic!("unexpected token at #{} {:?}", token_idx, unexpected_token) + } + } +} +fn maybe_ast_node( + token_idx: usize, + op_type: &str, + arity: usize, + rpn: &mut TokenStack, +) -> Result, String> { + let mut operands = Vec::with_capacity(arity); + for _ in 0..arity { + match ast_from_rpn(rpn) { + Err(reason) => return Err(reason), + Ok(operand) => operands.push(operand), + } + } + operands.reverse(); + Ok(ASTNode::new_node(token_idx, op_type, operands)) +} + +fn move_rest_of_ops_to_out( + out_stack: &mut TokenStack, + op_stack: &mut TokenStack, +) -> Result<(), String> { + loop { + match op_stack.pop() { + None => return Ok(()), + Some((token_idx, Token::ParOpen)) => { + return Err(format!( + "syntax error (Mismatched open-parenthesis at #{})", + token_idx + )) + } + Some((token_idx, Token::ParClose)) => { + return Err(format!( + "syntax error (Mismatched close-parenthesis at #{})", + token_idx + )) + } + Some(other) => out_stack.push(other), + } + } +} + +fn push_token_to_either_stack( + token_idx: usize, + token: &Token, + out_stack: &mut TokenStack, + op_stack: &mut TokenStack, +) -> Result<(), String> { + let result = match *token { + Token::Value { .. } => Ok(out_stack.push((token_idx, token.clone()))), + + Token::InfixOp { .. } => if op_stack.is_empty() { + Ok(op_stack.push((token_idx, token.clone()))) + } else { + push_op_to_stack(token_idx, token, out_stack, op_stack) + }, + + Token::PrefixOp { .. } => Ok(op_stack.push((token_idx, token.clone()))), + + Token::ParOpen => Ok(op_stack.push((token_idx, token.clone()))), + + Token::ParClose => move_till_match_paren(out_stack, op_stack), + }; + maybe_dump_shunting_yard_step(token_idx, token, out_stack, op_stack, &result); + result +} + +fn maybe_dump_shunting_yard_step( + token_idx: usize, + token: &Token, + out_stack: &TokenStack, + op_stack: &TokenStack, + result: &Result<(), String>, +) { + use std::env; + if let Ok(debug_var) = env::var("EXPR_DEBUG_SYA_STEP") { + if debug_var == "1" { + println!("EXPR_DEBUG_SYA_STEP"); + println!("\t{} => {:?}", token_idx, token); + println!("\t\tout: {:?}", out_stack); + println!("\t\top : {:?}", op_stack); + println!("\t\tresult: {:?}", result); + } + } +} + +fn push_op_to_stack( + token_idx: usize, + token: &Token, + out_stack: &mut TokenStack, + op_stack: &mut TokenStack, +) -> Result<(), String> { + if let &Token::InfixOp { + precedence: prec, + left_assoc: la, + .. + } = token + { + loop { + match op_stack.last() { + None => return Ok(op_stack.push((token_idx, token.clone()))), + + Some(&(_, Token::ParOpen)) => { + op_stack.push((token_idx, token.clone())); + return Ok(()); + } + + Some(&( + _, + Token::InfixOp { + precedence: prev_prec, + .. + }, + )) => if la && prev_prec >= prec || !la && prev_prec > prec { + out_stack.push(op_stack.pop().unwrap()) + } else { + op_stack.push((token_idx, token.clone())); + return Ok(()); + }, + + Some(&(_, Token::PrefixOp { .. })) => { + op_stack.push((token_idx, token.clone())); + return Ok(()); + } + + Some(_) => panic!("Non-operator on op_stack"), + } + } + } else { + panic!("Expected infix-op") + } +} + +fn move_till_match_paren( + out_stack: &mut TokenStack, + op_stack: &mut TokenStack, +) -> Result<(), String> { + loop { + match op_stack.pop() { + None => return Err("syntax error (Mismatched close-parenthesis)".to_string()), + Some((_, Token::ParOpen)) => return Ok(()), + Some(other) => out_stack.push(other), + } + } +} + +fn checked_binop Option, T>(cb: F, op: &str) -> Result { + match cb() { + Some(v) => Ok(v), + None => Err(format!("{}: Numerical result out of range", op)), + } +} + +fn infix_operator_two_ints(f: F, values: &[String]) -> Result +where + F: Fn(i64, i64) -> Result, +{ + assert!(values.len() == 2); + if let Ok(left) = values[0].parse::() { + if let Ok(right) = values[1].parse::() { + return match f(left, right) { + Ok(result) => Ok(result.to_string()), + Err(reason) => Err(reason), + }; + } + } + Err("Expected an integer operand".to_string()) +} + +fn infix_operator_two_ints_or_two_strings( + fi: FI, + fs: FS, + values: &[String], +) -> Result +where + FI: Fn(i64, i64) -> Result, + FS: Fn(&String, &String) -> Result, +{ + assert!(values.len() == 2); + if let (Some(a_int), Some(b_int)) = + (values[0].parse::().ok(), values[1].parse::().ok()) + { + match fi(a_int, b_int) { + Ok(result) => Ok(result.to_string()), + Err(reason) => Err(reason), + } + } else { + fs(&values[0], &values[1]) + } +} + +fn infix_operator_or(values: &[String]) -> Result { + assert!(values.len() == 2); + if value_as_bool(&values[0]) { + Ok(values[0].clone()) + } else { + Ok(values[1].clone()) + } +} + +fn infix_operator_and(values: &[String]) -> Result { + if value_as_bool(&values[0]) && value_as_bool(&values[1]) { + Ok(values[0].clone()) + } else { + Ok(0.to_string()) + } +} + +fn operator_match(values: &[String]) -> Result { + assert!(values.len() == 2); + let re = match Regex::with_options(&values[1], RegexOptions::REGEX_OPTION_NONE, Syntax::grep()) + { + Ok(m) => m, + Err(err) => return Err(err.description().to_string()), + }; + if re.captures_len() > 0 { + Ok(match re.captures(&values[0]) { + Some(captures) => captures.at(1).unwrap().to_string(), + None => "".to_string(), + }) + } else { + Ok(match re.find(&values[0]) { + Some((start, end)) => (end - start).to_string(), + None => "0".to_string(), + }) + } +} + +fn prefix_operator_length(values: &[String]) -> Result { + assert!(values.len() == 1); + Ok(values[0].len().to_string()) +} + +fn prefix_operator_index(values: &[String]) -> Result { + assert!(values.len() == 2); + let haystack = &values[0]; + let needles = &values[1]; + + let mut current_idx = 0; + for ch_h in haystack.chars() { + current_idx += 1; + + for ch_n in needles.chars() { + if ch_n == ch_h { + return Ok(current_idx.to_string()); + } + } + } + Ok("0".to_string()) +} + +fn prefix_operator_substr(values: &[String]) -> Result { + assert!(values.len() == 3); + let subj = &values[0]; + let mut idx = match values[1].parse::() { + Ok(i) => i, + Err(_) => return Err("expected integer as POS arg to 'substr'".to_string()), + }; + let mut len = match values[2].parse::() { + Ok(i) => i, + Err(_) => return Err("expected integer as LENGTH arg to 'substr'".to_string()), + }; + + if idx <= 0 || len <= 0 { + return Ok("".to_string()); + } + + let mut out_str = String::new(); + for ch in subj.chars() { + idx -= 1; + if idx <= 0 { + if len <= 0 { + break; + } + len -= 1; + + out_str.push(ch); + } + } + Ok(out_str) +} + +fn bool_as_int(b: bool) -> i64 { + if b { + 1 + } else { + 0 + } +} +fn bool_as_string(b: bool) -> String { + if b { + "1".to_string() + } else { + "0".to_string() + } +} +fn value_as_bool(s: &str) -> bool { + if s.is_empty() { + return false; + } + match s.parse::() { + Ok(n) => n != 0, + Err(_) => true, + } +} diff --git a/coreutils/src/expr/tokens.rs b/coreutils/src/expr/tokens.rs new file mode 100644 index 000000000..5684b0bed --- /dev/null +++ b/coreutils/src/expr/tokens.rs @@ -0,0 +1,171 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Roman Gafiyatullin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +//! +//! The following tokens are present in the expr grammar: +//! * integer literal; +//! * string literal; +//! * infix binary operators; +//! * prefix operators. +//! +//! According to the man-page of expr we have expression split into tokens (each token -- separate CLI-argument). +//! Hence all we need is to map the strings into the Token structures, except for some ugly fiddling with +-escaping. +//! + +#[derive(Debug, Clone)] +pub enum Token { + Value { + value: String, + }, + + ParOpen, + ParClose, + + InfixOp { + precedence: u8, + left_assoc: bool, + value: String, + }, + + PrefixOp { + arity: usize, + value: String, + }, +} +impl Token { + fn new_infix_op(v: &str, left_assoc: bool, precedence: u8) -> Self { + Token::InfixOp { + left_assoc, + precedence, + value: v.into(), + } + } + fn new_value(v: &str) -> Self { + Token::Value { value: v.into() } + } + + fn is_infix_plus(&self) -> bool { + match *self { + Token::InfixOp { ref value, .. } => value == "+", + _ => false, + } + } + fn is_a_number(&self) -> bool { + match *self { + Token::Value { ref value, .. } => match value.parse::() { + Ok(_) => true, + Err(_) => false, + }, + _ => false, + } + } + fn is_a_close_paren(&self) -> bool { + match *self { + Token::ParClose => true, + _ => false, + } + } +} + +pub fn strings_to_tokens(strings: &[String]) -> Result, String> { + let mut tokens_acc = Vec::with_capacity(strings.len()); + let mut tok_idx = 1; + + for s in strings { + let token_if_not_escaped = match s.as_ref() { + "(" => Token::ParOpen, + ")" => Token::ParClose, + + "^" => Token::new_infix_op(&s, false, 7), + + ":" => Token::new_infix_op(&s, true, 6), + + "*" => Token::new_infix_op(&s, true, 5), + "/" => Token::new_infix_op(&s, true, 5), + "%" => Token::new_infix_op(&s, true, 5), + + "+" => Token::new_infix_op(&s, true, 4), + "-" => Token::new_infix_op(&s, true, 4), + + "=" => Token::new_infix_op(&s, true, 3), + "!=" => Token::new_infix_op(&s, true, 3), + "<" => Token::new_infix_op(&s, true, 3), + ">" => Token::new_infix_op(&s, true, 3), + "<=" => Token::new_infix_op(&s, true, 3), + ">=" => Token::new_infix_op(&s, true, 3), + + "&" => Token::new_infix_op(&s, true, 2), + + "|" => Token::new_infix_op(&s, true, 1), + + "match" => Token::PrefixOp { + arity: 2, + value: s.clone(), + }, + "substr" => Token::PrefixOp { + arity: 3, + value: s.clone(), + }, + "index" => Token::PrefixOp { + arity: 2, + value: s.clone(), + }, + "length" => Token::PrefixOp { + arity: 1, + value: s.clone(), + }, + + _ => Token::new_value(&s), + }; + push_token_if_not_escaped(&mut tokens_acc, tok_idx, token_if_not_escaped, &s); + tok_idx += 1; + } + maybe_dump_tokens_acc(&tokens_acc); + + Ok(tokens_acc) +} + +fn maybe_dump_tokens_acc(tokens_acc: &[(usize, Token)]) { + use std::env; + + if let Ok(debug_var) = env::var("EXPR_DEBUG_TOKENS") { + if debug_var == "1" { + println!("EXPR_DEBUG_TOKENS"); + for token in tokens_acc { + println!("\t{:?}", token); + } + } + } +} + +fn push_token_if_not_escaped( + acc: &mut Vec<(usize, Token)>, + tok_idx: usize, + token: Token, + s: &str, +) { + // Smells heuristics... :( + let prev_is_plus = match acc.last() { + None => false, + Some(ref t) => t.1.is_infix_plus(), + }; + let should_use_as_escaped = if prev_is_plus && acc.len() >= 2 { + let pre_prev = &acc[acc.len() - 2]; + !(pre_prev.1.is_a_number() || pre_prev.1.is_a_close_paren()) + } else { + prev_is_plus + }; + + if should_use_as_escaped { + acc.pop(); + acc.push((tok_idx, Token::new_value(s))) + } else { + acc.push((tok_idx, token)) + } +} diff --git a/coreutils/src/factor/Cargo.toml b/coreutils/src/factor/Cargo.toml new file mode 100644 index 000000000..e7ba407b5 --- /dev/null +++ b/coreutils/src/factor/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "factor" +version = "0.0.1" +authors = [] + +[lib] +name = "uu_factor" +path = "factor.rs" + +[dependencies] +rand = "0.5" +uucore = "0.0.1" + +[[bin]] +name = "factor" +path = "../../uumain.rs" diff --git a/coreutils/src/factor/build.rs b/coreutils/src/factor/build.rs new file mode 100644 index 000000000..6e3368415 --- /dev/null +++ b/coreutils/src/factor/build.rs @@ -0,0 +1,153 @@ +/* +* This file is part of the uutils coreutils package. +* +* (c) kwantam +* +* For the full copyright and license information, please view the LICENSE file +* that was distributed with this source code. +*/ + +//! Generate a table of the multiplicative inverses of p_i mod 2^64 +//! for the first 1027 odd primes (all 13 bit and smaller primes). +//! You can supply a commandline argument to override the default +//! value of 1027 for the number of entries in the table. +//! +//! 2 has no multiplicative inverse mode 2^64 because 2 | 2^64, +//! and in any case divisibility by two is trivial by checking the LSB. + +#![cfg_attr(test, allow(dead_code))] + +use sieve::Sieve; +use std::env::{self, args}; +use std::fs::File; +use std::io::Write; +use std::num::Wrapping; +use std::path::Path; +use std::u64::MAX as MAX_U64; + +#[cfg(test)] +use numeric::is_prime; + +#[cfg(test)] +mod numeric; + +mod sieve; + +#[path = "../../mkmain.rs"] +mod mkmain; + +// extended Euclid algorithm +// precondition: a does not divide 2^64 +fn inv_mod_u64(a: u64) -> Option { + let mut t = 0u64; + let mut newt = 1u64; + let mut r = 0u64; + let mut newr = a; + + while newr != 0 { + let quot = if r == 0 { + // special case when we're just starting out + // This works because we know that + // a does not divide 2^64, so floor(2^64 / a) == floor((2^64-1) / a); + MAX_U64 + } else { + r + } / newr; + + let (tp, Wrapping(newtp)) = (newt, Wrapping(t) - (Wrapping(quot) * Wrapping(newt))); + t = tp; + newt = newtp; + + let (rp, Wrapping(newrp)) = (newr, Wrapping(r) - (Wrapping(quot) * Wrapping(newr))); + r = rp; + newr = newrp; + } + + if r > 1 { + // not invertible + return None; + } + + Some(t) +} + +#[cfg_attr(test, allow(dead_code))] +fn main() { + mkmain::main(); + + let out_dir = env::var("OUT_DIR").unwrap(); + let mut file = File::create(&Path::new(&out_dir).join("prime_table.rs")).unwrap(); + + // By default, we print the multiplicative inverses mod 2^64 of the first 1k primes + let n = args() + .skip(1) + .next() + .unwrap_or("1027".to_string()) + .parse::() + .ok() + .unwrap_or(1027); + + write!(file, "{}", PREAMBLE).unwrap(); + let mut cols = 3; + + // we want a total of n + 1 values + let mut primes = Sieve::odd_primes().take(n + 1); + + // in each iteration of the for loop, we use the value yielded + // by the previous iteration. This leaves one value left at the + // end, which we call NEXT_PRIME. + let mut x = primes.next().unwrap(); + for next in primes { + // format the table + let outstr = format!("({}, {}, {}),", x, inv_mod_u64(x).unwrap(), MAX_U64 / x); + if cols + outstr.len() > MAX_WIDTH { + write!(file, "\n {}", outstr).unwrap(); + cols = 4 + outstr.len(); + } else { + write!(file, " {}", outstr).unwrap(); + cols += 1 + outstr.len(); + } + + x = next; + } + + write!( + file, + "\n];\n\n#[allow(dead_code)]\npub const NEXT_PRIME: u64 = {};\n", + x + ).unwrap(); +} + +#[test] +fn test_inverter() { + let num = 10000; + + let invs = Sieve::odd_primes().map(|x| inv_mod_u64(x).unwrap()); + assert!(Sieve::odd_primes().zip(invs).take(num).all(|(x, y)| { + let Wrapping(z) = Wrapping(x) * Wrapping(y); + is_prime(x) && z == 1 + })); +} + +#[test] +fn test_generator() { + let prime_10001 = Sieve::primes().skip(10000).next(); + assert_eq!(prime_10001, Some(104743)); +} + +const MAX_WIDTH: usize = 102; +const PREAMBLE: &'static str = r##"/* +* This file is part of the uutils coreutils package. +* +* (c) kwantam +* +* For the full copyright and license information, please view the LICENSE file +* that was distributed with this source code. +*/ + +// *** NOTE: this file was automatically generated. +// Please do not edit by hand. Instead, modify and +// re-run src/factor/gen_tables.rs. + +pub const P_INVS_U64: &'static [(u64, u64, u64)] = &[ + "##; diff --git a/coreutils/src/factor/factor.rs b/coreutils/src/factor/factor.rs new file mode 100644 index 000000000..2f5d5bb2f --- /dev/null +++ b/coreutils/src/factor/factor.rs @@ -0,0 +1,178 @@ +#![crate_name = "uu_factor"] + +/* +* This file is part of the uutils coreutils package. +* +* (c) T. Jameson Little +* (c) Wiktor Kuropatwa +* 20150223 added Pollard rho method implementation +* (c) kwantam +* 20150429 sped up trial division by adding table of prime inverses +* +* For the full copyright and license information, please view the LICENSE file +* that was distributed with this source code. +*/ + +extern crate rand; + +#[macro_use] +extern crate uucore; + +use numeric::*; +use rand::distributions::{Distribution, Uniform}; +use rand::{SeedableRng, thread_rng}; +use rand::rngs::SmallRng; +use std::cmp::{max, min}; +use std::io::{stdin, BufRead}; +use std::num::Wrapping; +use std::mem::swap; + +mod numeric; + +include!(concat!(env!("OUT_DIR"), "/prime_table.rs")); + +static SYNTAX: &str = "[OPTION] [NUMBER]..."; +static SUMMARY: &str = "Print the prime factors of the given number(s). + If none are specified, read from standard input."; +static LONG_HELP: &str = ""; + +fn rho_pollard_pseudorandom_function(x: u64, a: u64, b: u64, num: u64) -> u64 { + if num < 1 << 63 { + (sm_mul(a, sm_mul(x, x, num), num) + b) % num + } else { + big_add(big_mul(a, big_mul(x, x, num), num), b, num) + } +} + +fn gcd(mut a: u64, mut b: u64) -> u64 { + while b > 0 { + a %= b; + swap(&mut a, &mut b); + } + a +} + +fn rho_pollard_find_divisor(num: u64) -> u64 { + let range = Uniform::new(1, num); + let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap(); + let mut x = range.sample(&mut rng); + let mut y = x; + let mut a = range.sample(&mut rng); + let mut b = range.sample(&mut rng); + + loop { + x = rho_pollard_pseudorandom_function(x, a, b, num); + y = rho_pollard_pseudorandom_function(y, a, b, num); + y = rho_pollard_pseudorandom_function(y, a, b, num); + let d = gcd(num, max(x, y) - min(x, y)); + if d == num { + // Failure, retry with different function + x = range.sample(&mut rng); + y = x; + a = range.sample(&mut rng); + b = range.sample(&mut rng); + } else if d > 1 { + return d; + } + } +} + +fn rho_pollard_factor(num: u64, factors: &mut Vec) { + if is_prime(num) { + factors.push(num); + return; + } + let divisor = rho_pollard_find_divisor(num); + rho_pollard_factor(divisor, factors); + rho_pollard_factor(num / divisor, factors); +} + +fn table_division(mut num: u64, factors: &mut Vec) { + if num < 2 { + return; + } + while num % 2 == 0 { + num /= 2; + factors.push(2); + } + if num == 1 { + return; + } + if is_prime(num) { + factors.push(num); + return; + } + for &(prime, inv, ceil) in P_INVS_U64 { + if num == 1 { + break; + } + + // inv = prime^-1 mod 2^64 + // ceil = floor((2^64-1) / prime) + // if (num * inv) mod 2^64 <= ceil, then prime divides num + // See http://math.stackexchange.com/questions/1251327/ + // for a nice explanation. + loop { + let Wrapping(x) = Wrapping(num) * Wrapping(inv); // x = num * inv mod 2^64 + if x <= ceil { + num = x; + factors.push(prime); + if is_prime(num) { + factors.push(num); + return; + } + } else { + break; + } + } + } + + // do we still have more factoring to do? + // Decide whether to use Pollard Rho or slow divisibility based on + // number's size: + //if num >= 1 << 63 { + // number is too big to use rho pollard without overflowing + //trial_division_slow(num, factors); + //} else if num > 1 { + // number is still greater than 1, but not so big that we have to worry + rho_pollard_factor(num, factors); + //} +} + +fn print_factors(num: u64) { + print!("{}:", num); + + let mut factors = Vec::new(); + // we always start with table division, and go from there + table_division(num, &mut factors); + factors.sort(); + + for fac in &factors { + print!(" {}", fac); + } + println!(); +} + +fn print_factors_str(num_str: &str) { + if let Err(e) = num_str.parse::().and_then(|x| Ok(print_factors(x))) { + show_warning!("{}: {}", num_str, e); + } +} + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP).parse(args); + + if matches.free.is_empty() { + let stdin = stdin(); + for line in stdin.lock().lines() { + for number in line.unwrap().split_whitespace() { + print_factors_str(number); + } + } + } else { + for num_str in &matches.free { + print_factors_str(num_str); + } + } + 0 +} diff --git a/coreutils/src/factor/numeric.rs b/coreutils/src/factor/numeric.rs new file mode 100644 index 000000000..0cce6093a --- /dev/null +++ b/coreutils/src/factor/numeric.rs @@ -0,0 +1,132 @@ +/* +* This file is part of the uutils coreutils package. +* +* (c) Wiktor Kuropatwa +* (c) kwantam +* 20150507 added big_ routines to prevent overflow when num > 2^63 +* +* For the full copyright and license information, please view the LICENSE file +* that was distributed with this source code. +*/ + +use std::u64::MAX as MAX_U64; +use std::num::Wrapping; + +pub fn big_add(a: u64, b: u64, m: u64) -> u64 { + let Wrapping(msb_mod_m) = Wrapping(MAX_U64) - Wrapping(m) + Wrapping(1); + let msb_mod_m = msb_mod_m % m; + + let Wrapping(res) = Wrapping(a) + Wrapping(b); + if b <= MAX_U64 - a { + res + } else { + (res + msb_mod_m) % m + } +} + +// computes (a + b) % m using the russian peasant algorithm +// CAUTION: Will overflow if m >= 2^63 +pub fn sm_mul(mut a: u64, mut b: u64, m: u64) -> u64 { + let mut result = 0; + while b > 0 { + if b & 1 != 0 { + result = (result + a) % m; + } + a = (a << 1) % m; + b >>= 1; + } + result +} + +// computes (a + b) % m using the russian peasant algorithm +// Only necessary when m >= 2^63; otherwise, just wastes time. +pub fn big_mul(mut a: u64, mut b: u64, m: u64) -> u64 { + // precompute 2^64 mod m, since we expect to wrap + let Wrapping(msb_mod_m) = Wrapping(MAX_U64) - Wrapping(m) + Wrapping(1); + let msb_mod_m = msb_mod_m % m; + + let mut result = 0; + while b > 0 { + if b & 1 != 0 { + let Wrapping(next_res) = Wrapping(result) + Wrapping(a); + let next_res = next_res % m; + result = if result <= MAX_U64 - a { + next_res + } else { + (next_res + msb_mod_m) % m + }; + } + let Wrapping(next_a) = Wrapping(a) << 1; + let next_a = next_a % m; + a = if a < 1 << 63 { + next_a + } else { + (next_a + msb_mod_m) % m + }; + b >>= 1; + } + result +} + +// computes a.pow(b) % m +fn pow(mut a: u64, mut b: u64, m: u64, mul: fn(u64, u64, u64) -> u64) -> u64 { + let mut result = 1; + while b > 0 { + if b & 1 != 0 { + result = mul(result, a, m); + } + a = mul(a, a, m); + b >>= 1; + } + result +} + +fn witness(mut a: u64, exponent: u64, m: u64) -> bool { + if a == 0 { + return false; + } + + let mul = if m < 1 << 63 { + sm_mul as fn(u64, u64, u64) -> u64 + } else { + big_mul as fn(u64, u64, u64) -> u64 + }; + + if pow(a, m - 1, m, mul) != 1 { + return true; + } + a = pow(a, exponent, m, mul); + if a == 1 { + return false; + } + loop { + if a == 1 { + return true; + } + if a == m - 1 { + return false; + } + a = mul(a, a, m); + } +} + +// uses deterministic (i.e., fixed witness set) Miller-Rabin test +pub fn is_prime(num: u64) -> bool { + if num < 2 { + return false; + } + if num % 2 == 0 { + return num == 2; + } + let mut exponent = num - 1; + while exponent & 1 == 0 { + exponent >>= 1; + } + + // These witnesses detect all composites up to at least 2^64. + // Discovered by Jim Sinclair, according to http://miller-rabin.appspot.com + let witnesses = [2, 325, 9375, 28178, 450775, 9780504, 1795265022]; + !witnesses + .iter() + .any(|&wit| witness(wit % num, exponent, num)) +} diff --git a/coreutils/src/factor/sieve.rs b/coreutils/src/factor/sieve.rs new file mode 100644 index 000000000..d0a2711f2 --- /dev/null +++ b/coreutils/src/factor/sieve.rs @@ -0,0 +1,225 @@ +/* +* This file is part of the uutils coreutils package. +* +* (c) kwantam +* +* For the full copyright and license information, please view the LICENSE file +* that was distributed with this source code. +*/ + +use std::iter::{Chain, Cycle, Map}; +use std::slice::Iter; + +/// A lazy Sieve of Eratosthenes. +/// +/// This is a reasonably efficient implementation based on +/// O'Neill, M. E. "[The Genuine Sieve of Eratosthenes.](http://dx.doi.org/10.1017%2FS0956796808007004)" +/// Journal of Functional Programming, Volume 19, Issue 1, 2009, pp. 95--106. +pub struct Sieve { + inner: Wheel, + filts: PrimeHeap, +} + +impl Iterator for Sieve { + type Item = u64; + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + #[inline] + fn next(&mut self) -> Option { + while let Some(n) = self.inner.next() { + let mut prime = true; + while let Some((next, inc)) = self.filts.peek() { + // need to keep checking the min element of the heap + // until we've found an element that's greater than n + if next > n { + break; // next heap element is bigger than n + } + + if next == n { + // n == next, and is composite. + prime = false; + } + // Increment the element in the prime heap. + self.filts.replace((next + inc, inc)); + } + + if prime { + // this is a prime; add it to the heap + self.filts.insert(n); + return Some(n); + } + } + None + } +} + +impl Sieve { + fn new() -> Sieve { + Sieve { + inner: Wheel::new(), + filts: PrimeHeap::new(), + } + } + + #[allow(dead_code)] + #[inline] + pub fn primes() -> PrimeSieve { + fn deref(x: &u64) -> u64 { + *x + } + let deref = deref as fn(&u64) -> u64; + INIT_PRIMES.iter().map(deref).chain(Sieve::new()) + } + + #[allow(dead_code)] + #[inline] + pub fn odd_primes() -> PrimeSieve { + fn deref(x: &u64) -> u64 { + *x + } + let deref = deref as fn(&u64) -> u64; + (&INIT_PRIMES[1..]).iter().map(deref).chain(Sieve::new()) + } +} + +pub type PrimeSieve = Chain, fn(&u64) -> u64>, Sieve>; + +/// An iterator that generates an infinite list of numbers that are +/// not divisible by any of 2, 3, 5, or 7. +struct Wheel { + next: u64, + increment: Cycle>, +} + +impl Iterator for Wheel { + type Item = u64; + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (1, None) + } + + #[inline] + fn next(&mut self) -> Option { + let increment = self.increment.next().unwrap(); // infinite iterator, no check necessary + let ret = self.next; + self.next = ret + increment; + Some(ret) + } +} + +impl Wheel { + #[inline] + fn new() -> Wheel { + Wheel { + next: 11u64, + increment: WHEEL_INCS.iter().cycle(), + } + } +} + +/// The increments of a wheel of circumference 210 +/// (i.e., a wheel that skips all multiples of 2, 3, 5, 7) +const WHEEL_INCS: &'static [u64] = &[ + 2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6, 6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4, 2, 4, 8, 6, 4, 6, 2, 4, 6, + 2, 6, 6, 4, 2, 4, 6, 2, 6, 4, 2, 4, 2, 10, 2, 10, +]; +const INIT_PRIMES: &'static [u64] = &[2, 3, 5, 7]; + +/// A min-heap of "infinite lists" of prime multiples, where a list is +/// represented as (head, increment). +#[derive(Debug)] +struct PrimeHeap { + data: Vec<(u64, u64)>, +} + +impl PrimeHeap { + fn new() -> PrimeHeap { + PrimeHeap { data: Vec::new() } + } + + fn peek(&self) -> Option<(u64, u64)> { + if let Some(&(x, y)) = self.data.get(0) { + Some((x, y)) + } else { + None + } + } + + fn insert(&mut self, next: u64) { + let mut idx = self.data.len(); + let key = next * next; + + let item = (key, next); + self.data.push(item); + loop { + // break if we've bubbled to the top + if idx == 0 { + break; + } + + let paridx = (idx - 1) / 2; + let (k, _) = self.data[paridx]; + if key < k { + // bubble up, found a smaller key + self.data.swap(idx, paridx); + idx = paridx; + } else { + // otherwise, parent is smaller, so we're done + break; + } + } + } + + fn remove(&mut self) -> (u64, u64) { + let ret = self.data.swap_remove(0); + + let mut idx = 0; + let len = self.data.len(); + let (key, _) = self.data[0]; + loop { + let child1 = 2 * idx + 1; + let child2 = 2 * idx + 2; + + // no more children + if child1 >= len { + break; + } + + // find lesser child + let (c1key, _) = self.data[child1]; + let (minidx, minkey) = if child2 >= len { + (child1, c1key) + } else { + let (c2key, _) = self.data[child2]; + if c1key < c2key { + (child1, c1key) + } else { + (child2, c2key) + } + }; + + if minkey < key { + self.data.swap(minidx, idx); + idx = minidx; + continue; + } + + // smaller than both children, so done + break; + } + + ret + } + + /// More efficient than inserting and removing in two steps + /// because we save one traversal of the heap. + fn replace(&mut self, next: (u64, u64)) -> (u64, u64) { + self.data.push(next); + self.remove() + } +} diff --git a/coreutils/src/false/Cargo.toml b/coreutils/src/false/Cargo.toml new file mode 100644 index 000000000..9ae600840 --- /dev/null +++ b/coreutils/src/false/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "false" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_false" +path = "false.rs" + +[dependencies] +uucore = "0.0.1" + +[[bin]] +name = "false" +path = "../../uumain.rs" diff --git a/coreutils/src/false/false.rs b/coreutils/src/false/false.rs new file mode 100644 index 000000000..bdfe1c1b6 --- /dev/null +++ b/coreutils/src/false/false.rs @@ -0,0 +1,14 @@ +#![crate_name = "uu_false"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +pub fn uumain(_: Vec) -> i32 { + 1 +} diff --git a/coreutils/src/fmt/Cargo.toml b/coreutils/src/fmt/Cargo.toml new file mode 100644 index 000000000..ce50a9029 --- /dev/null +++ b/coreutils/src/fmt/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "fmt" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_fmt" +path = "fmt.rs" + +[dependencies] +libc = "0.2.42" +unicode-width = "0.1.5" +uucore = "0.0.1" + +[[bin]] +name = "fmt" +path = "../../uumain.rs" diff --git a/coreutils/src/fmt/fmt.rs b/coreutils/src/fmt/fmt.rs new file mode 100644 index 000000000..816e47ede --- /dev/null +++ b/coreutils/src/fmt/fmt.rs @@ -0,0 +1,207 @@ +#![crate_name = "uu_fmt"] + +/* + * This file is part of `fmt` from the uutils coreutils package. + * + * (c) kwantam + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate unicode_width; + +#[macro_use] +extern crate uucore; + +use std::cmp; +use std::io::{BufReader, BufWriter, Read}; +use std::fs::File; +use std::io::{stdin, stdout, Write}; +use linebreak::break_lines; +use parasplit::ParagraphStream; + +macro_rules! silent_unwrap( + ($exp:expr) => ( + match $exp { + Ok(_) => (), + Err(_) => ::std::process::exit(1), + } + ) +); + +mod linebreak; +mod parasplit; + +// program's NAME and VERSION are used for -V and -h +static SYNTAX: &str = "[OPTION]... [FILE]..."; +static SUMMARY: &str = "Reformat paragraphs from input files (or stdin) to stdout."; +static LONG_HELP: &str = ""; + +pub type FileOrStdReader = BufReader>; +pub struct FmtOptions { + crown: bool, + tagged: bool, + mail: bool, + split_only: bool, + use_prefix: bool, + prefix: String, + xprefix: bool, + use_anti_prefix: bool, + anti_prefix: String, + xanti_prefix: bool, + uniform: bool, + quick: bool, + width: usize, + goal: usize, + tabwidth: usize, +} + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optflag("c", "crown-margin", "First and second line of paragraph may have different indentations, in which case the first line's indentation is preserved, and each subsequent line's indentation matches the second line.") + .optflag("t", "tagged-paragraph", "Like -c, except that the first and second line of a paragraph *must* have different indentation or they are treated as separate paragraphs.") + .optflag("m", "preserve-headers", "Attempt to detect and preserve mail headers in the input. Be careful when combining this flag with -p.") + .optflag("s", "split-only", "Split lines only, do not reflow.") + .optflag("u", "uniform-spacing", "Insert exactly one space between words, and two between sentences. Sentence breaks in the input are detected as [?!.] followed by two spaces or a newline; other punctuation is not interpreted as a sentence break.") + .optopt("p", "prefix", "Reformat only lines beginning with PREFIX, reattaching PREFIX to reformatted lines. Unless -x is specified, leading whitespace will be ignored when matching PREFIX.", "PREFIX") + .optopt("P", "skip-prefix", "Do not reformat lines beginning with PSKIP. Unless -X is specified, leading whitespace will be ignored when matching PSKIP", "PSKIP") + .optflag("x", "exact-prefix", "PREFIX must match at the beginning of the line with no preceding whitespace.") + .optflag("X", "exact-skip-prefix", "PSKIP must match at the beginning of the line with no preceding whitespace.") + .optopt("w", "width", "Fill output lines up to a maximum of WIDTH columns, default 79.", "WIDTH") + .optopt("g", "goal", "Goal width, default ~0.94*WIDTH. Must be less than WIDTH.", "GOAL") + .optflag("q", "quick", "Break lines more quickly at the expense of a potentially more ragged appearance.") + .optopt("T", "tab-width", "Treat tabs as TABWIDTH spaces for determining line length, default 8. Note that this is used only for calculating line lengths; tabs are preserved in the output.", "TABWIDTH") + .parse(args); + + let mut fmt_opts = FmtOptions { + crown: false, + tagged: false, + mail: false, + uniform: false, + quick: false, + split_only: false, + use_prefix: false, + prefix: String::new(), + xprefix: false, + use_anti_prefix: false, + anti_prefix: String::new(), + xanti_prefix: false, + width: 79, + goal: 74, + tabwidth: 8, + }; + + if matches.opt_present("t") { + fmt_opts.tagged = true; + } + if matches.opt_present("c") { + fmt_opts.crown = true; + fmt_opts.tagged = false; + } + if matches.opt_present("m") { + fmt_opts.mail = true; + } + if matches.opt_present("u") { + fmt_opts.uniform = true; + } + if matches.opt_present("q") { + fmt_opts.quick = true; + } + if matches.opt_present("s") { + fmt_opts.split_only = true; + fmt_opts.crown = false; + fmt_opts.tagged = false; + } + if matches.opt_present("x") { + fmt_opts.xprefix = true; + } + if matches.opt_present("X") { + fmt_opts.xanti_prefix = true; + } + + if let Some(s) = matches.opt_str("p") { + fmt_opts.prefix = s; + fmt_opts.use_prefix = true; + }; + + if let Some(s) = matches.opt_str("P") { + fmt_opts.anti_prefix = s; + fmt_opts.use_anti_prefix = true; + }; + + if let Some(s) = matches.opt_str("w") { + fmt_opts.width = match s.parse::() { + Ok(t) => t, + Err(e) => { + crash!(1, "Invalid WIDTH specification: `{}': {}", s, e); + } + }; + fmt_opts.goal = cmp::min(fmt_opts.width * 94 / 100, fmt_opts.width - 3); + }; + + if let Some(s) = matches.opt_str("g") { + fmt_opts.goal = match s.parse::() { + Ok(t) => t, + Err(e) => { + crash!(1, "Invalid GOAL specification: `{}': {}", s, e); + } + }; + if !matches.opt_present("w") { + fmt_opts.width = cmp::max(fmt_opts.goal * 100 / 94, fmt_opts.goal + 3); + } else if fmt_opts.goal > fmt_opts.width { + crash!(1, "GOAL cannot be greater than WIDTH."); + } + }; + + if let Some(s) = matches.opt_str("T") { + fmt_opts.tabwidth = match s.parse::() { + Ok(t) => t, + Err(e) => { + crash!(1, "Invalid TABWIDTH specification: `{}': {}", s, e); + } + }; + }; + + if fmt_opts.tabwidth < 1 { + fmt_opts.tabwidth = 1; + } + + // immutable now + let fmt_opts = fmt_opts; + + let mut files = matches.free; + if files.is_empty() { + files.push("-".to_owned()); + } + + let mut ostream = BufWriter::new(stdout()); + + for i in files.iter().map(|x| &x[..]) { + let mut fp = match i { + "-" => BufReader::new(Box::new(stdin()) as Box), + _ => match File::open(i) { + Ok(f) => BufReader::new(Box::new(f) as Box), + Err(e) => { + show_warning!("{}: {}", i, e); + continue; + } + }, + }; + let p_stream = ParagraphStream::new(&fmt_opts, &mut fp); + for para_result in p_stream { + match para_result { + Err(s) => { + silent_unwrap!(ostream.write_all(s.as_bytes())); + silent_unwrap!(ostream.write_all(b"\n")); + } + Ok(para) => break_lines(¶, &fmt_opts, &mut ostream), + } + } + + // flush the output after each file + silent_unwrap!(ostream.flush()); + } + + 0 +} diff --git a/coreutils/src/fmt/linebreak.rs b/coreutils/src/fmt/linebreak.rs new file mode 100644 index 000000000..2bf7b0ef9 --- /dev/null +++ b/coreutils/src/fmt/linebreak.rs @@ -0,0 +1,507 @@ +/* + * This file is part of `fmt` from the uutils coreutils package. + * + * (c) kwantam + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use FmtOptions; +use parasplit::{ParaWords, Paragraph, WordInfo}; +use std::io::{BufWriter, Stdout, Write}; +use std::i64; +use std::cmp; +use std::mem; + +struct BreakArgs<'a> { + opts: &'a FmtOptions, + init_len: usize, + indent_str: &'a str, + indent_len: usize, + uniform: bool, + ostream: &'a mut BufWriter, +} + +impl<'a> BreakArgs<'a> { + fn compute_width<'b>(&self, winfo: &WordInfo<'b>, posn: usize, fresh: bool) -> usize { + if fresh { + 0 + } else { + let post = winfo.after_tab; + match winfo.before_tab { + None => post, + Some(pre) => { + post + ((pre + posn) / self.opts.tabwidth + 1) * self.opts.tabwidth - posn + } + } + } + } +} + +pub fn break_lines(para: &Paragraph, opts: &FmtOptions, ostream: &mut BufWriter) { + // indent + let p_indent = ¶.indent_str[..]; + let p_indent_len = para.indent_len; + + // words + let p_words = ParaWords::new(opts, para); + let mut p_words_words = p_words.words(); + + // the first word will *always* appear on the first line + // make sure of this here + let (w, w_len) = match p_words_words.next() { + Some(winfo) => (winfo.word, winfo.word_nchars), + None => { + silent_unwrap!(ostream.write_all(b"\n")); + return; + } + }; + // print the init, if it exists, and get its length + let p_init_len = w_len + if opts.crown || opts.tagged { + // handle "init" portion + silent_unwrap!(ostream.write_all(para.init_str.as_bytes())); + para.init_len + } else if !para.mail_header { + // for non-(crown, tagged) that's the same as a normal indent + silent_unwrap!(ostream.write_all(p_indent.as_bytes())); + p_indent_len + } else { + // except that mail headers get no indent at all + 0 + }; + // write first word after writing init + silent_unwrap!(ostream.write_all(w.as_bytes())); + + // does this paragraph require uniform spacing? + let uniform = para.mail_header || opts.uniform; + + let mut break_args = BreakArgs { + opts, + init_len: p_init_len, + indent_str: &p_indent[..], + indent_len: p_indent_len, + uniform, + ostream, + }; + + if opts.quick || para.mail_header { + break_simple(p_words_words, &mut break_args); + } else { + break_knuth_plass(p_words_words, &mut break_args); + } +} + +// break_simple implements a "greedy" breaking algorithm: print words until +// maxlength would be exceeded, then print a linebreak and indent and continue. +fn break_simple<'a, T: Iterator>>(iter: T, args: &mut BreakArgs<'a>) { + iter.fold((args.init_len, false), |l, winfo| { + accum_words_simple(args, l, winfo) + }); + silent_unwrap!(args.ostream.write_all(b"\n")); +} + +fn accum_words_simple<'a>( + args: &mut BreakArgs<'a>, + (l, prev_punct): (usize, bool), + winfo: &'a WordInfo<'a>, +) -> (usize, bool) { + // compute the length of this word, considering how tabs will expand at this position on the line + let wlen = winfo.word_nchars + args.compute_width(winfo, l, false); + + let slen = compute_slen( + args.uniform, + winfo.new_line, + winfo.sentence_start, + prev_punct, + ); + + if l + wlen + slen > args.opts.width { + write_newline(args.indent_str, args.ostream); + write_with_spaces(&winfo.word[winfo.word_start..], 0, args.ostream); + (args.indent_len + winfo.word_nchars, winfo.ends_punct) + } else { + write_with_spaces(winfo.word, slen, args.ostream); + (l + wlen + slen, winfo.ends_punct) + } +} + +// break_knuth_plass implements an "optimal" breaking algorithm in the style of +// Knuth, D.E., and Plass, M.F. "Breaking Paragraphs into Lines." in Software, +// Practice and Experience. Vol. 11, No. 11, November 1981. +// http://onlinelibrary.wiley.com/doi/10.1002/spe.4380111102/pdf +fn break_knuth_plass<'a, T: Clone + Iterator>>( + mut iter: T, + args: &mut BreakArgs<'a>, +) { + // run the algorithm to get the breakpoints + let breakpoints = find_kp_breakpoints(iter.clone(), args); + + // iterate through the breakpoints (note that breakpoints is in reverse break order, so we .rev() it + let (mut prev_punct, mut fresh) = breakpoints.iter().rev().fold( + (false, false), + |(mut prev_punct, mut fresh), &(next_break, break_before)| { + if fresh { + write_newline(args.indent_str, args.ostream); + } + // at each breakpoint, keep emitting words until we find the word matching this breakpoint + for winfo in &mut iter { + let (slen, word) = slice_if_fresh( + fresh, + winfo.word, + winfo.word_start, + args.uniform, + winfo.new_line, + winfo.sentence_start, + prev_punct, + ); + fresh = false; + prev_punct = winfo.ends_punct; + + // We find identical breakpoints here by comparing addresses of the references. + // This is OK because the backing vector is not mutating once we are linebreaking. + let winfo_ptr = winfo as *const _; + let next_break_ptr = next_break as *const _; + if winfo_ptr == next_break_ptr { + // OK, we found the matching word + if break_before { + write_newline(args.indent_str, args.ostream); + write_with_spaces(&winfo.word[winfo.word_start..], 0, args.ostream); + } else { + // breaking after this word, so that means "fresh" is true for the next iteration + write_with_spaces(word, slen, args.ostream); + fresh = true; + } + break; + } else { + write_with_spaces(word, slen, args.ostream); + } + } + (prev_punct, fresh) + }, + ); + + // after the last linebreak, write out the rest of the final line. + for winfo in iter { + if fresh { + write_newline(args.indent_str, args.ostream); + } + let (slen, word) = slice_if_fresh( + fresh, + winfo.word, + winfo.word_start, + args.uniform, + winfo.new_line, + winfo.sentence_start, + prev_punct, + ); + prev_punct = winfo.ends_punct; + fresh = false; + write_with_spaces(word, slen, args.ostream); + } + silent_unwrap!(args.ostream.write_all(b"\n")); +} + +struct LineBreak<'a> { + prev: usize, + linebreak: Option<&'a WordInfo<'a>>, + break_before: bool, + demerits: i64, + prev_rat: f32, + length: usize, + fresh: bool, +} + +fn find_kp_breakpoints<'a, T: Iterator>>( + iter: T, + args: &BreakArgs<'a>, +) -> Vec<(&'a WordInfo<'a>, bool)> { + let mut iter = iter.peekable(); + // set up the initial null linebreak + let mut linebreaks = vec![ + LineBreak { + prev: 0, + linebreak: None, + break_before: false, + demerits: 0, + prev_rat: 0.0f32, + length: args.init_len, + fresh: false, + }, + ]; + // this vec holds the current active linebreaks; next_ holds the breaks that will be active for + // the next word + let active_breaks = &mut vec![0]; + let next_active_breaks = &mut vec![]; + + let stretch = (args.opts.width - args.opts.goal) as isize; + let minlength = args.opts.goal - stretch as usize; + let mut new_linebreaks = vec![]; + let mut is_sentence_start = false; + let mut least_demerits = 0; + loop { + let w = match iter.next() { + None => break, + Some(w) => w, + }; + + // if this is the last word, we don't add additional demerits for this break + let (is_last_word, is_sentence_end) = match iter.peek() { + None => (true, true), + Some(&&WordInfo { + sentence_start: st, + new_line: nl, + .. + }) => (false, st || (nl && w.ends_punct)), + }; + + // should we be adding extra space at the beginning of the next sentence? + let slen = compute_slen(args.uniform, w.new_line, is_sentence_start, false); + + let mut ld_new = i64::MAX; + let mut ld_next = i64::MAX; + let mut ld_idx = 0; + new_linebreaks.clear(); + next_active_breaks.clear(); + // go through each active break, extending it and possibly adding a new active + // break if we are above the minimum required length + for &i in active_breaks.iter() { + let active = &mut linebreaks[i]; + // normalize demerits to avoid overflow, and record if this is the least + active.demerits -= least_demerits; + if active.demerits < ld_next { + ld_next = active.demerits; + ld_idx = i; + } + + // get the new length + let tlen = w.word_nchars + args.compute_width(w, active.length, active.fresh) + slen + + active.length; + + // if tlen is longer than args.opts.width, we drop this break from the active list + // otherwise, we extend the break, and possibly add a new break at this point + if tlen <= args.opts.width { + // this break will still be active next time + next_active_breaks.push(i); + // we can put this word on this line + active.fresh = false; + active.length = tlen; + + // if we're above the minlength, we can also consider breaking here + if tlen >= minlength { + let (new_demerits, new_ratio) = if is_last_word { + // there is no penalty for the final line's length + (0, 0.0) + } else { + compute_demerits( + (args.opts.goal - tlen) as isize, + stretch, + w.word_nchars as isize, + active.prev_rat, + ) + }; + + // do not even consider adding a line that has too many demerits + // also, try to detect overflow by checking signum + let total_demerits = new_demerits + active.demerits; + if new_demerits < BAD_INFTY_SQ && total_demerits < ld_new + && active.demerits.signum() <= new_demerits.signum() + { + ld_new = total_demerits; + new_linebreaks.push(LineBreak { + prev: i, + linebreak: Some(w), + break_before: false, + demerits: total_demerits, + prev_rat: new_ratio, + length: args.indent_len, + fresh: true, + }); + } + } + } + } + + // if we generated any new linebreaks, add the last one to the list + // the last one is always the best because we don't add to new_linebreaks unless + // it's better than the best one so far + match new_linebreaks.pop() { + None => (), + Some(lb) => { + next_active_breaks.push(linebreaks.len()); + linebreaks.push(lb); + } + } + + if next_active_breaks.is_empty() { + // every potential linebreak is too long! choose the linebreak with the least demerits, ld_idx + let new_break = + restart_active_breaks(args, &linebreaks[ld_idx], ld_idx, w, slen, minlength); + next_active_breaks.push(linebreaks.len()); + linebreaks.push(new_break); + least_demerits = 0; + } else { + // next time around, normalize out the demerits fields + // on active linebreaks to make overflow less likely + least_demerits = cmp::max(ld_next, 0); + } + // swap in new list of active breaks + mem::swap(active_breaks, next_active_breaks); + // If this was the last word in a sentence, the next one must be the first in the next. + is_sentence_start = is_sentence_end; + } + + // return the best path + build_best_path(&linebreaks, active_breaks) +} + +fn build_best_path<'a>(paths: &[LineBreak<'a>], active: &[usize]) -> Vec<(&'a WordInfo<'a>, bool)> { + let mut breakwords = vec![]; + // of the active paths, we select the one with the fewest demerits + let mut best_idx = match active.iter().min_by_key(|&&a| paths[a].demerits) { + None => crash!( + 1, + "Failed to find a k-p linebreak solution. This should never happen." + ), + Some(&s) => s, + }; + + // now, chase the pointers back through the break list, recording + // the words at which we should break + loop { + let next_best = &paths[best_idx]; + match next_best.linebreak { + None => return breakwords, + Some(prev) => { + breakwords.push((prev, next_best.break_before)); + best_idx = next_best.prev + } + } + } +} + +// "infinite" badness is more like (1+BAD_INFTY)^2 because of how demerits are computed +const BAD_INFTY: i64 = 10_000_000; +const BAD_INFTY_SQ: i64 = BAD_INFTY * BAD_INFTY; +// badness = BAD_MULT * abs(r) ^ 3 +const BAD_MULT: f32 = 100.0; +// DR_MULT is multiplier for delta-R between lines +const DR_MULT: f32 = 600.0; +// DL_MULT is penalty multiplier for short words at end of line +const DL_MULT: f32 = 300.0; + +fn compute_demerits(delta_len: isize, stretch: isize, wlen: isize, prev_rat: f32) -> (i64, f32) { + // how much stretch are we using? + let ratio = if delta_len == 0 { + 0.0f32 + } else { + delta_len as f32 / stretch as f32 + }; + + // compute badness given the stretch ratio + let bad_linelen = if ratio.abs() > 1.0f32 { + BAD_INFTY + } else { + (BAD_MULT * ratio.powf(3f32).abs()) as i64 + }; + + // we penalize lines ending in really short words + let bad_wordlen = if wlen >= stretch { + 0 + } else { + (DL_MULT + * ((stretch - wlen) as f32 / (stretch - 1) as f32) + .powf(3f32) + .abs()) as i64 + }; + + // we penalize lines that have very different ratios from previous lines + let bad_delta_r = (DR_MULT * (((ratio - prev_rat) / 2.0).powf(3f32)).abs()) as i64; + + let demerits = i64::pow(1 + bad_linelen + bad_wordlen + bad_delta_r, 2); + + (demerits, ratio) +} + +fn restart_active_breaks<'a>( + args: &BreakArgs<'a>, + active: &LineBreak<'a>, + act_idx: usize, + w: &'a WordInfo<'a>, + slen: usize, + min: usize, +) -> LineBreak<'a> { + let (break_before, line_length) = if active.fresh { + // never break before a word if that word would be the first on a line + (false, args.indent_len) + } else { + // choose the lesser evil: breaking too early, or breaking too late + let wlen = w.word_nchars + args.compute_width(w, active.length, active.fresh); + let underlen = (min - active.length) as isize; + let overlen = ((wlen + slen + active.length) - args.opts.width) as isize; + if overlen > underlen { + // break early, put this word on the next line + (true, args.indent_len + w.word_nchars) + } else { + (false, args.indent_len) + } + }; + + // restart the linebreak. This will be our only active path. + LineBreak { + prev: act_idx, + linebreak: Some(w), + break_before, + demerits: 0, // this is the only active break, so we can reset the demerit count + prev_rat: if break_before { 1.0 } else { -1.0 }, + length: line_length, + fresh: !break_before, + } +} + +// Number of spaces to add before a word, based on mode, newline, sentence start. +fn compute_slen(uniform: bool, newline: bool, start: bool, punct: bool) -> usize { + if uniform || newline { + if start || (newline && punct) { + 2 + } else { + 1 + } + } else { + 0 + } +} + +// If we're on a fresh line, slen=0 and we slice off leading whitespace. +// Otherwise, compute slen and leave whitespace alone. +fn slice_if_fresh( + fresh: bool, + word: &str, + start: usize, + uniform: bool, + newline: bool, + sstart: bool, + punct: bool, +) -> (usize, &str) { + if fresh { + (0, &word[start..]) + } else { + (compute_slen(uniform, newline, sstart, punct), word) + } +} + +// Write a newline and add the indent. +fn write_newline(indent: &str, ostream: &mut BufWriter) { + silent_unwrap!(ostream.write_all(b"\n")); + silent_unwrap!(ostream.write_all(indent.as_bytes())); +} + +// Write the word, along with slen spaces. +fn write_with_spaces(word: &str, slen: usize, ostream: &mut BufWriter) { + if slen == 2 { + silent_unwrap!(ostream.write_all(b" ")); + } else if slen == 1 { + silent_unwrap!(ostream.write_all(b" ")); + } + silent_unwrap!(ostream.write_all(word.as_bytes())); +} diff --git a/coreutils/src/fmt/parasplit.rs b/coreutils/src/fmt/parasplit.rs new file mode 100644 index 000000000..5ffe4593e --- /dev/null +++ b/coreutils/src/fmt/parasplit.rs @@ -0,0 +1,623 @@ +/* + * This file is part of `fmt` from the uutils coreutils package. + * + * (c) kwantam + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use std::iter::Peekable; +use std::io::{BufRead, Lines}; +use std::slice::Iter; +use unicode_width::UnicodeWidthChar; +use FileOrStdReader; +use FmtOptions; + +fn char_width(c: char) -> usize { + if (c as usize) < 0xA0 { + // if it is ASCII, call it exactly 1 wide (including control chars) + // calling control chars' widths 1 is consistent with OpenBSD fmt + 1 + } else { + // otherwise, get the unicode width + // note that we shouldn't actually get None here because only c < 0xA0 + // can return None, but for safety and future-proofing we do it this way + UnicodeWidthChar::width(c).unwrap_or(1) + } +} + +// lines with PSKIP, lacking PREFIX, or which are entirely blank are +// NoFormatLines; otherwise, they are FormatLines +#[derive(Debug)] +pub enum Line { + FormatLine(FileLine), + NoFormatLine(String, bool), +} + +impl Line { + // when we know that it's a FormatLine, as in the ParagraphStream iterator + fn get_formatline(self) -> FileLine { + match self { + Line::FormatLine(fl) => fl, + Line::NoFormatLine(..) => panic!("Found NoFormatLine when expecting FormatLine"), + } + } + + // when we know that it's a NoFormatLine, as in the ParagraphStream iterator + fn get_noformatline(self) -> (String, bool) { + match self { + Line::NoFormatLine(s, b) => (s, b), + Line::FormatLine(..) => panic!("Found FormatLine when expecting NoFormatLine"), + } + } +} + +// each line's prefix has to be considered to know whether to merge it with +// the next line or not +#[derive(Debug)] +pub struct FileLine { + line: String, + indent_end: usize, // the end of the indent, always the start of the text + pfxind_end: usize, // the end of the PREFIX's indent, that is, the spaces before the prefix + indent_len: usize, // display length of indent taking into account tabs + prefix_len: usize, // PREFIX indent length taking into account tabs +} + +// iterator that produces a stream of Lines from a file +pub struct FileLines<'a> { + opts: &'a FmtOptions, + lines: Lines<&'a mut FileOrStdReader>, +} + +impl<'a> FileLines<'a> { + fn new<'b>(opts: &'b FmtOptions, lines: Lines<&'b mut FileOrStdReader>) -> FileLines<'b> { + FileLines { + opts, + lines, + } + } + + // returns true if this line should be formatted + fn match_prefix(&self, line: &str) -> (bool, usize) { + if !self.opts.use_prefix { + return (true, 0); + } + + FileLines::match_prefix_generic(&self.opts.prefix[..], line, self.opts.xprefix) + } + + // returns true if this line should be formatted + fn match_anti_prefix(&self, line: &str) -> bool { + if !self.opts.use_anti_prefix { + return true; + } + + match FileLines::match_prefix_generic( + &self.opts.anti_prefix[..], + line, + self.opts.xanti_prefix, + ) { + (true, _) => false, + (_, _) => true, + } + } + + fn match_prefix_generic(pfx: &str, line: &str, exact: bool) -> (bool, usize) { + if line.starts_with(pfx) { + return (true, 0); + } + + if !exact { + // we do it this way rather than byte indexing to support unicode whitespace chars + for (i, char) in line.char_indices() { + if line[i..].starts_with(pfx) { + return (true, i); + } else if !char.is_whitespace() { + break; + } + } + } + + (false, 0) + } + + fn compute_indent(&self, string: &str, prefix_end: usize) -> (usize, usize, usize) { + let mut prefix_len = 0; + let mut indent_len = 0; + let mut indent_end = 0; + for (os, c) in string.char_indices() { + if os == prefix_end { + // we found the end of the prefix, so this is the printed length of the prefix here + prefix_len = indent_len; + } + + if (os >= prefix_end) && !c.is_whitespace() { + // found first non-whitespace after prefix, this is indent_end + indent_end = os; + break; + } else if c == '\t' { + // compute tab length + indent_len = (indent_len / self.opts.tabwidth + 1) * self.opts.tabwidth; + } else { + // non-tab character + indent_len += char_width(c); + } + } + (indent_end, prefix_len, indent_len) + } +} + +impl<'a> Iterator for FileLines<'a> { + type Item = Line; + + fn next(&mut self) -> Option { + let n = match self.lines.next() { + Some(t) => match t { + Ok(tt) => tt, + Err(_) => return None, + }, + None => return None, + }; + + // if this line is entirely whitespace, + // emit a blank line + // Err(true) indicates that this was a linebreak, + // which is important to know when detecting mail headers + if n.chars().all(|c| c.is_whitespace()) { + return Some(Line::NoFormatLine("".to_owned(), true)); + } + + // if this line does not match the prefix, + // emit the line unprocessed and iterate again + let (pmatch, poffset) = self.match_prefix(&n[..]); + if !pmatch { + return Some(Line::NoFormatLine(n, false)); + } else if n[poffset + self.opts.prefix.len()..] + .chars() + .all(|c| c.is_whitespace()) + { + // if the line matches the prefix, but is blank after, + // don't allow lines to be combined through it (that is, + // treat it like a blank line, except that since it's + // not truly blank we will not allow mail headers on the + // following line) + return Some(Line::NoFormatLine(n, false)); + } + + // skip if this line matches the anti_prefix + // (NOTE definition of match_anti_prefix is TRUE if we should process) + if !self.match_anti_prefix(&n[..]) { + return Some(Line::NoFormatLine(n, false)); + } + + // figure out the indent, prefix, and prefixindent ending points + let prefix_end = poffset + self.opts.prefix.len(); + let (indent_end, prefix_len, indent_len) = self.compute_indent(&n[..], prefix_end); + + Some(Line::FormatLine(FileLine { + line: n, + indent_end, + pfxind_end: poffset, + indent_len, + prefix_len, + })) + } +} + +// a paragraph : a collection of FileLines that are to be formatted +// plus info about the paragraph's indentation +// (but we only retain the String from the FileLine; the other info +// is only there to help us in deciding how to merge lines into Paragraphs +#[derive(Debug)] +pub struct Paragraph { + lines: Vec, // the lines of the file + pub init_str: String, // string representing the init, that is, the first line's indent + pub init_len: usize, // printable length of the init string considering TABWIDTH + init_end: usize, // byte location of end of init in first line String + pub indent_str: String, // string representing indent + pub indent_len: usize, // length of above + indent_end: usize, // byte location of end of indent (in crown and tagged mode, only applies to 2nd line and onward) + pub mail_header: bool, // we need to know if this is a mail header because we do word splitting differently in that case +} + +// an iterator producing a stream of paragraphs from a stream of lines +// given a set of options. +pub struct ParagraphStream<'a> { + lines: Peekable>, + next_mail: bool, + opts: &'a FmtOptions, +} + +impl<'a> ParagraphStream<'a> { + pub fn new<'b>(opts: &'b FmtOptions, reader: &'b mut FileOrStdReader) -> ParagraphStream<'b> { + let lines = FileLines::new(opts, reader.lines()).peekable(); + // at the beginning of the file, we might find mail headers + ParagraphStream { + lines, + next_mail: true, + opts, + } + } + + // detect RFC822 mail header + fn is_mail_header(line: &FileLine) -> bool { + // a mail header begins with either "From " (envelope sender line) + // or with a sequence of printable ASCII chars (33 to 126, inclusive, + // except colon) followed by a colon. + if line.indent_end > 0 { + false + } else { + let l_slice = &line.line[..]; + if l_slice.starts_with("From ") { + true + } else { + let colon_posn = match l_slice.find(':') { + Some(n) => n, + None => return false, + }; + + // header field must be nonzero length + if colon_posn == 0 { + return false; + } + + l_slice[..colon_posn].chars().all(|x| match x as usize { + y if y < 33 || y > 126 => false, + _ => true, + }) + } + } + } +} + +impl<'a> Iterator for ParagraphStream<'a> { + type Item = Result; + + fn next(&mut self) -> Option> { + // return a NoFormatLine in an Err; it should immediately be output + let noformat = match self.lines.peek() { + None => return None, + Some(l) => match *l { + Line::FormatLine(_) => false, + Line::NoFormatLine(_, _) => true, + }, + }; + + // found a NoFormatLine, immediately dump it out + if noformat { + let (s, nm) = self.lines.next().unwrap().get_noformatline(); + self.next_mail = nm; + return Some(Err(s)); + } + + // found a FormatLine, now build a paragraph + let mut init_str = String::new(); + let mut init_end = 0; + let mut init_len = 0; + let mut indent_str = String::new(); + let mut indent_end = 0; + let mut indent_len = 0; + let mut prefix_len = 0; + let mut pfxind_end = 0; + let mut p_lines = Vec::new(); + + let mut in_mail = false; + let mut second_done = false; // for when we use crown or tagged mode + loop { + { + // peek ahead + // need to explicitly force fl out of scope before we can call self.lines.next() + let fl = match self.lines.peek() { + None => break, + Some(l) => match *l { + Line::FormatLine(ref x) => x, + Line::NoFormatLine(..) => break, + }, + }; + + if p_lines.is_empty() { + // first time through the loop, get things set up + // detect mail header + if self.opts.mail && self.next_mail && ParagraphStream::is_mail_header(fl) { + in_mail = true; + // there can't be any indent or pfxind because otherwise is_mail_header + // would fail since there cannot be any whitespace before the colon in a + // valid header field + indent_str.push_str(" "); + indent_len = 2; + } else { + if self.opts.crown || self.opts.tagged { + init_str.push_str(&fl.line[..fl.indent_end]); + init_len = fl.indent_len; + init_end = fl.indent_end; + } else { + second_done = true; + } + + // these will be overwritten in the 2nd line of crown or tagged mode, but + // we are not guaranteed to get to the 2nd line, e.g., if the next line + // is a NoFormatLine or None. Thus, we set sane defaults the 1st time around + indent_str.push_str(&fl.line[..fl.indent_end]); + indent_len = fl.indent_len; + indent_end = fl.indent_end; + + // save these to check for matching lines + prefix_len = fl.prefix_len; + pfxind_end = fl.pfxind_end; + + // in tagged mode, add 4 spaces of additional indenting by default + // (gnu fmt's behavior is different: it seems to find the closest column to + // indent_end that is divisible by 3. But honestly that behavior seems + // pretty arbitrary. + // Perhaps a better default would be 1 TABWIDTH? But ugh that's so big. + if self.opts.tagged { + indent_str.push_str(" "); + indent_len += 4; + } + } + } else if in_mail { + // lines following mail headers must begin with spaces + if fl.indent_end == 0 || (self.opts.use_prefix && fl.pfxind_end == 0) { + break; // this line does not begin with spaces + } + } else if !second_done { + // now we have enough info to handle crown margin and tagged mode + if prefix_len != fl.prefix_len || pfxind_end != fl.pfxind_end { + // in both crown and tagged modes we require that prefix_len is the same + break; + } else if self.opts.tagged && indent_len - 4 == fl.indent_len + && indent_end == fl.indent_end + { + // in tagged mode, indent has to be *different* on following lines + break; + } else { + // this is part of the same paragraph, get the indent info from this line + indent_str.clear(); + indent_str.push_str(&fl.line[..fl.indent_end]); + indent_len = fl.indent_len; + indent_end = fl.indent_end; + } + second_done = true; + } else { + // detect mismatch + if indent_end != fl.indent_end || pfxind_end != fl.pfxind_end + || indent_len != fl.indent_len + || prefix_len != fl.prefix_len + { + break; + } + } + } + + p_lines.push(self.lines.next().unwrap().get_formatline().line); + + // when we're in split-only mode, we never join lines, so stop here + if self.opts.split_only { + break; + } + } + + // if this was a mail header, then the next line can be detected as one. Otherwise, it cannot. + // NOTE next_mail is true at ParagraphStream instantiation, and is set to true after a blank + // NoFormatLine. + self.next_mail = in_mail; + + Some(Ok(Paragraph { + lines: p_lines, + init_str, + init_len, + init_end, + indent_str, + indent_len, + indent_end, + mail_header: in_mail, + })) + } +} + +pub struct ParaWords<'a> { + opts: &'a FmtOptions, + para: &'a Paragraph, + words: Vec>, +} + +impl<'a> ParaWords<'a> { + pub fn new<'b>(opts: &'b FmtOptions, para: &'b Paragraph) -> ParaWords<'b> { + let mut pw = ParaWords { + opts, + para, + words: Vec::new(), + }; + pw.create_words(); + pw + } + + fn create_words(&mut self) { + if self.para.mail_header { + // no extra spacing for mail headers; always exactly 1 space + // safe to trim_left on every line of a mail header, since the + // first line is guaranteed not to have any spaces + self.words.extend( + self.para + .lines + .iter() + .flat_map(|x| x.split_whitespace()) + .map(|x| WordInfo { + word: x, + word_start: 0, + word_nchars: x.len(), // OK for mail headers; only ASCII allowed (unicode is escaped) + before_tab: None, + after_tab: 0, + sentence_start: false, + ends_punct: false, + new_line: false, + }), + ); + } else { + // first line + self.words.extend(if self.opts.crown || self.opts.tagged { + // crown and tagged mode has the "init" in the first line, so slice from there + WordSplit::new(self.opts, &self.para.lines[0][self.para.init_end..]) + } else { + // otherwise we slice from the indent + WordSplit::new(self.opts, &self.para.lines[0][self.para.indent_end..]) + }); + + if self.para.lines.len() > 1 { + let indent_end = self.para.indent_end; + let opts = self.opts; + self.words.extend( + self.para + .lines + .iter() + .skip(1) + .flat_map(|x| WordSplit::new(opts, &x[indent_end..])), + ); + } + } + } + + pub fn words(&'a self) -> Iter<'a, WordInfo<'a>> { + self.words.iter() + } +} + +struct WordSplit<'a> { + opts: &'a FmtOptions, + string: &'a str, + length: usize, + position: usize, + prev_punct: bool, +} + +impl<'a> WordSplit<'a> { + fn analyze_tabs(&self, string: &str) -> (Option, usize, Option) { + // given a string, determine (length before tab) and (printed length after first tab) + // if there are no tabs, beforetab = -1 and aftertab is the printed length + let mut beforetab = None; + let mut aftertab = 0; + let mut word_start = None; + for (os, c) in string.char_indices() { + if !c.is_whitespace() { + word_start = Some(os); + break; + } else if c == '\t' { + if beforetab == None { + beforetab = Some(aftertab); + aftertab = 0; + } else { + aftertab = (aftertab / self.opts.tabwidth + 1) * self.opts.tabwidth; + } + } else { + aftertab += 1; + } + } + (beforetab, aftertab, word_start) + } +} + +impl<'a> WordSplit<'a> { + fn new<'b>(opts: &'b FmtOptions, string: &'b str) -> WordSplit<'b> { + // wordsplits *must* start at a non-whitespace character + let trim_string = string.trim_start(); + WordSplit { + opts, + string: trim_string, + length: string.len(), + position: 0, + prev_punct: false, + } + } + + fn is_punctuation(c: char) -> bool { + match c { + '!' | '.' | '?' => true, + _ => false, + } + } +} + +pub struct WordInfo<'a> { + pub word: &'a str, + pub word_start: usize, + pub word_nchars: usize, + pub before_tab: Option, + pub after_tab: usize, + pub sentence_start: bool, + pub ends_punct: bool, + pub new_line: bool, +} + +// returns (&str, is_start_of_sentence) +impl<'a> Iterator for WordSplit<'a> { + type Item = WordInfo<'a>; + + fn next(&mut self) -> Option> { + if self.position >= self.length { + return None; + } + + let old_position = self.position; + let new_line = old_position == 0; + + // find the start of the next word, and record if we find a tab character + let (before_tab, after_tab, word_start) = + match self.analyze_tabs(&self.string[old_position..]) { + (b, a, Some(s)) => (b, a, s + old_position), + (_, _, None) => { + self.position = self.length; + return None; + } + }; + + // find the beginning of the next whitespace + // note that this preserves the invariant that self.position + // points to whitespace character OR end of string + let mut word_nchars = 0; + self.position = match self.string[word_start..].find(|x: char| { + if !x.is_whitespace() { + word_nchars += char_width(x); + false + } else { + true + } + }) { + None => self.length, + Some(s) => s + word_start, + }; + + let word_start_relative = word_start - old_position; + // if the previous sentence was punctuation and this sentence has >2 whitespace or one tab, is a new sentence. + let is_start_of_sentence = + self.prev_punct && (before_tab.is_some() || word_start_relative > 1); + + // now record whether this word ends in punctuation + self.prev_punct = match self.string[..self.position].chars().rev().next() { + Some(ch) => WordSplit::is_punctuation(ch), + _ => panic!("fatal: expected word not to be empty"), + }; + + let (word, word_start_relative, before_tab, after_tab) = if self.opts.uniform { + (&self.string[word_start..self.position], 0, None, 0) + } else { + ( + &self.string[old_position..self.position], + word_start_relative, + before_tab, + after_tab, + ) + }; + + Some(WordInfo { + word, + word_start: word_start_relative, + word_nchars, + before_tab, + after_tab, + sentence_start: is_start_of_sentence, + ends_punct: self.prev_punct, + new_line, + }) + } +} diff --git a/coreutils/src/fold/Cargo.toml b/coreutils/src/fold/Cargo.toml new file mode 100644 index 000000000..fb004cc16 --- /dev/null +++ b/coreutils/src/fold/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "fold" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_fold" +path = "fold.rs" + +[dependencies] +uucore = "0.0.1" + +[[bin]] +name = "fold" +path = "../../uumain.rs" diff --git a/coreutils/src/fold/fold.rs b/coreutils/src/fold/fold.rs new file mode 100644 index 000000000..8e083df32 --- /dev/null +++ b/coreutils/src/fold/fold.rs @@ -0,0 +1,215 @@ +#![crate_name = "uu_fold"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * 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 std::fs::File; +use std::io::{stdin, BufRead, BufReader, Read}; +use std::path::Path; + +static SYNTAX: &str = "[OPTION]... [FILE]..."; +static SUMMARY: &str = "Writes each file (or standard input if no files are given) + to standard output whilst breaking long lines"; +static LONG_HELP: &str = ""; + +pub fn uumain(args: Vec) -> i32 { + let (args, obs_width) = handle_obsolete(&args[..]); + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optflag( + "b", + "bytes", + "count using bytes rather than columns (meaning control characters \ + such as newline are not treated specially)", + ) + .optflag( + "s", + "spaces", + "break lines at word boundaries rather than a hard cut-off", + ) + .optopt( + "w", + "width", + "set WIDTH as the maximum line width rather than 80", + "WIDTH", + ) + .parse(args); + + let bytes = matches.opt_present("b"); + let spaces = matches.opt_present("s"); + let poss_width = if matches.opt_present("w") { + matches.opt_str("w") + } else { + obs_width + }; + let width = match poss_width { + Some(inp_width) => match inp_width.parse::() { + Ok(width) => width, + Err(e) => crash!(1, "illegal width value (\"{}\"): {}", inp_width, e), + }, + None => 80, + }; + let files = if matches.free.is_empty() { + vec!["-".to_owned()] + } else { + matches.free + }; + fold(files, bytes, spaces, width); + + 0 +} + +fn handle_obsolete(args: &[String]) -> (Vec, Option) { + for (i, arg) in args.iter().enumerate() { + let slice = &arg; + if slice.starts_with('-') && slice.len() > 1 + && slice.chars().nth(1).unwrap().is_digit(10) + { + let mut v = args.to_vec(); + v.remove(i); + return (v, Some(slice[1..].to_owned())); + } + } + (args.to_vec(), None) +} + +#[inline] +fn fold(filenames: Vec, bytes: bool, spaces: bool, width: usize) { + for filename in &filenames { + let filename: &str = &filename; + let mut stdin_buf; + let mut file_buf; + let buffer = BufReader::new(if filename == "-" { + stdin_buf = stdin(); + &mut stdin_buf as &mut Read + } else { + file_buf = safe_unwrap!(File::open(Path::new(filename))); + &mut file_buf as &mut Read + }); + fold_file(buffer, bytes, spaces, width); + } +} + +#[inline] +fn fold_file(mut file: BufReader, bytes: bool, spaces: bool, width: usize) { + let mut line = String::new(); + while safe_unwrap!(file.read_line(&mut line)) > 0 { + if bytes { + let len = line.len(); + let mut i = 0; + while i < len { + let width = if len - i >= width { width } else { len - i }; + let slice = { + let slice = &line[i..i + width]; + if spaces && i + width < len { + match slice.rfind(|ch: char| ch.is_whitespace()) { + Some(m) => &slice[..m + 1], + None => slice, + } + } else { + slice + } + }; + print!("{}", slice); + i += slice.len(); + } + } else { + let mut len = line.chars().count(); + let newline = line.ends_with('\n'); + if newline { + if len == 1 { + println!(); + continue; + } + len -= 1; + line.truncate(len); + } + let mut output = String::new(); + let mut count = 0; + for (i, ch) in line.chars().enumerate() { + if count >= width { + let (val, ncount) = { + let slice = &output[..]; + let (out, val, ncount) = if spaces && i + 1 < len { + match rfind_whitespace(slice) { + Some(m) => { + let routput = &slice[m + 1..slice.chars().count()]; + let ncount = routput.chars().fold(0, |out, ch: char| { + out + match ch { + '\t' => 8, + '\x08' => if out > 0 { + !0 + } else { + 0 + }, + '\r' => return 0, + _ => 1, + } + }); + (&slice[0..m + 1], routput, ncount) + } + None => (slice, "", 0), + } + } else { + (slice, "", 0) + }; + println!("{}", out); + (val.to_owned(), ncount) + }; + output = val; + count = ncount; + } + match ch { + '\t' => { + count += 8; + if count > width { + println!("{}", output); + output.truncate(0); + count = 8; + } + } + '\x08' => { + if count > 0 { + count -= 1; + let len = output.len() - 1; + output.truncate(len); + } + continue; + } + '\r' => { + output.truncate(0); + count = 0; + continue; + } + _ => count += 1, + }; + output.push(ch); + } + if count > 0 { + if newline { + println!("{}", output); + } else { + print!("{}", output); + } + } + } + } +} + +#[inline] +fn rfind_whitespace(slice: &str) -> Option { + for (i, ch) in slice.chars().rev().enumerate() { + if ch.is_whitespace() { + return Some(slice.chars().count() - (i + 1)); + } + } + None +} diff --git a/coreutils/src/groups/Cargo.toml b/coreutils/src/groups/Cargo.toml new file mode 100644 index 000000000..3bef6484e --- /dev/null +++ b/coreutils/src/groups/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "groups" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_groups" +path = "groups.rs" + +[dependencies.uucore] +version = "0.0.1" +features = ["entries"] + +[[bin]] +name = "groups" +path = "../../uumain.rs" diff --git a/coreutils/src/groups/groups.rs b/coreutils/src/groups/groups.rs new file mode 100644 index 000000000..406429eea --- /dev/null +++ b/coreutils/src/groups/groups.rs @@ -0,0 +1,47 @@ +#![crate_name = "uu_groups"] + +// This file is part of the uutils coreutils package. +// +// (c) Alan Andrade +// (c) Jian Zeng +// +// 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 uucore::entries::{get_groups, Locate, Passwd, gid2grp}; + +static SYNTAX: &str = "[user]"; +static SUMMARY: &str = "display current group names"; + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, "").parse(args); + + if matches.free.is_empty() { + println!( + "{}", + get_groups() + .unwrap() + .iter() + .map(|&g| gid2grp(g).unwrap()) + .collect::>() + .join(" ") + ); + } else if let Ok(p) = Passwd::locate(matches.free[0].as_str()) { + println!( + "{}", + p.belongs_to() + .iter() + .map(|&g| gid2grp(g).unwrap()) + .collect::>() + .join(" ") + ); + } else { + crash!(1, "unknown user {}", matches.free[0]); + } + + 0 +} diff --git a/coreutils/src/hashsum/Cargo.toml b/coreutils/src/hashsum/Cargo.toml new file mode 100644 index 000000000..7b9662435 --- /dev/null +++ b/coreutils/src/hashsum/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "hashsum" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_hashsum" +path = "hashsum.rs" + +[dependencies] +digest = "0.6.2" +getopts = "0.2.18" +hex = "0.2.0" +libc = "0.2.42" +md5 = "0.3.5" +regex = "1.0.1" +regex-syntax = "0.6.7" +sha1 = "0.6.0" +sha2 = "0.6.0" +sha3 = "0.6.0" +uucore = "0.0.1" + +[[bin]] +name = "hashsum" +path = "../../uumain.rs" diff --git a/coreutils/src/hashsum/digest.rs b/coreutils/src/hashsum/digest.rs new file mode 100644 index 000000000..d85403ee6 --- /dev/null +++ b/coreutils/src/hashsum/digest.rs @@ -0,0 +1,132 @@ +extern crate digest; +extern crate md5; +extern crate sha1; +extern crate sha2; +extern crate sha3; + +use digest::digest::{ExtendableOutput, Input, XofReader}; +use hex::ToHex; + +pub trait Digest { + fn new() -> Self + where + Self: Sized; + fn input(&mut self, input: &[u8]); + fn result(&mut self, out: &mut [u8]); + fn reset(&mut self); + fn output_bits(&self) -> usize; + fn output_bytes(&self) -> usize { + (self.output_bits() + 7) / 8 + } + fn result_str(&mut self) -> String { + let mut buf: Vec = vec![0; self.output_bytes()]; + self.result(&mut buf); + buf.to_hex() + } +} + +impl Digest for md5::Context { + fn new() -> Self { + md5::Context::new() + } + + fn input(&mut self, input: &[u8]) { + self.consume(input) + } + + fn result(&mut self, out: &mut [u8]) { + out.copy_from_slice(&*self.compute()); + } + + fn reset(&mut self) { + *self = md5::Context::new(); + } + + fn output_bits(&self) -> usize { + 128 + } +} + +impl Digest for sha1::Sha1 { + fn new() -> Self { + sha1::Sha1::new() + } + + fn input(&mut self, input: &[u8]) { + self.update(input); + } + + fn result(&mut self, out: &mut [u8]) { + out.copy_from_slice(&self.digest().bytes()); + } + + fn reset(&mut self) { + self.reset(); + } + + fn output_bits(&self) -> usize { + 160 + } +} + +// Implements the Digest trait for sha2 / sha3 algorithms with fixed ouput +macro_rules! impl_digest_sha { + ($type: ty, $size: expr) => ( + impl Digest for $type { + fn new() -> Self { + Self::default() + } + + fn input(&mut self, input: &[u8]) { + digest::Digest::input(self, input); + } + + fn result(&mut self, out: &mut [u8]) { + out.copy_from_slice(digest::Digest::result(*self).as_slice()); + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { $size } + } + ) +} + +// Implements the Digest trait for sha2 / sha3 algorithms with variable ouput +macro_rules! impl_digest_shake { + ($type: ty) => ( + impl Digest for $type { + fn new() -> Self { + Self::default() + } + + fn input(&mut self, input: &[u8]) { + self.process(input); + } + + fn result(&mut self, out: &mut [u8]) { + self.xof_result().read(out); + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { 0 } + } + ) +} + +impl_digest_sha!(sha2::Sha224, 224); +impl_digest_sha!(sha2::Sha256, 256); +impl_digest_sha!(sha2::Sha384, 384); +impl_digest_sha!(sha2::Sha512, 512); + +impl_digest_sha!(sha3::Sha3_224, 224); +impl_digest_sha!(sha3::Sha3_256, 256); +impl_digest_sha!(sha3::Sha3_384, 384); +impl_digest_sha!(sha3::Sha3_512, 512); +impl_digest_shake!(sha3::Shake128); +impl_digest_shake!(sha3::Shake256); diff --git a/coreutils/src/hashsum/hashsum.rs b/coreutils/src/hashsum/hashsum.rs new file mode 100644 index 000000000..a902290c4 --- /dev/null +++ b/coreutils/src/hashsum/hashsum.rs @@ -0,0 +1,543 @@ +#![crate_name = "uu_hashsum"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * (c) Vsevolod Velichko + * (c) Gil Cottle + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate hex; +extern crate md5; +extern crate regex; +extern crate regex_syntax; +extern crate sha1; +extern crate sha2; +extern crate sha3; + +#[macro_use] +extern crate uucore; + +mod digest; + +use digest::Digest; +use hex::ToHex; +use md5::Context as Md5; +use regex::Regex; +use sha1::Sha1; +use sha2::{Sha224, Sha256, Sha384, Sha512}; +use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}; +use std::fs::File; +use std::io::{self, stdin, BufRead, BufReader, Read}; +use std::path::Path; + +static NAME: &str = "hashsum"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn is_custom_binary(program: &str) -> bool { + match program { + "md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" + | "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum" + | "shake128sum" | "shake256sum" => true, + _ => false, + } +} + +fn detect_algo( + program: &str, + matches: &getopts::Matches, +) -> (&'static str, Box, usize) { + let mut alg: Option> = None; + let mut name: &'static str = ""; + let mut output_bits = 0; + match program { + "md5sum" => ("MD5", Box::new(Md5::new()) as Box, 128), + "sha1sum" => ("SHA1", Box::new(Sha1::new()) as Box, 160), + "sha224sum" => ("SHA224", Box::new(Sha224::new()) as Box, 224), + "sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box, 256), + "sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box, 384), + "sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box, 512), + "sha3sum" => match matches.opt_str("bits") { + Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Ok(224) => ("SHA3-224", Box::new(Sha3_224::new()) as Box, 224), + Ok(256) => ("SHA3-256", Box::new(Sha3_256::new()) as Box, 256), + Ok(384) => ("SHA3-384", Box::new(Sha3_384::new()) as Box, 384), + Ok(512) => ("SHA3-512", Box::new(Sha3_512::new()) as Box, 512), + Ok(_) => crash!( + 1, + "Invalid output size for SHA3 (expected 224, 256, 384, or 512)" + ), + Err(err) => crash!(1, "{}", err), + }, + None => crash!(1, "--bits required for SHA3"), + }, + "sha3-224sum" => ("SHA3-224", Box::new(Sha3_224::new()) as Box, 224), + "sha3-256sum" => ("SHA3-256", Box::new(Sha3_256::new()) as Box, 256), + "sha3-384sum" => ("SHA3-384", Box::new(Sha3_384::new()) as Box, 384), + "sha3-512sum" => ("SHA3-512", Box::new(Sha3_512::new()) as Box, 512), + "shake128sum" => match matches.opt_str("bits") { + Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Ok(bits) => ("SHAKE128", Box::new(Shake128::new()) as Box, bits), + Err(err) => crash!(1, "{}", err), + }, + None => crash!(1, "--bits required for SHAKE-128"), + }, + "shake256sum" => match matches.opt_str("bits") { + Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Ok(bits) => ("SHAKE256", Box::new(Shake256::new()) as Box, bits), + Err(err) => crash!(1, "{}", err), + }, + None => crash!(1, "--bits required for SHAKE-256"), + }, + _ => { + { + let mut set_or_crash = |n, val, bits| -> () { + if alg.is_some() { + crash!(1, "You cannot combine multiple hash algorithms!") + }; + name = n; + alg = Some(val); + output_bits = bits + }; + if matches.opt_present("md5") { + set_or_crash("MD5", Box::new(Md5::new()), 128) + } + if matches.opt_present("sha1") { + set_or_crash("SHA1", Box::new(Sha1::new()), 160) + } + if matches.opt_present("sha224") { + set_or_crash("SHA224", Box::new(Sha224::new()), 224) + } + if matches.opt_present("sha256") { + set_or_crash("SHA256", Box::new(Sha256::new()), 256) + } + if matches.opt_present("sha384") { + set_or_crash("SHA384", Box::new(Sha384::new()), 384) + } + if matches.opt_present("sha512") { + set_or_crash("SHA512", Box::new(Sha512::new()), 512) + } + if matches.opt_present("sha3") { + match matches.opt_str("bits") { + Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Ok(224) => set_or_crash( + "SHA3-224", + Box::new(Sha3_224::new()) as Box, + 224, + ), + Ok(256) => set_or_crash( + "SHA3-256", + Box::new(Sha3_256::new()) as Box, + 256, + ), + Ok(384) => set_or_crash( + "SHA3-384", + Box::new(Sha3_384::new()) as Box, + 384, + ), + Ok(512) => set_or_crash( + "SHA3-512", + Box::new(Sha3_512::new()) as Box, + 512, + ), + Ok(_) => crash!( + 1, + "Invalid output size for SHA3 (expected 224, 256, 384, or 512)" + ), + Err(err) => crash!(1, "{}", err), + }, + None => crash!(1, "--bits required for SHA3"), + } + } + if matches.opt_present("sha3-224") { + set_or_crash("SHA3-224", Box::new(Sha3_224::new()), 224) + } + if matches.opt_present("sha3-256") { + set_or_crash("SHA3-256", Box::new(Sha3_256::new()), 256) + } + if matches.opt_present("sha3-384") { + set_or_crash("SHA3-384", Box::new(Sha3_384::new()), 384) + } + if matches.opt_present("sha3-512") { + set_or_crash("SHA3-512", Box::new(Sha3_512::new()), 512) + } + if matches.opt_present("shake128") { + match matches.opt_str("bits") { + Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits), + Err(err) => crash!(1, "{}", err), + }, + None => crash!(1, "--bits required for SHAKE-128"), + } + } + if matches.opt_present("shake256") { + match matches.opt_str("bits") { + Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits), + Err(err) => crash!(1, "{}", err), + }, + None => crash!(1, "--bits required for SHAKE-256"), + } + } + } + if alg.is_none() { + crash!(1, "You must specify hash algorithm!") + }; + (name, alg.unwrap(), output_bits) + } + } +} + +pub fn uumain(args: Vec) -> i32 { + let program = &args[0]; + let binary_name = Path::new(program).file_name().unwrap().to_str().unwrap(); + + // Default binary in Windows, text mode otherwise + let binary_flag_default = cfg!(windows); + + let mut opts = getopts::Options::new(); + opts.optflag( + "b", + "binary", + &format!( + "read in binary mode{}", + if binary_flag_default { + " (default)" + } else { + "" + } + ), + ); + opts.optflag("c", "check", "read hashsums from the FILEs and check them"); + opts.optflag("", "tag", "create a BSD-style checksum"); + opts.optflag( + "t", + "text", + &format!( + "read in text mode{}", + if binary_flag_default { + "" + } else { + " (default)" + } + ), + ); + opts.optflag( + "q", + "quiet", + "don't print OK for each successfully verified file", + ); + opts.optflag( + "s", + "status", + "don't output anything, status code shows success", + ); + opts.optflag( + "", + "strict", + "exit non-zero for improperly formatted checksum lines", + ); + opts.optflag( + "w", + "warn", + "warn about improperly formatted checksum lines", + ); + opts.optflag("h", "help", "display this help and exit"); + opts.optflag("V", "version", "output version information and exit"); + + if !is_custom_binary(program) { + opts.optflag("", "md5", "work with MD5"); + opts.optflag("", "sha1", "work with SHA1"); + opts.optflag("", "sha224", "work with SHA224"); + opts.optflag("", "sha256", "work with SHA256"); + opts.optflag("", "sha384", "work with SHA384"); + opts.optflag("", "sha512", "work with SHA512"); + opts.optflag("", "sha3", "work with SHA3"); + opts.optflag("", "sha3-224", "work with SHA3-224"); + opts.optflag("", "sha3-256", "work with SHA3-256"); + opts.optflag("", "sha3-384", "work with SHA3-384"); + opts.optflag("", "sha3-512", "work with SHA3-512"); + opts.optflag( + "", + "shake128", + "work with SHAKE128 using BITS for the output size", + ); + opts.optflag( + "", + "shake256", + "work with SHAKE256 using BITS for the output size", + ); + } + + // Needed for variable-length output sums (e.g. SHAKE) + opts.optopt( + "", + "bits", + "set the size of the output (only for SHAKE)", + "BITS", + ); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "{}", f), + }; + + if matches.opt_present("help") { + usage(program, binary_name, &opts); + } else if matches.opt_present("version") { + version(); + } else { + let (name, algo, bits) = detect_algo(binary_name, &matches); + + let binary_flag = matches.opt_present("binary"); + let text_flag = matches.opt_present("text"); + if binary_flag && text_flag { + crash!(1, "cannot set binary and text mode at the same time"); + } + let binary = if binary_flag { + true + } else if text_flag { + false + } else { + binary_flag_default + }; + let check = matches.opt_present("check"); + let tag = matches.opt_present("tag"); + let status = matches.opt_present("status"); + let quiet = matches.opt_present("quiet") || status; + let strict = matches.opt_present("strict"); + let warn = matches.opt_present("warn") && !status; + let files = if matches.free.is_empty() { + vec!["-".to_owned()] + } else { + matches.free + }; + match hashsum( + name, + algo, + files, + binary, + check, + tag, + status, + quiet, + strict, + warn, + bits, + ) { + Ok(()) => return 0, + Err(e) => return e, + } + } + + 0 +} + +fn version() { + println!("{} {}", NAME, VERSION); +} + +fn usage(program: &str, binary_name: &str, opts: &getopts::Options) { + let spec = if is_custom_binary(binary_name) { + format!(" {} [OPTION]... [FILE]...", program) + } else { + format!( + " {} {{--md5|--sha1|--sha224|--sha256|--sha384|--sha512|\ + --sha3|--sha3-224|--sha3-256|--sha3-384|--sha3-512|\ + --shake128|--shake256}} [OPTION]... [FILE]...", + program + ) + }; + + let msg = format!( + "{} {} + +Usage: +{} + +Compute and check message digests.", + NAME, VERSION, spec + ); + + print!("{}", opts.usage(&msg)); +} + +fn hashsum( + algoname: &str, + mut digest: Box, + files: Vec, + binary: bool, + check: bool, + tag: bool, + status: bool, + quiet: bool, + strict: bool, + warn: bool, + output_bits: usize, +) -> Result<(), i32> { + let mut bad_format = 0; + let mut failed = 0; + let binary_marker = if binary { "*" } else { " " }; + for filename in &files { + let filename: &str = filename; + let stdin_buf; + let file_buf; + let mut file = BufReader::new(if filename == "-" { + stdin_buf = stdin(); + Box::new(stdin_buf) as Box + } else { + file_buf = safe_unwrap!(File::open(filename)); + Box::new(file_buf) as Box + }); + if check { + // Set up Regexes for line validation and parsing + let bytes = digest.output_bits() / 4; + let gnu_re = safe_unwrap!(Regex::new(&format!( + r"^(?P[a-fA-F0-9]{{{}}}) (?P[ \*])(?P.*)", + bytes + ))); + let bsd_re = safe_unwrap!(Regex::new(&format!( + r"^{algorithm} \((?P.*)\) = (?P[a-fA-F0-9]{{{digest_size}}})", + algorithm = algoname, + digest_size = bytes + ))); + + let buffer = file; + for (i, line) in buffer.lines().enumerate() { + let line = safe_unwrap!(line); + let (ck_filename, sum, binary_check) = match gnu_re.captures(&line) { + Some(caps) => ( + caps.name("fileName").unwrap().as_str(), + caps.name("digest").unwrap().as_str().to_ascii_lowercase(), + caps.name("binary").unwrap().as_str() == "*", + ), + None => match bsd_re.captures(&line) { + Some(caps) => ( + caps.name("fileName").unwrap().as_str(), + caps.name("digest").unwrap().as_str().to_ascii_lowercase(), + true, + ), + None => { + bad_format += 1; + if strict { + return Err(1); + } + if warn { + show_warning!( + "{}: {}: improperly formatted {} checksum line", + filename, + i + 1, + algoname + ); + } + continue; + } + }, + }; + let f = safe_unwrap!(File::open(ck_filename)); + let mut ckf = BufReader::new(Box::new(f) as Box); + let real_sum = safe_unwrap!(digest_reader( + &mut digest, + &mut ckf, + binary_check, + output_bits + )).to_ascii_lowercase(); + if sum == real_sum { + if !quiet { + println!("{}: OK", ck_filename); + } + } else { + if !status { + println!("{}: FAILED", ck_filename); + } + failed += 1; + } + } + } else { + let sum = safe_unwrap!(digest_reader(&mut digest, &mut file, binary, output_bits)); + if tag { + println!("{} ({}) = {}", algoname, filename, sum); + } else { + println!("{} {}{}", sum, binary_marker, filename); + } + } + } + if !status { + if bad_format == 1 { + show_warning!("{} line is improperly formatted", bad_format); + } else if bad_format > 1 { + show_warning!("{} lines are improperly formatted", bad_format); + } + if failed > 0 { + show_warning!("{} computed checksum did NOT match", failed); + } + } + + Ok(()) +} + +fn digest_reader<'a, T: Read>( + digest: &mut Box, + reader: &mut BufReader, + binary: bool, + output_bits: usize, +) -> io::Result { + digest.reset(); + + // Digest file, do not hold too much in memory at any given moment + let windows = cfg!(windows); + let mut buffer = Vec::with_capacity(524_288); + let mut vec = Vec::with_capacity(524_288); + let mut looking_for_newline = false; + loop { + match reader.read_to_end(&mut buffer) { + Ok(0) => { + break; + } + Ok(nread) => { + if windows && !binary { + // Windows text mode returns '\n' when reading '\r\n' + for &b in buffer.iter().take(nread) { + if looking_for_newline { + if b != b'\n' { + vec.push(b'\r'); + } + if b != b'\r' { + vec.push(b); + looking_for_newline = false; + } + } else if b != b'\r' { + vec.push(b); + } else { + looking_for_newline = true; + } + } + digest.input(&vec); + vec.clear(); + } else { + digest.input(&buffer[..nread]); + } + } + Err(e) => return Err(e), + } + } + if windows && looking_for_newline { + vec.push(b'\r'); + digest.input(&vec); + } + + if digest.output_bits() > 0 { + Ok(digest.result_str()) + } else { + // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) + let mut bytes = Vec::new(); + bytes.resize((output_bits + 7) / 8, 0); + digest.result(&mut bytes); + Ok(bytes.to_hex()) + } +} diff --git a/coreutils/src/head/Cargo.toml b/coreutils/src/head/Cargo.toml new file mode 100644 index 000000000..75d604608 --- /dev/null +++ b/coreutils/src/head/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "head" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_head" +path = "head.rs" + +[dependencies] +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "head" +path = "../../uumain.rs" diff --git a/coreutils/src/head/head.rs b/coreutils/src/head/head.rs new file mode 100644 index 000000000..efc0ab8db --- /dev/null +++ b/coreutils/src/head/head.rs @@ -0,0 +1,197 @@ +#![crate_name = "uu_head"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alan Andrade + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Synced with: https://raw.github.com/avsm/src/master/usr.bin/head/head.c + */ + +#[macro_use] +extern crate uucore; + +use std::io::{stdin, BufRead, BufReader, Read}; +use std::fs::File; +use std::path::Path; +use std::str::from_utf8; + +static SYNTAX: &str = ""; +static SUMMARY: &str = ""; +static LONG_HELP: &str = ""; + +enum FilterMode { + Bytes(usize), + Lines(usize), +} + +struct Settings { + mode: FilterMode, + verbose: bool, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + mode: FilterMode::Lines(10), + verbose: false, + } + } +} + +pub fn uumain(args: Vec) -> i32 { + let mut settings: Settings = Default::default(); + + // handle obsolete -number syntax + let new_args = match obsolete(&args[0..]) { + (args, Some(n)) => { + settings.mode = FilterMode::Lines(n); + args + } + (args, None) => args, + }; + + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optopt( + "c", + "bytes", + "Print the first K bytes. With the leading '-', print all but the last K bytes", + "[-]K", + ) + .optopt( + "n", + "lines", + "Print the first K lines. With the leading '-', print all but the last K lines", + "[-]K", + ) + .optflag("q", "quiet", "never print headers giving file names") + .optflag("v", "verbose", "always print headers giving file names") + .optflag("h", "help", "display this help and exit") + .optflag("V", "version", "output version information and exit") + .parse(new_args); + + let use_bytes = matches.opt_present("c"); + + // TODO: suffixes (e.g. b, kB, etc.) + match matches.opt_str("n") { + Some(n) => { + if use_bytes { + show_error!("cannot specify both --bytes and --lines."); + return 1; + } + match n.parse::() { + Ok(m) => settings.mode = FilterMode::Lines(m), + Err(e) => { + show_error!("invalid line count '{}': {}", n, e); + return 1; + } + } + } + None => if let Some(count) = matches.opt_str("c") { + match count.parse::() { + Ok(m) => settings.mode = FilterMode::Bytes(m), + Err(e) => { + show_error!("invalid byte count '{}': {}", count, e); + return 1; + } + } + } + }; + + let quiet = matches.opt_present("q"); + let verbose = matches.opt_present("v"); + let files = matches.free; + + // GNU implementation allows multiple declarations of "-q" and "-v" with the + // last flag winning. This can't be simulated with the getopts cargo unless + // we manually parse the arguments. Given the declaration of both flags, + // verbose mode always wins. This is a potential future improvement. + if files.len() > 1 && !quiet && !verbose { + settings.verbose = true; + } + if quiet { + settings.verbose = false; + } + if verbose { + settings.verbose = true; + } + + if files.is_empty() { + let mut buffer = BufReader::new(stdin()); + head(&mut buffer, &settings); + } else { + let mut firstime = true; + + for file in &files { + if settings.verbose { + if !firstime { + println!(); + } + println!("==> {} <==", file); + } + firstime = false; + + let path = Path::new(file); + let reader = File::open(&path).unwrap(); + let mut buffer = BufReader::new(reader); + if !head(&mut buffer, &settings) { + break; + } + } + } + + 0 +} + +// It searches for an option in the form of -123123 +// +// In case is found, the options vector will get rid of that object so that +// getopts works correctly. +fn obsolete(options: &[String]) -> (Vec, Option) { + let mut options: Vec = options.to_vec(); + let mut a = 1; + let b = options.len(); + + while a < b { + let current = options[a].clone(); + let current = current.as_bytes(); + + if current.len() > 1 && current[0] == b'-' { + let len = current.len(); + for pos in 1..len { + // Ensure that the argument is only made out of digits + if !(current[pos] as char).is_numeric() { + break; + } + + // If this is the last number + if pos == len - 1 { + options.remove(a); + let number: Option = + from_utf8(¤t[1..len]).unwrap().parse::().ok(); + return (options, Some(number.unwrap())); + } + } + } + + a += 1; + } + + (options, None) +} + +// TODO: handle errors on read +fn head(reader: &mut BufReader, settings: &Settings) -> bool { + match settings.mode { + FilterMode::Bytes(count) => for byte in reader.bytes().take(count) { + print!("{}", byte.unwrap() as char); + }, + FilterMode::Lines(count) => for line in reader.lines().take(count) { + println!("{}", line.unwrap()); + }, + } + true +} diff --git a/coreutils/src/hostid/Cargo.toml b/coreutils/src/hostid/Cargo.toml new file mode 100644 index 000000000..8414c740b --- /dev/null +++ b/coreutils/src/hostid/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "hostid" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_hostid" +path = "hostid.rs" + +[dependencies] +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "hostid" +path = "../../uumain.rs" diff --git a/coreutils/src/hostid/hostid.rs b/coreutils/src/hostid/hostid.rs new file mode 100644 index 000000000..955868aea --- /dev/null +++ b/coreutils/src/hostid/hostid.rs @@ -0,0 +1,48 @@ +#![crate_name = "uu_hostid"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Maciej Dziardziel + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +extern crate libc; + +#[macro_use] +extern crate uucore; + +use libc::c_long; + +static SYNTAX: &str = "[options]"; +static SUMMARY: &str = ""; +static LONG_HELP: &str = ""; + +// currently rust libc interface doesn't include gethostid +extern "C" { + pub fn gethostid() -> c_long; +} + +pub fn uumain(args: Vec) -> i32 { + new_coreopts!(SYNTAX, SUMMARY, LONG_HELP).parse(args); + hostid(); + 0 +} + +fn hostid() { + /* + * POSIX says gethostid returns a "32-bit identifier" but is silent + * whether it's sign-extended. Turn off any sign-extension. This + * is a no-op unless unsigned int is wider than 32 bits. + */ + + let mut result: c_long; + unsafe { + result = gethostid(); + } + + result &= 0xffff_ffff; + println!("{:0>8x}", result); +} diff --git a/coreutils/src/hostname/Cargo.toml b/coreutils/src/hostname/Cargo.toml new file mode 100644 index 000000000..7b1497245 --- /dev/null +++ b/coreutils/src/hostname/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "hostname" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_hostname" +path = "hostname.rs" + +[dependencies] +libc = "0.2.42" +winapi = { version = "0.3", features = ["sysinfoapi", "winsock2"] } +getopts = "0.2" +uucore = "0.0.1" + +[[bin]] +name = "hostname" +path = "../../uumain.rs" diff --git a/coreutils/src/hostname/hostname.rs b/coreutils/src/hostname/hostname.rs new file mode 100644 index 000000000..7808b6c38 --- /dev/null +++ b/coreutils/src/hostname/hostname.rs @@ -0,0 +1,214 @@ +#![crate_name = "uu_hostname"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alan Andrade + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate libc; +#[cfg(windows)] +extern crate winapi; + +#[macro_use] +extern crate uucore; + +use std::collections::hash_set::HashSet; +use std::iter::repeat; +use std::io; +use std::str; +use std::net::ToSocketAddrs; +use getopts::Matches; + +#[cfg(windows)] +use winapi::um::winsock2::{GetHostNameW, WSACleanup, WSAStartup}; +#[cfg(windows)] +use winapi::um::sysinfoapi::{ComputerNamePhysicalDnsHostname, SetComputerNameExW}; +#[cfg(windows)] +use winapi::shared::minwindef::MAKEWORD; +#[cfg(windows)] +use uucore::wide::*; + +#[cfg(not(windows))] +use libc::gethostname; +#[cfg(not(windows))] +use libc::sethostname; + +const SYNTAX: &str = "[OPTION]... [HOSTNAME]"; +const SUMMARY: &str = "Print or set the system's host name."; +const LONG_HELP: &str = ""; + +pub fn uumain(args: Vec) -> i32 { + #[cfg(windows)] + unsafe { + let mut data = std::mem::uninitialized(); + if WSAStartup(MAKEWORD(2, 2), &mut data as *mut _) != 0 { + eprintln!("Failed to start Winsock 2.2"); + return 1; + } + } + let result = execute(args); + #[cfg(windows)] + unsafe { + WSACleanup(); + } + result +} + +fn execute(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optflag("d", "domain", "Display the name of the DNS domain if possible") + .optflag("i", "ip-address", "Display the network address(es) of the host") + // TODO: support --long + .optflag("f", "fqdn", "Display the FQDN (Fully Qualified Domain Name) (default)") + .optflag("s", "short", "Display the short hostname (the portion before the first dot) if \ + possible") + .parse(args); + + match matches.free.len() { + 0 => display_hostname(matches), + 1 => { + if let Err(err) = xsethostname(matches.free.last().unwrap()) { + show_error!("{}", err); + 1 + } else { + 0 + } + } + _ => { + show_error!("{}", msg_wrong_number_of_arguments!(0, 1)); + 1 + } + } +} + +fn display_hostname(matches: Matches) -> i32 { + let hostname = return_if_err!(1, xgethostname()); + + if matches.opt_present("i") { + // XXX: to_socket_addrs needs hostname:port so append a dummy port and remove it later. + // This was originally supposed to use std::net::lookup_host, but that seems to be + // deprecated. Perhaps we should use the dns-lookup crate? + let hostname = hostname + ":1"; + match hostname.to_socket_addrs() { + Ok(addresses) => { + let mut hashset = HashSet::new(); + let mut output = String::new(); + for addr in addresses { + // XXX: not sure why this is necessary... + if !hashset.contains(&addr) { + let mut ip = format!("{}", addr); + if ip.ends_with(":1") { + let len = ip.len(); + ip.truncate(len - 2); + } + output.push_str(&ip); + output.push_str(" "); + hashset.insert(addr); + } + } + let len = output.len(); + if len > 0 { + println!("{}", &output[0..len - 1]); + } + + 0 + } + Err(f) => { + show_error!("{}", f); + + 1 + } + } + } else { + if matches.opt_present("s") || matches.opt_present("d") { + let mut it = hostname.char_indices().filter(|&ci| ci.1 == '.'); + if let Some(ci) = it.next() { + if matches.opt_present("s") { + println!("{}", &hostname[0..ci.0]); + } else { + println!("{}", &hostname[ci.0 + 1..]); + } + return 0; + } + } + + println!("{}", hostname); + + 0 + } +} + +#[cfg(not(windows))] +fn xgethostname() -> io::Result { + use std::ffi::CStr; + + let namelen = 256; + let mut name: Vec = repeat(0).take(namelen).collect(); + let err = unsafe { + gethostname( + name.as_mut_ptr() as *mut libc::c_char, + namelen as libc::size_t, + ) + }; + + if err == 0 { + let null_pos = name.iter().position(|byte| *byte == 0).unwrap_or(namelen); + if null_pos == namelen { + name.push(0); + } + + Ok(CStr::from_bytes_with_nul(&name[..null_pos + 1]) + .unwrap() + .to_string_lossy() + .into_owned()) + } else { + Err(io::Error::last_os_error()) + } +} + +#[cfg(windows)] +fn xgethostname() -> io::Result { + let namelen = 256; + let mut name: Vec = repeat(0).take(namelen).collect(); + let err = unsafe { GetHostNameW(name.as_mut_ptr(), namelen as libc::c_int) }; + + if err == 0 { + Ok(String::from_wide_null(&name)) + } else { + Err(io::Error::last_os_error()) + } +} + +#[cfg(not(windows))] +fn xsethostname(name: &str) -> io::Result<()> { + let vec_name: Vec = name.bytes().map(|c| c as libc::c_char).collect(); + + let err = unsafe { sethostname(vec_name.as_ptr(), vec_name.len() as _) }; + + if err != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(windows)] +fn xsethostname(name: &str) -> io::Result<()> { + use std::ffi::OsStr; + + let wide_name = OsStr::new(name).to_wide_null(); + + let err = unsafe { SetComputerNameExW(ComputerNamePhysicalDnsHostname, wide_name.as_ptr()) }; + + if err == 0 { + // NOTE: the above is correct, failure is when the function returns 0 apparently + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} diff --git a/coreutils/src/id/Cargo.toml b/coreutils/src/id/Cargo.toml new file mode 100644 index 000000000..f9aef3e30 --- /dev/null +++ b/coreutils/src/id/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "id" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_id" +path = "id.rs" + +[dependencies.uucore] +version = "0.0.1" +features = ["entries", "process"] + +[[bin]] +name = "id" +path = "../../uumain.rs" diff --git a/coreutils/src/id/id.rs b/coreutils/src/id/id.rs new file mode 100644 index 000000000..0b87fb5ec --- /dev/null +++ b/coreutils/src/id/id.rs @@ -0,0 +1,323 @@ +#![crate_name = "uu_id"] +// This file is part of the uutils coreutils package. +// +// (c) Alan Andrade +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// +// Synced with: +// http://ftp-archive.freebsd.org/mirror/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/shellutils/src/id.c +// http://www.opensource.apple.com/source/shell_cmds/shell_cmds-118/id/id.c +// +#![allow(non_camel_case_types)] +#![allow(dead_code)] + +#[macro_use] +extern crate uucore; +pub use uucore::libc; +use uucore::libc::{getlogin, uid_t}; +use uucore::entries::{self, Group, Locate, Passwd}; +use uucore::process::{getegid, geteuid, getgid, getuid}; +use std::ffi::CStr; + +macro_rules! cstr2cow { + ($v:expr) => ( + unsafe { CStr::from_ptr($v).to_string_lossy() } + ) +} + +#[cfg(not(target_os = "linux"))] +mod audit { + pub use std::mem::uninitialized; + use super::libc::{c_int, c_uint, dev_t, pid_t, uid_t, uint64_t}; + + pub type au_id_t = uid_t; + pub type au_asid_t = pid_t; + pub type au_event_t = c_uint; + pub type au_emod_t = c_uint; + pub type au_class_t = c_int; + + #[repr(C)] + pub struct au_mask { + pub am_success: c_uint, + pub am_failure: c_uint, + } + pub type au_mask_t = au_mask; + + #[repr(C)] + pub struct au_tid_addr { + pub port: dev_t, + } + pub type au_tid_addr_t = au_tid_addr; + + #[repr(C)] + pub struct c_auditinfo_addr { + pub ai_auid: au_id_t, // Audit user ID + pub ai_mask: au_mask_t, // Audit masks. + pub ai_termid: au_tid_addr_t, // Terminal ID. + pub ai_asid: au_asid_t, // Audit session ID. + pub ai_flags: uint64_t, // Audit session flags + } + pub type c_auditinfo_addr_t = c_auditinfo_addr; + + extern "C" { + pub fn getaudit(auditinfo_addr: *mut c_auditinfo_addr_t) -> c_int; + } +} + +static SYNTAX: &'static str = "[OPTION]... [USER]"; +static SUMMARY: &'static str = "Print user and group information for the specified USER,\n or (when USER omitted) for the current user."; + +pub fn uumain(args: Vec) -> i32 { + let mut opts = new_coreopts!(SYNTAX, SUMMARY, ""); + opts.optflag( + "A", + "", + "Display the process audit (not available on Linux)", + ); + opts.optflag("G", "", "Display the different group IDs"); + opts.optflag("g", "", "Display the effective group ID as a number"); + opts.optflag( + "n", + "", + "Display the name of the user or group ID for the -G, -g and -u options", + ); + opts.optflag("P", "", "Display the id as a password file entry"); + opts.optflag("p", "", "Make the output human-readable"); + opts.optflag("r", "", "Display the real ID for the -g and -u options"); + opts.optflag("u", "", "Display the effective user ID as a number"); + + let matches = opts.parse(args); + + if matches.opt_present("A") { + auditid(); + return 0; + } + + let possible_pw = if matches.free.is_empty() { + None + } else { + match Passwd::locate(matches.free[0].as_str()) { + Ok(p) => Some(p), + Err(_) => crash!(1, "No such user/group: {}", matches.free[0]), + } + }; + + let nflag = matches.opt_present("n"); + let uflag = matches.opt_present("u"); + let gflag = matches.opt_present("g"); + let rflag = matches.opt_present("r"); + + if gflag { + let id = possible_pw + .map(|p| p.gid()) + .unwrap_or(if rflag { getgid() } else { getegid() }); + println!( + "{}", + if nflag { + entries::gid2grp(id).unwrap_or(id.to_string()) + } else { + id.to_string() + } + ); + return 0; + } + + if uflag { + let id = possible_pw + .map(|p| p.uid()) + .unwrap_or(if rflag { getuid() } else { geteuid() }); + println!( + "{}", + if nflag { + entries::uid2usr(id).unwrap_or(id.to_string()) + } else { + id.to_string() + } + ); + return 0; + } + + if matches.opt_present("G") { + println!( + "{}", + if nflag { + possible_pw + .map(|p| p.belongs_to()) + .unwrap_or(entries::get_groups().unwrap()) + .iter() + .map(|&id| entries::gid2grp(id).unwrap()) + .collect::>() + .join(" ") + } else { + possible_pw + .map(|p| p.belongs_to()) + .unwrap_or(entries::get_groups().unwrap()) + .iter() + .map(|&id| id.to_string()) + .collect::>() + .join(" ") + } + ); + return 0; + } + + if matches.opt_present("P") { + pline(possible_pw.map(|v| v.uid())); + return 0; + }; + + if matches.opt_present("p") { + pretty(possible_pw); + return 0; + } + + if possible_pw.is_some() { + id_print(possible_pw, false, false) + } else { + id_print(possible_pw, true, true) + } + + 0 +} + +fn pretty(possible_pw: Option) { + if let Some(p) = possible_pw { + print!("uid\t{}\ngroups\t", p.name()); + println!( + "{}", + p.belongs_to() + .iter() + .map(|&gr| entries::gid2grp(gr).unwrap()) + .collect::>() + .join(" ") + ); + } else { + let login = cstr2cow!(getlogin() as *const _); + let rid = getuid(); + if let Ok(p) = Passwd::locate(rid) { + if login == p.name() { + println!("login\t{}", login); + } + println!("uid\t{}", p.name()); + } else { + println!("uid\t{}", rid); + } + + let eid = getegid(); + if eid == rid { + if let Ok(p) = Passwd::locate(eid) { + println!("euid\t{}", p.name()); + } else { + println!("euid\t{}", eid); + } + } + + let rid = getgid(); + if rid != eid { + if let Ok(g) = Group::locate(rid) { + println!("euid\t{}", g.name()); + } else { + println!("euid\t{}", rid); + } + } + + println!( + "groups\t{}", + entries::get_groups() + .unwrap() + .iter() + .map(|&gr| entries::gid2grp(gr).unwrap()) + .collect::>() + .join(" ") + ); + } +} + +#[cfg(any(target_os = "macos", target_os = "freebsd"))] +fn pline(possible_uid: Option) { + let uid = possible_uid.unwrap_or(getuid()); + let pw = Passwd::locate(uid).unwrap(); + + println!( + "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}", + pw.name(), + pw.user_passwd(), + pw.uid(), + pw.gid(), + pw.user_access_class(), + pw.passwd_change_time(), + pw.expiration(), + pw.user_info(), + pw.user_dir(), + pw.user_shell() + ); +} + +#[cfg(target_os = "linux")] +fn pline(possible_uid: Option) { + let uid = possible_uid.unwrap_or(getuid()); + let pw = Passwd::locate(uid).unwrap(); + + println!( + "{}:{}:{}:{}:{}:{}:{}", + pw.name(), + pw.user_passwd(), + pw.uid(), + pw.gid(), + pw.user_info(), + pw.user_dir(), + pw.user_shell() + ); +} + +#[cfg(target_os = "linux")] +fn auditid() {} + +#[cfg(not(target_os = "linux"))] +fn auditid() { + let mut auditinfo: audit::c_auditinfo_addr_t = unsafe { audit::uninitialized() }; + let address = &mut auditinfo as *mut audit::c_auditinfo_addr_t; + if unsafe { audit::getaudit(address) } < 0 { + println!("couldn't retrieve information"); + return; + } + + println!("auid={}", auditinfo.ai_auid); + println!("mask.success=0x{:x}", auditinfo.ai_mask.am_success); + println!("mask.failure=0x{:x}", auditinfo.ai_mask.am_failure); + println!("termid.port=0x{:x}", auditinfo.ai_termid.port); + println!("asid={}", auditinfo.ai_asid); +} + +fn id_print(possible_pw: Option, p_euid: bool, p_egid: bool) { + let (uid, gid) = possible_pw + .map(|p| (p.uid(), p.gid())) + .unwrap_or((getuid(), getgid())); + + let groups = Passwd::locate(uid).unwrap().belongs_to(); + + print!("uid={}({})", uid, entries::uid2usr(uid).unwrap()); + print!(" gid={}({})", gid, entries::gid2grp(gid).unwrap()); + + let euid = geteuid(); + if p_euid && (euid != uid) { + print!(" euid={}({})", euid, entries::uid2usr(euid).unwrap()); + } + + let egid = getegid(); + if p_egid && (egid != gid) { + print!(" egid={}({})", euid, entries::gid2grp(egid).unwrap()); + } + + println!( + " groups={}", + groups + .iter() + .map(|&gr| format!("{}({})", gr, entries::gid2grp(gr).unwrap())) + .collect::>() + .join(",") + ); +} diff --git a/coreutils/src/install/Cargo.toml b/coreutils/src/install/Cargo.toml new file mode 100644 index 000000000..d2f8caac0 --- /dev/null +++ b/coreutils/src/install/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "install" +version = "0.0.1" +authors = ["Ben Eills "] +build = "../../mkmain.rs" + +[lib] +name = "uu_install" +path = "install.rs" + +[dependencies] +getopts = "0.2.18" +libc = ">= 0.2" +uucore = "0.0.1" + +[dev-dependencies] +time = "0.1.40" + +[[bin]] +name = "install" +path = "../../uumain.rs" diff --git a/coreutils/src/install/install.rs b/coreutils/src/install/install.rs new file mode 100644 index 000000000..4d7aeb611 --- /dev/null +++ b/coreutils/src/install/install.rs @@ -0,0 +1,416 @@ +#![crate_name = "uu_install"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Ben Eills + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +extern crate getopts; +extern crate libc; + +mod mode; + +#[macro_use] +extern crate uucore; + +use std::fs; +use std::path::{Path, PathBuf}; +use std::result::Result; + +static NAME: &str = "install"; +static SUMMARY: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing + DIRECTORY, while setting permission modes and owner/group"; +static LONG_HELP: &str = ""; + +const DEFAULT_MODE: u32 = 755; + +#[allow(dead_code)] +pub struct Behaviour { + main_function: MainFunction, + specified_mode: Option, + suffix: String, + verbose: bool, +} + +#[derive(Clone, Eq, PartialEq)] +pub enum MainFunction { + /// Create directories + Directory, + /// Install files to locations (primary functionality) + Standard, +} + +impl Behaviour { + /// Determine the mode for chmod after copy. + pub fn mode(&self) -> u32 { + match self.specified_mode { + Some(x) => x, + None => DEFAULT_MODE, + } + } +} + +/// Main install utility function, called from main.rs. +/// +/// Returns a program return code. +/// +pub fn uumain(args: Vec) -> i32 { + let matches = parse_opts(args); + + if let Err(s) = check_unimplemented(&matches) { + show_error!("Unimplemented feature: {}", s); + return 2; + } + + let behaviour = match behaviour(&matches) { + Ok(x) => x, + Err(ret) => { + return ret; + } + }; + + let paths: Vec = { + fn string_to_path<'a>(s: &'a String) -> &'a Path { + Path::new(s) + }; + let to_owned = |p: &Path| p.to_owned(); + let arguments = matches.free.iter().map(string_to_path); + + arguments.map(to_owned).collect() + }; + + match behaviour.main_function { + MainFunction::Directory => directory(&paths[..], behaviour), + MainFunction::Standard => standard(&paths[..], behaviour), + } +} + +/// Build a specification of the command line. +/// +/// Returns a getopts::Options struct. +/// +fn parse_opts(args: Vec) -> getopts::Matches { + let syntax = format!( + "SOURCE DEST + {} SOURCE... DIRECTORY", + NAME + ); + new_coreopts!(&syntax, SUMMARY, LONG_HELP) + // TODO implement flag + .optflagopt("", "backup", "(unimplemented) make a backup of each existing destination\n \ + file", "CONTROL") + // TODO implement flag + .optflag("b", "", "(unimplemented) like --backup but does not accept an argument") + .optflag("c", "", "ignored") + // TODO implement flag + .optflag("C", "compare", "(unimplemented) compare each pair of source and destination\n \ + files, and in some cases, do not modify the destination at all") + .optflag("d", "directory", "treat all arguments as directory names.\n \ + create all components of the specified directories") + // TODO implement flag + .optflag("D", "", "(unimplemented) create all leading components of DEST except the\n \ + last, then copy SOURCE to DEST") + // TODO implement flag + .optflagopt("g", "group", "(unimplemented) set group ownership, instead of process'\n \ + current group", "GROUP") + .optflagopt("m", "mode", "set permission mode (as in chmod), instead\n \ + of rwxr-xr-x", "MODE") + // TODO implement flag + .optflagopt("o", "owner", "(unimplemented) set ownership (super-user only)", + "OWNER") + // TODO implement flag + .optflag("p", "preserve-timestamps", "(unimplemented) apply access/modification times\n \ + of SOURCE files to corresponding destination files") + // TODO implement flag + .optflag("s", "strip", "(unimplemented) strip symbol tables") + // TODO implement flag + .optflagopt("", "strip-program", "(unimplemented) program used to strip binaries", + "PROGRAM") + // TODO implement flag + .optopt("S", "suffix", "(unimplemented) override the usual backup suffix", "SUFFIX") + // TODO implement flag + .optopt("t", "target-directory", "(unimplemented) move all SOURCE arguments into\n \ + DIRECTORY", "DIRECTORY") + // TODO implement flag + .optflag("T", "no-target-directory", "(unimplemented) treat DEST as a normal file") + .optflag("v", "verbose", "explain what is being done") + // TODO implement flag + .optflag("P", "preserve-context", "(unimplemented) preserve security context") + // TODO implement flag + .optflagopt("Z", "context", "(unimplemented) set security context of files and\n \ + directories", "CONTEXT") + .parse(args) +} + +/// Check for unimplemented command line arguments. +/// +/// Either return the degenerate Ok value, or an Err with string. +/// +/// # Errors +/// +/// Error datum is a string of the unimplemented argument. +/// +fn check_unimplemented(matches: &getopts::Matches) -> Result<(), &str> { + if matches.opt_present("backup") { + Err("--backup") + } else if matches.opt_present("b") { + Err("-b") + } else if matches.opt_present("compare") { + Err("--compare, -C") + } else if matches.opt_present("D") { + Err("-D") + } else if matches.opt_present("group") { + Err("--group, -g") + } else if matches.opt_present("owner") { + Err("--owner, -o") + } else if matches.opt_present("preserve-timestamps") { + Err("--preserve-timestamps, -p") + } else if matches.opt_present("strip") { + Err("--strip, -s") + } else if matches.opt_present("strip-program") { + Err("--strip-program") + } else if matches.opt_present("suffix") { + Err("--suffix, -S") + } else if matches.opt_present("target-directory") { + Err("--target-directory, -t") + } else if matches.opt_present("no-target-directory") { + Err("--no-target-directory, -T") + } else if matches.opt_present("preserve-context") { + Err("--preserve-context, -P") + } else if matches.opt_present("context") { + Err("--context, -Z") + } else { + Ok(()) + } +} + +/// Determine behaviour, given command line arguments. +/// +/// If successful, returns a filled-out Behaviour struct. +/// +/// # Errors +/// +/// In event of failure, returns an integer intended as a program return code. +/// +fn behaviour(matches: &getopts::Matches) -> Result { + let main_function = if matches.opt_present("directory") { + MainFunction::Directory + } else { + MainFunction::Standard + }; + + let considering_dir: bool = MainFunction::Directory == main_function; + + let specified_mode: Option = if matches.opt_present("mode") { + match matches.opt_str("mode") { + Some(x) => match mode::parse(&x[..], considering_dir) { + Ok(y) => Some(y), + Err(err) => { + show_error!("Invalid mode string: {}", err); + return Err(1); + } + }, + None => { + show_error!( + "option '--mode' requires an argument\n \ + Try '{} --help' for more information.", + NAME + ); + return Err(1); + } + } + } else { + None + }; + + let backup_suffix = if matches.opt_present("suffix") { + match matches.opt_str("suffix") { + Some(x) => x, + None => { + show_error!( + "option '--suffix' requires an argument\n\ + Try '{} --help' for more information.", + NAME + ); + return Err(1); + } + } + } else { + "~".to_owned() + }; + + Ok(Behaviour { + main_function: main_function, + specified_mode: specified_mode, + suffix: backup_suffix, + verbose: matches.opt_present("v"), + }) +} + +/// Creates directories. +/// +/// GNU man pages describe this functionality as creating 'all components of +/// the specified directories'. +/// +/// Returns an integer intended as a program return code. +/// +fn directory(paths: &[PathBuf], b: Behaviour) -> i32 { + if paths.len() < 1 { + println!("{} with -d requires at least one argument.", NAME); + 1 + } else { + let mut all_successful = true; + + for directory in paths.iter() { + let path = directory.as_path(); + + if path.exists() { + show_info!("cannot create directory '{}': File exists", path.display()); + all_successful = false; + } + + if let Err(e) = fs::create_dir(directory) { + show_info!("{}: {}", path.display(), e.to_string()); + all_successful = false; + } + + if mode::chmod(&path, b.mode()).is_err() { + all_successful = false; + } + + if b.verbose { + show_info!("created directory '{}'", path.display()); + } + } + if all_successful { + 0 + } else { + 1 + } + } +} + +/// Test if the path is a a new file path that can be +/// created immediately +fn is_new_file_path(path: &Path) -> bool { + path.is_file() || !path.exists() && path.parent().map(|p| p.is_dir()).unwrap_or(true) +} + +/// Perform an install, given a list of paths and behaviour. +/// +/// Returns an integer intended as a program return code. +/// +fn standard(paths: &[PathBuf], b: Behaviour) -> i32 { + if paths.len() < 2 { + println!("{} requires at least 2 arguments.", NAME); + 1 + } else { + let sources = &paths[0..paths.len() - 1]; + let target = &paths[paths.len() - 1]; + + if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 { + copy_file_to_file(&sources[0], target, &b) + } else { + copy_files_into_dir(sources, target, &b) + } + } +} + +/// Copy some files into a directory. +/// +/// Prints verbose information and error messages. +/// Returns an integer intended as a program return code. +/// +/// # Parameters +/// +/// _files_ must all exist as non-directories. +/// _target_dir_ must be a directory. +/// +fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behaviour) -> i32 { + if !target_dir.is_dir() { + show_error!("target ‘{}’ is not a directory", target_dir.display()); + return 1; + } + + 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() + ); + + all_successful = false; + continue; + } + }; + + if copy(sourcepath, &targetpath, b).is_err() { + all_successful = false; + } + } + if all_successful { + 0 + } else { + 1 + } +} + +/// Copy a file to another file. +/// +/// Prints verbose information and error messages. +/// Returns an integer intended as a program return code. +/// +/// # Parameters +/// +/// _file_ must exist as a non-directory. +/// _target_ must be a non-directory +/// +fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behaviour) -> i32 { + if copy(file, &target, b).is_err() { + 1 + } else { + 0 + } +} + +/// Copy one file to a new location, changing metadata. +/// +/// # Parameters +/// +/// _from_ must exist as a non-directory. +/// _to_ must be a non-existent file, whose parent directory exists. +/// +/// # Errors +/// +/// If the copy system call fails, we print a verbose error and return an empty error value. +/// +fn copy(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<(), ()> { + let io_result = fs::copy(from, to); + + if let Err(err) = io_result { + show_error!( + "install: cannot install ‘{}’ to ‘{}’: {}", + from.display(), + to.display(), + err + ); + return Err(()); + } + + if mode::chmod(&to, b.mode()).is_err() { + return Err(()); + } + + if b.verbose { + show_info!("'{}' -> '{}'", from.display(), to.display()); + } + + Ok(()) +} diff --git a/coreutils/src/install/mode.rs b/coreutils/src/install/mode.rs new file mode 100644 index 000000000..90826ecc7 --- /dev/null +++ b/coreutils/src/install/mode.rs @@ -0,0 +1,47 @@ +extern crate libc; + +use std::path::Path; +use std::fs; +#[cfg(not(any(windows, target_os = "sunrise")))] +use uucore::mode; + +/// Takes a user-supplied string and tries to parse to u16 mode bitmask. +#[cfg(any(unix, target_os = "redox"))] +pub fn parse(mode_string: &str, considering_dir: bool) -> Result { + let numbers: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + + // Passing 000 as the existing permissions seems to mirror GNU behaviour. + if mode_string.contains(numbers) { + mode::parse_numeric(0, mode_string) + } else { + mode::parse_symbolic(0, mode_string, considering_dir) + } +} + +/// Takes a user-supplied string and tries to parse to u16 mode bitmask. +#[cfg(any(windows, target_os = "sunrise"))] +pub fn parse(mode_string: &str, considering_dir: bool) -> Result { + Ok(0777) +} + +/// chmod a file or directory on UNIX. +/// +/// Adapted from mkdir.rs. Handles own error printing. +/// +#[cfg(any(unix, target_os = "redox"))] +pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|err| { + show_info!("{}: chmod failed with error {}", path.display(), err); + }) +} + +/// chmod a file or directory on Windows. +/// +/// Adapted from mkdir.rs. +/// +#[cfg(any(windows, target_os = "sunrise"))] +pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> { + // chmod on Windows only sets the readonly flag, which isn't even honored on directories + Ok(()) +} diff --git a/coreutils/src/join/Cargo.toml b/coreutils/src/join/Cargo.toml new file mode 100644 index 000000000..01980d092 --- /dev/null +++ b/coreutils/src/join/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "join" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_join" +path = "join.rs" + +[dependencies] +clap = "2.32.0" +uucore = "0.0.1" + +[[bin]] +name = "join" +path = "../../uumain.rs" diff --git a/coreutils/src/join/join.rs b/coreutils/src/join/join.rs new file mode 100755 index 000000000..6d2ccb7ed --- /dev/null +++ b/coreutils/src/join/join.rs @@ -0,0 +1,724 @@ +#![crate_name = "uu_join"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Konstantin Pospelov + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate clap; + +#[macro_use] +extern crate uucore; + +use std::fs::File; +use std::io::{stdin, BufRead, BufReader, Lines, Stdin}; +use std::cmp::{min, Ordering}; +use clap::{App, Arg}; + +static NAME: &str = "join"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(Copy, Clone, PartialEq)] +enum FileNum { + None, + File1, + File2, +} + +#[derive(Copy, Clone)] +enum Sep { + Char(char), + Line, + Whitespaces, +} + +#[derive(Copy, Clone, PartialEq)] +enum CheckOrder { + Default, + Disabled, + Enabled, +} + +struct Settings { + key1: usize, + key2: usize, + print_unpaired: FileNum, + print_joined: bool, + ignore_case: bool, + separator: Sep, + autoformat: bool, + format: Vec, + empty: String, + check_order: CheckOrder, + headers: bool, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + key1: 0, + key2: 0, + print_unpaired: FileNum::None, + print_joined: true, + ignore_case: false, + separator: Sep::Whitespaces, + autoformat: false, + format: vec![], + empty: String::new(), + check_order: CheckOrder::Default, + headers: false, + } + } +} + +/// Output representation. +struct Repr<'a> { + separator: char, + format: &'a [Spec], + empty: &'a str, +} + +impl<'a> Repr<'a> { + fn new(separator: char, format: &'a [Spec], empty: &'a str) -> Repr<'a> { + Repr { + separator, + format, + empty, + } + } + + fn uses_format(&self) -> bool { + !self.format.is_empty() + } + + /// Print the field or empty filler if the field is not set. + fn print_field(&self, field: Option<&str>) { + let value = match field { + Some(field) => field, + None => self.empty, + }; + + print!("{}", value); + } + + /// Print each field except the one at the index. + fn print_fields(&self, line: &Line, index: usize, max_fields: Option) { + for i in 0..min(max_fields.unwrap_or(usize::max_value()), line.fields.len()) { + if i != index { + print!("{}{}", self.separator, line.fields[i]); + } + } + if let Some(n) = max_fields { + for _ in line.fields.len()..n { + print!("{}", self.separator) + } + } + } + + /// Print each field or the empty filler if the field is not set. + fn print_format(&self, f: F) + where + F: Fn(&Spec) -> Option<&'a str>, + { + for i in 0..self.format.len() { + if i > 0 { + print!("{}", self.separator); + } + + let field = match f(&self.format[i]) { + Some(value) => value, + None => self.empty, + }; + + print!("{}", field); + } + } +} + +/// Input processing parameters. +struct Input { + separator: Sep, + ignore_case: bool, + check_order: CheckOrder, +} + +impl Input { + fn new(separator: Sep, ignore_case: bool, check_order: CheckOrder) -> Input { + Input { + separator, + ignore_case, + check_order, + } + } + + fn compare(&self, field1: Option<&str>, field2: Option<&str>) -> Ordering { + if let (Some(field1), Some(field2)) = (field1, field2) { + if self.ignore_case { + field1.to_lowercase().cmp(&field2.to_lowercase()) + } else { + field1.cmp(field2) + } + } else { + match field1 { + Some(_) => Ordering::Greater, + None => match field2 { + Some(_) => Ordering::Less, + None => Ordering::Equal, + }, + } + } + } +} + +enum Spec { + Key, + Field(FileNum, usize), +} + +impl Spec { + fn parse(format: &str) -> Spec { + let mut chars = format.chars(); + + let file_num = match chars.next() { + Some('0') => { + // Must be all alone without a field specifier. + if let None = chars.next() { + return Spec::Key; + } + + crash!(1, "invalid field specifier: '{}'", format); + } + Some('1') => FileNum::File1, + Some('2') => FileNum::File2, + _ => crash!(1, "invalid file number in field spec: '{}'", format), + }; + + if let Some('.') = chars.next() { + return Spec::Field(file_num, parse_field_number(chars.as_str())); + } + + crash!(1, "invalid field specifier: '{}'", format); + } +} + +struct Line { + fields: Vec, +} + +impl Line { + fn new(string: String, separator: Sep) -> Line { + let fields = match separator { + Sep::Whitespaces => string.split_whitespace().map(String::from).collect(), + Sep::Char(sep) => string.split(sep).map(String::from).collect(), + Sep::Line => vec![string], + }; + + Line { fields } + } + + /// Get field at index. + fn get_field(&self, index: usize) -> Option<&str> { + if index < self.fields.len() { + Some(&self.fields[index]) + } else { + None + } + } +} + +struct State<'a> { + key: usize, + file_name: &'a str, + file_num: FileNum, + print_unpaired: bool, + lines: Lines>, + seq: Vec, + max_fields: Option, + line_num: usize, + has_failed: bool, +} + +impl<'a> State<'a> { + fn new( + file_num: FileNum, + name: &'a str, + stdin: &'a Stdin, + key: usize, + print_unpaired: FileNum, + ) -> State<'a> { + let f = if name == "-" { + Box::new(stdin.lock()) as Box + } else { + match File::open(name) { + Ok(file) => Box::new(BufReader::new(file)) as Box, + Err(err) => crash!(1, "{}: {}", name, err), + } + }; + + State { + key: key, + file_name: name, + file_num: file_num, + print_unpaired: print_unpaired == file_num, + lines: f.lines(), + seq: Vec::new(), + max_fields: None, + line_num: 0, + has_failed: false, + } + } + + /// Skip the current unpaired line. + fn skip_line(&mut self, input: &Input, repr: &Repr) { + if self.print_unpaired { + self.print_first_line(repr); + } + + self.reset_next_line(input); + } + + /// Keep reading line sequence until the key does not change, return + /// the first line whose key differs. + fn extend(&mut self, input: &Input) -> Option { + while let Some(line) = self.next_line(input) { + let diff = input.compare(self.get_current_key(), line.get_field(self.key)); + + if diff == Ordering::Equal { + self.seq.push(line); + } else { + return Some(line); + } + } + + return None; + } + + /// Print lines in the buffers as headers. + fn print_headers(&self, other: &State, repr: &Repr) { + if self.has_line() { + if other.has_line() { + self.combine(other, repr); + } else { + self.print_first_line(repr); + } + } else if other.has_line() { + other.print_first_line(repr); + } + } + + /// Combine two line sequences. + fn combine(&self, other: &State, repr: &Repr) { + let key = self.get_current_key(); + + for line1 in &self.seq { + for line2 in &other.seq { + if repr.uses_format() { + repr.print_format(|spec| match spec { + &Spec::Key => key, + &Spec::Field(file_num, field_num) => { + if file_num == self.file_num { + return line1.get_field(field_num); + } + + if file_num == other.file_num { + return line2.get_field(field_num); + } + + None + } + }); + } else { + repr.print_field(key); + repr.print_fields(&line1, self.key, self.max_fields); + repr.print_fields(&line2, other.key, other.max_fields); + } + + println!(); + } + } + } + + /// Reset with the next line. + fn reset(&mut self, next_line: Option) { + self.seq.clear(); + + if let Some(line) = next_line { + self.seq.push(line); + } + } + + fn reset_read_line(&mut self, input: &Input) { + let line = self.read_line(input.separator); + self.reset(line); + } + + fn reset_next_line(&mut self, input: &Input) { + let line = self.next_line(input); + self.reset(line); + } + + fn has_line(&self) -> bool { + !self.seq.is_empty() + } + + fn initialize(&mut self, read_sep: Sep, autoformat: bool) { + if let Some(line) = self.read_line(read_sep) { + if autoformat { + self.max_fields = Some(line.fields.len()); + } + + self.seq.push(line); + } + } + + fn finalize(&mut self, input: &Input, repr: &Repr) { + if self.has_line() && self.print_unpaired { + self.print_first_line(repr); + + while let Some(line) = self.next_line(input) { + self.print_line(&line, repr); + } + } + } + + /// Get the next line without the order check. + fn read_line(&mut self, sep: Sep) -> Option { + let value = self.lines.next()?; + self.line_num += 1; + Some(Line::new(crash_if_err!(1, value), sep)) + } + + /// Get the next line with the order check. + fn next_line(&mut self, input: &Input) -> Option { + let line = self.read_line(input.separator)?; + + if input.check_order == CheckOrder::Disabled { + return Some(line); + } + + let diff = input.compare(self.get_current_key(), line.get_field(self.key)); + + if diff == Ordering::Greater { + eprintln!("{}:{}: is not sorted", self.file_name, self.line_num); + + // This is fatal if the check is enabled. + if input.check_order == CheckOrder::Enabled { + exit!(1); + } + + self.has_failed = true; + } + + Some(line) + } + + /// Gets the key value of the lines stored in seq. + fn get_current_key(&self) -> Option<&str> { + self.seq[0].get_field(self.key) + } + + fn print_line(&self, line: &Line, repr: &Repr) { + if repr.uses_format() { + repr.print_format(|spec| match spec { + &Spec::Key => line.get_field(self.key), + &Spec::Field(file_num, field_num) => if file_num == self.file_num { + line.get_field(field_num) + } else { + None + }, + }); + } else { + repr.print_field(line.get_field(self.key)); + repr.print_fields(line, self.key, self.max_fields); + } + + println!(); + } + + fn print_first_line(&self, repr: &Repr) { + self.print_line(&self.seq[0], repr); + } +} + +pub fn uumain(args: Vec) -> i32 { + let matches = App::new(NAME) + .version(VERSION) + .about( + "For each pair of input lines with identical join fields, write a line to +standard output. The default join field is the first, delimited by blanks. + +When FILE1 or FILE2 (not both) is -, read standard input.", + ) + .help_message("display this help and exit") + .version_message("display version and exit") + .arg( + Arg::with_name("a") + .short("a") + .takes_value(true) + .possible_values(&["1", "2"]) + .value_name("FILENUM") + .help( + "also print unpairable lines from file FILENUM, where +FILENUM is 1 or 2, corresponding to FILE1 or FILE2", + ), + ) + .arg( + Arg::with_name("v") + .short("v") + .value_name("FILENUM") + .help("like -a FILENUM, but suppress joined output lines"), + ) + .arg( + Arg::with_name("e") + .short("e") + .takes_value(true) + .value_name("EMPTY") + .help("replace missing input fields with EMPTY"), + ) + .arg( + Arg::with_name("i") + .short("i") + .long("ignore-case") + .help("ignore differences in case when comparing fields"), + ) + .arg( + Arg::with_name("j") + .short("j") + .takes_value(true) + .value_name("FIELD") + .help("equivalent to '-1 FIELD -2 FIELD'"), + ) + .arg( + Arg::with_name("o") + .short("o") + .takes_value(true) + .value_name("FORMAT") + .help("obey FORMAT while constructing output line"), + ) + .arg( + Arg::with_name("t") + .short("t") + .takes_value(true) + .value_name("CHAR") + .help("use CHAR as input and output field separator"), + ) + .arg( + Arg::with_name("1") + .short("1") + .takes_value(true) + .value_name("FIELD") + .help("join on this FIELD of file 1"), + ) + .arg( + Arg::with_name("2") + .short("2") + .takes_value(true) + .value_name("FIELD") + .help("join on this FIELD of file 2"), + ) + .arg(Arg::with_name("check-order").long("check-order").help( + "check that the input is correctly sorted, \ + even if all input lines are pairable", + )) + .arg( + Arg::with_name("nocheck-order") + .long("nocheck-order") + .help("do not check that the input is correctly sorted"), + ) + .arg(Arg::with_name("header").long("header").help( + "treat the first line in each file as field headers, \ + print them without trying to pair them", + )) + .arg( + Arg::with_name("file1") + .required(true) + .value_name("FILE1") + .hidden(true), + ) + .arg( + Arg::with_name("file2") + .required(true) + .value_name("FILE2") + .hidden(true), + ) + .get_matches_from(args); + + let keys = parse_field_number_option(matches.value_of("j")); + let key1 = parse_field_number_option(matches.value_of("1")); + let key2 = parse_field_number_option(matches.value_of("2")); + + let mut settings: Settings = Default::default(); + + if let Some(value) = matches.value_of("v") { + settings.print_unpaired = parse_file_number(value); + settings.print_joined = false; + } else if let Some(value) = matches.value_of("a") { + settings.print_unpaired = parse_file_number(value); + } + + settings.ignore_case = matches.is_present("i"); + settings.key1 = get_field_number(keys, key1); + settings.key2 = get_field_number(keys, key2); + + if let Some(value) = matches.value_of("t") { + settings.separator = match value.len() { + 0 => Sep::Line, + 1 => Sep::Char(value.chars().nth(0).unwrap()), + _ => crash!(1, "multi-character tab {}", value), + }; + } + + if let Some(format) = matches.value_of("o") { + if format == "auto" { + settings.autoformat = true; + } else { + settings.format = format + .split(|c| c == ' ' || c == ',' || c == '\t') + .map(Spec::parse) + .collect(); + } + } + + if let Some(empty) = matches.value_of("e") { + settings.empty = empty.to_string(); + } + + if matches.is_present("nocheck-order") { + settings.check_order = CheckOrder::Disabled; + } + + if matches.is_present("check-order") { + settings.check_order = CheckOrder::Enabled; + } + + if matches.is_present("header") { + settings.headers = true; + } + + let file1 = matches.value_of("file1").unwrap(); + let file2 = matches.value_of("file2").unwrap(); + + if file1 == "-" && file2 == "-" { + crash!(1, "both files cannot be standard input"); + } + + exec(file1, file2, &settings) +} + +fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 { + let stdin = stdin(); + + let mut state1 = State::new( + FileNum::File1, + &file1, + &stdin, + settings.key1, + settings.print_unpaired, + ); + + let mut state2 = State::new( + FileNum::File2, + &file2, + &stdin, + settings.key2, + settings.print_unpaired, + ); + + let input = Input::new( + settings.separator, + settings.ignore_case, + settings.check_order, + ); + + let repr = Repr::new( + match settings.separator { + Sep::Char(sep) => sep, + _ => ' ', + }, + &settings.format, + &settings.empty, + ); + + state1.initialize(settings.separator, settings.autoformat); + state2.initialize(settings.separator, settings.autoformat); + + if settings.headers { + state1.print_headers(&state2, &repr); + state1.reset_read_line(&input); + state2.reset_read_line(&input); + } + + while state1.has_line() && state2.has_line() { + let diff = input.compare(state1.get_current_key(), state2.get_current_key()); + + match diff { + Ordering::Less => { + state1.skip_line(&input, &repr); + } + Ordering::Greater => { + state2.skip_line(&input, &repr); + } + Ordering::Equal => { + let next_line1 = state1.extend(&input); + let next_line2 = state2.extend(&input); + + if settings.print_joined { + state1.combine(&state2, &repr); + } + + state1.reset(next_line1); + state2.reset(next_line2); + } + } + } + + state1.finalize(&input, &repr); + state2.finalize(&input, &repr); + + (state1.has_failed || state2.has_failed) as i32 +} + +/// Check that keys for both files and for a particular file are not +/// contradictory and return the key index. +fn get_field_number(keys: Option, key: Option) -> usize { + if let Some(keys) = keys { + if let Some(key) = key { + if keys != key { + // Show zero-based field numbers as one-based. + crash!(1, "incompatible join fields {}, {}", keys + 1, key + 1); + } + } + + return keys; + } + + match key { + Some(key) => key, + None => 0, + } +} + +/// Parse the specified field string as a natural number and return +/// the zero-based field number. +fn parse_field_number(value: &str) -> usize { + match value.parse::() { + Ok(result) if result > 0 => result - 1, + _ => crash!(1, "invalid field number: '{}'", value), + } +} + +fn parse_file_number(value: &str) -> FileNum { + match value { + "1" => FileNum::File1, + "2" => FileNum::File2, + value => crash!(1, "invalid file number: '{}'", value), + } +} + +fn parse_field_number_option(value: Option<&str>) -> Option { + Some(parse_field_number(value?)) +} diff --git a/coreutils/src/kill/Cargo.toml b/coreutils/src/kill/Cargo.toml new file mode 100644 index 000000000..1b1ebda7b --- /dev/null +++ b/coreutils/src/kill/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "kill" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_kill" +path = "kill.rs" + +[dependencies] +libc = "0.2.42" + +[dependencies.uucore] +version = "0.0.1" +features = ["signals"] + +[[bin]] +name = "kill" +path = "../../uumain.rs" diff --git a/coreutils/src/kill/kill.rs b/coreutils/src/kill/kill.rs new file mode 100644 index 000000000..739efb8df --- /dev/null +++ b/coreutils/src/kill/kill.rs @@ -0,0 +1,171 @@ +#![crate_name = "uu_kill"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Maciej Dziardziel + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +extern crate libc; + +#[macro_use] +extern crate uucore; + +use libc::{c_int, pid_t}; +use std::io::Error; +use uucore::signals::ALL_SIGNALS; + +static SYNTAX: &str = "[options] [...]"; +static SUMMARY: &str = ""; +static LONG_HELP: &str = ""; + +static EXIT_OK: i32 = 0; +static EXIT_ERR: i32 = 1; + +#[derive(Clone, Copy)] +pub enum Mode { + Kill, + Table, + List, +} + +pub fn uumain(args: Vec) -> i32 { + let (args, obs_signal) = handle_obsolete(args); + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP) + .optopt("s", "signal", "specify the to be sent", "SIGNAL") + .optflagopt( + "l", + "list", + "list all signal names, or convert one to a name", + "LIST", + ) + .optflag("L", "table", "list all signal names in a nice table") + .parse(args); + + let mode = if matches.opt_present("table") { + Mode::Table + } else if matches.opt_present("list") { + Mode::List + } else { + Mode::Kill + }; + + match mode { + Mode::Kill => { + return kill( + &matches + .opt_str("signal") + .unwrap_or(obs_signal.unwrap_or("9".to_owned())), + matches.free, + ) + } + Mode::Table => table(), + Mode::List => list(matches.opt_str("list")), + } + + 0 +} + +fn handle_obsolete(mut args: Vec) -> (Vec, Option) { + let mut i = 0; + while i < args.len() { + // this is safe because slice is valid when it is referenced + let slice = &args[i].clone(); + if slice.chars().next().unwrap() == '-' && slice.len() > 1 + && slice.chars().nth(1).unwrap().is_digit(10) + { + let val = &slice[1..]; + match val.parse() { + Ok(num) => { + if uucore::signals::is_signal(num) { + args.remove(i); + return (args, Some(val.to_owned())); + } + } + Err(_) => break, /* getopts will error out for us */ + } + } + i += 1; + } + (args, None) +} + +fn table() { + let mut name_width = 0; + /* Compute the maximum width of a signal name. */ + for s in &ALL_SIGNALS { + if s.name.len() > name_width { + name_width = s.name.len() + } + } + + for (idx, signal) in ALL_SIGNALS.iter().enumerate() { + print!("{0: >#2} {1: <#8}", idx + 1, signal.name); + //TODO: obtain max signal width here + + if (idx + 1) % 7 == 0 { + println!(""); + } + } +} + +fn print_signal(signal_name_or_value: &str) { + for signal in &ALL_SIGNALS { + if signal.name == signal_name_or_value + || (format!("SIG{}", signal.name)) == signal_name_or_value + { + println!("{}", signal.value); + exit!(EXIT_OK as i32) + } else if signal_name_or_value == signal.value.to_string() { + println!("{}", signal.name); + exit!(EXIT_OK as i32) + } + } + crash!(EXIT_ERR, "unknown signal name {}", signal_name_or_value) +} + +fn print_signals() { + let mut pos = 0; + for (idx, signal) in ALL_SIGNALS.iter().enumerate() { + pos += signal.name.len(); + print!("{}", signal.name); + if idx > 0 && pos > 73 { + println!(""); + pos = 0; + } else { + pos += 1; + print!(" "); + } + } +} + +fn list(arg: Option) { + match arg { + Some(ref x) => print_signal(x), + None => print_signals(), + }; +} + +fn kill(signalname: &str, pids: std::vec::Vec) -> i32 { + let mut status = 0; + let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname); + let signal_value = match optional_signal_value { + Some(x) => x, + None => crash!(EXIT_ERR, "unknown signal name {}", signalname), + }; + for pid in &pids { + match pid.parse::() { + Ok(x) => { + if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 { + show_error!("{}", Error::last_os_error()); + status = 1; + } + } + Err(e) => crash!(EXIT_ERR, "failed to parse argument {}: {}", pid, e), + }; + } + status +} diff --git a/coreutils/src/link/Cargo.toml b/coreutils/src/link/Cargo.toml new file mode 100644 index 000000000..ff523cfdc --- /dev/null +++ b/coreutils/src/link/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "link" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_link" +path = "link.rs" + +[dependencies] +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "link" +path = "../../uumain.rs" diff --git a/coreutils/src/link/link.rs b/coreutils/src/link/link.rs new file mode 100644 index 000000000..4ff654f4b --- /dev/null +++ b/coreutils/src/link/link.rs @@ -0,0 +1,46 @@ +#![crate_name = "uu_link"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Michael Gehring + * + * 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 std::fs::hard_link; +use std::path::Path; +use std::io::Error; + +static SYNTAX: &str = "[OPTIONS] FILE1 FILE2"; +static SUMMARY: &str = "Create a link named FILE2 to FILE1"; +static LONG_HELP: &str = ""; + +pub fn normalize_error_message(e: Error) -> String { + match e.raw_os_error() { + Some(2) => String::from("No such file or directory (os error 2)"), + _ => format!("{}", e), + } +} + +pub fn uumain(args: Vec) -> i32 { + let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP).parse(args); + if matches.free.len() != 2 { + crash!(1, "{}", msg_wrong_number_of_arguments!(2)); + } + + let old = Path::new(&matches.free[0]); + let new = Path::new(&matches.free[1]); + + match hard_link(old, new) { + Ok(_) => 0, + Err(err) => { + show_error!("{}", normalize_error_message(err)); + 1 + } + } +} diff --git a/coreutils/src/ln/Cargo.toml b/coreutils/src/ln/Cargo.toml new file mode 100644 index 000000000..c9fe57a13 --- /dev/null +++ b/coreutils/src/ln/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ln" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_ln" +path = "ln.rs" + +[dependencies] +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "ln" +path = "../../uumain.rs" diff --git a/coreutils/src/ln/ln.rs b/coreutils/src/ln/ln.rs new file mode 100644 index 000000000..263c4de10 --- /dev/null +++ b/coreutils/src/ln/ln.rs @@ -0,0 +1,354 @@ +#![crate_name = "uu_ln"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Joseph Crail + * + * 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 std::fs; +use std::io::{stdin, Result}; +#[cfg(any(unix, target_os = "redox"))] +use std::os::unix::fs::symlink; +#[cfg(windows)] +use std::os::windows::fs::{symlink_dir, symlink_file}; +use std::path::{Path, PathBuf}; + +static NAME: &str = "ln"; +static SUMMARY: &str = ""; +static LONG_HELP: &str = " + In the 1st form, create a link to TARGET with the name LINK_NAME. + In the 2nd form, create a link to TARGET in the current directory. + In the 3rd and 4th forms, create links to each TARGET in DIRECTORY. + Create hard links by default, symbolic links with --symbolic. + By default, each destination (name of new link) should not already exist. + When creating hard links, each TARGET must exist. Symbolic links + can hold arbitrary text; if later resolved, a relative link is + interpreted in relation to its parent directory. +"; + +pub struct Settings { + overwrite: OverwriteMode, + backup: BackupMode, + suffix: String, + symbolic: bool, + target_dir: Option, + no_target_dir: bool, + verbose: bool, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum OverwriteMode { + NoClobber, + Interactive, + Force, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum BackupMode { + NoBackup, + SimpleBackup, + NumberedBackup, + ExistingBackup, +} + +pub fn uumain(args: Vec) -> i32 { + let syntax = format!( + "[OPTION]... [-T] TARGET LINK_NAME (1st form) + {0} [OPTION]... TARGET (2nd form) + {0} [OPTION]... TARGET... DIRECTORY (3rd form) + {0} [OPTION]... -t DIRECTORY TARGET... (4th form)", + NAME + ); + let matches = new_coreopts!(&syntax, SUMMARY, LONG_HELP) + .optflag("b", "", "make a backup of each file that would otherwise be overwritten or \ + removed") + .optflagopt("", "backup", "make a backup of each file that would otherwise be overwritten \ + or removed", "METHOD") + // TODO: opts.optflag("d", "directory", "allow users with appropriate privileges to attempt \ + // to make hard links to directories"); + .optflag("f", "force", "remove existing destination files") + .optflag("i", "interactive", "prompt whether to remove existing destination files") + // TODO: opts.optflag("L", "logical", "dereference TARGETs that are symbolic links"); + // TODO: opts.optflag("n", "no-dereference", "treat LINK_NAME as a normal file if it is a \ + // symbolic link to a directory"); + // TODO: opts.optflag("P", "physical", "make hard links directly to symbolic links"); + // TODO: opts.optflag("r", "relative", "create symbolic links relative to link location"); + .optflag("s", "symbolic", "make symbolic links instead of hard links") + .optopt("S", "suffix", "override the usual backup suffix", "SUFFIX") + .optopt("t", "target-directory", "specify the DIRECTORY in which to create the links", + "DIRECTORY") + .optflag("T", "no-target-directory", "treat LINK_NAME as a normal file always") + .optflag("v", "verbose", "print name of each linked file") + .parse(args); + + let overwrite_mode = if matches.opt_present("force") { + OverwriteMode::Force + } else if matches.opt_present("interactive") { + OverwriteMode::Interactive + } else { + OverwriteMode::NoClobber + }; + + let backup_mode = if matches.opt_present("b") { + BackupMode::ExistingBackup + } else if matches.opt_present("backup") { + match matches.opt_str("backup") { + None => BackupMode::ExistingBackup, + Some(mode) => match &mode[..] { + "simple" | "never" => BackupMode::SimpleBackup, + "numbered" | "t" => BackupMode::NumberedBackup, + "existing" | "nil" => BackupMode::ExistingBackup, + "none" | "off" => BackupMode::NoBackup, + x => { + show_error!( + "invalid argument '{}' for 'backup method'\n\ + Try '{} --help' for more information.", + x, + NAME + ); + return 1; + } + }, + } + } else { + BackupMode::NoBackup + }; + + let backup_suffix = if matches.opt_present("suffix") { + match matches.opt_str("suffix") { + Some(x) => x, + None => { + show_error!( + "option '--suffix' requires an argument\n\ + Try '{} --help' for more information.", + NAME + ); + return 1; + } + } + } else { + "~".to_owned() + }; + + if matches.opt_present("T") && matches.opt_present("t") { + show_error!("cannot combine --target-directory (-t) and --no-target-directory (-T)"); + return 1; + } + + let settings = Settings { + overwrite: overwrite_mode, + backup: backup_mode, + suffix: backup_suffix, + symbolic: matches.opt_present("s"), + target_dir: matches.opt_str("t"), + no_target_dir: matches.opt_present("T"), + verbose: matches.opt_present("v"), + }; + + let string_to_path = |s: &String| PathBuf::from(s); + let paths: Vec = matches.free.iter().map(string_to_path).collect(); + + exec(&paths[..], &settings) +} + +fn exec(files: &[PathBuf], settings: &Settings) -> i32 { + if files.len() == 0 { + show_error!( + "missing file operand\nTry '{} --help' for more information.", + NAME + ); + return 1; + } + + // Handle cases where we create links in a directory first. + if let Some(ref name) = settings.target_dir { + // 4th form: a directory is specified by -t. + return link_files_in_dir(files, &PathBuf::from(name), &settings); + } + if !settings.no_target_dir { + if files.len() == 1 { + // 2nd form: the target directory is the current directory. + return link_files_in_dir(files, &PathBuf::from("."), &settings); + } + let last_file = &PathBuf::from(files.last().unwrap()); + if files.len() > 2 || last_file.is_dir() { + // 3rd form: create links in the last argument. + return link_files_in_dir(&files[0..files.len() - 1], last_file, &settings); + } + } + + // 1st form. Now there should be only two operands, but if -T is + // specified we may have a wrong number of operands. + if files.len() == 1 { + show_error!( + "missing destination file operand after '{}'", + files[0].to_string_lossy() + ); + return 1; + } + if files.len() > 2 { + show_error!( + "extra operand '{}'\nTry '{} --help' for more information.", + files[2].display(), + NAME + ); + return 1; + } + assert!(files.len() != 0); + + match link(&files[0], &files[1], settings) { + Ok(_) => 0, + Err(e) => { + show_error!("{}", e); + 1 + } + } +} + +fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Settings) -> i32 { + if !target_dir.is_dir() { + show_error!("target '{}' is not a directory", target_dir.display()); + return 1; + } + + let mut all_successful = true; + for srcpath in files.iter() { + let targetpath = match srcpath.as_os_str().to_str() { + Some(name) => { + match Path::new(name).file_name() { + Some(basename) => target_dir.join(basename), + // This can be None only for "." or "..". Trying + // to create a link with such name will fail with + // EEXIST, which agrees with the bahavior of GNU + // coreutils. + None => target_dir.join(name), + } + } + None => { + show_error!( + "cannot stat '{}': No such file or directory", + srcpath.display() + ); + all_successful = false; + continue; + } + }; + + if let Err(e) = link(srcpath, &targetpath, settings) { + show_error!( + "cannot link '{}' to '{}': {}", + targetpath.display(), + srcpath.display(), + e + ); + all_successful = false; + } + } + if all_successful { + 0 + } else { + 1 + } +} + +fn link(src: &PathBuf, dst: &PathBuf, settings: &Settings) -> Result<()> { + let mut backup_path = None; + + if is_symlink(dst) || dst.exists() { + match settings.overwrite { + OverwriteMode::NoClobber => {} + OverwriteMode::Interactive => { + print!("{}: overwrite '{}'? ", NAME, dst.display()); + if !read_yes() { + return Ok(()); + } + fs::remove_file(dst)? + } + OverwriteMode::Force => fs::remove_file(dst)?, + }; + + backup_path = match settings.backup { + BackupMode::NoBackup => None, + BackupMode::SimpleBackup => Some(simple_backup_path(dst, &settings.suffix)), + BackupMode::NumberedBackup => Some(numbered_backup_path(dst)), + BackupMode::ExistingBackup => Some(existing_backup_path(dst, &settings.suffix)), + }; + if let Some(ref p) = backup_path { + fs::rename(dst, p)?; + } + } + + if settings.symbolic { + symlink(src, dst)?; + } else { + fs::hard_link(src, dst)?; + } + + if settings.verbose { + print!("'{}' -> '{}'", dst.display(), src.display()); + match backup_path { + Some(path) => println!(" (backup: '{}')", path.display()), + None => println!(""), + } + } + Ok(()) +} + +fn read_yes() -> bool { + let mut s = String::new(); + match stdin().read_line(&mut s) { + Ok(_) => match s.char_indices().nth(0) { + Some((_, x)) => x == 'y' || x == 'Y', + _ => false, + }, + _ => false, + } +} + +fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { + let mut p = path.as_os_str().to_str().unwrap().to_owned(); + p.push_str(suffix); + PathBuf::from(p) +} + +fn numbered_backup_path(path: &PathBuf) -> PathBuf { + let mut i: u64 = 1; + loop { + let new_path = simple_backup_path(path, &format!(".~{}~", i)); + if !new_path.exists() { + return new_path; + } + i += 1; + } +} + +fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { + let test_path = simple_backup_path(path, &".~1~".to_owned()); + if test_path.exists() { + return numbered_backup_path(path); + } + simple_backup_path(path, suffix) +} + +#[cfg(windows)] +pub fn symlink>(src: P, dst: P) -> Result<()> { + if src.as_ref().is_dir() { + symlink_dir(src, dst) + } else { + symlink_file(src, dst) + } +} + +pub fn is_symlink>(path: P) -> bool { + match fs::symlink_metadata(path) { + Ok(m) => m.file_type().is_symlink(), + Err(_) => false, + } +} diff --git a/coreutils/src/logname/Cargo.toml b/coreutils/src/logname/Cargo.toml new file mode 100644 index 000000000..f81e10fa3 --- /dev/null +++ b/coreutils/src/logname/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "logname" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_logname" +path = "logname.rs" + +[dependencies] +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "logname" +path = "../../uumain.rs" diff --git a/coreutils/src/logname/logname.rs b/coreutils/src/logname/logname.rs new file mode 100644 index 000000000..f0551c021 --- /dev/null +++ b/coreutils/src/logname/logname.rs @@ -0,0 +1,54 @@ +#![crate_name = "uu_logname"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Benoit Benedetti + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* last synced with: logname (GNU coreutils) 8.22 */ + +extern crate libc; + +#[macro_use] +extern crate uucore; + +use std::ffi::CStr; + +extern "C" { + // POSIX requires using getlogin (or equivalent code) + pub fn getlogin() -> *const libc::c_char; +} + +fn get_userlogin() -> Option { + unsafe { + let login: *const libc::c_char = getlogin(); + if login.is_null() { + None + } else { + Some(String::from_utf8_lossy(CStr::from_ptr(login).to_bytes()).to_string()) + } + } +} + +static SYNTAX: &str = ""; +static SUMMARY: &str = "Print user's login name"; +static LONG_HELP: &str = ""; + +pub fn uumain(args: Vec) -> i32 { + new_coreopts!(SYNTAX, SUMMARY, LONG_HELP).parse(args); + + exec(); + + 0 +} + +fn exec() { + match get_userlogin() { + Some(userlogin) => println!("{}", userlogin), + None => show_error!("no login name"), + } +} diff --git a/coreutils/src/ls/Cargo.toml b/coreutils/src/ls/Cargo.toml new file mode 100644 index 000000000..feafa2c8d --- /dev/null +++ b/coreutils/src/ls/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "ls" +version = "0.0.1" +authors = ["Jeremiah Peschka "] +build = "../../mkmain.rs" + +[lib] +name = "uu_ls" +path = "ls.rs" + +[dependencies] +getopts = "0.2.18" +number_prefix = "0.2.8" +term_grid = "0.1.5" +termsize = "0.1.6" +time = "0.1.40" +lazy_static = "1.0.1" +unicode-width = "0.1.5" + +[dependencies.uucore] +version = "0.0.1" +features = ["entries", "fs"] + +[[bin]] +name = "ls" +path = "../../uumain.rs" diff --git a/coreutils/src/ls/ls.rs b/coreutils/src/ls/ls.rs new file mode 100644 index 000000000..b91739319 --- /dev/null +++ b/coreutils/src/ls/ls.rs @@ -0,0 +1,681 @@ +#![crate_name = "uu_ls"] + +// This file is part of the uutils coreutils package. +// +// (c) Jeremiah Peschka +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +extern crate getopts; +extern crate term_grid; +extern crate termsize; +extern crate time; +extern crate unicode_width; +extern crate number_prefix; +use number_prefix::{Standalone, Prefixed, decimal_prefix}; +use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; +use time::{strftime, Timespec}; +use std::time::SystemTime; + +#[cfg(unix)] +#[macro_use] +extern crate lazy_static; + +#[macro_use] +extern crate uucore; +#[cfg(unix)] +use uucore::libc::{mode_t, S_ISGID, S_ISUID, S_ISVTX, S_IWOTH, + S_IXGRP, S_IXOTH, S_IXUSR}; + +use std::fs; +use std::fs::{DirEntry, FileType, Metadata}; +use std::path::{Path, PathBuf}; +use std::cmp::Reverse; +#[cfg(unix)] +use std::collections::HashMap; + +#[cfg(any(unix, target_os = "redox"))] +use std::os::unix::fs::MetadataExt; +#[cfg(unix)] +use std::os::unix::fs::FileTypeExt; +#[cfg(unix)] +use unicode_width::UnicodeWidthStr; + +#[cfg(windows)] +use std::os::windows::fs::MetadataExt; + +static NAME: &str = "ls"; +static SUMMARY: &str = ""; +static LONG_HELP: &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 '.' +"; + +#[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:"; + +#[cfg(unix)] +lazy_static! { + static ref LS_COLORS: String = std::env::var("LS_COLORS").unwrap_or(DEFAULT_COLORS.to_string()); + static ref COLOR_MAP: HashMap<&'static str, &'static str> = { + let codes = LS_COLORS.split(":"); + let mut map = HashMap::new(); + for c in codes { + let p: Vec<_> = c.split("=").collect(); + if p.len() == 2 { + map.insert(p[0], p[1]); + } + } + map + }; + static ref RESET_CODE: &'static str = COLOR_MAP.get("rs").unwrap_or(&"0"); + static ref LEFT_CODE: &'static str = COLOR_MAP.get("lc").unwrap_or(&"\x1b["); + static ref RIGHT_CODE: &'static str = COLOR_MAP.get("rc").unwrap_or(&"m"); + static ref END_CODE: &'static str = COLOR_MAP.get("ec").unwrap_or(&""); +} + +pub fn uumain(args: Vec) -> i32 { + let syntax = format!( + "[OPTION]... DIRECTORY + {0} [OPTION]... [FILE]...", + NAME + ); + let matches = new_coreopts!(&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 '.').", + ) + .optflag( + "A", + "almost-all", + "In a directory, do not ignore all file names that start with '.', only ignore \ + '.' and '..'.", + ) + .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.", + ) + .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.", + ) + .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.", + ) + .optflag( + "h", + "human-readable", + "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.", + ) + .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.", + ) + .optflag( + "R", + "recursive", + "List the contents of all directories recursively.", + ) + .optflag("S", "", "Sort by file size, largest first.") + .optflag( + "t", + "", + "Sort by modification time (the 'mtime' in the inode), newest first.", + ) + .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.", + ) + .optflag("", "color", "Color output based on file type.") + .parse(args); + + list(matches); + 0 +} + +fn list(options: getopts::Matches) { + let locs: Vec = if options.free.is_empty() { + vec![String::from(".")] + } else { + options.free.iter().cloned().collect() + }; + + let mut files = Vec::::new(); + let mut dirs = Vec::::new(); + for loc in locs { + let p = PathBuf::from(&loc); + let mut dir = false; + + if p.is_dir() && !options.opt_present("d") { + dir = true; + if options.opt_present("l") && !(options.opt_present("L")) { + if let Ok(md) = p.symlink_metadata() { + if md.file_type().is_symlink() && !p.ends_with("/") { + dir = false; + } + } + } + } + if dir { + dirs.push(p); + } else { + files.push(p); + } + } + sort_entries(&mut files, &options); + display_items(&files, None, &options); + + sort_entries(&mut dirs, &options); + for dir in dirs { + if options.free.len() > 1 { + println!("\n{}:", dir.to_string_lossy()); + } + enter_directory(&dir, &options); + } +} + +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") { + entries.sort_by_key(|k| { + Reverse(get_metadata(k, options).and_then(|md| md.created()).unwrap_or(SystemTime::UNIX_EPOCH)) + }); + } 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.opt_present("S") { + entries.sort_by_key(|k| get_metadata(k, options).map(|md| md.len()).unwrap_or(0)); + reverse = !reverse; + } else if !options.opt_present("U") { + entries.sort(); + } + + if reverse { + entries.reverse(); + } +} + +fn max(lhs: usize, rhs: usize) -> usize { + if lhs > rhs { + lhs + } else { + rhs + } +} + +fn should_display(entry: &DirEntry, options: &getopts::Matches) -> bool { + let ffi_name = entry.file_name(); + let name = ffi_name.to_string_lossy(); + if !options.opt_present("a") && !options.opt_present("A") { + if name.starts_with('.') { + return false; + } + } + if options.opt_present("B") && name.ends_with('~') { + return false; + } + return true; +} + +fn enter_directory(dir: &PathBuf, options: &getopts::Matches) { + let mut entries = + safe_unwrap!(fs::read_dir(dir).and_then(|e| e.collect::, _>>())); + + entries.retain(|e| should_display(e, options)); + + let mut entries: Vec<_> = entries.iter().map(DirEntry::path).collect(); + sort_entries(&mut entries, options); + + if options.opt_present("a") { + let mut display_entries = entries.clone(); + + // ".." doesn't exist on Sunrise + #[cfg(not(target_os = "sunrise"))] + display_entries.insert(0, dir.join("..")); + display_entries.insert(0, dir.join(".")); + display_items(&display_entries, Some(dir), options); + } else { + display_items(&entries, Some(dir), options); + } + + if options.opt_present("R") { + for e in entries.iter().filter(|p| p.is_dir()) { + println!("\n{}:", e.to_string_lossy()); + enter_directory(&e, options); + } + } +} + +fn get_metadata(entry: &PathBuf, options: &getopts::Matches) -> std::io::Result { + if options.opt_present("L") { + entry.metadata().or(entry.symlink_metadata()) + } else { + entry.symlink_metadata() + } +} + +fn display_dir_entry_size(entry: &PathBuf, options: &getopts::Matches) -> (usize, usize) { + if let Ok(md) = get_metadata(entry, options) { + ( + display_symlink_count(&md).len(), + display_file_size(&md, options).len(), + ) + } else { + (0, 0) + } +} + +fn pad_left(string: String, count: usize) -> String { + if count > string.len() { + let pad = count - string.len(); + let pad = String::from_utf8(vec![' ' as u8; pad]).unwrap(); + format!("{}{}", pad, string) + } else { + string + } +} + +fn display_items(items: &Vec, strip: Option<&Path>, options: &getopts::Matches) { + if options.opt_present("long") || options.opt_present("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); + max_links = max(links, max_links); + max_size = max(size, max_size); + } + for item in items { + display_item_long(item, strip, max_links, max_size, options); + } + } else { + if !options.opt_present("1") { + let names = items + .iter() + .filter_map(|i| { + let md = get_metadata(i, options); + 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)), + } + }); + + 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 md = get_metadata(i, options); + if let Ok(md) = md { + println!("{}", display_file_name(&i, strip, &md, options).contents); + } + } + } +} + +use uucore::fs::display_permissions; + +fn display_item_long( + item: &PathBuf, + strip: Option<&Path>, + max_links: usize, + max_size: usize, + options: &getopts::Matches, +) { + let md = match get_metadata(item, options) { + Err(e) => { + let filename = get_file_name(&item, strip); + show_error!("{}: {}", filename, e); + return; + } + Ok(md) => md, + }; + + println!( + "{}{}{} {} {} {} {} {} {}", + get_inode(&md, options), + 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 + ); +} + +#[cfg(unix)] +fn get_inode(metadata: &Metadata, options: &getopts::Matches) -> String { + if options.opt_present("inode") { + format!("{:8} ", metadata.ino()) + } else { + "".to_string() + } +} + +#[cfg(not(unix))] +fn get_inode(_metadata: &Metadata, _options: &getopts::Matches) -> String { + "".to_string() +} + +// Currently getpwuid is `linux` target only. If it's broken out into +// a posix-compliant attribute this can be updated... +#[cfg(unix)] +use uucore::entries; + +#[cfg(unix)] +fn display_uname(metadata: &Metadata, options: &getopts::Matches) -> String { + if options.opt_present("numeric-uid-gid") { + metadata.uid().to_string() + } else { + entries::uid2usr(metadata.uid()).unwrap_or(metadata.uid().to_string()) + } +} + +#[cfg(unix)] +fn display_group(metadata: &Metadata, options: &getopts::Matches) -> String { + if options.opt_present("numeric-uid-gid") { + metadata.gid().to_string() + } else { + entries::gid2grp(metadata.gid()).unwrap_or(metadata.gid().to_string()) + } +} + +#[cfg(not(unix))] +#[allow(unused_variables)] +fn display_uname(metadata: &Metadata, _options: &getopts::Matches) -> String { + "somebody".to_string() +} + +#[cfg(not(unix))] +#[allow(unused_variables)] +fn display_group(metadata: &Metadata, _options: &getopts::Matches) -> String { + "somegroup".to_string() +} + +#[cfg(unix)] +fn display_date(metadata: &Metadata, options: &getopts::Matches) -> String { + let secs = if options.opt_present("c") { + metadata.ctime() + } else { + metadata.mtime() + }; + 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: &getopts::Matches) -> String { + if let Ok(mtime) = metadata.modified() { + let time = time::at(Timespec::new( + mtime + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() as i64, + 0, + )); + strftime("%F %R", &time).unwrap() + } else { + "???".to_string() + } +} + +fn display_file_size(metadata: &Metadata, options: &getopts::Matches) -> String { + if options.opt_present("human-readable") { + match decimal_prefix(metadata.len() as f64) { + Standalone(bytes) => bytes.to_string(), + Prefixed(prefix, bytes) => format!("{:.2}{}", bytes, prefix).to_uppercase() + } + } else { + metadata.len().to_string() + } +} + +fn display_file_type(file_type: FileType) -> String { + if file_type.is_dir() { + "d".to_string() + } else if file_type.is_symlink() { + "l".to_string() + } else { + "-".to_string() + } +} + +fn get_file_name(name: &Path, strip: Option<&Path>) -> String { + let mut name = match strip { + Some(prefix) => name.strip_prefix(prefix).unwrap_or(name), + None => name, + }; + if name.as_os_str().len() == 0 { + name = Path::new("."); + } + name.to_string_lossy().into_owned() +} + +#[cfg(not(unix))] +fn display_file_name( + path: &Path, + strip: Option<&Path>, + metadata: &Metadata, + options: &getopts::Matches, +) -> Cell { + let mut name = get_file_name(path, strip); + + if !options.opt_present("long") { + name = get_inode(metadata, options) + &name; + } + + if options.opt_present("classify") { + let file_type = metadata.file_type(); + if file_type.is_dir() { + name.push('/'); + } else if file_type.is_symlink() { + name.push('@'); + } + } + + if options.opt_present("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(); + name.push_str(" -> "); + name.push_str(&target_name); + } + } + + name.into() +} + +#[cfg(unix)] +fn color_name(name: String, typ: &str) -> String { + let mut typ = typ; + if !COLOR_MAP.contains_key(typ) { + if typ == "or" { + typ = "ln"; + } else if typ == "mi" { + typ = "fi"; + } + }; + if let Some(code) = COLOR_MAP.get(typ) { + format!( + "{}{}{}{}{}{}{}{}", + *LEFT_CODE, code, *RIGHT_CODE, name, *END_CODE, *LEFT_CODE, *RESET_CODE, *RIGHT_CODE, + ) + } else { + name + } +} + +#[cfg(unix)] +macro_rules! has { + ($mode:expr, $perm:expr) => ( + $mode & ($perm as mode_t) != 0 + ) +} + +#[cfg(unix)] +fn display_file_name( + path: &Path, + strip: Option<&Path>, + metadata: &Metadata, + options: &getopts::Matches, +) -> Cell { + let mut name = get_file_name(path, strip); + if !options.opt_present("long") { + name = get_inode(metadata, options) + &name; + } + let mut width = UnicodeWidthStr::width(&*name); + + let color = options.opt_present("color"); + let classify = options.opt_present("classify"); + let ext; + + if color || classify { + let file_type = metadata.file_type(); + + let (code, sym) = if file_type.is_dir() { + ("di", Some('/')) + } else if file_type.is_symlink() { + if path.exists() { + ("ln", Some('@')) + } else { + ("or", Some('@')) + } + } else if file_type.is_socket() { + ("so", Some('=')) + } else if file_type.is_fifo() { + ("pi", Some('|')) + } else if file_type.is_block_device() { + ("bd", None) + } else if file_type.is_char_device() { + ("cd", None) + } else if file_type.is_file() { + let mode = metadata.mode() as mode_t; + let sym = if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) { + Some('*') + } else { + None + }; + if has!(mode, S_ISUID) { + ("su", sym) + } else if has!(mode, S_ISGID) { + ("sg", sym) + } else if has!(mode, S_ISVTX) && has!(mode, S_IWOTH) { + ("tw", sym) + } else if has!(mode, S_ISVTX) { + ("st", sym) + } else if has!(mode, S_IWOTH) { + ("ow", sym) + } else if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) { + ("ex", sym) + } else if metadata.nlink() > 1 { + ("mh", sym) + } else if let Some(e) = path.extension() { + ext = format!("*.{}", e.to_string_lossy()); + (ext.as_str(), None) + } else { + ("fi", None) + } + } else { + ("", None) + }; + + if color { + name = color_name(name, code); + } + if classify { + if let Some(s) = sym { + name.push(s); + width += 1; + } + } + } + + if options.opt_present("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" }; + let target_name = color_name(target.to_string_lossy().to_string(), code); + name.push_str(" -> "); + name.push_str(&target_name); + } + } + + Cell { + contents: name, + width: width, + } +} + +#[cfg(not(unix))] +#[allow(unused_variables)] +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") +} + +#[cfg(unix)] +fn display_symlink_count(metadata: &Metadata) -> String { + metadata.nlink().to_string() +} diff --git a/coreutils/src/mkdir/Cargo.toml b/coreutils/src/mkdir/Cargo.toml new file mode 100644 index 000000000..9bbabbe43 --- /dev/null +++ b/coreutils/src/mkdir/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mkdir" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_mkdir" +path = "mkdir.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "mkdir" +path = "../../uumain.rs" diff --git a/coreutils/src/mkdir/mkdir.rs b/coreutils/src/mkdir/mkdir.rs new file mode 100644 index 000000000..1e5ab9b94 --- /dev/null +++ b/coreutils/src/mkdir/mkdir.rs @@ -0,0 +1,150 @@ +#![crate_name = "uu_mkdir"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Nicholas Juszczak + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate libc; + +#[macro_use] +extern crate uucore; + +use std::fs; +use std::path::Path; + +static NAME: &str = "mkdir"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +/** + * Handles option parsing + */ +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + // Linux-specific options, not implemented + // opts.optflag("Z", "context", "set SELinux security context" + + // " of each created directory to CTX"), + opts.optopt("m", "mode", "set file mode", "755"); + opts.optflag("p", "parents", "make parent directories as needed"); + opts.optflag("v", "verbose", "print a message for each printed directory"); + opts.optflag("h", "help", "display this help"); + opts.optflag("V", "version", "display this version"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "Invalid options\n{}", f), + }; + + if args.len() == 1 || matches.opt_present("help") { + print_help(&opts); + return 0; + } + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + let verbose = matches.opt_present("verbose"); + let recursive = matches.opt_present("parents"); + + // Translate a ~str in octal form to u16, default to 755 + // Not tested on Windows + let mode_match = matches.opts_str(&["mode".to_owned()]); + let mode: u16 = if mode_match.is_some() { + let m = mode_match.unwrap(); + let res: Option = u16::from_str_radix(&m, 8).ok(); + if res.is_some() { + res.unwrap() + } else { + crash!(1, "no mode given"); + } + } else { + 0o755 as u16 + }; + + let dirs = matches.free; + if dirs.is_empty() { + crash!(1, "missing operand"); + } + exec(dirs, recursive, mode, verbose) +} + +fn print_help(opts: &getopts::Options) { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + print!( + "{}", + opts.usage("Create the given DIRECTORY(ies) if they do not exist") + ); +} + +/** + * Create the list of new directories + */ +fn exec(dirs: Vec, recursive: bool, mode: u16, verbose: bool) -> i32 { + let mut status = 0; + let empty = Path::new(""); + for dir in &dirs { + let path = Path::new(dir); + if !recursive { + if let Some(parent) = path.parent() { + if parent != empty && !parent.exists() { + show_info!( + "cannot create directory '{}': No such file or directory", + path.display() + ); + status = 1; + continue; + } + } + } + status |= mkdir(path, recursive, mode, verbose); + } + status +} + +/** + * Wrapper to catch errors, return 1 if failed + */ +fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> i32 { + let create_dir = if recursive { fs::create_dir_all } else { fs::create_dir }; + if let Err(e) = create_dir(path) { + show_info!("{}: {}", path.display(), e.to_string()); + return 1; + } + + if verbose { + show_info!("created directory '{}'", path.display()); + } + + #[cfg(any(unix, target_os = "redox"))] + fn chmod(path: &Path, mode: u16) -> i32 { + use fs::{Permissions, set_permissions}; + use std::os::unix::fs::{PermissionsExt}; + + let mode = Permissions::from_mode(mode as u32); + + if let Err(err) = set_permissions(path, mode) { + show_error!( + "{}: {}", + path.display(), + err + ); + return 1; + } + 0 + } + #[cfg(any(windows, target_os = "sunrise"))] + #[allow(unused_variables)] + fn chmod(path: &Path, mode: u16) -> i32 { + // chmod on Windows only sets the readonly flag, which isn't even honored on directories + 0 + } + chmod(path, mode) +} diff --git a/coreutils/src/mkfifo/Cargo.toml b/coreutils/src/mkfifo/Cargo.toml new file mode 100644 index 000000000..555799cf6 --- /dev/null +++ b/coreutils/src/mkfifo/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mkfifo" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_mkfifo" +path = "mkfifo.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "mkfifo" +path = "../../uumain.rs" diff --git a/coreutils/src/mkfifo/mkfifo.rs b/coreutils/src/mkfifo/mkfifo.rs new file mode 100644 index 000000000..df1012787 --- /dev/null +++ b/coreutils/src/mkfifo/mkfifo.rs @@ -0,0 +1,95 @@ +#![crate_name = "uu_mkfifo"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Michael Gehring + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate libc; + +#[macro_use] +extern crate uucore; + +use libc::mkfifo; +use std::ffi::CString; +use std::io::Error; + +static NAME: &str = "mkfifo"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optopt( + "m", + "mode", + "file permissions for the fifo", + "(default 0666)", + ); + 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(err) => panic!("{}", err), + }; + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + if matches.opt_present("help") || matches.free.is_empty() { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTIONS] NAME... + +Create a FIFO with the given name.", + NAME, VERSION + ); + + print!("{}", opts.usage(&msg)); + if matches.free.is_empty() { + return 1; + } + return 0; + } + + let mode = match matches.opt_str("m") { + Some(m) => match usize::from_str_radix(&m, 8) { + Ok(m) => m, + Err(e) => { + show_error!("invalid mode: {}", e); + return 1; + } + }, + None => 0o666, + }; + + let mut exit_status = 0; + for f in &matches.free { + let err = unsafe { + mkfifo( + CString::new(f.as_bytes()).unwrap().as_ptr(), + mode as libc::mode_t, + ) + }; + if err == -1 { + show_error!( + "creating '{}': {}", + f, + Error::last_os_error().raw_os_error().unwrap() + ); + exit_status = 1; + } + } + + exit_status +} diff --git a/coreutils/src/mknod/Cargo.toml b/coreutils/src/mknod/Cargo.toml new file mode 100644 index 000000000..5d45c7621 --- /dev/null +++ b/coreutils/src/mknod/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mknod" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_mknod" +path = "mknod.rs" + +[dependencies] +getopts = "0.2.18" +libc = "^0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "mknod" +path = "../../uumain.rs" diff --git a/coreutils/src/mknod/mknod.rs b/coreutils/src/mknod/mknod.rs new file mode 100644 index 000000000..0efd7cc92 --- /dev/null +++ b/coreutils/src/mknod/mknod.rs @@ -0,0 +1,200 @@ +#![crate_name = "uu_mknod"] + +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// + +extern crate getopts; +extern crate libc; + +mod parsemode; + +#[macro_use] +extern crate uucore; + +use libc::{dev_t, mode_t}; +use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; + +use getopts::Options; + +use std::ffi::CString; + +static NAME: &str = "mknod"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + +#[inline(always)] +fn makedev(maj: u64, min: u64) -> dev_t { + // pick up from + ((min & 0xff) | ((maj & 0xfff) << 8) | (((min & !0xff)) << 12) | (((maj & !0xfff)) << 32)) + as dev_t +} + +#[cfg(windows)] +fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { + panic!("Unsupported for windows platform") +} + +#[cfg(unix)] +fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { + unsafe { libc::mknod(path.as_ptr(), mode, dev) } +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + // Linux-specific options, not implemented + // opts.optflag("Z", "", "set the SELinux security context to default type"); + // opts.optopt("", "context", "like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX"); + opts.optopt( + "m", + "mode", + "set file permission bits to MODE, not a=rw - umask", + "MODE", + ); + + opts.optflag("", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "{}\nTry '{} --help' for more information.", f, NAME), + }; + + if matches.opt_present("help") { + println!( + "Usage: {0} [OPTION]... NAME TYPE [MAJOR MINOR] + +Mandatory arguments to long options are mandatory for short options too. + -m, --mode=MODE set file permission bits to MODE, not a=rw - umask + --help display this help and exit + --version output version information and exit + +Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they +must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X, +it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal; +otherwise, as decimal. TYPE may be: + + b create a block (buffered) special file + c, u create a character (unbuffered) special file + p create a FIFO + +NOTE: your shell may have its own version of mknod, which usually supersedes +the version described here. Please refer to your shell's documentation +for details about the options it supports.", + NAME + ); + return 0; + } + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + let mut last_umask: mode_t = 0; + let mut newmode: mode_t = MODE_RW_UGO; + if matches.opt_present("mode") { + match parsemode::parse_mode(matches.opt_str("mode")) { + Ok(parsed) => { + if parsed > 0o777 { + show_info!("mode must specify only file permission bits"); + return 1; + } + newmode = parsed; + } + Err(e) => { + show_info!("{}", e); + return 1; + } + } + unsafe { + last_umask = libc::umask(0); + } + } + + let mut ret = 0i32; + match matches.free.len() { + 0 => disp_err!("missing operand"), + 1 => disp_err!("missing operand after ‘{}’", matches.free[0]), + _ => { + let args = &matches.free; + let c_str = CString::new(args[0].as_str()).expect("Failed to convert to CString"); + + // Only check the first character, to allow mnemonic usage like + // 'mknod /dev/rst0 character 18 0'. + let ch = args[1] + .chars() + .nth(0) + .expect("Failed to get the first char"); + + if ch == 'p' { + if args.len() > 2 { + show_info!("{}: extra operand ‘{}’", NAME, args[2]); + if args.len() == 4 { + eprintln!("Fifos do not have major and minor device numbers."); + } + eprintln!("Try '{} --help' for more information.", NAME); + return 1; + } + + ret = _makenod(c_str, S_IFIFO | newmode, 0); + } else { + if args.len() < 4 { + show_info!("missing operand after ‘{}’", args[args.len() - 1]); + if args.len() == 2 { + eprintln!("Special files require major and minor device numbers."); + } + eprintln!("Try '{} --help' for more information.", NAME); + return 1; + } else if args.len() > 4 { + disp_err!("extra operand ‘{}’", args[4]); + return 1; + } else if !"bcu".contains(ch) { + disp_err!("invalid device type ‘{}’", args[1]); + return 1; + } + + let maj = args[2].parse::(); + let min = args[3].parse::(); + if maj.is_err() { + show_info!("invalid major device number ‘{}’", args[2]); + return 1; + } else if min.is_err() { + show_info!("invalid minor device number ‘{}’", args[3]); + return 1; + } + + let (maj, min) = (maj.unwrap(), min.unwrap()); + let dev = makedev(maj, min); + if ch == 'b' { + // block special file + ret = _makenod(c_str, S_IFBLK | newmode, dev); + } else { + // char special file + ret = _makenod(c_str, S_IFCHR | newmode, dev); + } + } + } + } + + if last_umask != 0 { + unsafe { + libc::umask(last_umask); + } + } + if ret == -1 { + let c_str = CString::new(format!("{}: {}", NAME, matches.free[0]).as_str()) + .expect("Failed to convert to CString"); + unsafe { + libc::perror(c_str.as_ptr()); + } + } + + ret +} diff --git a/coreutils/src/mknod/parsemode.rs b/coreutils/src/mknod/parsemode.rs new file mode 100644 index 000000000..688b3d5c7 --- /dev/null +++ b/coreutils/src/mknod/parsemode.rs @@ -0,0 +1,35 @@ +extern crate libc; +use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; + +use uucore::mode; + +pub fn parse_mode(mode: Option) -> Result { + let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + if let Some(mode) = mode { + let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + let result = if mode.contains(arr) { + mode::parse_numeric(fperm as u32, mode.as_str()) + } else { + mode::parse_symbolic(fperm as u32, mode.as_str(), true) + }; + result.map(|mode| mode as mode_t) + } else { + Ok(fperm) + } +} + +#[test] +fn symbolic_modes() { + assert_eq!(parse_mode(Some("u+x".to_owned())).unwrap(), 0o766); + assert_eq!(parse_mode(Some("+x".to_owned())).unwrap(), 0o777); + assert_eq!(parse_mode(Some("a-w".to_owned())).unwrap(), 0o444); + assert_eq!(parse_mode(Some("g-r".to_owned())).unwrap(), 0o626); +} + +#[test] +fn numeric_modes() { + assert_eq!(parse_mode(Some("644".to_owned())).unwrap(), 0o644); + assert_eq!(parse_mode(Some("+100".to_owned())).unwrap(), 0o766); + assert_eq!(parse_mode(Some("-4".to_owned())).unwrap(), 0o662); + assert_eq!(parse_mode(None).unwrap(), 0o666); +} diff --git a/coreutils/src/mktemp/Cargo.toml b/coreutils/src/mktemp/Cargo.toml new file mode 100644 index 000000000..f2b98733b --- /dev/null +++ b/coreutils/src/mktemp/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mktemp" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_mktemp" +path = "mktemp.rs" + +[dependencies] +uucore = "0.0.1" +getopts = "0.2.18" +rand = "0.5" +tempfile = "2.1.5" + +[[bin]] +name = "mktemp" +path = "../../uumain.rs" diff --git a/coreutils/src/mktemp/mktemp.rs b/coreutils/src/mktemp/mktemp.rs new file mode 100644 index 000000000..7c65d3e41 --- /dev/null +++ b/coreutils/src/mktemp/mktemp.rs @@ -0,0 +1,242 @@ +#![crate_name = "uu_mktemp"] + +// This file is part of the uutils coreutils package. +// +// (c) Sunrin SHIMURA +// Collaborator: Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// + +extern crate getopts; +extern crate rand; +extern crate tempfile; + +#[macro_use] +extern crate uucore; + +use std::env; +use std::path::{is_separator, PathBuf}; +use std::mem::forget; +use std::iter; + +use rand::Rng; +use tempfile::NamedTempFileOptions; + +mod tempdir; + +static NAME: &str = "mktemp"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static DEFAULT_TEMPLATE: &str = "tmp.XXXXXXXXXX"; + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + opts.optflag("d", "directory", "Make a directory instead of a file"); + opts.optflag( + "u", + "dry-run", + "do not create anything; merely print a name (unsafe)", + ); + opts.optflag("q", "quiet", "Fail silently if an error occurs."); + opts.optopt( + "", + "suffix", + "append SUFF to TEMPLATE; SUFF must not contain a path separator. \ + This option is implied if TEMPLATE does not end with X.", + "SUFF", + ); + opts.optopt( + "p", + "tmpdir", + "interpret TEMPLATE relative to DIR; if DIR is not specified, use \ + $TMPDIR if set, else /tmp. With this option, TEMPLATE must not \ + be an absolute name; unlike with -t, TEMPLATE may contain \ + slashes, but mktemp creates only the final component", + "DIR", + ); + // deprecated option of GNU coreutils + // opts.optflag("t", "", "Generate a template (using the supplied prefix and TMPDIR if set) \ + // to create a filename template"); + opts.optflag("", "help", "Print this help and exit"); + opts.optflag("", "version", "print the version and exit"); + + // >> early return options + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "Invalid options\n{}", f), + }; + + if matches.opt_present("help") { + print_help(&opts); + return 0; + } + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + if 1 < matches.free.len() { + crash!(1, "Too many templates"); + } + // << + + let make_dir = matches.opt_present("directory"); + let dry_run = matches.opt_present("dry-run"); + let suffix_opt = matches.opt_str("suffix"); + let suppress_file_err = matches.opt_present("quiet"); + + let template = if matches.free.is_empty() { + DEFAULT_TEMPLATE + } else { + &matches.free[0][..] + }; + + let (prefix, rand, suffix) = match parse_template(template) { + Some((p, r, s)) => match suffix_opt { + Some(suf) => { + if s == "" { + (p, r, suf) + } else { + crash!( + 1, + "Template should end with 'X' when you specify suffix option." + ) + } + } + None => (p, r, s.to_owned()), + }, + None => ("", 0, "".to_owned()), + }; + + if rand < 3 { + crash!(1, "Too few 'X's in template") + } + + if suffix.chars().any(is_separator) { + crash!(1, "suffix cannot contain any path separators"); + } + + let tmpdir = match matches.opt_str("tmpdir") { + Some(s) => { + if PathBuf::from(prefix).is_absolute() { + show_info!( + "invalid template, ‘{}’; with --tmpdir, it may not be absolute", + template + ); + return 1; + } + PathBuf::from(s) + } + None => env::temp_dir(), + }; + + if dry_run { + dry_exec(tmpdir, prefix, rand, &suffix) + } else { + exec(tmpdir, prefix, rand, &suffix, make_dir, suppress_file_err) + } +} + +fn print_help(opts: &getopts::Options) { + let usage = format!( + " Create a temporary file or directory, safely, and print its name. +TEMPLATE must contain at least 3 consecutive 'X's in last component. +If TEMPLATE is not specified, use {}, and --tmpdir is implied", + DEFAULT_TEMPLATE + ); + + println!("{} {}", NAME, VERSION); + println!("SYNOPSIS"); + println!(" {} [OPTION]... [FILE]", NAME); + println!("Usage:"); + print!("{}", opts.usage(&usage[..])); +} + +fn parse_template(temp: &str) -> Option<(&str, usize, &str)> { + let right = match temp.rfind('X') { + Some(r) => r + 1, + None => return None, + }; + let left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1); + let prefix = &temp[..left]; + let rand = right - left; + let suffix = &temp[right..]; + Some((prefix, rand, suffix)) +} + +pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> i32 { + let len = prefix.len() + suffix.len() + rand; + let mut buf = String::with_capacity(len); + buf.push_str(prefix); + buf.extend(iter::repeat('X').take(rand)); + buf.push_str(suffix); + + // Randomize. + unsafe { + // We guarantee utf8. + let bytes = &mut buf.as_mut_vec()[prefix.len()..prefix.len() + rand]; + rand::thread_rng().fill(bytes); + for byte in bytes.iter_mut() { + *byte = match *byte % 62 { + v @ 0...9 => (v + '0' as u8), + v @ 10...35 => (v - 10 + 'a' as u8), + v @ 36...61 => (v - 36 + 'A' as u8), + _ => unreachable!(), + } + } + } + tmpdir.push(String::from(buf)); + println!("{}", tmpdir.display()); + 0 +} + +fn exec( + tmpdir: PathBuf, + prefix: &str, + rand: usize, + suffix: &str, + make_dir: bool, + quiet: bool, +) -> i32 { + if make_dir { + match tempdir::new_in(&tmpdir, prefix, rand, suffix) { + Ok(ref f) => { + println!("{}", f); + return 0; + } + Err(e) => { + if !quiet { + show_info!("{}", e); + } + return 1; + } + } + } + + let tmpfile = NamedTempFileOptions::new() + .prefix(prefix) + .rand_bytes(rand) + .suffix(suffix) + .create_in(tmpdir); + + let tmpfile = match tmpfile { + Ok(f) => f, + Err(e) => { + if !quiet { + show_info!("failed to create tempfile: {}", e); + } + return 1; + } + }; + + let tmpname = tmpfile.path().to_string_lossy().to_string(); + + println!("{}", tmpname); + + // CAUTION: Not to call `drop` of tmpfile, which removes the tempfile, + // I call a dangerous function `forget`. + forget(tmpfile); + + 0 +} diff --git a/coreutils/src/mktemp/tempdir.rs b/coreutils/src/mktemp/tempdir.rs new file mode 100644 index 000000000..b3f2e60e1 --- /dev/null +++ b/coreutils/src/mktemp/tempdir.rs @@ -0,0 +1,50 @@ +// Mainly taken from crate `tempdir` + +extern crate rand; +use rand::{thread_rng, Rng}; +use rand::distributions::Alphanumeric; + +use std::io::Result as IOResult; +use std::io::{Error, ErrorKind}; +use std::path::Path; + +// How many times should we (re)try finding an unused random name? It should be +// enough that an attacker will run out of luck before we run out of patience. +const NUM_RETRIES: u32 = 1 << 31; + +#[cfg(any(unix, target_os = "redox"))] +fn create_dir>(path: P) -> IOResult<()> { + use std::fs::DirBuilder; + use std::os::unix::fs::DirBuilderExt; + + DirBuilder::new().mode(0o700).create(path) +} + +#[cfg(windows)] +fn create_dir>(path: P) -> IOResult<()> { + ::std::fs::create_dir(path) +} + +pub fn new_in>( + tmpdir: P, + prefix: &str, + rand: usize, + suffix: &str, +) -> IOResult { + let mut rng = thread_rng(); + for _ in 0..NUM_RETRIES { + let rand_chars: String = rng.sample_iter(&Alphanumeric).take(rand).collect(); + let leaf = format!("{}{}{}", prefix, rand_chars, suffix); + let path = tmpdir.as_ref().join(&leaf); + match create_dir(&path) { + Ok(_) => return Ok(path.to_string_lossy().into_owned()), + Err(ref e) if e.kind() == ErrorKind::AlreadyExists => {} + Err(e) => return Err(e), + } + } + + Err(Error::new( + ErrorKind::AlreadyExists, + "too many temporary directories already exist", + )) +} diff --git a/coreutils/src/more/Cargo.toml b/coreutils/src/more/Cargo.toml new file mode 100644 index 000000000..315d8f52d --- /dev/null +++ b/coreutils/src/more/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "more" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_more" +path = "more.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[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" + +[[bin]] +name = "more" +path = "../../uumain.rs" diff --git a/coreutils/src/more/more.rs b/coreutils/src/more/more.rs new file mode 100644 index 000000000..bf72acd9c --- /dev/null +++ b/coreutils/src/more/more.rs @@ -0,0 +1,168 @@ +#![crate_name = "uu_more"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Martin Kysel + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use getopts::Options; +use std::io::{stdout, Read, Write}; +use std::fs::File; + +#[cfg(all(unix, not(target_os = "fuchsia")))] +extern crate nix; +#[cfg(all(unix, not(target_os = "fuchsia")))] +use nix::sys::termios; + +#[cfg(target_os = "redox")] +extern crate redox_termios; +#[cfg(target_os = "redox")] +extern crate syscall; + +#[derive(Clone, Eq, PartialEq)] +pub enum Mode { + More, + Help, + Version, +} + +static NAME: &str = "more"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + 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(e) => { + show_error!("{}", e); + panic!() + } + }; + let usage = opts.usage("more TARGET."); + let mode = if matches.opt_present("version") { + Mode::Version + } else if matches.opt_present("help") { + Mode::Help + } else { + Mode::More + }; + + match mode { + Mode::More => more(matches), + Mode::Help => help(&usage), + Mode::Version => version(), + } + + 0 +} + +fn version() { + println!("{} {}", NAME, VERSION); +} + +fn help(usage: &str) { + let msg = format!( + "{0} {1}\n\n\ + Usage: {0} TARGET\n \ + \n\ + {2}", + NAME, VERSION, usage + ); + println!("{}", msg); +} + +#[cfg(all(unix, not(target_os = "fuchsia")))] +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); + // Disable local echo + term.c_lflag.remove(termios::ECHO); + termios::tcsetattr(0, termios::TCSADRAIN, &term).unwrap(); + term +} + +#[cfg(any(windows, target_os = "fuchsia", target_os = "sunrise"))] +#[inline(always)] +fn setup_term() -> usize { + 0 +} + +#[cfg(target_os = "redox")] +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; + syscall::write(fd, &term).unwrap(); + let _ = syscall::close(fd); + term +} + +#[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(); +} + +#[cfg(any(windows, target_os = "fuchsia", target_os = "sunrise"))] +#[inline(always)] +fn reset_term(_: &mut usize) {} + +#[cfg(any(target_os = "redox"))] +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; + syscall::write(fd, &term).unwrap(); + let _ = syscall::close(fd); +} + +fn more(matches: getopts::Matches) { + let files = matches.free; + let mut f = File::open(files.first().unwrap()).unwrap(); + let mut buffer = [0; 1024]; + + let mut term = setup_term(); + + let mut end = false; + while let Ok(sz) = f.read(&mut buffer) { + if sz == 0 { + break; + } + stdout().write(&buffer[0..sz]).unwrap(); + for byte in std::io::stdin().bytes() { + match byte.unwrap() { + b' ' => break, + b'q' | 27 => { + end = true; + break; + } + _ => (), + } + } + + if end { + break; + } + } + + reset_term(&mut term); + println!(""); +} diff --git a/coreutils/src/mv/Cargo.toml b/coreutils/src/mv/Cargo.toml new file mode 100644 index 000000000..5b60e4579 --- /dev/null +++ b/coreutils/src/mv/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mv" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_mv" +path = "mv.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "mv" +path = "../../uumain.rs" diff --git a/coreutils/src/mv/mv.rs b/coreutils/src/mv/mv.rs new file mode 100644 index 000000000..b6e6b6870 --- /dev/null +++ b/coreutils/src/mv/mv.rs @@ -0,0 +1,437 @@ +#![crate_name = "uu_mv"] + +// This file is part of the uutils coreutils package. +// +// (c) Orvar Segerström +// (c) Sokovikov Evgeniy +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::fs; +use std::env; +use std::io::{stdin, Result}; +use std::path::{Path, PathBuf}; + +static NAME: &str = "mv"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub struct Behaviour { + overwrite: OverwriteMode, + backup: BackupMode, + suffix: String, + update: bool, + target_dir: Option, + no_target_dir: bool, + verbose: bool, +} + +#[derive(Clone, Eq, PartialEq)] +pub enum OverwriteMode { + NoClobber, + Interactive, + Force, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum BackupMode { + NoBackup, + SimpleBackup, + NumberedBackup, + ExistingBackup, +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflagopt( + "", + "backup", + "make a backup of each existing destination file", + "CONTROL", + ); + opts.optflag("b", "", "like --backup but does not accept an argument"); + opts.optflag("f", "force", "do not prompt before overwriting"); + opts.optflag("i", "interactive", "prompt before override"); + opts.optflag("n", "no-clobber", "do not overwrite an existing file"); + opts.optflag( + "", + "strip-trailing-slashes", + "remove any trailing slashes from each SOURCE\n \ + argument", + ); + opts.optopt("S", "suffix", "override the usual backup suffix", "SUFFIX"); + opts.optopt( + "t", + "target-directory", + "move all SOURCE arguments into DIRECTORY", + "DIRECTORY", + ); + opts.optflag("T", "no-target-directory", "treat DEST as a normal file"); + opts.optflag( + "u", + "update", + "move only when the SOURCE file is newer\n \ + than the destination file or when the\n \ + destination file is missing", + ); + opts.optflag("v", "verbose", "explain what is being done"); + 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) => { + show_error!("Invalid options\n{}", f); + return 1; + } + }; + let usage = opts.usage("Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."); + + let overwrite_mode = determine_overwrite_mode(&matches); + let backup_mode = determine_backup_mode(&matches); + + if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { + show_error!( + "options --backup and --no-clobber are mutually exclusive\n\ + Try '{} --help' for more information.", + NAME + ); + return 1; + } + + let backup_suffix = determine_backup_suffix(backup_mode, &matches); + + if matches.opt_present("T") && matches.opt_present("t") { + show_error!("cannot combine --target-directory (-t) and --no-target-directory (-T)"); + return 1; + } + + let behaviour = Behaviour { + overwrite: overwrite_mode, + backup: backup_mode, + suffix: backup_suffix, + update: matches.opt_present("u"), + target_dir: matches.opt_str("t"), + no_target_dir: matches.opt_present("T"), + verbose: matches.opt_present("v"), + }; + + let paths: Vec = { + fn strip_slashes<'a>(p: &'a Path) -> &'a Path { + p.components().as_path() + } + let to_owned = |p: &Path| p.to_owned(); + let arguments = matches.free.iter().map(Path::new); + if matches.opt_present("strip-trailing-slashes") { + arguments.map(strip_slashes).map(to_owned).collect() + } else { + arguments.map(to_owned).collect() + } + }; + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + 0 + } else if matches.opt_present("help") { + help(&usage); + 0 + } else { + exec(&paths[..], behaviour) + } +} + +fn determine_overwrite_mode(matches: &getopts::Matches) -> OverwriteMode { + // This does not exactly match the GNU implementation: + // The GNU mv defaults to Force, but if more than one of the + // overwrite options are supplied, only the last takes effect. + // To default to no-clobber in that situation seems safer: + // + if matches.opt_present("no-clobber") { + OverwriteMode::NoClobber + } else if matches.opt_present("interactive") { + OverwriteMode::Interactive + } else { + OverwriteMode::Force + } +} + +fn determine_backup_mode(matches: &getopts::Matches) -> BackupMode { + if matches.opt_present("b") { + BackupMode::SimpleBackup + } else if matches.opt_present("backup") { + match matches.opt_str("backup") { + None => BackupMode::SimpleBackup, + Some(mode) => match &mode[..] { + "simple" | "never" => BackupMode::SimpleBackup, + "numbered" | "t" => BackupMode::NumberedBackup, + "existing" | "nil" => BackupMode::ExistingBackup, + "none" | "off" => BackupMode::NoBackup, + x => { + crash!( + 1, + "invalid argument ‘{}’ for ‘backup type’\n\ + Try '{} --help' for more information.", + x, + NAME + ); + } + }, + } + } else { + BackupMode::NoBackup + } +} + +fn determine_backup_suffix(backup_mode: BackupMode, matches: &getopts::Matches) -> String { + if matches.opt_present("suffix") { + match matches.opt_str("suffix") { + Some(x) => x, + None => { + crash!( + 1, + "option '--suffix' requires an argument\n\ + Try '{} --help' for more information.", + NAME + ); + } + } + } else { + if let (Ok(s), BackupMode::SimpleBackup) = (env::var("SIMPLE_BACKUP_SUFFIX"), backup_mode) { + s + } else { + "~".to_owned() + } + } +} + +fn help(usage: &str) { + println!( + "{0} {1}\n\n\ + Usage: {0} SOURCE DEST\n \ + or: {0} SOURCE... DIRECTORY\n\n\ + {2}", + NAME, VERSION, usage + ); +} + +fn exec(files: &[PathBuf], b: Behaviour) -> i32 { + if let Some(ref name) = b.target_dir { + return move_files_into_dir(files, &PathBuf::from(name), &b); + } + match files.len() { + 0 | 1 => { + show_error!( + "missing file operand\n\ + Try '{} --help' for more information.", + NAME + ); + return 1; + } + 2 => { + let source = &files[0]; + let target = &files[1]; + if !source.exists() { + show_error!( + "cannot stat ‘{}’: No such file or directory", + source.display() + ); + return 1; + } + + if target.is_dir() { + if b.no_target_dir { + if !source.is_dir() { + show_error!( + "cannot overwrite directory ‘{}’ with non-directory", + target.display() + ); + return 1; + } + + return match rename(source, target, &b) { + Err(e) => { + show_error!( + "cannot move ‘{}’ to ‘{}’: {}", + source.display(), target.display(), e + ); + 1 + } + _ => 0, + }; + } + + return move_files_into_dir(&[source.clone()], target, &b); + } else if target.exists() && source.is_dir() { + show_error!( + "cannot overwrite non-directory ‘{}’ with directory ‘{}’", + target.display(), source.display() + ); + return 1; + } + + if let Err(e) = rename(source, target, &b) { + show_error!("{}", e); + return 1; + } + } + _ => { + if b.no_target_dir { + show_error!( + "mv: extra operand ‘{}’\n\ + Try '{} --help' for more information.", + files[2].display(), + NAME + ); + return 1; + } + let target_dir = files.last().unwrap(); + move_files_into_dir(&files[..files.len() - 1], target_dir, &b); + } + } + 0 +} + +fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behaviour) -> i32 { + if !target_dir.is_dir() { + show_error!("target ‘{}’ is not a directory", target_dir.display()); + return 1; + } + + let mut all_successful = true; + for sourcepath in files.iter() { + let targetpath = match sourcepath.file_name() { + Some(name) => target_dir.join(name), + None => { + show_error!( + "cannot stat ‘{}’: No such file or directory", + sourcepath.display() + ); + + all_successful = false; + continue; + } + }; + + if let Err(e) = rename(sourcepath, &targetpath, b) { + show_error!( + "mv: cannot move ‘{}’ to ‘{}’: {}", + sourcepath.display(), + targetpath.display(), + e + ); + all_successful = false; + } + } + if all_successful { + 0 + } else { + 1 + } +} + +fn rename(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<()> { + let mut backup_path = None; + + if to.exists() { + match b.overwrite { + OverwriteMode::NoClobber => return Ok(()), + OverwriteMode::Interactive => { + print!("{}: overwrite ‘{}’? ", NAME, to.display()); + if !read_yes() { + return Ok(()); + } + } + OverwriteMode::Force => {} + }; + + backup_path = match b.backup { + BackupMode::NoBackup => None, + BackupMode::SimpleBackup => Some(simple_backup_path(to, &b.suffix)), + BackupMode::NumberedBackup => Some(numbered_backup_path(to)), + BackupMode::ExistingBackup => Some(existing_backup_path(to, &b.suffix)), + }; + if let Some(ref p) = backup_path { + fs::rename(to, p)?; + } + + if b.update { + if fs::metadata(from)?.modified()? <= fs::metadata(to)?.modified()? + { + return Ok(()); + } + } + } + + // "to" may no longer exist if it was backed up + if to.exists() && to.is_dir() { + // normalize behavior between *nix and windows + if from.is_dir() { + if is_empty_dir(to) { + fs::remove_dir(to)? + } else { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "Directory not empty")); + } + } + } + + fs::rename(from, to)?; + + if b.verbose { + print!("‘{}’ -> ‘{}’", from.display(), to.display()); + match backup_path { + Some(path) => println!(" (backup: ‘{}’)", path.display()), + None => println!(""), + } + } + Ok(()) +} + +fn read_yes() -> bool { + let mut s = String::new(); + match stdin().read_line(&mut s) { + Ok(_) => match s.chars().nth(0) { + Some(x) => x == 'y' || x == 'Y', + _ => false, + }, + _ => false, + } +} + +fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { + let mut p = path.to_string_lossy().into_owned(); + p.push_str(suffix); + PathBuf::from(p) +} + +fn numbered_backup_path(path: &PathBuf) -> PathBuf { + (1_u64..) + .map(|i| path.with_extension(format!("~{}~", i))) + .skip_while(|p| p.exists()) + .next() + .expect("cannot create backup") +} + +fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { + let test_path = path.with_extension("~1~"); + if test_path.exists() { + numbered_backup_path(path) + } else { + simple_backup_path(path, suffix) + } +} + +fn is_empty_dir(path: &PathBuf) -> bool { + match fs::read_dir(path) { + Ok(contents) => { + return contents.peekable().peek().is_none(); + }, + Err(_e) => { return false; } + } +} diff --git a/coreutils/src/nice/Cargo.toml b/coreutils/src/nice/Cargo.toml new file mode 100644 index 000000000..8401f1032 --- /dev/null +++ b/coreutils/src/nice/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "nice" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_nice" +path = "nice.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "nice" +path = "../../uumain.rs" diff --git a/coreutils/src/nice/nice.rs b/coreutils/src/nice/nice.rs new file mode 100644 index 000000000..e76165a8e --- /dev/null +++ b/coreutils/src/nice/nice.rs @@ -0,0 +1,134 @@ +#![crate_name = "uu_nice"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate libc; + +#[macro_use] +extern crate uucore; + +use libc::{c_char, c_int, execvp}; +use std::ffi::CString; +use std::io::Error; + +const NAME: &str = "nice"; +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +// XXX: PRIO_PROCESS is 0 on at least FreeBSD and Linux. Don't know about Mac OS X. +const PRIO_PROCESS: c_int = 0; + +extern "C" { + fn getpriority(which: c_int, who: c_int) -> c_int; + fn setpriority(which: c_int, who: c_int, prio: c_int) -> c_int; +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optopt( + "n", + "adjustment", + "add N to the niceness (default is 10)", + "N", + ); + 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(err) => { + show_error!("{}", err); + return 125; + } + }; + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + if matches.opt_present("help") { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTIONS] [COMMAND [ARGS]] + +Run COMMAND with an adjusted niceness, which affects process scheduling. +With no COMMAND, print the current niceness. Niceness values range from at +least -20 (most favorable to the process) to 19 (least favorable to the +process).", + NAME, VERSION + ); + + print!("{}", opts.usage(&msg)); + return 0; + } + + let mut niceness = unsafe { getpriority(PRIO_PROCESS, 0) }; + if Error::last_os_error().raw_os_error().unwrap() != 0 { + show_error!("{}", Error::last_os_error()); + return 125; + } + + let adjustment = match matches.opt_str("adjustment") { + Some(nstr) => { + if matches.free.is_empty() { + show_error!( + "A command must be given with an adjustment. + Try \"{} --help\" for more information.", + args[0] + ); + return 125; + } + match nstr.parse() { + Ok(num) => num, + Err(e) => { + show_error!("\"{}\" is not a valid number: {}", nstr, e); + return 125; + } + } + } + None => { + if matches.free.is_empty() { + println!("{}", niceness); + return 0; + } + 10 as c_int + } + }; + + niceness += adjustment; + unsafe { + setpriority(PRIO_PROCESS, 0, niceness); + } + if Error::last_os_error().raw_os_error().unwrap() != 0 { + show_warning!("{}", Error::last_os_error()); + } + + let cstrs: Vec = matches + .free + .iter() + .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(0 as *const c_char); + unsafe { + execvp(args[0], args.as_mut_ptr()); + } + + show_error!("{}", Error::last_os_error()); + if Error::last_os_error().raw_os_error().unwrap() as c_int == libc::ENOENT { + 127 + } else { + 126 + } +} diff --git a/coreutils/src/nl/Cargo.toml b/coreutils/src/nl/Cargo.toml new file mode 100644 index 000000000..5959c8bd5 --- /dev/null +++ b/coreutils/src/nl/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "nl" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_nl" +path = "nl.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +aho-corasick = "0.7.3" +memchr = "2.2.0" +regex = "1.0.1" +regex-syntax = "0.6.7" +uucore = "0.0.1" + +[[bin]] +name = "nl" +path = "../../uumain.rs" diff --git a/coreutils/src/nl/helper.rs b/coreutils/src/nl/helper.rs new file mode 100644 index 000000000..b34c2a1e1 --- /dev/null +++ b/coreutils/src/nl/helper.rs @@ -0,0 +1,143 @@ +extern crate getopts; +extern crate regex; + +// parse_style parses a style string into a NumberingStyle. +fn parse_style(chars: &[char]) -> Result<::NumberingStyle, String> { + if chars.len() == 1 && chars[0] == 'a' { + Ok(::NumberingStyle::NumberForAll) + } else if chars.len() == 1 && chars[0] == 't' { + Ok(::NumberingStyle::NumberForNonEmpty) + } else if chars.len() == 1 && chars[0] == 'n' { + Ok(::NumberingStyle::NumberForNone) + } else if chars.len() > 1 && chars[0] == 'p' { + let s: String = chars[1..].iter().cloned().collect(); + match regex::Regex::new(&s) { + Ok(re) => Ok(::NumberingStyle::NumberForRegularExpression(re)), + Err(_) => Err(String::from("Illegal regular expression")), + } + } else { + Err(String::from("Illegal style encountered")) + } +} + +// parse_options loads the options into the settings, returning an array of +// error messages. +pub fn parse_options(settings: &mut ::Settings, opts: &getopts::Matches) -> Vec { + // This vector holds error messages encountered. + let mut errs: Vec = vec![]; + settings.renumber = !opts.opt_present("p"); + match opts.opt_str("s") { + None => {} + Some(val) => { + settings.number_separator = val; + } + } + match opts.opt_str("n") { + None => {} + Some(val) => match val.as_ref() { + "ln" => { + settings.number_format = ::NumberFormat::Left; + } + "rn" => { + settings.number_format = ::NumberFormat::Right; + } + "rz" => { + settings.number_format = ::NumberFormat::RightZero; + } + _ => { + errs.push(String::from("Illegal value for -n")); + } + }, + } + match opts.opt_str("b") { + None => {} + Some(val) => { + let chars: Vec = val.chars().collect(); + match parse_style(&chars) { + Ok(s) => { + settings.body_numbering = s; + } + Err(message) => { + errs.push(message); + } + } + } + } + match opts.opt_str("f") { + None => {} + Some(val) => { + let chars: Vec = val.chars().collect(); + match parse_style(&chars) { + Ok(s) => { + settings.footer_numbering = s; + } + Err(message) => { + errs.push(message); + } + } + } + } + match opts.opt_str("h") { + None => {} + Some(val) => { + let chars: Vec = val.chars().collect(); + match parse_style(&chars) { + Ok(s) => { + settings.header_numbering = s; + } + Err(message) => { + errs.push(message); + } + } + } + } + match opts.opt_str("i") { + None => {} + Some(val) => { + let conv: Option = val.parse().ok(); + match conv { + None => { + errs.push(String::from("Illegal value for -i")); + } + Some(num) => settings.line_increment = num, + } + } + } + match opts.opt_str("w") { + None => {} + Some(val) => { + let conv: Option = val.parse().ok(); + match conv { + None => { + errs.push(String::from("Illegal value for -w")); + } + Some(num) => settings.number_width = num, + } + } + } + match opts.opt_str("v") { + None => {} + Some(val) => { + let conv: Option = val.parse().ok(); + match conv { + None => { + errs.push(String::from("Illegal value for -v")); + } + Some(num) => settings.starting_line_number = num, + } + } + } + match opts.opt_str("l") { + None => {} + Some(val) => { + let conv: Option = val.parse().ok(); + match conv { + None => { + errs.push(String::from("Illegal value for -l")); + } + Some(num) => settings.join_blank_lines = num, + } + } + } + errs +} diff --git a/coreutils/src/nl/nl.rs b/coreutils/src/nl/nl.rs new file mode 100644 index 000000000..6359b4b93 --- /dev/null +++ b/coreutils/src/nl/nl.rs @@ -0,0 +1,384 @@ +#![crate_name = "uu_nl"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Tobias Bohumir Schottdorf + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +extern crate aho_corasick; +extern crate getopts; +extern crate memchr; +extern crate regex; +extern crate regex_syntax; + +#[macro_use] +extern crate uucore; + +use std::fs::File; +use std::io::{stdin, BufRead, BufReader, Read}; +use std::iter::repeat; +use std::path::Path; + +mod helper; + +static NAME: &str = "nl"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static USAGE: &str = "nl [OPTION]... [FILE]..."; +// A regular expression matching everything. + +// Settings store options used by nl to produce its output. +pub struct Settings { + // The variables corresponding to the options -h, -b, and -f. + header_numbering: NumberingStyle, + body_numbering: NumberingStyle, + footer_numbering: NumberingStyle, + // The variable corresponding to -d + section_delimiter: [char; 2], + // The variables corresponding to the options -v, -i, -l, -w. + starting_line_number: u64, + line_increment: u64, + join_blank_lines: u64, + number_width: usize, // Used with String::from_char, hence usize. + // The format of the number and the (default value for) + // renumbering each page. + number_format: NumberFormat, + renumber: bool, + // The string appended to each line number output. + number_separator: String, +} + +// NumberingStyle stores which lines are to be numbered. +// The possible options are: +// 1. Number all lines +// 2. Number only nonempty lines +// 3. Don't number any lines at all +// 4. Number all lines that match a basic regular expression. +enum NumberingStyle { + NumberForAll, + NumberForNonEmpty, + NumberForNone, + NumberForRegularExpression(regex::Regex), +} + +// NumberFormat specifies how line numbers are output within their allocated +// space. They are justified to the left or right, in the latter case with +// the option of having all unused space to its left turned into leading zeroes. +enum NumberFormat { + Left, + Right, + RightZero, +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optopt( + "b", + "body-numbering", + "use STYLE for numbering body lines", + "STYLE", + ); + opts.optopt( + "d", + "section-delimiter", + "use CC for separating logical pages", + "CC", + ); + opts.optopt( + "f", + "footer-numbering", + "use STYLE for numbering footer lines", + "STYLE", + ); + opts.optopt( + "h", + "header-numbering", + "use STYLE for numbering header lines", + "STYLE", + ); + opts.optopt( + "i", + "line-increment", + "line number increment at each line", + "", + ); + opts.optopt( + "l", + "join-blank-lines", + "group of NUMBER empty lines counted as one", + "NUMBER", + ); + opts.optopt( + "n", + "number-format", + "insert line numbers according to FORMAT", + "FORMAT", + ); + opts.optflag( + "p", + "no-renumber", + "do not reset line numbers at logical pages", + ); + opts.optopt( + "s", + "number-separator", + "add STRING after (possible) line number", + "STRING", + ); + opts.optopt( + "v", + "starting-line-number", + "first line number on each logical page", + "NUMBER", + ); + opts.optopt( + "w", + "number-width", + "use NUMBER columns for line numbers", + "NUMBER", + ); + opts.optflag("", "help", "display this help and exit"); + opts.optflag("V", "version", "version"); + + // A mutable settings object, initialized with the defaults. + let mut settings = Settings { + header_numbering: NumberingStyle::NumberForNone, + body_numbering: NumberingStyle::NumberForAll, + footer_numbering: NumberingStyle::NumberForNone, + section_delimiter: ['\\', ':'], + starting_line_number: 1, + line_increment: 1, + join_blank_lines: 1, + number_width: 6, + number_format: NumberFormat::Right, + renumber: true, + number_separator: String::from("\t"), + }; + + let given_options = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => { + show_error!("{}", f); + print_usage(&opts); + return 1; + } + }; + + if given_options.opt_present("help") { + print_usage(&opts); + return 0; + } + if given_options.opt_present("version") { + version(); + return 0; + } + + // Update the settings from the command line options, and terminate the + // program if some options could not successfully be parsed. + let parse_errors = helper::parse_options(&mut settings, &given_options); + if !parse_errors.is_empty() { + show_error!("Invalid arguments supplied."); + for message in &parse_errors { + println!("{}", message); + } + return 1; + } + + let files = given_options.free; + let mut read_stdin = files.is_empty(); + + for file in &files { + if file == "-" { + // If both file names and '-' are specified, we choose to treat first all + // regular files, and then read from stdin last. + read_stdin = true; + continue; + } + let path = Path::new(file); + let reader = File::open(path).unwrap(); + let mut buffer = BufReader::new(reader); + nl(&mut buffer, &settings); + } + + if read_stdin { + let mut buffer = BufReader::new(stdin()); + nl(&mut buffer, &settings); + } + 0 +} + +// nl implements the main functionality for an individual buffer. +fn nl(reader: &mut BufReader, settings: &Settings) { + let regexp: regex::Regex = regex::Regex::new(r".?").unwrap(); + let mut line_no = settings.starting_line_number; + // The current line number's width as a string. Using to_string is inefficient + // but since we only do it once, it should not hurt. + let mut line_no_width = line_no.to_string().len(); + let line_no_width_initial = line_no_width; + // Stores the smallest integer with one more digit than line_no, so that + // when line_no >= line_no_threshold, we need to use one more digit. + let mut line_no_threshold = 10u64.pow(line_no_width as u32); + let mut empty_line_count: u64 = 0; + let fill_char = match settings.number_format { + NumberFormat::RightZero => '0', + _ => ' ', + }; + // Initially, we use the body's line counting settings + let mut regex_filter = match settings.body_numbering { + NumberingStyle::NumberForRegularExpression(ref re) => re, + _ => ®exp, + }; + let mut line_filter: fn(&str, ®ex::Regex) -> bool = pass_regex; + for mut l in reader.lines().map(|r| r.unwrap()) { + // Sanitize the string. We want to print the newline ourselves. + if !l.is_empty() && l.chars().rev().next().unwrap() == '\n' { + l.pop(); + } + // Next we iterate through the individual chars to see if this + // is one of the special lines starting a new "section" in the + // document. + let line = l; + let mut odd = false; + // matched_group counts how many copies of section_delimiter + // this string consists of (0 if there's anything else) + let mut matched_groups = 0u8; + for c in line.chars() { + // If this is a newline character, the loop should end. + if c == '\n' { + break; + } + // If we have already seen three groups (corresponding to + // a header) or the current char does not form part of + // a new group, then this line is not a segment indicator. + if matched_groups >= 3 || settings.section_delimiter[if odd { 1 } else { 0 }] != c { + matched_groups = 0; + break; + } + if odd { + // We have seen a new group and count it. + matched_groups += 1; + } + odd = !odd; + } + + // See how many groups we matched. That will tell us if this is + // a line starting a new segment, and the number of groups + // indicates what type of segment. + if matched_groups > 0 { + // The current line is a section delimiter, so we output + // a blank line. + println!(""); + // However the line does not count as a blank line, so we + // reset the counter used for --join-blank-lines. + empty_line_count = 0; + match *match matched_groups { + 3 => { + // This is a header, so we may need to reset the + // line number and the line width + if settings.renumber { + line_no = settings.starting_line_number; + line_no_width = line_no_width_initial; + line_no_threshold = 10u64.pow(line_no_width as u32); + } + &settings.header_numbering + } + 1 => &settings.footer_numbering, + // The only option left is 2, but rust wants + // a catch-all here. + _ => &settings.body_numbering, + } { + NumberingStyle::NumberForAll => { + line_filter = pass_all; + } + NumberingStyle::NumberForNonEmpty => { + line_filter = pass_nonempty; + } + NumberingStyle::NumberForNone => { + line_filter = pass_none; + } + NumberingStyle::NumberForRegularExpression(ref re) => { + line_filter = pass_regex; + regex_filter = re; + } + } + continue; + } + // From this point on we format and print a "regular" line. + if line == "" { + // The line is empty, which means that we have to care + // about the --join-blank-lines parameter. + empty_line_count += 1; + } else { + // This saves us from having to check for an empty string + // in the next selector. + empty_line_count = 0; + } + if !line_filter(&line, regex_filter) + || (empty_line_count > 0 && empty_line_count < settings.join_blank_lines) + { + // No number is printed for this line. Either we did not + // want to print one in the first place, or it is a blank + // line but we are still collecting more blank lines via + // the option --join-blank-lines. + println!("{}", line); + continue; + } + // If we make it here, then either we are printing a non-empty + // line or assigning a line number to an empty line. Either + // way, start counting empties from zero once more. + empty_line_count = 0; + // A line number is to be printed. + let mut w: usize = 0; + if settings.number_width > line_no_width { + w = settings.number_width - line_no_width; + } + let fill: String = repeat(fill_char).take(w).collect(); + match settings.number_format { + NumberFormat::Left => println!( + "{1}{0}{2}{3}", + fill, line_no, settings.number_separator, line + ), + _ => println!( + "{0}{1}{2}{3}", + fill, line_no, settings.number_separator, line + ), + } + // Now update the variables for the (potential) next + // line. + line_no += settings.line_increment; + while line_no >= line_no_threshold { + // The line number just got longer. + line_no_threshold *= 10; + line_no_width += 1; + } + } +} + +fn pass_regex(line: &str, re: ®ex::Regex) -> bool { + re.is_match(line) +} + +fn pass_nonempty(line: &str, _: ®ex::Regex) -> bool { + line.len() > 0 +} + +fn pass_none(_: &str, _: ®ex::Regex) -> bool { + false +} + +fn pass_all(_: &str, _: ®ex::Regex) -> bool { + true +} + +fn print_usage(opts: &getopts::Options) { + println!("{}", opts.usage(USAGE)); +} + +fn version() { + println!("{} {}", NAME, VERSION); +} diff --git a/coreutils/src/nohup/Cargo.toml b/coreutils/src/nohup/Cargo.toml new file mode 100644 index 000000000..0302fa8d7 --- /dev/null +++ b/coreutils/src/nohup/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "nohup" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_nohup" +path = "nohup.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" + +[dependencies.uucore] +version = "0.0.1" +features = ["fs"] + +[[bin]] +name = "nohup" +path = "../../uumain.rs" diff --git a/coreutils/src/nohup/nohup.rs b/coreutils/src/nohup/nohup.rs new file mode 100644 index 000000000..d9d7de6a3 --- /dev/null +++ b/coreutils/src/nohup/nohup.rs @@ -0,0 +1,166 @@ +#![crate_name = "uu_nohup"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) 2014 Vsevolod Velichko + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate libc; + +#[macro_use] +extern crate uucore; + +use libc::{c_char, execvp, signal, dup2}; +use libc::{SIGHUP, SIG_IGN}; +use std::ffi::CString; +use std::fs::{File, OpenOptions}; +use std::io::Error; +use std::os::unix::prelude::*; +use std::path::{Path, PathBuf}; +use std::env; +use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive}; + +static NAME: &str = "nohup"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg(target_os = "macos")] +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() +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + 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) }; + + if unsafe { _vprocmgr_detach_from_console(0) } != std::ptr::null() { + crash!(2, "Cannot detach from console") + }; + + let cstrs: Vec = matches + .free + .iter() + .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()) } +} + +fn replace_fds() { + if is_stdin_interactive() { + let new_stdin = match File::open(Path::new("/dev/null")) { + Ok(t) => t, + Err(e) => crash!(2, "Cannot replace STDIN: {}", e), + }; + if unsafe { dup2(new_stdin.as_raw_fd(), 0) } != 0 { + crash!(2, "Cannot replace STDIN: {}", Error::last_os_error()) + } + } + + if is_stdout_interactive() { + let new_stdout = find_stdout(); + let fd = new_stdout.as_raw_fd(); + + if unsafe { dup2(fd, 1) } != 1 { + crash!(2, "Cannot replace STDOUT: {}", Error::last_os_error()) + } + } + + if is_stderr_interactive() { + if unsafe { dup2(1, 2) } != 2 { + crash!(2, "Cannot replace STDERR: {}", Error::last_os_error()) + } + } +} + +fn find_stdout() -> File { + match OpenOptions::new() + .write(true) + .create(true) + .append(true) + .open(Path::new("nohup.out")) + { + Ok(t) => { + show_warning!("Output is redirected to: nohup.out"); + t + } + Err(e) => { + let home = match env::var("HOME") { + Err(_) => crash!(2, "Cannot replace STDOUT: {}", e), + Ok(h) => h, + }; + let mut homeout = PathBuf::from(home); + homeout.push("nohup.out"); + match OpenOptions::new() + .write(true) + .create(true) + .append(true) + .open(&homeout) + { + Ok(t) => { + show_warning!("Output is redirected to: {:?}", homeout); + t + } + Err(e) => crash!(2, "Cannot replace STDOUT: {}", e), + } + } + } +} + +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)); +} diff --git a/coreutils/src/nproc/Cargo.toml b/coreutils/src/nproc/Cargo.toml new file mode 100644 index 000000000..c622e3a2e --- /dev/null +++ b/coreutils/src/nproc/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "nproc" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_nproc" +path = "nproc.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +num_cpus = "1.10" +uucore = "0.0.1" + +[[bin]] +name = "nproc" +path = "../../uumain.rs" diff --git a/coreutils/src/nproc/nproc.rs b/coreutils/src/nproc/nproc.rs new file mode 100644 index 000000000..6a35520cd --- /dev/null +++ b/coreutils/src/nproc/nproc.rs @@ -0,0 +1,132 @@ +#![crate_name = "uu_nproc"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Michael Gehring + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate num_cpus; + +#[cfg(unix)] +extern crate libc; + +#[macro_use] +extern crate uucore; + +use std::env; + +#[cfg(target_os = "linux")] +pub const _SC_NPROCESSORS_CONF: libc::c_int = 83; +#[cfg(target_os = "macos")] +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; +#[cfg(target_os = "netbsd")] +pub const _SC_NPROCESSORS_CONF: libc::c_int = 1001; + +static NAME: &str = "nproc"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag( + "", + "all", + "print the number of cores available to the system", + ); + opts.optopt("", "ignore", "ignore up to N cores", "N"); + 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(err) => { + show_error!("{}", err); + return 1; + } + }; + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + if matches.opt_present("help") { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTIONS]... + +Print the number of cores available to the current process.", + NAME, VERSION + ); + + print!("{}", opts.usage(&msg)); + return 0; + } + + let mut ignore = match matches.opt_str("ignore") { + Some(numstr) => match numstr.parse() { + Ok(num) => num, + Err(e) => { + show_error!("\"{}\" is not a valid number: {}", numstr, e); + return 1; + } + }, + None => 0, + }; + + if !matches.opt_present("all") { + ignore += match env::var("OMP_NUM_THREADS") { + Ok(threadstr) => match threadstr.parse() { + Ok(num) => num, + Err(_) => 0, + }, + Err(_) => 0, + }; + } + + let mut cores = if matches.opt_present("all") { + num_cpus_all() + } else { + num_cpus::get() + }; + + if cores <= ignore { + cores = 1; + } else { + cores -= ignore; + } + println!("{}", cores); + 0 +} + +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "netbsd"))] +fn num_cpus_all() -> usize { + let nprocs = unsafe { libc::sysconf(_SC_NPROCESSORS_CONF) }; + if nprocs == 1 { + // In some situation, /proc and /sys are not mounted, and sysconf returns 1. + // However, we want to guarantee that `nproc --all` >= `nproc`. + num_cpus::get() + } else { + if nprocs > 0 { + nprocs as usize + } else { + 1 + } + } +} + +// Other platform(e.g., windows), num_cpus::get() directly. +#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd", + target_os = "netbsd")))] +fn num_cpus_all() -> usize { + num_cpus::get() +} diff --git a/coreutils/src/numfmt/Cargo.toml b/coreutils/src/numfmt/Cargo.toml new file mode 100644 index 000000000..c56d040f5 --- /dev/null +++ b/coreutils/src/numfmt/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "numfmt" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_numfmt" +path = "numfmt.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "numfmt" +path = "../../uumain.rs" diff --git a/coreutils/src/numfmt/numfmt.rs b/coreutils/src/numfmt/numfmt.rs new file mode 100644 index 000000000..3c34d4a19 --- /dev/null +++ b/coreutils/src/numfmt/numfmt.rs @@ -0,0 +1,363 @@ +#![crate_name = "uu_numfmt"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Yury Krivopalov + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +use getopts::{Matches, Options}; +use std::fmt; +use std::io::BufRead; + +static NAME: &str = "numfmt"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +const IEC_BASES: [f64; 10] = [ + //premature optimization + 1., + 1024., + 1048576., + 1073741824., + 1099511627776., + 1125899906842624., + 1152921504606846976., + 1180591620717411303424., + 1208925819614629174706176., + 1237940039285380274899124224., +]; + +type Result = std::result::Result; + +type WithI = bool; + +enum Unit { + Auto, + Si, + Iec(WithI), + None, +} + +enum RawSuffix { + K, + M, + G, + T, + P, + E, + Z, + Y, +} + +type Suffix = (RawSuffix, WithI); + +struct DisplayableSuffix(Suffix); + +impl fmt::Display for DisplayableSuffix { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let DisplayableSuffix((ref raw_suffix, ref with_i)) = *self; + match raw_suffix { + RawSuffix::K => write!(f, "K"), + RawSuffix::M => write!(f, "M"), + RawSuffix::G => write!(f, "G"), + RawSuffix::T => write!(f, "T"), + RawSuffix::P => write!(f, "P"), + RawSuffix::E => write!(f, "E"), + RawSuffix::Z => write!(f, "Z"), + RawSuffix::Y => write!(f, "Y"), + }.and_then(|()| match with_i { + true => write!(f, "i"), + false => Ok(()), + }) + } +} + +fn parse_suffix(s: String) -> Result<(f64, Option)> { + let with_i = s.ends_with("i"); + let mut iter = s.chars(); + if with_i { + iter.next_back(); + } + let suffix: Option = match iter.next_back() { + Some('K') => Ok(Some((RawSuffix::K, with_i))), + Some('M') => Ok(Some((RawSuffix::M, with_i))), + Some('G') => Ok(Some((RawSuffix::G, with_i))), + Some('T') => Ok(Some((RawSuffix::T, with_i))), + Some('P') => Ok(Some((RawSuffix::P, with_i))), + Some('E') => Ok(Some((RawSuffix::E, with_i))), + Some('Z') => Ok(Some((RawSuffix::Z, with_i))), + Some('Y') => Ok(Some((RawSuffix::Y, with_i))), + Some('0'...'9') => Ok(None), + _ => Err("Failed to parse suffix"), + }?; + + let suffix_len = match suffix { + None => 0, + Some((_, false)) => 1, + Some((_, true)) => 2, + }; + + let number = s[..s.len() - suffix_len] + .parse::() + .map_err(|err| err.to_string())?; + + Ok((number, suffix)) +} + +fn parse_unit(s: String) -> Result { + match &s[..] { + "auto" => Ok(Unit::Auto), + "si" => Ok(Unit::Si), + "iec" => Ok(Unit::Iec(false)), + "iec-i" => Ok(Unit::Iec(true)), + "none" => Ok(Unit::None), + _ => Err("Unsupported unit is specified".to_owned()), + } +} + +struct TransformOptions { + from: Transform, + to: Transform, +} + +struct Transform { + unit: Unit, +} + +struct NumfmtOptions { + transform: TransformOptions, + padding: isize, + header: usize, +} + +fn remove_suffix(i: f64, s: Option, u: &Unit) -> Result { + match (s, u) { + (None, _) => Ok(i), + (Some((raw_suffix, false)), &Unit::Auto) | (Some((raw_suffix, false)), &Unit::Si) => { + match raw_suffix { + RawSuffix::K => Ok(i * 1e3), + RawSuffix::M => Ok(i * 1e6), + RawSuffix::G => Ok(i * 1e9), + RawSuffix::T => Ok(i * 1e12), + RawSuffix::P => Ok(i * 1e15), + RawSuffix::E => Ok(i * 1e18), + RawSuffix::Z => Ok(i * 1e21), + RawSuffix::Y => Ok(i * 1e24), + } + } + (Some((raw_suffix, false)), &Unit::Iec(false)) + | (Some((raw_suffix, true)), &Unit::Auto) + | (Some((raw_suffix, true)), &Unit::Iec(true)) => match raw_suffix { + RawSuffix::K => Ok(i * IEC_BASES[1]), + RawSuffix::M => Ok(i * IEC_BASES[2]), + RawSuffix::G => Ok(i * IEC_BASES[3]), + RawSuffix::T => Ok(i * IEC_BASES[4]), + RawSuffix::P => Ok(i * IEC_BASES[5]), + RawSuffix::E => Ok(i * IEC_BASES[6]), + RawSuffix::Z => Ok(i * IEC_BASES[7]), + RawSuffix::Y => Ok(i * IEC_BASES[8]), + }, + (_, _) => Err("This suffix is unsupported for specified unit".to_owned()), + } +} + +fn transform_from(s: String, opts: &Transform) -> Result { + let (i, suffix) = parse_suffix(s)?; + remove_suffix(i, suffix, &opts.unit).map(|n| n.round()) +} + +fn consider_suffix(i: f64, u: &Unit) -> Result<(f64, Option)> { + let j = i.abs(); + match *u { + Unit::Si => match j { + _ if j < 1e3 => Ok((i, None)), + _ if j < 1e6 => Ok((i / 1e3, Some((RawSuffix::K, false)))), + _ if j < 1e9 => Ok((i / 1e6, Some((RawSuffix::M, false)))), + _ if j < 1e12 => Ok((i / 1e9, Some((RawSuffix::G, false)))), + _ if j < 1e15 => Ok((i / 1e12, Some((RawSuffix::T, false)))), + _ if j < 1e18 => Ok((i / 1e15, Some((RawSuffix::P, false)))), + _ if j < 1e21 => Ok((i / 1e18, Some((RawSuffix::E, false)))), + _ if j < 1e24 => Ok((i / 1e21, Some((RawSuffix::Z, false)))), + _ if j < 1e27 => Ok((i / 1e24, Some((RawSuffix::Y, false)))), + _ => Err("Number is too big and unsupported".to_owned()), + }, + Unit::Iec(with_i) => match j { + _ if j < IEC_BASES[1] => Ok((i, None)), + _ if j < IEC_BASES[2] => Ok((i / IEC_BASES[1], Some((RawSuffix::K, with_i)))), + _ if j < IEC_BASES[3] => Ok((i / IEC_BASES[2], Some((RawSuffix::M, with_i)))), + _ if j < IEC_BASES[4] => Ok((i / IEC_BASES[3], Some((RawSuffix::G, with_i)))), + _ if j < IEC_BASES[5] => Ok((i / IEC_BASES[4], Some((RawSuffix::T, with_i)))), + _ if j < IEC_BASES[6] => Ok((i / IEC_BASES[5], Some((RawSuffix::P, with_i)))), + _ if j < IEC_BASES[7] => Ok((i / IEC_BASES[6], Some((RawSuffix::E, with_i)))), + _ if j < IEC_BASES[8] => Ok((i / IEC_BASES[7], Some((RawSuffix::Z, with_i)))), + _ if j < IEC_BASES[9] => Ok((i / IEC_BASES[8], Some((RawSuffix::Y, with_i)))), + _ => Err("Number is too big and unsupported".to_owned()), + }, + Unit::Auto => Err("Unit 'auto' isn't supported with --to options".to_owned()), + Unit::None => Ok((i, None)), + } +} + +fn transform_to(s: f64, opts: &Transform) -> Result { + let (i2, s) = consider_suffix(s, &opts.unit)?; + Ok(match s { + None => format!("{}", i2), + Some(s) => format!("{:.1}{}", i2, DisplayableSuffix(s)), + }) +} + +fn format_string(source: String, options: &NumfmtOptions) -> Result { + let number = transform_to( + transform_from(source, &options.transform.from)?, + &options.transform.to, + )?; + + Ok(match options.padding { + p if p == 0 => number, + p if p > 0 => format!("{:>padding$}", number, padding = p as usize), + p => format!("{: Result { + let transform = TransformOptions { + from: Transform { + unit: args + .opt_str("from") + .map(parse_unit) + .unwrap_or(Ok(Unit::None))?, + }, + to: Transform { + unit: args + .opt_str("to") + .map(parse_unit) + .unwrap_or(Ok(Unit::None))?, + }, + }; + + let padding = match args.opt_str("padding") { + Some(s) => s.parse::().map_err(|err| err.to_string()), + None => Ok(0), + }?; + + let header = match args.opt_default("header", "1") { + Some(s) => s.parse::().map_err(|err| err.to_string()), + None => Ok(0), + }?; + + Ok(NumfmtOptions { + transform: transform, + padding: padding, + header: header, + }) +} + +fn handle_args(args: &Vec, options: NumfmtOptions) -> Result<()> { + for l in args { + println!("{}", format_string(l.clone(), &options)?) + } + Ok(()) +} + +fn handle_stdin(options: NumfmtOptions) -> Result<()> { + let stdin = std::io::stdin(); + let locked_stdin = stdin.lock(); + + let mut lines = locked_stdin.lines(); + for l in lines.by_ref().take(options.header) { + l.map(|s| println!("{}", s)).map_err(|e| e.to_string())? + } + + for l in lines { + l.map_err(|e| e.to_string()).and_then(|l| { + let l = format_string(l, &options)?; + Ok(println!("{}", l)) + })? + } + Ok(()) +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + opts.optflag("h", "help", "display this help and exit"); + opts.optflag("V", "version", "output version information and exit"); + opts.optopt( + "", + "from", + "auto-scale input numbers to UNITs; default is 'none'; see UNIT above", + "UNIT", + ); + opts.optopt( + "", + "to", + "auto-scale output numbers to UNITs; see Unit above", + "UNIT", + ); + opts.optopt( + "", + "padding", + "pad the output to N characters; positive N will right-align; negative N will left-align; padding is ignored if the output is wider than N", + "N" + ); + opts.optflagopt( + "", + "header", + "print (without converting) the first N header lines; N defaults to 1 if not specified", + "N", + ); + + let matches = opts.parse(&args[1..]).unwrap(); + if matches.opt_present("help") { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {0} [STRING]... [OPTION]...", NAME); + println!(""); + print!( + "{}", + opts.usage("Convert numbers from/to human-readable strings") + ); + println!( + "UNIT options: + none no auto-scaling is done; suffixes will trigger an error + + auto accept optional single/two letter suffix: + + 1K = 1000, 1Ki = 1024, 1M = 1000000, 1Mi = 1048576, + + si accept optional single letter suffix: + + 1K = 1000, 1M = 1000000, ... + + iec accept optional single letter suffix: + + 1K = 1024, 1M = 1048576, ... + + iec-i accept optional two-letter suffix: + + 1Ki = 1024, 1Mi = 1048576, ..." + ); + + return 0; + } + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + let options = parse_options(&matches).unwrap(); + + if matches.free.len() == 0 { + handle_stdin(options).unwrap() + } else { + handle_args(&matches.free, options).unwrap() + }; + + 0 +} diff --git a/coreutils/src/od/Cargo.toml b/coreutils/src/od/Cargo.toml new file mode 100644 index 000000000..66fa397f1 --- /dev/null +++ b/coreutils/src/od/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "od" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_od" +path = "od.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +byteorder = "1.3.2" +half = "1.1.1" +uucore = "0.0.1" + +[[bin]] +name = "od" +path = "../../uumain.rs" diff --git a/coreutils/src/od/byteorder_io.rs b/coreutils/src/od/byteorder_io.rs new file mode 100644 index 000000000..33f617910 --- /dev/null +++ b/coreutils/src/od/byteorder_io.rs @@ -0,0 +1,50 @@ +// workaround until https://github.com/BurntSushi/byteorder/issues/41 has been fixed +// based on: https://github.com/netvl/immeta/blob/4460ee/src/utils.rs#L76 + +use byteorder::{BigEndian, LittleEndian, NativeEndian}; +use byteorder::ByteOrder as ByteOrderTrait; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ByteOrder { + Little, + Big, + Native, +} + +macro_rules! gen_byte_order_ops { + ($($read_name:ident, $write_name:ident -> $tpe:ty),+) => { + impl ByteOrder { + $( + #[allow(dead_code)] + #[inline] + pub fn $read_name(self, source: &[u8]) -> $tpe { + match self { + ByteOrder::Little => LittleEndian::$read_name(source), + ByteOrder::Big => BigEndian::$read_name(source), + ByteOrder::Native => NativeEndian::$read_name(source), + } + } + + #[allow(dead_code)] + pub fn $write_name(self, target: &mut [u8], n: $tpe) { + match self { + ByteOrder::Little => LittleEndian::$write_name(target, n), + ByteOrder::Big => BigEndian::$write_name(target, n), + ByteOrder::Native => NativeEndian::$write_name(target, n), + } + } + )+ + } + } +} + +gen_byte_order_ops! { + read_u16, write_u16 -> u16, + read_u32, write_u32 -> u32, + read_u64, write_u64 -> u64, + read_i16, write_i16 -> i16, + read_i32, write_i32 -> i32, + read_i64, write_i64 -> i64, + read_f32, write_f32 -> f32, + read_f64, write_f64 -> f64 +} diff --git a/coreutils/src/od/formatteriteminfo.rs b/coreutils/src/od/formatteriteminfo.rs new file mode 100644 index 000000000..29d5cb6d9 --- /dev/null +++ b/coreutils/src/od/formatteriteminfo.rs @@ -0,0 +1,56 @@ +use std::fmt; + +#[derive(Copy)] +pub enum FormatWriter { + IntWriter(fn(u64) -> String), + FloatWriter(fn(f64) -> String), + MultibyteWriter(fn(&[u8]) -> String), +} + +impl Clone for FormatWriter { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +impl PartialEq for FormatWriter { + fn eq(&self, other: &FormatWriter) -> bool { + use formatteriteminfo::FormatWriter::*; + + match (self, other) { + (&IntWriter(ref a), &IntWriter(ref b)) => a == b, + (&FloatWriter(ref a), &FloatWriter(ref b)) => a == b, + (&MultibyteWriter(ref a), &MultibyteWriter(ref b)) => *a as usize == *b as usize, + _ => false, + } + } +} + +impl Eq for FormatWriter {} + +impl fmt::Debug for FormatWriter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &FormatWriter::IntWriter(ref p) => { + f.write_str("IntWriter:")?; + fmt::Pointer::fmt(p, f) + } + &FormatWriter::FloatWriter(ref p) => { + f.write_str("FloatWriter:")?; + fmt::Pointer::fmt(p, f) + } + &FormatWriter::MultibyteWriter(ref p) => { + f.write_str("MultibyteWriter:")?; + fmt::Pointer::fmt(&(*p as *const ()), f) + } + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct FormatterItemInfo { + pub byte_size: usize, + pub print_width: usize, // including a space in front of the text + pub formatter: FormatWriter, +} diff --git a/coreutils/src/od/inputdecoder.rs b/coreutils/src/od/inputdecoder.rs new file mode 100644 index 000000000..cd4a0feec --- /dev/null +++ b/coreutils/src/od/inputdecoder.rs @@ -0,0 +1,205 @@ +use std::io; +use byteorder_io::ByteOrder; +use multifilereader::HasError; +use peekreader::PeekRead; +use half::f16; + +/// Processes an input and provides access to the data read in various formats +/// +/// Currently only useful if the input implements `PeekRead`. +pub struct InputDecoder<'a, I> +where + I: 'a, +{ + /// The input from which data is read + input: &'a mut I, + + /// A memory buffer, it's size is set in `new`. + data: Vec, + /// The number of bytes in the buffer reserved for the peek data from `PeekRead`. + reserved_peek_length: usize, + + /// The number of (valid) bytes in the buffer. + used_normal_length: usize, + /// The number of peek bytes in the buffer. + used_peek_length: usize, + + /// Byte order used to read data from the buffer. + byte_order: ByteOrder, +} + +impl<'a, I> InputDecoder<'a, I> { + /// Creates a new `InputDecoder` with an allocated buffer of `normal_length` + `peek_length` bytes. + /// `byte_order` determines how to read multibyte formats from the buffer. + pub fn new( + input: &mut I, + normal_length: usize, + peek_length: usize, + byte_order: ByteOrder, + ) -> InputDecoder { + let mut bytes: Vec = Vec::with_capacity(normal_length + peek_length); + unsafe { + bytes.set_len(normal_length + peek_length); + } // fast but uninitialized + + InputDecoder { + input: input, + data: bytes, + reserved_peek_length: peek_length, + used_normal_length: 0, + used_peek_length: 0, + byte_order: byte_order, + } + } +} + +impl<'a, I> InputDecoder<'a, I> +where + I: PeekRead, +{ + /// calls `peek_read` on the internal stream to (re)fill the buffer. Returns a + /// MemoryDecoder providing access to the result or returns an i/o error. + pub fn peek_read(&mut self) -> io::Result { + match self.input + .peek_read(self.data.as_mut_slice(), self.reserved_peek_length) + { + Ok((n, p)) => { + self.used_normal_length = n; + self.used_peek_length = p; + Ok(MemoryDecoder { + data: &mut self.data, + used_normal_length: self.used_normal_length, + used_peek_length: self.used_peek_length, + byte_order: self.byte_order, + }) + } + Err(e) => Err(e), + } + } +} + +impl<'a, I> HasError for InputDecoder<'a, I> +where + I: HasError, +{ + /// calls has_error on the internal stream. + fn has_error(&self) -> bool { + self.input.has_error() + } +} + +/// Provides access to the internal data in various formats +pub struct MemoryDecoder<'a> { + /// A reference to the parents' data + data: &'a mut Vec, + /// The number of (valid) bytes in the buffer. + used_normal_length: usize, + /// The number of peek bytes in the buffer. + used_peek_length: usize, + /// Byte order used to read data from the buffer. + byte_order: ByteOrder, +} + +impl<'a> MemoryDecoder<'a> { + /// Set a part of the internal buffer to zero. + /// access to the whole buffer is possible, not just to the valid data. + pub fn zero_out_buffer(&mut self, start: usize, end: usize) { + for i in start..end { + self.data[i] = 0; + } + } + + /// Returns the current length of the buffer. (ie. how much valid data it contains.) + pub fn length(&self) -> usize { + self.used_normal_length + } + + /// Creates a clone of the internal buffer. The clone only contain the valid data. + pub fn clone_buffer(&self, other: &mut Vec) { + other.clone_from(&self.data); + other.resize(self.used_normal_length, 0); + } + + /// Returns a slice to the internal buffer starting at `start`. + pub fn get_buffer(&self, start: usize) -> &[u8] { + &self.data[start..self.used_normal_length] + } + + /// Returns a slice to the internal buffer including the peek data starting at `start`. + pub fn get_full_buffer(&self, start: usize) -> &[u8] { + &self.data[start..self.used_normal_length + self.used_peek_length] + } + + /// Returns a u8/u16/u32/u64 from the internal buffer at position `start`. + pub fn read_uint(&self, start: usize, byte_size: usize) -> u64 { + match byte_size { + 1 => self.data[start] as u64, + 2 => self.byte_order.read_u16(&self.data[start..start + 2]) as u64, + 4 => self.byte_order.read_u32(&self.data[start..start + 4]) as u64, + 8 => self.byte_order.read_u64(&self.data[start..start + 8]), + _ => panic!("Invalid byte_size: {}", byte_size), + } + } + + /// Returns a f32/f64 from the internal buffer at position `start`. + pub fn read_float(&self, start: usize, byte_size: usize) -> f64 { + match byte_size { + 2 => f64::from(f16::from_bits( + self.byte_order.read_u16(&self.data[start..start + 2]), + )), + 4 => self.byte_order.read_f32(&self.data[start..start + 4]) as f64, + 8 => self.byte_order.read_f64(&self.data[start..start + 8]), + _ => panic!("Invalid byte_size: {}", byte_size), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + use peekreader::PeekReader; + use byteorder_io::ByteOrder; + + #[test] + fn smoke_test() { + let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xff, 0xff]; + let mut input = PeekReader::new(Cursor::new(&data)); + let mut sut = InputDecoder::new(&mut input, 8, 2, ByteOrder::Little); + + match sut.peek_read() { + Ok(mut mem) => { + assert_eq!(8, mem.length()); + + assert_eq!(-2.0, mem.read_float(0, 8)); + assert_eq!(-2.0, mem.read_float(4, 4)); + assert_eq!(0xc000000000000000, mem.read_uint(0, 8)); + assert_eq!(0xc0000000, mem.read_uint(4, 4)); + assert_eq!(0xc000, mem.read_uint(6, 2)); + assert_eq!(0xc0, mem.read_uint(7, 1)); + assert_eq!(&[0, 0xc0], mem.get_buffer(6)); + assert_eq!(&[0, 0xc0, 0xff, 0xff], mem.get_full_buffer(6)); + + let mut copy: Vec = Vec::new(); + mem.clone_buffer(&mut copy); + assert_eq!(vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0], copy); + + mem.zero_out_buffer(7, 8); + assert_eq!(&[0, 0, 0xff, 0xff], mem.get_full_buffer(6)); + } + Err(e) => { + assert!(false, e); + } + } + + match sut.peek_read() { + Ok(mem) => { + assert_eq!(2, mem.length()); + assert_eq!(0xffff, mem.read_uint(0, 2)); + } + Err(e) => { + assert!(false, e); + } + } + } +} diff --git a/coreutils/src/od/inputoffset.rs b/coreutils/src/od/inputoffset.rs new file mode 100644 index 000000000..102f1656b --- /dev/null +++ b/coreutils/src/od/inputoffset.rs @@ -0,0 +1,114 @@ +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Radix { + Decimal, + Hexadecimal, + Octal, + NoPrefix, +} + +/// provides the byte offset printed at the left margin +pub struct InputOffset { + /// The radix to print the byte offset. NoPrefix will not print a byte offset. + radix: Radix, + /// The current position. Initialize at `new`, increase using `increase_position`. + byte_pos: usize, + /// An optional label printed in parentheses, typically different from `byte_pos`, + /// but will increase with the same value if `byte_pos` in increased. + label: Option, +} + +impl InputOffset { + /// creates a new `InputOffset` using the provided values. + pub fn new(radix: Radix, byte_pos: usize, label: Option) -> InputOffset { + InputOffset { + radix: radix, + byte_pos: byte_pos, + label: label, + } + } + + /// Increase `byte_pos` and `label` if a label is used. + pub fn increase_position(&mut self, n: usize) { + self.byte_pos += n; + if let Some(l) = self.label { + self.label = Some(l + n); + } + } + + #[cfg(test)] + fn set_radix(&mut self, radix: Radix) { + self.radix = radix; + } + + /// returns a string with the current byte offset + pub fn format_byte_offset(&self) -> String { + match (self.radix, self.label) { + (Radix::Decimal, None) => format!("{:07}", self.byte_pos), + (Radix::Decimal, Some(l)) => format!("{:07} ({:07})", self.byte_pos, l), + (Radix::Hexadecimal, None) => format!("{:06X}", self.byte_pos), + (Radix::Hexadecimal, Some(l)) => format!("{:06X} ({:06X})", self.byte_pos, l), + (Radix::Octal, None) => format!("{:07o}", self.byte_pos), + (Radix::Octal, Some(l)) => format!("{:07o} ({:07o})", self.byte_pos, l), + (Radix::NoPrefix, None) => String::from(""), + (Radix::NoPrefix, Some(l)) => format!("({:07o})", l), + } + } + + /// Prints the byte offset followed by a newline, or nothing at all if + /// both `Radix::NoPrefix` was set and no label (--traditional) is used. + pub fn print_final_offset(&self) { + if self.radix != Radix::NoPrefix || self.label.is_some() { + print!("{}\n", self.format_byte_offset()); + } + } +} + +#[test] +fn test_input_offset() { + let mut sut = InputOffset::new(Radix::Hexadecimal, 10, None); + assert_eq!("00000A", &sut.format_byte_offset()); + sut.increase_position(10); + assert_eq!("000014", &sut.format_byte_offset()); + + // note normally the radix will not change after initialisation + sut.set_radix(Radix::Decimal); + assert_eq!("0000020", &sut.format_byte_offset()); + + sut.set_radix(Radix::Hexadecimal); + assert_eq!("000014", &sut.format_byte_offset()); + + sut.set_radix(Radix::Octal); + assert_eq!("0000024", &sut.format_byte_offset()); + + sut.set_radix(Radix::NoPrefix); + assert_eq!("", &sut.format_byte_offset()); + + sut.increase_position(10); + sut.set_radix(Radix::Octal); + assert_eq!("0000036", &sut.format_byte_offset()); +} + +#[test] +fn test_input_offset_with_label() { + let mut sut = InputOffset::new(Radix::Hexadecimal, 10, Some(20)); + assert_eq!("00000A (000014)", &sut.format_byte_offset()); + sut.increase_position(10); + assert_eq!("000014 (00001E)", &sut.format_byte_offset()); + + // note normally the radix will not change after initialisation + sut.set_radix(Radix::Decimal); + assert_eq!("0000020 (0000030)", &sut.format_byte_offset()); + + sut.set_radix(Radix::Hexadecimal); + assert_eq!("000014 (00001E)", &sut.format_byte_offset()); + + sut.set_radix(Radix::Octal); + assert_eq!("0000024 (0000036)", &sut.format_byte_offset()); + + sut.set_radix(Radix::NoPrefix); + assert_eq!("(0000036)", &sut.format_byte_offset()); + + sut.increase_position(10); + sut.set_radix(Radix::Octal); + assert_eq!("0000036 (0000050)", &sut.format_byte_offset()); +} diff --git a/coreutils/src/od/mockstream.rs b/coreutils/src/od/mockstream.rs new file mode 100644 index 000000000..3523caf58 --- /dev/null +++ b/coreutils/src/od/mockstream.rs @@ -0,0 +1,109 @@ +// https://github.com/lazy-bitfield/rust-mockstream/pull/2 + +use std::io::{Cursor, Error, ErrorKind, Read, Result}; +use std::error::Error as errorError; + +/// `FailingMockStream` mocks a stream which will fail upon read or write +/// +/// # Examples +/// +/// ``` +/// use std::io::{Cursor, Read}; +/// +/// struct CountIo {} +/// +/// impl CountIo { +/// fn read_data(&self, r: &mut Read) -> usize { +/// let mut count: usize = 0; +/// let mut retries = 3; +/// +/// loop { +/// let mut buffer = [0; 5]; +/// match r.read(&mut buffer) { +/// Err(_) => { +/// if retries == 0 { break; } +/// retries -= 1; +/// }, +/// Ok(0) => break, +/// Ok(n) => count += n, +/// } +/// } +/// count +/// } +/// } +/// +/// #[test] +/// fn test_io_retries() { +/// let mut c = Cursor::new(&b"1234"[..]) +/// .chain(FailingMockStream::new(ErrorKind::Other, "Failing", 3)) +/// .chain(Cursor::new(&b"5678"[..])); +/// +/// let sut = CountIo {}; +/// // this will fail unless read_data performs at least 3 retries on I/O errors +/// assert_eq!(8, sut.read_data(&mut c)); +/// } +/// ``` +#[derive(Clone)] +pub struct FailingMockStream { + kind: ErrorKind, + message: &'static str, + repeat_count: i32, +} + +impl FailingMockStream { + /// Creates a FailingMockStream + /// + /// When `read` or `write` is called, it will return an error `repeat_count` times. + /// `kind` and `message` can be specified to define the exact error. + pub fn new(kind: ErrorKind, message: &'static str, repeat_count: i32) -> FailingMockStream { + FailingMockStream { + kind: kind, + message: message, + repeat_count: repeat_count, + } + } + + fn error(&mut self) -> Result { + if self.repeat_count == 0 { + return Ok(0); + } else { + if self.repeat_count > 0 { + self.repeat_count -= 1; + } + Err(Error::new(self.kind, self.message)) + } + } +} + +impl Read for FailingMockStream { + fn read(&mut self, _: &mut [u8]) -> Result { + self.error() + } +} + +#[test] +fn test_failing_mock_stream_read() { + let mut s = FailingMockStream::new(ErrorKind::BrokenPipe, "The dog ate the ethernet cable", 1); + let mut v = [0; 4]; + let error = s.read(v.as_mut()).unwrap_err(); + assert_eq!(error.kind(), ErrorKind::BrokenPipe); + assert_eq!(error.description(), "The dog ate the ethernet cable"); + // after a single error, it will return Ok(0) + assert_eq!(s.read(v.as_mut()).unwrap(), 0); +} + +#[test] +fn test_failing_mock_stream_chain_interrupted() { + let mut c = Cursor::new(&b"abcd"[..]) + .chain(FailingMockStream::new( + ErrorKind::Interrupted, + "Interrupted", + 5, + )) + .chain(Cursor::new(&b"ABCD"[..])); + + let mut v = [0; 8]; + c.read_exact(v.as_mut()).unwrap(); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41, 0x42, 0x43, 0x44]); + assert_eq!(c.read(v.as_mut()).unwrap(), 0); +} diff --git a/coreutils/src/od/multifilereader.rs b/coreutils/src/od/multifilereader.rs new file mode 100644 index 000000000..20dfa1ae1 --- /dev/null +++ b/coreutils/src/od/multifilereader.rs @@ -0,0 +1,209 @@ +use std; +use std::io; +use std::io::BufReader; +use std::fs::File; +use std::vec::Vec; + +pub enum InputSource<'a> { + FileName(&'a str), + Stdin, + #[allow(dead_code)] + Stream(Box), +} + +// MultifileReader - concatenate all our input, file or stdin. +pub struct MultifileReader<'a> { + ni: Vec>, + curr_file: Option>, + any_err: bool, +} + +pub trait HasError { + fn has_error(&self) -> bool; +} + +impl<'b> MultifileReader<'b> { + pub fn new<'a>(fnames: Vec>) -> MultifileReader<'a> { + let mut mf = MultifileReader { + ni: fnames, + curr_file: None, // normally this means done; call next_file() + any_err: false, + }; + mf.next_file(); + mf + } + + fn next_file(&mut self) { + // loop retries with subsequent files if err - normally 'loops' once + loop { + if self.ni.len() == 0 { + self.curr_file = None; + break; + } + match self.ni.remove(0) { + InputSource::Stdin => { + self.curr_file = Some(Box::new(BufReader::new(std::io::stdin()))); + break; + } + InputSource::FileName(fname) => { + match File::open(fname) { + Ok(f) => { + self.curr_file = Some(Box::new(BufReader::new(f))); + break; + } + Err(e) => { + // If any file can't be opened, + // print an error at the time that the file is needed, + // then move on the the next file. + // This matches the behavior of the original `od` + eprintln!( + "{}: '{}': {}", + executable!().split("::").next().unwrap(), // remove module + fname, + e + ); + self.any_err = true + } + } + } + InputSource::Stream(s) => { + self.curr_file = Some(s); + break; + } + } + } + } +} + +impl<'b> io::Read for MultifileReader<'b> { + // Fill buf with bytes read from the list of files + // Returns Ok() + // Handles io errors itself, thus always returns OK + // Fills the provided buffer completely, unless it has run out of input. + // If any call returns short (< buf.len()), all subsequent calls will return Ok<0> + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut xfrd = 0; + // while buffer we are filling is not full.. May go thru several files. + 'fillloop: while xfrd < buf.len() { + match self.curr_file { + None => break, + Some(ref mut curr_file) => { + loop { + // stdin may return on 'return' (enter), even though the buffer isn't full. + xfrd += match curr_file.read(&mut buf[xfrd..]) { + Ok(0) => break, + Ok(n) => n, + Err(e) => { + eprintln!( + "{}: I/O: {}", + executable!().split("::").next().unwrap(), // remove module + e + ); + self.any_err = true; + break; + } + }; + if xfrd == buf.len() { + // transferred all that was asked for. + break 'fillloop; + } + } + } + } + self.next_file(); + } + Ok(xfrd) + } +} + +impl<'b> HasError for MultifileReader<'b> { + fn has_error(&self) -> bool { + self.any_err + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{Cursor, ErrorKind, Read}; + use mockstream::*; + + #[test] + fn test_multi_file_reader_one_read() { + let mut inputs = Vec::new(); + inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..])))); + inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..])))); + let mut v = [0; 10]; + + let mut sut = MultifileReader::new(inputs); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 8); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41, 0x42, 0x43, 0x44, 0, 0]); + assert_eq!(sut.read(v.as_mut()).unwrap(), 0); + } + + #[test] + fn test_multi_file_reader_two_reads() { + let mut inputs = Vec::new(); + inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..])))); + inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..])))); + let mut v = [0; 5]; + + let mut sut = MultifileReader::new(inputs); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 5); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41]); + assert_eq!(sut.read(v.as_mut()).unwrap(), 3); + assert_eq!(v, [0x42, 0x43, 0x44, 0x64, 0x41]); // last two bytes are not overwritten + } + + #[test] + fn test_multi_file_reader_read_error() { + let c = Cursor::new(&b"1234"[..]) + .chain(FailingMockStream::new(ErrorKind::Other, "Failing", 1)) + .chain(Cursor::new(&b"5678"[..])); + let mut inputs = Vec::new(); + inputs.push(InputSource::Stream(Box::new(c))); + inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..])))); + let mut v = [0; 5]; + + let mut sut = MultifileReader::new(inputs); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 5); + assert_eq!(v, [49, 50, 51, 52, 65]); + assert_eq!(sut.read(v.as_mut()).unwrap(), 3); + assert_eq!(v, [66, 67, 68, 52, 65]); // last two bytes are not overwritten + + // note: no retry on i/o error, so 5678 is missing + } + + #[test] + fn test_multi_file_reader_read_error_at_start() { + let mut inputs = Vec::new(); + inputs.push(InputSource::Stream(Box::new(FailingMockStream::new( + ErrorKind::Other, + "Failing", + 1, + )))); + inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..])))); + inputs.push(InputSource::Stream(Box::new(FailingMockStream::new( + ErrorKind::Other, + "Failing", + 1, + )))); + inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..])))); + inputs.push(InputSource::Stream(Box::new(FailingMockStream::new( + ErrorKind::Other, + "Failing", + 1, + )))); + let mut v = [0; 5]; + + let mut sut = MultifileReader::new(inputs); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 5); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41]); + assert_eq!(sut.read(v.as_mut()).unwrap(), 3); + assert_eq!(v, [0x42, 0x43, 0x44, 0x64, 0x41]); // last two bytes are not overwritten + } +} diff --git a/coreutils/src/od/od.rs b/coreutils/src/od/od.rs new file mode 100644 index 000000000..03fd0d55a --- /dev/null +++ b/coreutils/src/od/od.rs @@ -0,0 +1,498 @@ +#![crate_name = "uu_od"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Ben Hirsch + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate byteorder; +extern crate getopts; +extern crate half; + +#[macro_use] +extern crate uucore; + +mod multifilereader; +mod partialreader; +mod peekreader; +mod byteorder_io; +mod formatteriteminfo; +mod prn_int; +mod prn_char; +mod prn_float; +mod parse_nrofbytes; +mod parse_formats; +mod parse_inputs; +mod inputoffset; +mod inputdecoder; +mod output_info; +#[cfg(test)] +mod mockstream; + +use std::cmp; +use byteorder_io::*; +use multifilereader::*; +use partialreader::*; +use peekreader::*; +use formatteriteminfo::*; +use parse_nrofbytes::parse_number_of_bytes; +use parse_formats::{parse_format_flags, ParsedFormatterItemInfo}; +use prn_char::format_ascii_dump; +use parse_inputs::{parse_inputs, CommandLineInputs}; +use inputoffset::{InputOffset, Radix}; +use inputdecoder::{InputDecoder, MemoryDecoder}; +use output_info::OutputInfo; + +static VERSION: &str = env!("CARGO_PKG_VERSION"); +const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes + +static USAGE: &str = r#"Usage: + od [OPTION]... [--] [FILENAME]... + od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]] + od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]] + +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 +commandline. Each format will be printed on a new line. Only the line +containing the first format will be prefixed with the offset. + +If no filename is specified, or it is "-", stdin will be used. After a "--", no +more options will be recognised. This allows for filenames starting with a "-". + +If a filename is a valid number which can be used as an offset in the second +form, you can force it to be recognised as a filename if you include an option +like "-j0", which is only valid in the first form. + +RADIX is one of o,d,x,n for octal, decimal, hexadecimal or none. + +BYTES is decimal by default, octal if prefixed with a "0", or hexadecimal if +prefixed with "0x". The suffixes b, KB, K, MB, M, GB, G, will multiply the +number with 512, 1000, 1024, 1000^2, 1024^2, 1000^3, 1024^3, 1000^2, 1024^2. + +OFFSET and LABEL are octal by default, hexadecimal if prefixed with "0x" or +decimal if a "." suffix is added. The "b" suffix will multiply with 512. + +TYPE contains one or more format specifications consisting of: + a for printable 7-bits ASCII + c for utf-8 characters or octal for undefined characters + d[SIZE] for signed decimal + f[SIZE] for floating point + o[SIZE] for octal + u[SIZE] for unsigned decimal + x[SIZE] for hexadecimal +SIZE is the number of bytes which can be the number 1, 2, 4, 8 or 16, + or C, I, S, L for 1, 2, 4, 8 bytes for integer types, + or F, D, L for 4, 8, 16 bytes for floating point. +Any type specification can have a "z" suffic, which will add a ASCII dump at + the end of the line. + +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 +} + +struct OdOptions { + byte_order: ByteOrder, + skip_bytes: usize, + read_bytes: Option, + label: Option, + input_strings: Vec, + formats: Vec, + line_bytes: usize, + output_duplicates: bool, + radix: Radix, +} + +impl OdOptions { + fn new(matches: getopts::Matches, args: Vec) -> Result { + let byte_order = match matches.opt_str("endian").as_ref().map(String::as_ref) { + None => ByteOrder::Native, + Some("little") => ByteOrder::Little, + Some("big") => ByteOrder::Big, + Some(s) => { + return Err(format!("Invalid argument --endian={}", s)); + } + }; + + let mut skip_bytes = match matches.opt_default("skip-bytes", "0") { + None => 0, + Some(s) => match parse_number_of_bytes(&s) { + Ok(i) => i, + Err(_) => { + return Err(format!("Invalid argument --skip-bytes={}", s)); + } + }, + }; + + let mut label: Option = None; + + let input_strings = match parse_inputs(&matches) { + Ok(CommandLineInputs::FileNames(v)) => v, + Ok(CommandLineInputs::FileAndOffset((f, s, l))) => { + skip_bytes = s; + label = l; + vec![f] + } + Err(e) => { + return Err(format!("Invalid inputs: {}", e)); + } + }; + + let formats = match parse_format_flags(&args) { + Ok(f) => f, + Err(e) => { + return Err(format!("{}", e)); + } + }; + + let mut line_bytes = match matches.opt_default("w", "32") { + None => 16, + Some(s) => match s.parse::() { + Ok(i) => i, + Err(_) => 0, + }, + }; + let min_bytes = formats.iter().fold(1, |max, next| { + cmp::max(max, next.formatter_item_info.byte_size) + }); + if line_bytes == 0 || line_bytes % min_bytes != 0 { + show_warning!("invalid width {}; using {} instead", line_bytes, min_bytes); + line_bytes = min_bytes; + } + + let output_duplicates = matches.opt_present("v"); + + let read_bytes = match matches.opt_str("read-bytes") { + None => None, + Some(s) => match parse_number_of_bytes(&s) { + Ok(i) => Some(i), + Err(_) => { + return Err(format!("Invalid argument --read-bytes={}", s)); + } + }, + }; + + let radix = match matches.opt_str("A") { + None => Radix::Octal, + Some(s) => { + let st = s.into_bytes(); + if st.len() != 1 { + return Err(format!("Radix must be one of [d, o, n, x]")); + } else { + let radix: char = + *(st.get(0).expect("byte string of length 1 lacks a 0th elem")) as char; + match radix { + 'd' => Radix::Decimal, + 'x' => Radix::Hexadecimal, + 'o' => Radix::Octal, + 'n' => Radix::NoPrefix, + _ => return Err(format!("Radix must be one of [d, o, n, x]")), + } + } + } + }; + + Ok(OdOptions { + byte_order: byte_order, + skip_bytes: skip_bytes, + read_bytes: read_bytes, + label: label, + input_strings: input_strings, + formats: formats, + line_bytes: line_bytes, + output_duplicates: output_duplicates, + radix: radix, + }) + } +} + +/// parses and validates commandline parameters, prepares data structures, +/// opens the input and calls `odfunc` to process the input. +pub fn uumain(args: Vec) -> i32 { + let opts = create_getopts_options(); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => { + disp_err!("{}", f); + return 1; + } + }; + + if matches.opt_present("help") { + println!("{}", opts.usage(&USAGE)); + return 0; + } + if matches.opt_present("version") { + println!("{} {}", executable!(), VERSION); + return 0; + } + + let od_options = match OdOptions::new(matches, args) { + Err(s) => { + disp_err!("{}", s); + return 1; + } + Ok(o) => o, + }; + + let mut input_offset = + InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label); + + let mut input = open_input_peek_reader( + &od_options.input_strings, + od_options.skip_bytes, + od_options.read_bytes, + ); + let mut input_decoder = InputDecoder::new( + &mut input, + od_options.line_bytes, + PEEK_BUFFER_SIZE, + od_options.byte_order, + ); + + let output_info = OutputInfo::new( + od_options.line_bytes, + &od_options.formats[..], + od_options.output_duplicates, + ); + + odfunc(&mut input_offset, &mut input_decoder, &output_info) +} + +/// Loops through the input line by line, calling print_bytes to take care of the output. +fn odfunc( + input_offset: &mut InputOffset, + input_decoder: &mut InputDecoder, + output_info: &OutputInfo, +) -> i32 +where + I: PeekRead + HasError, +{ + let mut duplicate_line = false; + let mut previous_bytes: Vec = Vec::new(); + let line_bytes = output_info.byte_size_line; + + loop { + // print each line data (or multi-format raster of several lines describing the same data). + + match input_decoder.peek_read() { + Ok(mut memory_decoder) => { + let length = memory_decoder.length(); + + if length == 0 { + input_offset.print_final_offset(); + break; + } + + // not enough byte for a whole element, this should only happen on the last line. + if length != line_bytes { + // set zero bytes in the part of the buffer that will be used, but is not filled. + let mut max_used = length + output_info.byte_size_block; + if max_used > line_bytes { + max_used = line_bytes; + } + + memory_decoder.zero_out_buffer(length, max_used); + } + + if !output_info.output_duplicates && length == line_bytes + && memory_decoder.get_buffer(0) == &previous_bytes[..] + { + if !duplicate_line { + duplicate_line = true; + println!("*"); + } + } else { + duplicate_line = false; + if length == line_bytes { + // save a copy of the input unless it is the last line + memory_decoder.clone_buffer(&mut previous_bytes); + } + + print_bytes( + &input_offset.format_byte_offset(), + &memory_decoder, + &output_info, + ); + } + + input_offset.increase_position(length); + } + Err(e) => { + show_error!("{}", e); + input_offset.print_final_offset(); + return 1; + } + }; + } + + if input_decoder.has_error() { + 1 + } else { + 0 + } +} + +/// Outputs a single line of input, into one or more lines human readable output. +fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &OutputInfo) { + let mut first = true; // First line of a multi-format raster. + for f in output_info.spaced_formatters_iter() { + let mut output_text = String::new(); + + let mut b = 0; + while b < input_decoder.length() { + output_text.push_str(&format!( + "{:>width$}", + "", + width = f.spacing[b % output_info.byte_size_block] + )); + + match f.formatter_item_info.formatter { + FormatWriter::IntWriter(func) => { + let p = input_decoder.read_uint(b, f.formatter_item_info.byte_size); + output_text.push_str(&func(p)); + } + FormatWriter::FloatWriter(func) => { + let p = input_decoder.read_float(b, f.formatter_item_info.byte_size); + output_text.push_str(&func(p)); + } + FormatWriter::MultibyteWriter(func) => { + output_text.push_str(&func(input_decoder.get_full_buffer(b))); + } + } + + b += f.formatter_item_info.byte_size; + } + + if f.add_ascii_dump { + let missing_spacing = output_info + .print_width_line + .saturating_sub(output_text.chars().count()); + output_text.push_str(&format!( + "{:>width$} {}", + "", + format_ascii_dump(input_decoder.get_buffer(0)), + width = missing_spacing + )); + } + + if first { + print!("{}", prefix); // print offset + // if printing in multiple formats offset is printed only once + first = false; + } else { + // this takes the space of the file offset on subsequent + // lines of multi-format rasters. + print!("{:>width$}", "", width = prefix.chars().count()); + } + print!("{}\n", output_text); + } +} + +/// returns a reader implementing `PeekRead + Read + HasError` providing the combined input +/// +/// `skip_bytes` is the number of bytes skipped from the input +/// `read_bytes` is an optional limit to the number of bytes to read +fn open_input_peek_reader<'a>( + input_strings: &'a Vec, + skip_bytes: usize, + read_bytes: Option, +) -> PeekReader>> { + // should return "impl PeekRead + Read + HasError" when supported in (stable) rust + let inputs = input_strings + .iter() + .map(|w| match w as &str { + "-" => InputSource::Stdin, + x => InputSource::FileName(x), + }) + .collect::>(); + + let mf = MultifileReader::new(inputs); + let pr = PartialReader::new(mf, skip_bytes, read_bytes); + let input = PeekReader::new(pr); + input +} diff --git a/coreutils/src/od/output_info.rs b/coreutils/src/od/output_info.rs new file mode 100644 index 000000000..2ce6596d2 --- /dev/null +++ b/coreutils/src/od/output_info.rs @@ -0,0 +1,445 @@ +use std::cmp; +use std::slice::Iter; +use parse_formats::ParsedFormatterItemInfo; +use formatteriteminfo::FormatterItemInfo; + +/// Size in bytes of the max datatype. ie set to 16 for 128-bit numbers. +const MAX_BYTES_PER_UNIT: usize = 8; + +/// Contains information to output single output line in human readable form +pub struct SpacedFormatterItemInfo { + /// Contains a function pointer to output data, and information about the output format. + pub formatter_item_info: FormatterItemInfo, + /// Contains the number of spaces to add to align data with other output formats. + /// + /// If the corresponding data is a single byte, each entry in this array contains + /// the number of spaces to insert when outputting each byte. If the corresponding + /// data is multi-byte, only the fist byte position is used. For example a 32-bit + /// datatype, could use positions 0, 4, 8, 12, .... + /// As each block is formatted identically, only the spacing for a single block is set. + pub spacing: [usize; MAX_BYTES_PER_UNIT], + /// if set adds a ascii dump at the end of the line + pub add_ascii_dump: bool, +} + +/// Contains information about all output lines. +pub struct OutputInfo { + /// The number of bytes of a line. + pub byte_size_line: usize, + /// The width of a line in human readable format. + pub print_width_line: usize, + + /// The number of bytes in a block. (This is the size of the largest datatype in `spaced_formatters`.) + pub byte_size_block: usize, + /// The width of a block in human readable format. (The size of the largest format.) + pub print_width_block: usize, + /// All formats. + spaced_formatters: Vec, + /// determines if duplicate output lines should be printed, or + /// skipped with a "*" showing one or more skipped lines. + pub output_duplicates: bool, +} + +impl OutputInfo { + /// Returns an iterator over the `SpacedFormatterItemInfo` vector. + pub fn spaced_formatters_iter(&self) -> Iter { + self.spaced_formatters.iter() + } + + /// Creates a new `OutputInfo` based on the parameters + pub fn new( + line_bytes: usize, + formats: &[ParsedFormatterItemInfo], + output_duplicates: bool, + ) -> OutputInfo { + let byte_size_block = formats.iter().fold(1, |max, next| { + cmp::max(max, next.formatter_item_info.byte_size) + }); + let print_width_block = formats.iter().fold(1, |max, next| { + cmp::max( + max, + next.formatter_item_info.print_width + * (byte_size_block / next.formatter_item_info.byte_size), + ) + }); + let print_width_line = print_width_block * (line_bytes / byte_size_block); + + let spaced_formatters = + OutputInfo::create_spaced_formatter_info(&formats, byte_size_block, print_width_block); + + OutputInfo { + byte_size_line: line_bytes, + print_width_line: print_width_line, + byte_size_block: byte_size_block, + print_width_block: print_width_block, + spaced_formatters: spaced_formatters, + output_duplicates: output_duplicates, + } + } + + fn create_spaced_formatter_info( + formats: &[ParsedFormatterItemInfo], + byte_size_block: usize, + print_width_block: usize, + ) -> Vec { + formats + .iter() + .map(|f| SpacedFormatterItemInfo { + formatter_item_info: f.formatter_item_info, + add_ascii_dump: f.add_ascii_dump, + spacing: OutputInfo::calculate_alignment(f, byte_size_block, print_width_block), + }) + .collect() + } + + /// calculates proper alignment for a single line of output + /// + /// Multiple representations of the same data, will be right-aligned for easy reading. + /// For example a 64 bit octal and a 32-bit decimal with a 16-bit hexadecimal looks like this: + /// ``` + /// 1777777777777777777777 1777777777777777777777 + /// 4294967295 4294967295 4294967295 4294967295 + /// ffff ffff ffff ffff ffff ffff ffff ffff + /// ``` + /// In this example is additional spacing before the first and third decimal number, + /// and there is additional spacing before the 1st, 3rd, 5th and 7th hexadecimal number. + /// This way both the octal and decimal, as well as the decimal and hexadecimal numbers + /// left align. Note that the alignment below both octal numbers is identical. + /// + /// This function calculates the required spacing for a single line, given the size + /// of a block, and the width of a block. The size of a block is the largest type + /// and the width is width of the the type which needs the most space to print that + /// number of bytes. So both numbers might refer to different types. All widths + /// include a space at the front. For example the width of a 8-bit hexadecimal, + /// is 3 characters, for example " FF". + /// + /// This algorithm first calculates how many spaces needs to be added, based the + /// block size and the size of the type, and the widths of the block and the type. + /// The required spaces are spread across the available positions. + /// If the blocksize is 8, and the size of the type is 8 too, there will be just + /// one value in a block, so all spacing will be assigned to position 0. + /// If the blocksize is 8, and the size of the type is 2, the spacing will be + /// spread across position 0, 2, 4, 6. All 4 positions will get an additional + /// space as long as there are more then 4 spaces available. If there are 2 + /// spaces available, they will be assigned to position 0 and 4. If there is + /// 1 space available, it will be assigned to position 0. This will be combined, + /// For example 7 spaces will be assigned to position 0, 2, 4, 6 like: 3, 1, 2, 1. + /// And 7 spaces with 2 positions will be assigned to position 0 and 4 like 4, 3. + /// + /// Here is another example showing the alignment of 64-bit unsigned decimal numbers, + /// 32-bit hexadecimal number, 16-bit octal numbers and 8-bit hexadecimal numbers: + /// ``` + /// 18446744073709551615 18446744073709551615 + /// ffffffff ffffffff ffffffff ffffffff + /// 177777 177777 177777 177777 177777 177777 177777 177777 + /// ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + /// ``` + /// + /// This algorithm assumes the size of all types is a power of 2 (1, 2, 4, 8, 16, ...) + /// Increase MAX_BYTES_PER_UNIT to allow larger types. + fn calculate_alignment( + sf: &TypeSizeInfo, + byte_size_block: usize, + print_width_block: usize, + ) -> [usize; MAX_BYTES_PER_UNIT] { + if byte_size_block > MAX_BYTES_PER_UNIT { + panic!( + "{}-bits types are unsupported. Current max={}-bits.", + 8 * byte_size_block, + 8 * MAX_BYTES_PER_UNIT + ); + } + let mut spacing = [0; MAX_BYTES_PER_UNIT]; + + let mut byte_size = sf.byte_size(); + let mut items_in_block = byte_size_block / byte_size; + let thisblock_width = sf.print_width() * items_in_block; + let mut missing_spacing = print_width_block - thisblock_width; + + while items_in_block > 0 { + let avg_spacing: usize = missing_spacing / items_in_block; + for i in 0..items_in_block { + spacing[i * byte_size] += avg_spacing; + missing_spacing -= avg_spacing; + } + + items_in_block /= 2; + byte_size *= 2; + } + + spacing + } +} + +trait TypeSizeInfo { + fn byte_size(&self) -> usize; + fn print_width(&self) -> usize; +} + +impl TypeSizeInfo for ParsedFormatterItemInfo { + fn byte_size(&self) -> usize { + self.formatter_item_info.byte_size + } + fn print_width(&self) -> usize { + self.formatter_item_info.print_width + } +} + +#[cfg(test)] +struct TypeInfo { + byte_size: usize, + print_width: usize, +} + +#[cfg(test)] +impl TypeSizeInfo for TypeInfo { + fn byte_size(&self) -> usize { + self.byte_size + } + fn print_width(&self) -> usize { + self.print_width + } +} + +#[test] +fn test_calculate_alignment() { + // For this example `byte_size_block` is 8 and 'print_width_block' is 23: + // 1777777777777777777777 1777777777777777777777 + // 4294967295 4294967295 4294967295 4294967295 + // ffff ffff ffff ffff ffff ffff ffff ffff + + // the first line has no additional spacing: + assert_eq!( + [0, 0, 0, 0, 0, 0, 0, 0], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 8, + print_width: 23, + }, + 8, + 23 + ) + ); + // the second line a single space at the start of the block: + assert_eq!( + [1, 0, 0, 0, 0, 0, 0, 0], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 4, + print_width: 11, + }, + 8, + 23 + ) + ); + // the third line two spaces at pos 0, and 1 space at pos 4: + assert_eq!( + [2, 0, 0, 0, 1, 0, 0, 0], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 2, + print_width: 5, + }, + 8, + 23 + ) + ); + + // For this example `byte_size_block` is 8 and 'print_width_block' is 28: + // 18446744073709551615 18446744073709551615 + // ffffffff ffffffff ffffffff ffffffff + // 177777 177777 177777 177777 177777 177777 177777 177777 + // ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + + assert_eq!( + [7, 0, 0, 0, 0, 0, 0, 0], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 8, + print_width: 21, + }, + 8, + 28 + ) + ); + assert_eq!( + [5, 0, 0, 0, 5, 0, 0, 0], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 4, + print_width: 9, + }, + 8, + 28 + ) + ); + assert_eq!( + [0, 0, 0, 0, 0, 0, 0, 0], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 2, + print_width: 7, + }, + 8, + 28 + ) + ); + assert_eq!( + [1, 0, 1, 0, 1, 0, 1, 0], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 3, + }, + 8, + 28 + ) + ); + + // 9 tests where 8 .. 16 spaces are spread across 8 positions + assert_eq!( + [1, 1, 1, 1, 1, 1, 1, 1], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 2, + }, + 8, + 16 + 8 + ) + ); + assert_eq!( + [2, 1, 1, 1, 1, 1, 1, 1], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 2, + }, + 8, + 16 + 9 + ) + ); + assert_eq!( + [2, 1, 1, 1, 2, 1, 1, 1], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 2, + }, + 8, + 16 + 10 + ) + ); + assert_eq!( + [3, 1, 1, 1, 2, 1, 1, 1], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 2, + }, + 8, + 16 + 11 + ) + ); + assert_eq!( + [2, 1, 2, 1, 2, 1, 2, 1], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 2, + }, + 8, + 16 + 12 + ) + ); + assert_eq!( + [3, 1, 2, 1, 2, 1, 2, 1], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 2, + }, + 8, + 16 + 13 + ) + ); + assert_eq!( + [3, 1, 2, 1, 3, 1, 2, 1], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 2, + }, + 8, + 16 + 14 + ) + ); + assert_eq!( + [4, 1, 2, 1, 3, 1, 2, 1], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 2, + }, + 8, + 16 + 15 + ) + ); + assert_eq!( + [2, 2, 2, 2, 2, 2, 2, 2], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 2, + }, + 8, + 16 + 16 + ) + ); + + // 4 tests where 15 spaces are spread across 8, 4, 2 or 1 position(s) + assert_eq!( + [4, 1, 2, 1, 3, 1, 2, 1], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 1, + print_width: 2, + }, + 8, + 16 + 15 + ) + ); + assert_eq!( + [5, 0, 3, 0, 4, 0, 3, 0], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 2, + print_width: 4, + }, + 8, + 16 + 15 + ) + ); + assert_eq!( + [8, 0, 0, 0, 7, 0, 0, 0], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 4, + print_width: 8, + }, + 8, + 16 + 15 + ) + ); + assert_eq!( + [15, 0, 0, 0, 0, 0, 0, 0], + OutputInfo::calculate_alignment( + &TypeInfo { + byte_size: 8, + print_width: 16, + }, + 8, + 16 + 15 + ) + ); +} diff --git a/coreutils/src/od/parse_formats.rs b/coreutils/src/od/parse_formats.rs new file mode 100644 index 000000000..f8c3659f0 --- /dev/null +++ b/coreutils/src/od/parse_formats.rs @@ -0,0 +1,559 @@ +use formatteriteminfo::FormatterItemInfo; +use prn_int::*; +use prn_char::*; +use prn_float::*; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct ParsedFormatterItemInfo { + pub formatter_item_info: FormatterItemInfo, + pub add_ascii_dump: bool, +} + +impl ParsedFormatterItemInfo { + pub fn new( + formatter_item_info: FormatterItemInfo, + add_ascii_dump: bool, + ) -> ParsedFormatterItemInfo { + ParsedFormatterItemInfo { + formatter_item_info: formatter_item_info, + add_ascii_dump: add_ascii_dump, + } + } +} + +fn od_argument_traditional_format(ch: char) -> Option { + match ch { + 'a' => Some(FORMAT_ITEM_A), + 'B' => Some(FORMAT_ITEM_OCT16), + 'b' => Some(FORMAT_ITEM_OCT8), + 'c' => Some(FORMAT_ITEM_C), + 'D' => Some(FORMAT_ITEM_DEC32U), + 'd' => Some(FORMAT_ITEM_DEC16U), + 'e' => Some(FORMAT_ITEM_F64), + 'F' => Some(FORMAT_ITEM_F64), + 'f' => Some(FORMAT_ITEM_F32), + 'H' => Some(FORMAT_ITEM_HEX32), + 'h' => Some(FORMAT_ITEM_HEX16), + 'i' => Some(FORMAT_ITEM_DEC32S), + 'I' => Some(FORMAT_ITEM_DEC64S), + 'L' => Some(FORMAT_ITEM_DEC64S), + 'l' => Some(FORMAT_ITEM_DEC64S), + 'O' => Some(FORMAT_ITEM_OCT32), + 'o' => Some(FORMAT_ITEM_OCT16), + 's' => Some(FORMAT_ITEM_DEC16S), + 'X' => Some(FORMAT_ITEM_HEX32), + 'x' => Some(FORMAT_ITEM_HEX16), + _ => None, + } +} + +fn od_format_type(type_char: FormatType, byte_size: u8) -> Option { + match (type_char, byte_size) { + (FormatType::Ascii, _) => Some(FORMAT_ITEM_A), + (FormatType::Char, _) => Some(FORMAT_ITEM_C), + + (FormatType::DecimalInt, 1) => Some(FORMAT_ITEM_DEC8S), + (FormatType::DecimalInt, 2) => Some(FORMAT_ITEM_DEC16S), + (FormatType::DecimalInt, 0) | (FormatType::DecimalInt, 4) => Some(FORMAT_ITEM_DEC32S), + (FormatType::DecimalInt, 8) => Some(FORMAT_ITEM_DEC64S), + + (FormatType::OctalInt, 1) => Some(FORMAT_ITEM_OCT8), + (FormatType::OctalInt, 2) => Some(FORMAT_ITEM_OCT16), + (FormatType::OctalInt, 0) | (FormatType::OctalInt, 4) => Some(FORMAT_ITEM_OCT32), + (FormatType::OctalInt, 8) => Some(FORMAT_ITEM_OCT64), + + (FormatType::UnsignedInt, 1) => Some(FORMAT_ITEM_DEC8U), + (FormatType::UnsignedInt, 2) => Some(FORMAT_ITEM_DEC16U), + (FormatType::UnsignedInt, 0) | (FormatType::UnsignedInt, 4) => Some(FORMAT_ITEM_DEC32U), + (FormatType::UnsignedInt, 8) => Some(FORMAT_ITEM_DEC64U), + + (FormatType::HexadecimalInt, 1) => Some(FORMAT_ITEM_HEX8), + (FormatType::HexadecimalInt, 2) => Some(FORMAT_ITEM_HEX16), + (FormatType::HexadecimalInt, 0) | (FormatType::HexadecimalInt, 4) => { + Some(FORMAT_ITEM_HEX32) + } + (FormatType::HexadecimalInt, 8) => Some(FORMAT_ITEM_HEX64), + + (FormatType::Float, 2) => Some(FORMAT_ITEM_F16), + (FormatType::Float, 0) | (FormatType::Float, 4) => Some(FORMAT_ITEM_F32), + (FormatType::Float, 8) => Some(FORMAT_ITEM_F64), + + _ => None, + } +} + +fn od_argument_with_option(ch: char) -> bool { + match ch { + 'A' | 'j' | 'N' | 'S' | 'w' => true, + _ => false, + } +} + +/// Parses format flags from commandline +/// +/// getopts, docopt, clap don't seem suitable to parse the commandline +/// arguments used for formats. In particular arguments can appear +/// multiple times and the order they appear in, is significant. +/// +/// arguments like -f, -o, -x can appear separate or combined: -fox +/// it can also be mixed with non format related flags like -v: -fvox +/// arguments with parameters like -w16 can only appear at the end: -fvoxw16 +/// parameters of -t/--format specify 1 or more formats. +/// if -- appears on the commandline, parsing should stop. +pub fn parse_format_flags(args: &Vec) -> Result, String> { + let mut formats = Vec::new(); + + // args[0] is the name of the binary + let mut arg_iter = args.iter().skip(1); + let mut expect_type_string = false; + + while let Some(arg) = arg_iter.next() { + if expect_type_string { + match parse_type_string(arg) { + Ok(v) => formats.extend(v.into_iter()), + Err(e) => return Err(e), + } + expect_type_string = false; + } else if arg.starts_with("--") { + if arg.len() == 2 { + break; + } + if arg.starts_with("--format=") { + let params: String = arg.chars().skip_while(|c| *c != '=').skip(1).collect(); + match parse_type_string(¶ms) { + Ok(v) => formats.extend(v.into_iter()), + Err(e) => return Err(e), + } + } + if arg == "--format" { + expect_type_string = true; + } + } else if arg.starts_with("-") { + let mut flags = arg.chars().skip(1); + let mut format_spec = String::new(); + while let Some(c) = flags.next() { + if expect_type_string { + format_spec.push(c); + } else if od_argument_with_option(c) { + break; + } else if c == 't' { + expect_type_string = true; + } else { + // not every option is a format + if let Some(r) = od_argument_traditional_format(c) { + formats.push(ParsedFormatterItemInfo::new(r, false)) + } + } + } + if !format_spec.is_empty() { + match parse_type_string(&format_spec) { + Ok(v) => formats.extend(v.into_iter()), + Err(e) => return Err(e), + } + expect_type_string = false; + } + } + } + if expect_type_string { + return Err(format!( + "missing format specification after '--format' / '-t'" + )); + } + + if formats.is_empty() { + formats.push(ParsedFormatterItemInfo::new(FORMAT_ITEM_OCT16, false)); // 2 byte octal is the default + } + + Ok(formats) +} + +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +enum FormatType { + Ascii, + Char, + DecimalInt, + OctalInt, + UnsignedInt, + HexadecimalInt, + Float, +} + +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +enum FormatTypeCategory { + Char, + Integer, + Float, +} + +fn format_type(ch: char) -> Option { + match ch { + 'a' => Some(FormatType::Ascii), + 'c' => Some(FormatType::Char), + 'd' => Some(FormatType::DecimalInt), + 'o' => Some(FormatType::OctalInt), + 'u' => Some(FormatType::UnsignedInt), + 'x' => Some(FormatType::HexadecimalInt), + 'f' => Some(FormatType::Float), + _ => None, + } +} + +fn format_type_category(t: FormatType) -> FormatTypeCategory { + match t { + FormatType::Ascii | FormatType::Char => FormatTypeCategory::Char, + FormatType::DecimalInt + | FormatType::OctalInt + | FormatType::UnsignedInt + | FormatType::HexadecimalInt => FormatTypeCategory::Integer, + FormatType::Float => FormatTypeCategory::Float, + } +} + +fn is_format_size_char( + ch: Option, + format_type: FormatTypeCategory, + byte_size: &mut u8, +) -> bool { + match (format_type, ch) { + (FormatTypeCategory::Integer, Some('C')) => { + *byte_size = 1; + true + } + (FormatTypeCategory::Integer, Some('S')) => { + *byte_size = 2; + true + } + (FormatTypeCategory::Integer, Some('I')) => { + *byte_size = 4; + true + } + (FormatTypeCategory::Integer, Some('L')) => { + *byte_size = 8; + true + } + + (FormatTypeCategory::Float, Some('F')) => { + *byte_size = 4; + true + } + (FormatTypeCategory::Float, Some('D')) => { + *byte_size = 8; + true + } + // FormatTypeCategory::Float, 'L' => *byte_size = 16, // TODO support f128 + _ => false, + } +} + +fn is_format_size_decimal( + ch: Option, + format_type: FormatTypeCategory, + decimal_size: &mut String, +) -> bool { + if format_type == FormatTypeCategory::Char { + return false; + } + match ch { + Some(d) if d.is_digit(10) => { + decimal_size.push(d); + return true; + } + _ => false, + } +} + +fn is_format_dump_char(ch: Option, show_ascii_dump: &mut bool) -> bool { + match ch { + Some('z') => { + *show_ascii_dump = true; + return true; + } + _ => false, + } +} + +fn parse_type_string(params: &String) -> Result, String> { + let mut formats = Vec::new(); + + let mut chars = params.chars(); + let mut ch = chars.next(); + + while ch.is_some() { + let type_char = ch.unwrap(); + let type_char = match format_type(type_char) { + Some(t) => t, + None => { + return Err(format!( + "unexpected char '{}' in format specification '{}'", + type_char, params + )); + } + }; + + let type_cat = format_type_category(type_char); + + ch = chars.next(); + + let mut byte_size = 0u8; + let mut show_ascii_dump = false; + if is_format_size_char(ch, type_cat, &mut byte_size) { + ch = chars.next(); + } else { + let mut decimal_size = String::new(); + while is_format_size_decimal(ch, type_cat, &mut decimal_size) { + ch = chars.next(); + } + if !decimal_size.is_empty() { + byte_size = match decimal_size.parse() { + Err(_) => { + return Err(format!( + "invalid number '{}' in format specification '{}'", + decimal_size, params + )) + } + Ok(n) => n, + } + } + } + if is_format_dump_char(ch, &mut show_ascii_dump) { + ch = chars.next(); + } + + match od_format_type(type_char, byte_size) { + Some(ft) => formats.push(ParsedFormatterItemInfo::new(ft, show_ascii_dump)), + None => { + return Err(format!( + "invalid size '{}' in format specification '{}'", + byte_size, params + )) + } + } + } + + Ok(formats) +} + +#[cfg(test)] +pub fn parse_format_flags_str( + args_str: &Vec<&'static str>, +) -> Result, String> { + let args = args_str.iter().map(|s| s.to_string()).collect(); + match parse_format_flags(&args) { + Err(e) => Err(e), + Ok(v) => { + // tests using this function assume add_ascii_dump is not set + Ok(v.into_iter() + .inspect(|f| assert!(!f.add_ascii_dump)) + .map(|f| f.formatter_item_info) + .collect()) + } + } +} + +#[test] +fn test_no_options() { + assert_eq!( + parse_format_flags_str(&vec!["od"]).unwrap(), + vec![FORMAT_ITEM_OCT16] + ); +} + +#[test] +fn test_one_option() { + assert_eq!( + parse_format_flags_str(&vec!["od", "-F"]).unwrap(), + vec![FORMAT_ITEM_F64] + ); +} + +#[test] +fn test_two_separate_options() { + assert_eq!( + parse_format_flags_str(&vec!["od", "-F", "-x"]).unwrap(), + vec![FORMAT_ITEM_F64, FORMAT_ITEM_HEX16] + ); +} + +#[test] +fn test_two_combined_options() { + assert_eq!( + parse_format_flags_str(&vec!["od", "-Fx"]).unwrap(), + vec![FORMAT_ITEM_F64, FORMAT_ITEM_HEX16] + ); +} + +#[test] +fn test_ignore_non_format_parameters() { + assert_eq!( + parse_format_flags_str(&vec!["od", "-d", "-Ax"]).unwrap(), + vec![FORMAT_ITEM_DEC16U] + ); +} + +#[test] +fn test_ignore_separate_parameters() { + assert_eq!( + parse_format_flags_str(&vec!["od", "-I", "-A", "x"]).unwrap(), + vec![FORMAT_ITEM_DEC64S] + ); +} + +#[test] +fn test_ignore_trailing_vals() { + assert_eq!( + parse_format_flags_str(&vec!["od", "-D", "--", "-x"]).unwrap(), + vec![FORMAT_ITEM_DEC32U] + ); +} + +#[test] +fn test_invalid_long_format() { + parse_format_flags_str(&vec!["od", "--format=X"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=xX"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=aC"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=fI"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=xD"]).unwrap_err(); + + parse_format_flags_str(&vec!["od", "--format=xC1"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=x1C"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=xz1"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=xzC"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=xzz"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=xCC"]).unwrap_err(); + + parse_format_flags_str(&vec!["od", "--format=c1"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=x256"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=d5"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format=f1"]).unwrap_err(); +} + +#[test] +fn test_long_format_a() { + assert_eq!( + parse_format_flags_str(&vec!["od", "--format=a"]).unwrap(), + vec![FORMAT_ITEM_A] + ); +} + +#[test] +fn test_long_format_cz() { + assert_eq!( + parse_format_flags(&vec!["od".to_string(), "--format=cz".to_string()]).unwrap(), + vec![ParsedFormatterItemInfo::new(FORMAT_ITEM_C, true)] + ); +} + +#[test] +fn test_long_format_d() { + assert_eq!( + parse_format_flags_str(&vec!["od", "--format=d8"]).unwrap(), + vec![FORMAT_ITEM_DEC64S] + ); +} + +#[test] +fn test_long_format_d_default() { + assert_eq!( + parse_format_flags_str(&vec!["od", "--format=d"]).unwrap(), + vec![FORMAT_ITEM_DEC32S] + ); +} + +#[test] +fn test_long_format_o_default() { + assert_eq!( + parse_format_flags_str(&vec!["od", "--format=o"]).unwrap(), + vec![FORMAT_ITEM_OCT32] + ); +} + +#[test] +fn test_long_format_u_default() { + assert_eq!( + parse_format_flags_str(&vec!["od", "--format=u"]).unwrap(), + vec![FORMAT_ITEM_DEC32U] + ); +} + +#[test] +fn test_long_format_x_default() { + assert_eq!( + parse_format_flags_str(&vec!["od", "--format=x"]).unwrap(), + vec![FORMAT_ITEM_HEX32] + ); +} + +#[test] +fn test_long_format_f_default() { + assert_eq!( + parse_format_flags_str(&vec!["od", "--format=f"]).unwrap(), + vec![FORMAT_ITEM_F32] + ); +} + +#[test] +fn test_long_format_next_arg() { + assert_eq!( + parse_format_flags_str(&vec!["od", "--format", "f8"]).unwrap(), + vec![FORMAT_ITEM_F64] + ); +} + +#[test] +fn test_short_format_next_arg() { + assert_eq!( + parse_format_flags_str(&vec!["od", "-t", "x8"]).unwrap(), + vec![FORMAT_ITEM_HEX64] + ); +} + +#[test] +fn test_short_format_combined_arg() { + assert_eq!( + parse_format_flags_str(&vec!["od", "-tu8"]).unwrap(), + vec![FORMAT_ITEM_DEC64U] + ); +} + +#[test] +fn test_format_next_arg_invalid() { + parse_format_flags_str(&vec!["od", "--format", "-v"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "--format"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "-t", "-v"]).unwrap_err(); + parse_format_flags_str(&vec!["od", "-t"]).unwrap_err(); +} + +#[test] +fn test_mixed_formats() { + assert_eq!( + parse_format_flags(&vec![ + "od".to_string(), + "--skip-bytes=2".to_string(), + "-vItu1z".to_string(), + "-N".to_string(), + "1000".to_string(), + "-xt".to_string(), + "acdx1".to_string(), + "--format=u2c".to_string(), + "--format".to_string(), + "f".to_string(), + "-xAx".to_string(), + "--".to_string(), + "-h".to_string(), + "--format=f8".to_string(), + ]).unwrap(), + vec![ + ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC64S, false), // I + ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC8U, true), // tu1z + ParsedFormatterItemInfo::new(FORMAT_ITEM_HEX16, false), // x + ParsedFormatterItemInfo::new(FORMAT_ITEM_A, false), // ta + ParsedFormatterItemInfo::new(FORMAT_ITEM_C, false), // tc + ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC32S, false), // td + ParsedFormatterItemInfo::new(FORMAT_ITEM_HEX8, false), // tx1 + ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC16U, false), // tu2 + ParsedFormatterItemInfo::new(FORMAT_ITEM_C, false), // tc + ParsedFormatterItemInfo::new(FORMAT_ITEM_F32, false), // tf + ParsedFormatterItemInfo::new(FORMAT_ITEM_HEX16, false), // x + ] + ); +} diff --git a/coreutils/src/od/parse_inputs.rs b/coreutils/src/od/parse_inputs.rs new file mode 100644 index 000000000..63af6eba5 --- /dev/null +++ b/coreutils/src/od/parse_inputs.rs @@ -0,0 +1,383 @@ +use getopts::Matches; + +/// Abstraction for getopts +pub trait CommandLineOpts { + /// returns all commandline parameters which do not belong to an option. + fn inputs(&self) -> Vec; + /// 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() + } + fn opts_present(&self, opts: &[&str]) -> bool { + self.opts_present(&opts.iter().map(|s| s.to_string()).collect::>()) + } +} + +/// Contains the Input filename(s) with an optional offset. +/// +/// `FileNames` is used for one or more file inputs ("-" = stdin) +/// `FileAndOffset` is used for a single file input, with an offset +/// and an optional label. Offset and label are specified in bytes. +/// `FileAndOffset` will be only used if an offset is specified, +/// but it might be 0. +#[derive(PartialEq, Debug)] +pub enum CommandLineInputs { + FileNames(Vec), + FileAndOffset((String, usize, Option)), +} + +/// Interprets the commandline inputs of od. +/// +/// Returns either an unspecified number of filenames. +/// Or it will return a single filename, with an offset and optional label. +/// Offset and label are specified in bytes. +/// '-' 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: &CommandLineOpts) -> Result { + let mut input_strings: Vec = matches.inputs(); + + if matches.opts_present(&["traditional"]) { + return parse_inputs_traditional(input_strings); + } + + // test if commandline contains: [file] + // fall-through if no (valid) offset is found + if input_strings.len() == 1 || input_strings.len() == 2 { + // if any of the options -A, -j, -N, -t, -v or -w are present there is no offset + if !matches.opts_present(&["A", "j", "N", "t", "v", "w"]) { + // test if the last input can be parsed as an offset. + let offset = parse_offset_operand(&input_strings[input_strings.len() - 1]); + match offset { + Ok(n) => { + // if there is just 1 input (stdin), an offset must start with '+' + if input_strings.len() == 1 && input_strings[0].starts_with("+") { + return Ok(CommandLineInputs::FileAndOffset(("-".to_string(), n, None))); + } + if input_strings.len() == 2 { + return Ok(CommandLineInputs::FileAndOffset(( + input_strings[0].clone(), + n, + None, + ))); + } + } + _ => { + // if it cannot be parsed, it is considered a filename + } + } + } + } + + if input_strings.len() == 0 { + input_strings.push("-".to_string()); + } + Ok(CommandLineInputs::FileNames(input_strings)) +} + +/// interprets inputs when --traditional is on the commandline +/// +/// normally returns CommandLineInputs::FileAndOffset, but if no offset is found, +/// it returns CommandLineInputs::FileNames (also to differentiate from the offset == 0) +pub fn parse_inputs_traditional(input_strings: Vec) -> 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), + }) + } + 2 => { + let offset0 = parse_offset_operand(&input_strings[0]); + let offset1 = parse_offset_operand(&input_strings[1]); + match (offset0, offset1) { + (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( + "-".to_string(), + n, + Some(m), + ))), + (_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( + input_strings[0].clone(), + m, + None, + ))), + _ => Err(format!("invalid offset: {}", input_strings[1])), + } + } + 3 => { + let offset = parse_offset_operand(&input_strings[1]); + let label = parse_offset_operand(&input_strings[2]); + match (offset, label) { + (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( + input_strings[0].clone(), + n, + Some(m), + ))), + (Err(_), _) => Err(format!("invalid offset: {}", input_strings[1])), + (_, Err(_)) => Err(format!("invalid label: {}", input_strings[2])), + } + } + _ => Err(format!( + "too many inputs after --traditional: {}", + input_strings[3] + )), + } +} + +/// parses format used by offset and label on the commandline +pub fn parse_offset_operand(s: &String) -> Result { + let mut start = 0; + let mut len = s.len(); + let mut radix = 8; + let mut multiply = 1; + + if s.starts_with("+") { + start += 1; + } + + if s[start..len].starts_with("0x") || s[start..len].starts_with("0X") { + start += 2; + radix = 16; + } else { + if s[start..len].ends_with("b") { + len -= 1; + multiply = 512; + } + if s[start..len].ends_with(".") { + len -= 1; + radix = 10; + } + } + match usize::from_str_radix(&s[start..len], radix) { + Ok(i) => Ok(i * multiply), + Err(_) => Err("parse failed"), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A mock for the commandline options type + /// + /// `inputs` are all commandline parameters which do not belong to an option. + /// `option_names` are the names of the options on the commandline. + struct MockOptions<'a> { + inputs: Vec, + option_names: Vec<&'a str>, + } + + impl<'a> MockOptions<'a> { + fn new(inputs: Vec<&'a str>, option_names: Vec<&'a str>) -> MockOptions<'a> { + MockOptions { + inputs: inputs.iter().map(|s| s.to_string()).collect::>(), + option_names: option_names, + } + } + } + + impl<'a> CommandLineOpts for MockOptions<'a> { + fn inputs(&self) -> Vec { + self.inputs.clone() + } + fn opts_present(&self, opts: &[&str]) -> bool { + for expected in opts.iter() { + for actual in self.option_names.iter() { + if *expected == *actual { + return true; + } + } + } + false + } + } + + #[test] + fn test_parse_inputs_normal() { + assert_eq!( + CommandLineInputs::FileNames(vec!["-".to_string()]), + parse_inputs(&MockOptions::new(vec![], vec![])).unwrap() + ); + + assert_eq!( + CommandLineInputs::FileNames(vec!["-".to_string()]), + parse_inputs(&MockOptions::new(vec!["-"], vec![])).unwrap() + ); + + assert_eq!( + CommandLineInputs::FileNames(vec!["file1".to_string()]), + parse_inputs(&MockOptions::new(vec!["file1"], vec![])).unwrap() + ); + + assert_eq!( + CommandLineInputs::FileNames(vec!["file1".to_string(), "file2".to_string()]), + parse_inputs(&MockOptions::new(vec!["file1", "file2"], vec![])).unwrap() + ); + + assert_eq!( + CommandLineInputs::FileNames(vec![ + "-".to_string(), + "file1".to_string(), + "file2".to_string(), + ]), + parse_inputs(&MockOptions::new(vec!["-", "file1", "file2"], vec![])).unwrap() + ); + } + + #[test] + fn test_parse_inputs_with_offset() { + // offset is found without filename, so stdin will be used. + assert_eq!( + CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)), + parse_inputs(&MockOptions::new(vec!["+10"], vec![])).unwrap() + ); + + // offset must start with "+" if no input is specified. + assert_eq!( + CommandLineInputs::FileNames(vec!["10".to_string()]), + parse_inputs(&MockOptions::new(vec!["10"], vec![""])).unwrap() + ); + + // offset is not valid, so it is considered a filename. + assert_eq!( + CommandLineInputs::FileNames(vec!["+10a".to_string()]), + parse_inputs(&MockOptions::new(vec!["+10a"], vec![""])).unwrap() + ); + + // if -j is included in the commandline, there cannot be an offset. + assert_eq!( + CommandLineInputs::FileNames(vec!["+10".to_string()]), + parse_inputs(&MockOptions::new(vec!["+10"], vec!["j"])).unwrap() + ); + + // if -v is included in the commandline, there cannot be an offset. + assert_eq!( + CommandLineInputs::FileNames(vec!["+10".to_string()]), + parse_inputs(&MockOptions::new(vec!["+10"], vec!["o", "v"])).unwrap() + ); + + assert_eq!( + CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)), + parse_inputs(&MockOptions::new(vec!["file1", "+10"], vec![])).unwrap() + ); + + // offset does not need to start with "+" if a filename is included. + assert_eq!( + CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)), + parse_inputs(&MockOptions::new(vec!["file1", "10"], vec![])).unwrap() + ); + + assert_eq!( + CommandLineInputs::FileNames(vec!["file1".to_string(), "+10a".to_string()]), + parse_inputs(&MockOptions::new(vec!["file1", "+10a"], vec![""])).unwrap() + ); + + assert_eq!( + CommandLineInputs::FileNames(vec!["file1".to_string(), "+10".to_string()]), + parse_inputs(&MockOptions::new(vec!["file1", "+10"], vec!["j"])).unwrap() + ); + + // offset must be last on the commandline + assert_eq!( + CommandLineInputs::FileNames(vec!["+10".to_string(), "file1".to_string()]), + parse_inputs(&MockOptions::new(vec!["+10", "file1"], vec![""])).unwrap() + ); + } + + #[test] + fn test_parse_inputs_traditional() { + // it should not return FileAndOffset to signal no offset was entered on the commandline. + assert_eq!( + CommandLineInputs::FileNames(vec!["-".to_string()]), + parse_inputs(&MockOptions::new(vec![], vec!["traditional"])).unwrap() + ); + + assert_eq!( + CommandLineInputs::FileNames(vec!["file1".to_string()]), + parse_inputs(&MockOptions::new(vec!["file1"], vec!["traditional"])).unwrap() + ); + + // offset does not need to start with a + + assert_eq!( + CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)), + parse_inputs(&MockOptions::new(vec!["10"], vec!["traditional"])).unwrap() + ); + + // valid offset and valid label + assert_eq!( + CommandLineInputs::FileAndOffset(("-".to_string(), 8, Some(8))), + parse_inputs(&MockOptions::new(vec!["10", "10"], vec!["traditional"])).unwrap() + ); + + assert_eq!( + CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)), + parse_inputs(&MockOptions::new(vec!["file1", "10"], vec!["traditional"])).unwrap() + ); + + // only one file is allowed, it must be the first + parse_inputs(&MockOptions::new(vec!["10", "file1"], vec!["traditional"])).unwrap_err(); + + assert_eq!( + CommandLineInputs::FileAndOffset(("file1".to_string(), 8, Some(8))), + parse_inputs(&MockOptions::new( + vec!["file1", "10", "10"], + vec!["traditional"] + )).unwrap() + ); + + parse_inputs(&MockOptions::new( + vec!["10", "file1", "10"], + vec!["traditional"], + )).unwrap_err(); + + parse_inputs(&MockOptions::new( + vec!["10", "10", "file1"], + vec!["traditional"], + )).unwrap_err(); + + parse_inputs(&MockOptions::new( + vec!["10", "10", "10", "10"], + vec!["traditional"], + )).unwrap_err(); + } + + fn parse_offset_operand_str(s: &str) -> Result { + parse_offset_operand(&String::from(s)) + } + + #[test] + fn test_parse_offset_operand_invalid() { + parse_offset_operand_str("").unwrap_err(); + parse_offset_operand_str("a").unwrap_err(); + parse_offset_operand_str("+").unwrap_err(); + parse_offset_operand_str("+b").unwrap_err(); + parse_offset_operand_str("0x1.").unwrap_err(); + parse_offset_operand_str("0x1.b").unwrap_err(); + parse_offset_operand_str("-").unwrap_err(); + parse_offset_operand_str("-1").unwrap_err(); + parse_offset_operand_str("1e10").unwrap_err(); + } + + #[test] + fn test_parse_offset_operand() { + assert_eq!(8, parse_offset_operand_str("10").unwrap()); // default octal + assert_eq!(0, parse_offset_operand_str("0").unwrap()); + assert_eq!(8, parse_offset_operand_str("+10").unwrap()); // optional leading '+' + assert_eq!(16, parse_offset_operand_str("0x10").unwrap()); // hex + assert_eq!(16, parse_offset_operand_str("0X10").unwrap()); // hex + assert_eq!(16, parse_offset_operand_str("+0X10").unwrap()); // hex + assert_eq!(10, parse_offset_operand_str("10.").unwrap()); // decimal + assert_eq!(10, parse_offset_operand_str("+10.").unwrap()); // decimal + assert_eq!(4096, parse_offset_operand_str("10b").unwrap()); // b suffix = *512 + assert_eq!(4096, parse_offset_operand_str("+10b").unwrap()); // b suffix = *512 + assert_eq!(5120, parse_offset_operand_str("10.b").unwrap()); // b suffix = *512 + assert_eq!(5120, parse_offset_operand_str("+10.b").unwrap()); // b suffix = *512 + assert_eq!(267, parse_offset_operand_str("0x10b").unwrap()); // hex + } +} diff --git a/coreutils/src/od/parse_nrofbytes.rs b/coreutils/src/od/parse_nrofbytes.rs new file mode 100644 index 000000000..c934de9c5 --- /dev/null +++ b/coreutils/src/od/parse_nrofbytes.rs @@ -0,0 +1,133 @@ +pub fn parse_number_of_bytes(s: &String) -> Result { + let mut start = 0; + let mut len = s.len(); + let mut radix = 10; + let mut multiply = 1; + + if s.starts_with("0x") || s.starts_with("0X") { + start = 2; + radix = 16; + } else if s.starts_with("0") { + radix = 8; + } + + let mut ends_with = s.chars().rev(); + match ends_with.next() { + Some('b') if radix != 16 => { + multiply = 512; + len -= 1; + } + Some('k') | Some('K') => { + multiply = 1024; + len -= 1; + } + Some('m') | Some('M') => { + multiply = 1024 * 1024; + len -= 1; + } + Some('G') => { + multiply = 1024 * 1024 * 1024; + len -= 1; + } + #[cfg(target_pointer_width = "64")] + Some('T') => { + multiply = 1024 * 1024 * 1024 * 1024; + len -= 1; + } + #[cfg(target_pointer_width = "64")] + Some('P') => { + multiply = 1024 * 1024 * 1024 * 1024 * 1024; + len -= 1; + } + #[cfg(target_pointer_width = "64")] + Some('E') => { + multiply = 1024 * 1024 * 1024 * 1024 * 1024 * 1024; + len -= 1; + } + Some('B') if radix != 16 => { + len -= 2; + multiply = match ends_with.next() { + Some('k') | Some('K') => 1000, + Some('m') | Some('M') => 1000 * 1000, + Some('G') => 1000 * 1000 * 1000, + #[cfg(target_pointer_width = "64")] + Some('T') => 1000 * 1000 * 1000 * 1000, + #[cfg(target_pointer_width = "64")] + Some('P') => 1000 * 1000 * 1000 * 1000 * 1000, + #[cfg(target_pointer_width = "64")] + Some('E') => 1000 * 1000 * 1000 * 1000 * 1000 * 1000, + _ => return Err("parse failed"), + } + } + _ => {} + } + + match usize::from_str_radix(&s[start..len], radix) { + Ok(i) => Ok(i * multiply), + Err(_) => Err("parse failed"), + } +} + +#[allow(dead_code)] +fn parse_number_of_bytes_str(s: &str) -> Result { + parse_number_of_bytes(&String::from(s)) +} + +#[test] +fn test_parse_number_of_bytes() { + // normal decimal numbers + assert_eq!(0, parse_number_of_bytes_str("0").unwrap()); + assert_eq!(5, parse_number_of_bytes_str("5").unwrap()); + assert_eq!(999, parse_number_of_bytes_str("999").unwrap()); + assert_eq!(2 * 512, parse_number_of_bytes_str("2b").unwrap()); + assert_eq!(2 * 1024, parse_number_of_bytes_str("2k").unwrap()); + assert_eq!(4 * 1024, parse_number_of_bytes_str("4K").unwrap()); + assert_eq!(2 * 1048576, parse_number_of_bytes_str("2m").unwrap()); + assert_eq!(4 * 1048576, parse_number_of_bytes_str("4M").unwrap()); + assert_eq!(1073741824, parse_number_of_bytes_str("1G").unwrap()); + assert_eq!(2000, parse_number_of_bytes_str("2kB").unwrap()); + assert_eq!(4000, parse_number_of_bytes_str("4KB").unwrap()); + assert_eq!(2000000, parse_number_of_bytes_str("2mB").unwrap()); + assert_eq!(4000000, parse_number_of_bytes_str("4MB").unwrap()); + assert_eq!(2000000000, parse_number_of_bytes_str("2GB").unwrap()); + + // octal input + assert_eq!(8, parse_number_of_bytes_str("010").unwrap()); + assert_eq!(8 * 512, parse_number_of_bytes_str("010b").unwrap()); + assert_eq!(8 * 1024, parse_number_of_bytes_str("010k").unwrap()); + assert_eq!(8 * 1048576, parse_number_of_bytes_str("010m").unwrap()); + + // hex input + assert_eq!(15, parse_number_of_bytes_str("0xf").unwrap()); + assert_eq!(15, parse_number_of_bytes_str("0XF").unwrap()); + assert_eq!(27, parse_number_of_bytes_str("0x1b").unwrap()); + assert_eq!(16 * 1024, parse_number_of_bytes_str("0x10k").unwrap()); + assert_eq!(16 * 1048576, parse_number_of_bytes_str("0x10m").unwrap()); + + // invalid input + parse_number_of_bytes_str("").unwrap_err(); + parse_number_of_bytes_str("-1").unwrap_err(); + parse_number_of_bytes_str("1e2").unwrap_err(); + parse_number_of_bytes_str("xyz").unwrap_err(); + parse_number_of_bytes_str("b").unwrap_err(); + parse_number_of_bytes_str("1Y").unwrap_err(); + parse_number_of_bytes_str("∞").unwrap_err(); +} + +#[test] +#[cfg(target_pointer_width = "64")] +fn test_parse_number_of_bytes_64bits() { + assert_eq!(1099511627776, parse_number_of_bytes_str("1T").unwrap()); + assert_eq!(1125899906842624, parse_number_of_bytes_str("1P").unwrap()); + assert_eq!( + 1152921504606846976, + parse_number_of_bytes_str("1E").unwrap() + ); + + assert_eq!(2000000000000, parse_number_of_bytes_str("2TB").unwrap()); + assert_eq!(2000000000000000, parse_number_of_bytes_str("2PB").unwrap()); + assert_eq!( + 2000000000000000000, + parse_number_of_bytes_str("2EB").unwrap() + ); +} diff --git a/coreutils/src/od/partialreader.rs b/coreutils/src/od/partialreader.rs new file mode 100644 index 000000000..42bca6428 --- /dev/null +++ b/coreutils/src/od/partialreader.rs @@ -0,0 +1,209 @@ +use std::cmp; +use std::io; +use std::io::Read; +use multifilereader::HasError; + +/// When a large number of bytes must be skipped, it will be read into a +/// dynamically allocated buffer. The buffer will be limited to this size. +const MAX_SKIP_BUFFER: usize = 64 * 1024; + +/// Wrapper for `std::io::Read` which can skip bytes at the beginning +/// of the input, and it can limit the returned bytes to a particular +/// number of bytes. +pub struct PartialReader { + inner: R, + skip: usize, + limit: Option, +} + +impl PartialReader { + /// Create a new `PartialReader` wrapping `inner`, which will skip + /// `skip` bytes, and limits the output to `limit` bytes. Set `limit` + /// to `None` if there should be no limit. + pub fn new(inner: R, skip: usize, limit: Option) -> Self { + PartialReader { + inner: inner, + skip: skip, + limit: limit, + } + } +} + +impl Read for PartialReader { + fn read(&mut self, out: &mut [u8]) -> io::Result { + if self.skip > 0 { + let buf_size = cmp::min(self.skip, MAX_SKIP_BUFFER); + let mut bytes: Vec = Vec::with_capacity(buf_size); + unsafe { + bytes.set_len(buf_size); + } + + while self.skip > 0 { + let skip_count = cmp::min(self.skip, buf_size); + + match self.inner.read_exact(&mut bytes[..skip_count]) { + Err(e) => return Err(e), + Ok(()) => self.skip -= skip_count, + } + } + } + match self.limit { + None => self.inner.read(out), + Some(0) => Ok(0), + Some(ref mut limit) => { + let slice = if *limit > out.len() { + out + } else { + &mut out[0..*limit] + }; + match self.inner.read(slice) { + Err(e) => Err(e), + Ok(r) => { + *limit -= r; + Ok(r) + } + } + } + } + } +} + +impl HasError for PartialReader { + fn has_error(&self) -> bool { + self.inner.has_error() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{Cursor, ErrorKind, Read}; + use std::error::Error; + use mockstream::*; + + #[test] + fn test_read_without_limits() { + let mut v = [0; 10]; + let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 0, None); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 8); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0]); + } + + #[test] + fn test_read_without_limits_with_error() { + let mut v = [0; 10]; + let f = FailingMockStream::new(ErrorKind::PermissionDenied, "No access", 3); + let mut sut = PartialReader::new(f, 0, None); + + let error = sut.read(v.as_mut()).unwrap_err(); + assert_eq!(error.kind(), ErrorKind::PermissionDenied); + assert_eq!(error.description(), "No access"); + } + + #[test] + fn test_read_skipping_bytes() { + let mut v = [0; 10]; + let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 2, None); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 6); + assert_eq!(v, [0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0, 0, 0]); + } + + #[test] + fn test_read_skipping_all() { + let mut v = [0; 10]; + let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 20, None); + + let error = sut.read(v.as_mut()).unwrap_err(); + assert_eq!(error.kind(), ErrorKind::UnexpectedEof); + } + + #[test] + fn test_read_skipping_with_error() { + let mut v = [0; 10]; + let f = FailingMockStream::new(ErrorKind::PermissionDenied, "No access", 3); + let mut sut = PartialReader::new(f, 2, None); + + let error = sut.read(v.as_mut()).unwrap_err(); + assert_eq!(error.kind(), ErrorKind::PermissionDenied); + assert_eq!(error.description(), "No access"); + } + + #[test] + fn test_read_skipping_with_two_reads_during_skip() { + let mut v = [0; 10]; + let c = Cursor::new(&b"a"[..]).chain(Cursor::new(&b"bcdefgh"[..])); + let mut sut = PartialReader::new(c, 2, None); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 6); + assert_eq!(v, [0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0, 0, 0]); + } + + #[test] + fn test_read_skipping_huge_number() { + let mut v = [0; 10]; + // test if it does not eat all memory.... + let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), usize::max_value(), None); + + sut.read(v.as_mut()).unwrap_err(); + } + + #[test] + fn test_read_limitting_all() { + let mut v = [0; 10]; + let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 0, Some(0)); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 0); + } + + #[test] + fn test_read_limitting() { + let mut v = [0; 10]; + let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 0, Some(6)); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 6); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0, 0, 0, 0]); + } + + #[test] + fn test_read_limitting_with_error() { + let mut v = [0; 10]; + let f = FailingMockStream::new(ErrorKind::PermissionDenied, "No access", 3); + let mut sut = PartialReader::new(f, 0, Some(6)); + + let error = sut.read(v.as_mut()).unwrap_err(); + assert_eq!(error.kind(), ErrorKind::PermissionDenied); + assert_eq!(error.description(), "No access"); + } + + #[test] + fn test_read_limitting_with_large_limit() { + let mut v = [0; 10]; + let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 0, Some(20)); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 8); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0]); + } + + #[test] + fn test_read_limitting_with_multiple_reads() { + let mut v = [0; 3]; + let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 0, Some(6)); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 3); + assert_eq!(v, [0x61, 0x62, 0x63]); + assert_eq!(sut.read(v.as_mut()).unwrap(), 3); + assert_eq!(v, [0x64, 0x65, 0x66]); + assert_eq!(sut.read(v.as_mut()).unwrap(), 0); + } + + #[test] + fn test_read_skipping_and_limitting() { + let mut v = [0; 10]; + let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 2, Some(4)); + + assert_eq!(sut.read(v.as_mut()).unwrap(), 4); + assert_eq!(v, [0x63, 0x64, 0x65, 0x66, 0, 0, 0, 0, 0, 0]); + } +} diff --git a/coreutils/src/od/peekreader.rs b/coreutils/src/od/peekreader.rs new file mode 100644 index 000000000..825335f20 --- /dev/null +++ b/coreutils/src/od/peekreader.rs @@ -0,0 +1,212 @@ +//! Contains the trait `PeekRead` and type `PeekReader` implementing it. + +use std::io; +use std::io::{Read, Write}; +use multifilereader::HasError; + +/// A trait which supplies a function to peek into a stream without +/// actually reading it. +/// +/// Like `std::io::Read`, it allows to read data from a stream, with +/// the additional possibility to reserve a part of the returned data +/// with the data which will be read in subsequent calls. +/// +pub trait PeekRead { + /// Reads data into a buffer. + /// + /// Fills `out` with data. The last `peek_size` bytes of `out` are + /// used for data which keeps available on subsequent calls. + /// `peek_size` must be smaller or equal to the size of `out`. + /// + /// Returns a tuple where the first number is the number of bytes + /// read from the stream, and the second number is the number of + /// bytes additionally read. Any of the numbers might be zero. + /// It can also return an error. + /// + /// A type implementing this trait, will typically also implement + /// `std::io::Read`. + /// + /// # Panics + /// Might panic if `peek_size` is larger then the size of `out` + fn peek_read(&mut self, out: &mut [u8], peek_size: usize) -> io::Result<(usize, usize)>; +} + +/// Wrapper for `std::io::Read` allowing to peek into the data to be read. +pub struct PeekReader { + inner: R, + temp_buffer: Vec, +} + +impl PeekReader { + /// Create a new `PeekReader` wrapping `inner` + pub fn new(inner: R) -> Self { + PeekReader { + inner: inner, + temp_buffer: Vec::new(), + } + } +} + +impl PeekReader { + fn read_from_tempbuffer(&mut self, mut out: &mut [u8]) -> usize { + match out.write(self.temp_buffer.as_mut_slice()) { + Ok(n) => { + self.temp_buffer.drain(..n); + n + } + Err(_) => 0, + } + } + + fn write_to_tempbuffer(&mut self, bytes: &[u8]) { + // if temp_buffer is not empty, data has to be inserted in front + let org_buffer: Vec<_> = self.temp_buffer.drain(..).collect(); + self.temp_buffer.write(bytes).unwrap(); + self.temp_buffer.extend(org_buffer); + } +} + +impl Read for PeekReader { + fn read(&mut self, out: &mut [u8]) -> io::Result { + let start_pos = self.read_from_tempbuffer(out); + match self.inner.read(&mut out[start_pos..]) { + Err(e) => Err(e), + Ok(n) => Ok(n + start_pos), + } + } +} + +impl PeekRead for PeekReader { + /// Reads data into a buffer. + /// + /// See `PeekRead::peek_read`. + /// + /// # Panics + /// If `peek_size` is larger then the size of `out` + fn peek_read(&mut self, out: &mut [u8], peek_size: usize) -> io::Result<(usize, usize)> { + assert!(out.len() >= peek_size); + match self.read(out) { + Err(e) => Err(e), + Ok(bytes_in_buffer) => { + let unused = out.len() - bytes_in_buffer; + if peek_size <= unused { + Ok((bytes_in_buffer, 0)) + } else { + let actual_peek_size = peek_size - unused; + let real_size = bytes_in_buffer - actual_peek_size; + self.write_to_tempbuffer(&out[real_size..bytes_in_buffer]); + Ok((real_size, actual_peek_size)) + } + } + } + } +} + +impl HasError for PeekReader { + fn has_error(&self) -> bool { + self.inner.has_error() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{Cursor, Read}; + + #[test] + fn test_read_normal() { + let mut sut = PeekReader::new(Cursor::new(&b"abcdefgh"[..])); + + let mut v = [0; 10]; + assert_eq!(sut.read(v.as_mut()).unwrap(), 8); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0]); + } + + #[test] + fn test_peek_read_without_buffer() { + let mut sut = PeekReader::new(Cursor::new(&b"abcdefgh"[..])); + + let mut v = [0; 10]; + assert_eq!(sut.peek_read(v.as_mut(), 0).unwrap(), (8, 0)); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0]); + } + + #[test] + fn test_peek_read_and_read() { + let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..])); + + let mut v = [0; 8]; + assert_eq!(sut.peek_read(v.as_mut(), 4).unwrap(), (4, 4)); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]); + + let mut v2 = [0; 8]; + assert_eq!(sut.read(v2.as_mut()).unwrap(), 6); + assert_eq!(v2, [0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0, 0]); + } + + #[test] + fn test_peek_read_multiple_times() { + let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..])); + + let mut s1 = [0; 8]; + assert_eq!(sut.peek_read(s1.as_mut(), 4).unwrap(), (4, 4)); + assert_eq!(s1, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]); + + let mut s2 = [0; 8]; + assert_eq!(sut.peek_read(s2.as_mut(), 4).unwrap(), (4, 2)); + assert_eq!(s2, [0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0, 0]); + + let mut s3 = [0; 8]; + assert_eq!(sut.peek_read(s3.as_mut(), 4).unwrap(), (2, 0)); + assert_eq!(s3, [0x69, 0x6a, 0, 0, 0, 0, 0, 0]); + } + + #[test] + fn test_peek_read_and_read_with_small_buffer() { + let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..])); + + let mut v = [0; 8]; + assert_eq!(sut.peek_read(v.as_mut(), 4).unwrap(), (4, 4)); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]); + + let mut v2 = [0; 2]; + assert_eq!(sut.read(v2.as_mut()).unwrap(), 2); + assert_eq!(v2, [0x65, 0x66]); + assert_eq!(sut.read(v2.as_mut()).unwrap(), 2); + assert_eq!(v2, [0x67, 0x68]); + assert_eq!(sut.read(v2.as_mut()).unwrap(), 2); + assert_eq!(v2, [0x69, 0x6a]); + } + + #[test] + fn test_peek_read_with_smaller_buffer() { + let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..])); + + let mut v = [0; 8]; + assert_eq!(sut.peek_read(v.as_mut(), 4).unwrap(), (4, 4)); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]); + + let mut v2 = [0; 2]; + assert_eq!(sut.peek_read(v2.as_mut(), 2).unwrap(), (0, 2)); + assert_eq!(v2, [0x65, 0x66]); + assert_eq!(sut.peek_read(v2.as_mut(), 0).unwrap(), (2, 0)); + assert_eq!(v2, [0x65, 0x66]); + assert_eq!(sut.peek_read(v2.as_mut(), 0).unwrap(), (2, 0)); + assert_eq!(v2, [0x67, 0x68]); + assert_eq!(sut.peek_read(v2.as_mut(), 0).unwrap(), (2, 0)); + assert_eq!(v2, [0x69, 0x6a]); + } + + #[test] + fn test_peek_read_peek_with_larger_peek_buffer() { + let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..])); + + let mut v = [0; 8]; + assert_eq!(sut.peek_read(v.as_mut(), 4).unwrap(), (4, 4)); + assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]); + + let mut v2 = [0; 8]; + assert_eq!(sut.peek_read(v2.as_mut(), 8).unwrap(), (0, 6)); + assert_eq!(v2, [0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0, 0]); + } +} diff --git a/coreutils/src/od/prn_char.rs b/coreutils/src/od/prn_char.rs new file mode 100644 index 000000000..4caf068fa --- /dev/null +++ b/coreutils/src/od/prn_char.rs @@ -0,0 +1,154 @@ +use std::str::from_utf8; +use formatteriteminfo::*; + +pub static FORMAT_ITEM_A: FormatterItemInfo = FormatterItemInfo { + byte_size: 1, + print_width: 4, + formatter: FormatWriter::IntWriter(format_item_a), +}; + +pub static FORMAT_ITEM_C: FormatterItemInfo = FormatterItemInfo { + byte_size: 1, + print_width: 4, + formatter: FormatWriter::MultibyteWriter(format_item_c), +}; + +static A_CHRS: [&str; 128] = [ + "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", "bs", "ht", "nl", "vt", "ff", "cr", + "so", "si", "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb", "can", "em", "sub", "esc", + "fs", "gs", "rs", "us", "sp", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", + ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", + "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", + "z", "{", "|", "}", "~", "del", +]; + +fn format_item_a(p: u64) -> String { + // itembytes == 1 + let b = (p & 0x7f) as u8; + format!("{:>4}", A_CHRS.get(b as usize).unwrap_or(&"??")) +} + +static C_CHRS: [&'static str; 128] = [ + "\\0", "001", "002", "003", "004", "005", "006", "\\a", "\\b", "\\t", "\\n", "\\v", "\\f", + "\\r", "016", "017", "020", "021", "022", "023", "024", "025", "026", "027", "030", "031", + "032", "033", "034", "035", "036", "037", " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", + "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", + "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", + "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", + "v", "w", "x", "y", "z", "{", "|", "}", "~", "177", +]; + +fn format_item_c(bytes: &[u8]) -> String { + // itembytes == 1 + let b = bytes[0]; + + if b & 0x80 == 0x00 { + match C_CHRS.get(b as usize) { + Some(s) => format!("{:>4}", s), + None => format!("{:>4}", b), + } + } else if (b & 0xc0) == 0x80 { + // second or subsequent octet of an utf-8 sequence + String::from(" **") + } else if ((b & 0xe0) == 0xc0) && (bytes.len() >= 2) { + // start of a 2 octet utf-8 sequence + match from_utf8(&bytes[0..2]) { + Ok(s) => format!("{:>4}", s), + Err(_) => format!(" {:03o}", b), + } + } else if ((b & 0xf0) == 0xe0) && (bytes.len() >= 3) { + // start of a 3 octet utf-8 sequence + match from_utf8(&bytes[0..3]) { + Ok(s) => format!("{:>4}", s), + Err(_) => format!(" {:03o}", b), + } + } else if ((b & 0xf8) == 0xf0) && (bytes.len() >= 4) { + // start of a 4 octet utf-8 sequence + match from_utf8(&bytes[0..4]) { + Ok(s) => format!("{:>4}", s), + Err(_) => format!(" {:03o}", b), + } + } else { + // invalid utf-8 + format!(" {:03o}", b) + } +} + +pub fn format_ascii_dump(bytes: &[u8]) -> String { + let mut result = String::new(); + + result.push('>'); + for c in bytes.iter() { + if *c >= 0x20 && *c <= 0x7e { + result.push_str(C_CHRS[*c as usize]); + } else { + result.push('.'); + } + } + result.push('<'); + + result +} + +#[test] +fn test_format_item_a() { + assert_eq!(" nul", format_item_a(0x00)); + assert_eq!(" soh", format_item_a(0x01)); + assert_eq!(" sp", format_item_a(0x20)); + assert_eq!(" A", format_item_a(0x41)); + assert_eq!(" ~", format_item_a(0x7e)); + assert_eq!(" del", format_item_a(0x7f)); + + assert_eq!(" nul", format_item_a(0x80)); + assert_eq!(" A", format_item_a(0xc1)); + assert_eq!(" ~", format_item_a(0xfe)); + assert_eq!(" del", format_item_a(0xff)); +} + +#[test] +fn test_format_item_c() { + assert_eq!(" \\0", format_item_c(&[0x00])); + assert_eq!(" 001", format_item_c(&[0x01])); + assert_eq!(" ", format_item_c(&[0x20])); + assert_eq!(" A", format_item_c(&[0x41])); + assert_eq!(" ~", format_item_c(&[0x7e])); + assert_eq!(" 177", format_item_c(&[0x7f])); + assert_eq!(" A", format_item_c(&[0x41, 0x21])); + + assert_eq!(" **", format_item_c(&[0x80])); + assert_eq!(" **", format_item_c(&[0x9f])); + + assert_eq!(" ß", format_item_c(&[0xc3, 0x9f])); + assert_eq!(" ß", format_item_c(&[0xc3, 0x9f, 0x21])); + + assert_eq!(" \u{1000}", format_item_c(&[0xe1, 0x80, 0x80])); + assert_eq!(" \u{1000}", format_item_c(&[0xe1, 0x80, 0x80, 0x21])); + + assert_eq!(" \u{1f496}", format_item_c(&[0xf0, 0x9f, 0x92, 0x96])); + assert_eq!( + " \u{1f496}", + format_item_c(&[0xf0, 0x9f, 0x92, 0x96, 0x21]) + ); + + assert_eq!(" 300", format_item_c(&[0xc0, 0x80])); // invalid utf-8 (MUTF-8 null) + assert_eq!(" 301", format_item_c(&[0xc1, 0xa1])); // invalid utf-8 + assert_eq!(" 303", format_item_c(&[0xc3, 0xc3])); // invalid utf-8 + assert_eq!(" 360", format_item_c(&[0xf0, 0x82, 0x82, 0xac])); // invalid utf-8 (overlong) + assert_eq!(" 360", format_item_c(&[0xf0, 0x9f, 0x92])); // invalid utf-8 (missing octet) + assert_eq!(" \u{10FFFD}", format_item_c(&[0xf4, 0x8f, 0xbf, 0xbd])); // largest valid utf-8 + assert_eq!(" 364", format_item_c(&[0xf4, 0x90, 0x00, 0x00])); // invalid utf-8 + assert_eq!(" 365", format_item_c(&[0xf5, 0x80, 0x80, 0x80])); // invalid utf-8 + assert_eq!(" 377", format_item_c(&[0xff])); // invalid utf-8 +} + +#[test] +fn test_format_ascii_dump() { + assert_eq!(">.<", format_ascii_dump(&[0x00])); + assert_eq!( + ">. A~.<", + format_ascii_dump(&[0x1f, 0x20, 0x41, 0x7e, 0x7f]) + ); +} diff --git a/coreutils/src/od/prn_float.rs b/coreutils/src/od/prn_float.rs new file mode 100644 index 000000000..54808b5ee --- /dev/null +++ b/coreutils/src/od/prn_float.rs @@ -0,0 +1,226 @@ +use std::num::FpCategory; +use half::f16; +use std::f32; +use std::f64; +use formatteriteminfo::*; + +pub static FORMAT_ITEM_F16: FormatterItemInfo = FormatterItemInfo { + byte_size: 2, + print_width: 10, + formatter: FormatWriter::FloatWriter(format_item_flo16), +}; + +pub static FORMAT_ITEM_F32: FormatterItemInfo = FormatterItemInfo { + byte_size: 4, + print_width: 15, + formatter: FormatWriter::FloatWriter(format_item_flo32), +}; + +pub static FORMAT_ITEM_F64: FormatterItemInfo = FormatterItemInfo { + byte_size: 8, + print_width: 25, + formatter: FormatWriter::FloatWriter(format_item_flo64), +}; + +pub fn format_item_flo16(f: f64) -> String { + format!(" {}", format_flo16(f16::from_f64(f))) +} + +pub fn format_item_flo32(f: f64) -> String { + // TODO: __truncdfsf2 in compiler-builtins + //format!(" {}", format_flo32(f as f32)) + unimplemented!() +} + +pub fn format_item_flo64(f: f64) -> String { + format!(" {}", format_flo64(f)) +} + +fn format_flo16(f: f16) -> String { + format_float(f64::from(f), 9, 4) +} + +// formats float with 8 significant digits, eg 12345678 or -1.2345678e+12 +// always returns a string of 14 characters +fn format_flo32(f: f32) -> String { + let width: usize = 14; + let precision: usize = 8; + + if f.classify() == FpCategory::Subnormal { + // subnormal numbers will be normal as f64, so will print with a wrong precision + format!("{:width$e}", f, width = width) // subnormal numbers + } else { + // TODO: __truncdfsf2 in compiler-builtins + //format_float(f as f64, width, precision) + unimplemented!() + } +} + +fn format_flo64(f: f64) -> String { + format_float(f, 24, 17) +} + +fn format_float(f: f64, width: usize, precision: usize) -> String { + if !f.is_normal() { + if f == -0.0 && f.is_sign_negative() { + return format!("{:>width$}", "-0", width = width); + } + if f == 0.0 || !f.is_finite() { + return format!("{:width$}", f, width = width); + } + return format!("{:width$e}", f, width = width); // subnormal numbers + } + + let mut l = f.abs().log10().floor() as i32; + + let r = 10f64.powi(l); + if (f > 0.0 && r > f) || (f < 0.0 && -r < f) { + // fix precision error + l = l - 1; + } + + if l >= 0 && l <= (precision as i32 - 1) { + format!( + "{:width$.dec$}", + f, + width = width, + dec = (precision - 1) - l as usize + ) + } else if l == -1 { + format!("{:width$.dec$}", f, width = width, dec = precision) + } else { + format!("{:width$.dec$e}", f, width = width, dec = precision - 1) + } +} + +#[test] +fn test_format_flo32() { + assert_eq!(format_flo32(1.0), " 1.0000000"); + assert_eq!(format_flo32(9.9999990), " 9.9999990"); + assert_eq!(format_flo32(10.0), " 10.000000"); + assert_eq!(format_flo32(99.999977), " 99.999977"); + assert_eq!(format_flo32(99.999992), " 99.999992"); + assert_eq!(format_flo32(100.0), " 100.00000"); + assert_eq!(format_flo32(999.99994), " 999.99994"); + assert_eq!(format_flo32(1000.0), " 1000.0000"); + assert_eq!(format_flo32(9999.9990), " 9999.9990"); + assert_eq!(format_flo32(10000.0), " 10000.000"); + assert_eq!(format_flo32(99999.992), " 99999.992"); + assert_eq!(format_flo32(100000.0), " 100000.00"); + assert_eq!(format_flo32(999999.94), " 999999.94"); + assert_eq!(format_flo32(1000000.0), " 1000000.0"); + assert_eq!(format_flo32(9999999.0), " 9999999.0"); + assert_eq!(format_flo32(10000000.0), " 10000000"); + assert_eq!(format_flo32(99999992.0), " 99999992"); + assert_eq!(format_flo32(100000000.0), " 1.0000000e8"); + assert_eq!(format_flo32(9.9999994e8), " 9.9999994e8"); + assert_eq!(format_flo32(1.0e9), " 1.0000000e9"); + assert_eq!(format_flo32(9.9999990e9), " 9.9999990e9"); + assert_eq!(format_flo32(1.0e10), " 1.0000000e10"); + + assert_eq!(format_flo32(0.1), " 0.10000000"); + assert_eq!(format_flo32(0.99999994), " 0.99999994"); + assert_eq!(format_flo32(0.010000001), " 1.0000001e-2"); + assert_eq!(format_flo32(0.099999994), " 9.9999994e-2"); + assert_eq!(format_flo32(0.001), " 1.0000000e-3"); + assert_eq!(format_flo32(0.0099999998), " 9.9999998e-3"); + + assert_eq!(format_flo32(-1.0), " -1.0000000"); + assert_eq!(format_flo32(-9.9999990), " -9.9999990"); + assert_eq!(format_flo32(-10.0), " -10.000000"); + assert_eq!(format_flo32(-99.999977), " -99.999977"); + assert_eq!(format_flo32(-99.999992), " -99.999992"); + assert_eq!(format_flo32(-100.0), " -100.00000"); + assert_eq!(format_flo32(-999.99994), " -999.99994"); + assert_eq!(format_flo32(-1000.0), " -1000.0000"); + assert_eq!(format_flo32(-9999.9990), " -9999.9990"); + assert_eq!(format_flo32(-10000.0), " -10000.000"); + assert_eq!(format_flo32(-99999.992), " -99999.992"); + assert_eq!(format_flo32(-100000.0), " -100000.00"); + assert_eq!(format_flo32(-999999.94), " -999999.94"); + assert_eq!(format_flo32(-1000000.0), " -1000000.0"); + assert_eq!(format_flo32(-9999999.0), " -9999999.0"); + assert_eq!(format_flo32(-10000000.0), " -10000000"); + assert_eq!(format_flo32(-99999992.0), " -99999992"); + assert_eq!(format_flo32(-100000000.0), " -1.0000000e8"); + assert_eq!(format_flo32(-9.9999994e8), " -9.9999994e8"); + assert_eq!(format_flo32(-1.0e9), " -1.0000000e9"); + assert_eq!(format_flo32(-9.9999990e9), " -9.9999990e9"); + assert_eq!(format_flo32(-1.0e10), " -1.0000000e10"); + + assert_eq!(format_flo32(-0.1), " -0.10000000"); + assert_eq!(format_flo32(-0.99999994), " -0.99999994"); + assert_eq!(format_flo32(-0.010000001), " -1.0000001e-2"); + assert_eq!(format_flo32(-0.099999994), " -9.9999994e-2"); + assert_eq!(format_flo32(-0.001), " -1.0000000e-3"); + assert_eq!(format_flo32(-0.0099999998), " -9.9999998e-3"); + + assert_eq!(format_flo32(3.4028233e38), " 3.4028233e38"); + assert_eq!(format_flo32(-3.4028233e38), " -3.4028233e38"); + assert_eq!(format_flo32(-1.1663108e-38), "-1.1663108e-38"); + assert_eq!(format_flo32(-4.7019771e-38), "-4.7019771e-38"); + assert_eq!(format_flo32(1e-45), " 1e-45"); + + assert_eq!(format_flo32(-3.402823466e+38), " -3.4028235e38"); + assert_eq!(format_flo32(f32::NAN), " NaN"); + assert_eq!(format_flo32(f32::INFINITY), " inf"); + assert_eq!(format_flo32(f32::NEG_INFINITY), " -inf"); + assert_eq!(format_flo32(-0.0), " -0"); + assert_eq!(format_flo32(0.0), " 0"); +} + +#[test] +fn test_format_flo64() { + assert_eq!(format_flo64(1.0), " 1.0000000000000000"); + assert_eq!(format_flo64(10.0), " 10.000000000000000"); + assert_eq!(format_flo64(1000000000000000.0), " 1000000000000000.0"); + assert_eq!( + format_flo64(10000000000000000.0), + " 10000000000000000" + ); + assert_eq!( + format_flo64(100000000000000000.0), + " 1.0000000000000000e17" + ); + + assert_eq!(format_flo64(-0.1), " -0.10000000000000001"); + assert_eq!(format_flo64(-0.01), " -1.0000000000000000e-2"); + + assert_eq!( + format_flo64(-2.2250738585072014e-308), + "-2.2250738585072014e-308" + ); + assert_eq!(format_flo64(4e-320), " 4e-320"); + assert_eq!(format_flo64(f64::NAN), " NaN"); + assert_eq!(format_flo64(f64::INFINITY), " inf"); + assert_eq!(format_flo64(f64::NEG_INFINITY), " -inf"); + assert_eq!(format_flo64(-0.0), " -0"); + assert_eq!(format_flo64(0.0), " 0"); +} + +#[test] +fn test_format_flo16() { + use half::consts::*; + + assert_eq!(format_flo16(f16::from_bits(0x8400u16)), "-6.104e-5"); + assert_eq!(format_flo16(f16::from_bits(0x8401u16)), "-6.109e-5"); + assert_eq!(format_flo16(f16::from_bits(0x8402u16)), "-6.115e-5"); + assert_eq!(format_flo16(f16::from_bits(0x8403u16)), "-6.121e-5"); + + assert_eq!(format_flo16(f16::from_f32(1.0)), " 1.000"); + assert_eq!(format_flo16(f16::from_f32(10.0)), " 10.00"); + assert_eq!(format_flo16(f16::from_f32(100.0)), " 100.0"); + assert_eq!(format_flo16(f16::from_f32(1000.0)), " 1000"); + assert_eq!(format_flo16(f16::from_f32(10000.0)), " 1.000e4"); + + assert_eq!(format_flo16(f16::from_f32(-0.2)), " -0.2000"); + assert_eq!(format_flo16(f16::from_f32(-0.02)), "-2.000e-2"); + + assert_eq!(format_flo16(MIN_POSITIVE_SUBNORMAL), " 5.966e-8"); + assert_eq!(format_flo16(MIN), " -6.550e4"); + assert_eq!(format_flo16(NAN), " NaN"); + assert_eq!(format_flo16(INFINITY), " inf"); + assert_eq!(format_flo16(NEG_INFINITY), " -inf"); + assert_eq!(format_flo16(NEG_ZERO), " -0"); + assert_eq!(format_flo16(ZERO), " 0"); +} diff --git a/coreutils/src/od/prn_int.rs b/coreutils/src/od/prn_int.rs new file mode 100644 index 000000000..cc47a16e7 --- /dev/null +++ b/coreutils/src/od/prn_int.rs @@ -0,0 +1,186 @@ +use formatteriteminfo::*; + +/// format string to print octal using `int_writer_unsigned` +macro_rules! OCT { () => { " {:0width$o}" }} +/// format string to print hexadecimal using `int_writer_unsigned` +macro_rules! HEX { () => { " {:0width$x}" }} +/// format string to print decimal using `int_writer_unsigned` or `int_writer_signed` +macro_rules! DEC { () => { " {:width$}" }} + +/// defines a static struct of type `FormatterItemInfo` called `$NAME` +/// +/// Used to format unsigned integer types with help of a function called `$function` +/// `$byte_size` is the size of the type, `$print_width` is the maximum width in +/// human-readable format. `$format_str` is one of OCT, HEX or DEC +macro_rules! int_writer_unsigned { + ($NAME:ident, $byte_size:expr, $print_width:expr, $function:ident, $format_str:expr) => { + fn $function(p: u64) -> String { + format!($format_str, + p, + width = $print_width - 1) + } + + pub static $NAME: FormatterItemInfo = FormatterItemInfo { + byte_size: $byte_size, + print_width: $print_width, + formatter: FormatWriter::IntWriter($function), + }; + } +} + +/// defines a static struct of type `FormatterItemInfo` called `$NAME` +/// +/// Used to format signed integer types with help of a function called `$function` +/// `$byte_size` is the size of the type, `$print_width` is the maximum width in +/// human-readable format. `$format_str` should be DEC +macro_rules! int_writer_signed { + ($NAME:ident, $byte_size:expr, $print_width:expr, $function:ident, $format_str:expr) => { + fn $function(p: u64) -> String { + let s = sign_extend(p, $byte_size); + format!($format_str, + s, + width = $print_width - 1) + } + + pub static $NAME: FormatterItemInfo = FormatterItemInfo { + byte_size: $byte_size, + print_width: $print_width, + formatter: FormatWriter::IntWriter($function), + }; + } +} + +/// Extends a signed number in `item` of `itembytes` bytes into a (signed) i64 +fn sign_extend(item: u64, itembytes: usize) -> i64 { + let shift = 64 - itembytes * 8; + (item << shift) as i64 >> shift +} + +int_writer_unsigned!(FORMAT_ITEM_OCT8, 1, 4, format_item_oct8, OCT!()); // max: 377 +int_writer_unsigned!(FORMAT_ITEM_OCT16, 2, 7, format_item_oct16, OCT!()); // max: 177777 +int_writer_unsigned!(FORMAT_ITEM_OCT32, 4, 12, format_item_oct32, OCT!()); // max: 37777777777 +int_writer_unsigned!(FORMAT_ITEM_OCT64, 8, 23, format_item_oct64, OCT!()); // max: 1777777777777777777777 + +int_writer_unsigned!(FORMAT_ITEM_HEX8, 1, 3, format_item_hex8, HEX!()); // max: ff +int_writer_unsigned!(FORMAT_ITEM_HEX16, 2, 5, format_item_hex16, HEX!()); // max: ffff +int_writer_unsigned!(FORMAT_ITEM_HEX32, 4, 9, format_item_hex32, HEX!()); // max: ffffffff +int_writer_unsigned!(FORMAT_ITEM_HEX64, 8, 17, format_item_hex64, HEX!()); // max: ffffffffffffffff + +int_writer_unsigned!(FORMAT_ITEM_DEC8U, 1, 4, format_item_dec_u8, DEC!()); // max: 255 +int_writer_unsigned!(FORMAT_ITEM_DEC16U, 2, 6, format_item_dec_u16, DEC!()); // max: 65535 +int_writer_unsigned!(FORMAT_ITEM_DEC32U, 4, 11, format_item_dec_u32, DEC!()); // max: 4294967295 +int_writer_unsigned!(FORMAT_ITEM_DEC64U, 8, 21, format_item_dec_u64, DEC!()); // max: 18446744073709551615 + +int_writer_signed!(FORMAT_ITEM_DEC8S, 1, 5, format_item_dec_s8, DEC!()); // max: -128 +int_writer_signed!(FORMAT_ITEM_DEC16S, 2, 7, format_item_dec_s16, DEC!()); // max: -32768 +int_writer_signed!(FORMAT_ITEM_DEC32S, 4, 12, format_item_dec_s32, DEC!()); // max: -2147483648 +int_writer_signed!(FORMAT_ITEM_DEC64S, 8, 21, format_item_dec_s64, DEC!()); // max: -9223372036854775808 + +#[test] +fn test_sign_extend() { + assert_eq!( + 0xffffffffffffff80u64 as i64, + sign_extend(0x0000000000000080, 1) + ); + assert_eq!( + 0xffffffffffff8000u64 as i64, + sign_extend(0x0000000000008000, 2) + ); + assert_eq!( + 0xffffffffff800000u64 as i64, + sign_extend(0x0000000000800000, 3) + ); + assert_eq!( + 0xffffffff80000000u64 as i64, + sign_extend(0x0000000080000000, 4) + ); + assert_eq!( + 0xffffff8000000000u64 as i64, + sign_extend(0x0000008000000000, 5) + ); + assert_eq!( + 0xffff800000000000u64 as i64, + sign_extend(0x0000800000000000, 6) + ); + assert_eq!( + 0xff80000000000000u64 as i64, + sign_extend(0x0080000000000000, 7) + ); + assert_eq!( + 0x8000000000000000u64 as i64, + sign_extend(0x8000000000000000, 8) + ); + + assert_eq!(0x000000000000007f, sign_extend(0x000000000000007f, 1)); + assert_eq!(0x0000000000007fff, sign_extend(0x0000000000007fff, 2)); + assert_eq!(0x00000000007fffff, sign_extend(0x00000000007fffff, 3)); + assert_eq!(0x000000007fffffff, sign_extend(0x000000007fffffff, 4)); + assert_eq!(0x0000007fffffffff, sign_extend(0x0000007fffffffff, 5)); + assert_eq!(0x00007fffffffffff, sign_extend(0x00007fffffffffff, 6)); + assert_eq!(0x007fffffffffffff, sign_extend(0x007fffffffffffff, 7)); + assert_eq!(0x7fffffffffffffff, sign_extend(0x7fffffffffffffff, 8)); +} + +#[test] +fn test_format_item_oct() { + assert_eq!(" 000", format_item_oct8(0)); + assert_eq!(" 377", format_item_oct8(0xff)); + assert_eq!(" 000000", format_item_oct16(0)); + assert_eq!(" 177777", format_item_oct16(0xffff)); + assert_eq!(" 00000000000", format_item_oct32(0)); + assert_eq!(" 37777777777", format_item_oct32(0xffffffff)); + assert_eq!(" 0000000000000000000000", format_item_oct64(0)); + assert_eq!( + " 1777777777777777777777", + format_item_oct64(0xffffffffffffffff) + ); +} + +#[test] +fn test_format_item_hex() { + assert_eq!(" 00", format_item_hex8(0)); + assert_eq!(" ff", format_item_hex8(0xff)); + assert_eq!(" 0000", format_item_hex16(0)); + assert_eq!(" ffff", format_item_hex16(0xffff)); + assert_eq!(" 00000000", format_item_hex32(0)); + assert_eq!(" ffffffff", format_item_hex32(0xffffffff)); + assert_eq!(" 0000000000000000", format_item_hex64(0)); + assert_eq!(" ffffffffffffffff", format_item_hex64(0xffffffffffffffff)); +} + +#[test] +fn test_format_item_dec_u() { + assert_eq!(" 0", format_item_dec_u8(0)); + assert_eq!(" 255", format_item_dec_u8(0xff)); + assert_eq!(" 0", format_item_dec_u16(0)); + assert_eq!(" 65535", format_item_dec_u16(0xffff)); + assert_eq!(" 0", format_item_dec_u32(0)); + assert_eq!(" 4294967295", format_item_dec_u32(0xffffffff)); + assert_eq!(" 0", format_item_dec_u64(0)); + assert_eq!( + " 18446744073709551615", + format_item_dec_u64(0xffffffffffffffff) + ); +} + +#[test] +fn test_format_item_dec_s() { + assert_eq!(" 0", format_item_dec_s8(0)); + assert_eq!(" 127", format_item_dec_s8(0x7f)); + assert_eq!(" -128", format_item_dec_s8(0x80)); + assert_eq!(" 0", format_item_dec_s16(0)); + assert_eq!(" 32767", format_item_dec_s16(0x7fff)); + assert_eq!(" -32768", format_item_dec_s16(0x8000)); + assert_eq!(" 0", format_item_dec_s32(0)); + assert_eq!(" 2147483647", format_item_dec_s32(0x7fffffff)); + assert_eq!(" -2147483648", format_item_dec_s32(0x80000000)); + assert_eq!(" 0", format_item_dec_s64(0)); + assert_eq!( + " 9223372036854775807", + format_item_dec_s64(0x7fffffffffffffff) + ); + assert_eq!( + " -9223372036854775808", + format_item_dec_s64(0x8000000000000000) + ); +} diff --git a/coreutils/src/paste/Cargo.toml b/coreutils/src/paste/Cargo.toml new file mode 100644 index 000000000..fb0d70cd2 --- /dev/null +++ b/coreutils/src/paste/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "paste" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_paste" +path = "paste.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "paste" +path = "../../uumain.rs" diff --git a/coreutils/src/paste/paste.rs b/coreutils/src/paste/paste.rs new file mode 100644 index 000000000..8ad29568b --- /dev/null +++ b/coreutils/src/paste/paste.rs @@ -0,0 +1,144 @@ +#![crate_name = "uu_paste"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::io::{stdin, BufRead, BufReader, Read}; +use std::iter::repeat; +use std::fs::File; +use std::path::Path; + +static NAME: &str = "paste"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag( + "s", + "serial", + "paste one file at a time instead of in parallel", + ); + opts.optopt( + "d", + "delimiters", + "reuse characters from LIST instead of TABs", + "LIST", + ); + 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(e) => crash!(1, "{}", e), + }; + + if matches.opt_present("help") { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTION]... [FILE]... + +Write lines consisting of the sequentially corresponding lines from each +FILE, separated by TABs, to standard output.", + NAME, VERSION + ); + print!("{}", opts.usage(&msg)); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else { + let serial = matches.opt_present("serial"); + let delimiters = matches.opt_str("delimiters").unwrap_or("\t".to_owned()); + paste(matches.free, serial, delimiters); + } + + 0 +} + +fn paste(filenames: Vec, serial: bool, delimiters: String) { + let mut files: Vec>> = filenames + .into_iter() + .map(|name| { + BufReader::new(if name == "-" { + Box::new(stdin()) as Box + } else { + let r = crash_if_err!(1, File::open(Path::new(&name))); + Box::new(r) as Box + }) + }) + .collect(); + + let delimiters: Vec = unescape(delimiters) + .chars() + .map(|x| x.to_string()) + .collect(); + let mut delim_count = 0; + + if serial { + for file in &mut files { + let mut output = String::new(); + loop { + let mut line = String::new(); + match file.read_line(&mut line) { + Ok(0) => break, + Ok(_) => { + output.push_str(line.trim_end()); + output.push_str(&delimiters[delim_count % delimiters.len()]); + } + Err(e) => crash!(1, "{}", e.to_string()), + } + delim_count += 1; + } + println!("{}", &output[..output.len() - 1]); + } + } else { + let mut eof: Vec = repeat(false).take(files.len()).collect(); + loop { + let mut output = String::new(); + let mut eof_count = 0; + for (i, file) in files.iter_mut().enumerate() { + if eof[i] { + eof_count += 1; + } else { + let mut line = String::new(); + match file.read_line(&mut line) { + Ok(0) => { + eof[i] = true; + eof_count += 1; + } + Ok(_) => output.push_str(line.trim_end()), + Err(e) => crash!(1, "{}", e.to_string()), + } + } + output.push_str(&delimiters[delim_count % delimiters.len()]); + delim_count += 1; + } + if files.len() == eof_count { + break; + } + println!("{}", &output[..output.len() - 1]); + delim_count = 0; + } + } +} + +// Unescape all special characters +// TODO: this will need work to conform to GNU implementation +fn unescape(s: String) -> String { + s.replace("\\n", "\n") + .replace("\\t", "\t") + .replace("\\\\", "\\") + .replace("\\", "") +} diff --git a/coreutils/src/pathchk/Cargo.toml b/coreutils/src/pathchk/Cargo.toml new file mode 100644 index 000000000..c6303a9cc --- /dev/null +++ b/coreutils/src/pathchk/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pathchk" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_pathchk" +path = "pathchk.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "pathchk" +path = "../../uumain.rs" diff --git a/coreutils/src/pathchk/pathchk.rs b/coreutils/src/pathchk/pathchk.rs new file mode 100644 index 000000000..8159c24c9 --- /dev/null +++ b/coreutils/src/pathchk/pathchk.rs @@ -0,0 +1,266 @@ +#![allow(unused_must_use)] // because we of writeln! +#![crate_name = "uu_pathchk"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Inokentiy Babushkin + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +extern crate getopts; +extern crate libc; + +#[macro_use] +extern crate uucore; + +use getopts::Options; +use std::fs; +use std::io::{ErrorKind, Write}; + +// operating mode +enum Mode { + Default, // use filesystem to determine information and limits + 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"); + +// 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: Vec) -> i32 { + // 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), + }; + + // 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") + { + Mode::Both + } else if matches.opt_present("posix") { + Mode::Basic + } else if matches.opt_present("posix-special") { + Mode::Extra + } else { + Mode::Default + }; + + // take necessary actions + match mode { + Mode::Help => { + help(opts); + 0 + } + Mode::Version => { + version(); + 0 + } + _ => { + let mut res = true; + if matches.free.len() == 0 { + show_error!("missing operand\nTry {} --help for more information", NAME); + res = false; + } + // 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 + } + } + } +} + +// 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); +} + +// check a path, given as a slice of it's components and an operating mode +fn check_path(mode: &Mode, path: &[String]) -> bool { + match *mode { + Mode::Basic => check_basic(&path), + Mode::Extra => check_default(&path) && check_extra(&path), + Mode::Both => check_basic(&path) && check_extra(&path), + _ => check_default(&path), + } +} + +// check a path in basic compatibility mode +fn check_basic(path: &[String]) -> bool { + let joined_path = path.join("/"); + let total_len = joined_path.len(); + // path length + if total_len > POSIX_PATH_MAX { + writeln!( + &mut std::io::stderr(), + "limit {} exceeded by length {} of file name {}", + POSIX_PATH_MAX, + total_len, + joined_path + ); + return false; + } else if total_len == 0 { + writeln!(&mut std::io::stderr(), "empty file name"); + return false; + } + // components: character portability and length + for p in path { + let component_len = p.len(); + if component_len > POSIX_NAME_MAX { + writeln!( + &mut std::io::stderr(), + "limit {} exceeded by length {} of file name component '{}'", + POSIX_NAME_MAX, + component_len, + p + ); + return false; + } + if !check_portable_chars(&p) { + return false; + } + } + // permission checks + check_searchable(&joined_path) +} + +// check a path in extra compatibility mode +fn check_extra(path: &[String]) -> bool { + // components: leading hyphens + for p in path { + if !no_leading_hyphen(&p) { + writeln!( + &mut std::io::stderr(), + "leading hyphen in file name component '{}'", + p + ); + return false; + } + } + // path length + if path.join("/").len() == 0 { + writeln!(&mut std::io::stderr(), "empty file name"); + return false; + } + true +} + +// check a path in default mode (using the file system) +fn check_default(path: &[String]) -> bool { + let joined_path = path.join("/"); + let total_len = joined_path.len(); + // path length + if total_len > libc::PATH_MAX as usize { + writeln!( + &mut std::io::stderr(), + "limit {} exceeded by length {} of file name '{}'", + libc::PATH_MAX, + total_len, + joined_path + ); + return false; + } + // components: length + for p in path { + let component_len = p.len(); + if component_len > libc::FILENAME_MAX as usize { + writeln!( + &mut std::io::stderr(), + "limit {} exceeded by length {} of file name component '{}'", + libc::FILENAME_MAX, + component_len, + p + ); + return false; + } + } + // permission checks + check_searchable(&joined_path) +} + +// check whether a path is or if other problems arise +fn check_searchable(path: &String) -> bool { + // we use lstat, just like the original implementation + match fs::symlink_metadata(path) { + Ok(_) => true, + Err(e) => if e.kind() == ErrorKind::NotFound { + true + } else { + writeln!(&mut std::io::stderr(), "{}", e); + false + }, + } +} + +// check for a hyphen at the beginning of a path segment +fn no_leading_hyphen(path_segment: &String) -> bool { + !path_segment.starts_with('-') +} + +// check whether a path segment contains only valid (read: portable) characters +fn check_portable_chars(path_segment: &String) -> bool { + let valid_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-".to_string(); + for ch in path_segment.chars() { + if !valid_str.contains(ch) { + writeln!( + &mut std::io::stderr(), + "nonportable character '{}' in file name component '{}'", + ch, + path_segment + ); + return false; + } + } + true +} diff --git a/coreutils/src/pinky/Cargo.toml b/coreutils/src/pinky/Cargo.toml new file mode 100644 index 000000000..56c60aecf --- /dev/null +++ b/coreutils/src/pinky/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "pinky" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_pinky" +path = "pinky.rs" + +[dependencies.uucore] +version = "0.0.1" +features = ["utmpx", "entries"] + +[[bin]] +name = "pinky" +path = "../../uumain.rs" diff --git a/coreutils/src/pinky/pinky.rs b/coreutils/src/pinky/pinky.rs new file mode 100644 index 000000000..0edf3e0f2 --- /dev/null +++ b/coreutils/src/pinky/pinky.rs @@ -0,0 +1,347 @@ +#![crate_name = "uu_pinky"] +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// +#![cfg_attr(feature = "clippy", feature(plugin))] +#![cfg_attr(feature = "clippy", plugin(clippy))] + +#[macro_use] +extern crate uucore; +use uucore::utmpx::{self, time, Utmpx}; +use uucore::libc::S_IWGRP; +use uucore::entries::{Locate, Passwd}; + +use std::io::prelude::*; +use std::io::BufReader; +use std::io::Result as IOResult; + +use std::fs::File; +use std::os::unix::fs::MetadataExt; + +use std::path::PathBuf; + +static SYNTAX: &str = "[OPTION]... [USER]..."; +static SUMMARY: &str = "A lightweight 'finger' program; print user information."; + +const BUFSIZE: usize = 1024; + +pub fn uumain(args: Vec) -> i32 { + let long_help = &format!( + " + -l produce long format output for the specified USERs + -b omit the user's home directory and shell in long format + -h omit the user's project file in long format + -p omit the user's plan file in long format + -s do short format output, this is the default + -f omit the line of column headings in short format + -w omit the user's full name in short format + -i omit the user's full name and remote host in short format + -q omit the user's full name, remote host and idle time + in short format + --help display this help and exit + --version output version information and exit + +The utmp file will be {}", + utmpx::DEFAULT_FILE + ); + let mut opts = new_coreopts!(SYNTAX, SUMMARY, &long_help); + opts.optflag( + "l", + "", + "produce long format output for the specified USERs", + ); + opts.optflag( + "b", + "", + "omit the user's home directory and shell in long format", + ); + opts.optflag("h", "", "omit the user's project file in long format"); + opts.optflag("p", "", "omit the user's plan file in long format"); + opts.optflag("s", "", "do short format output, this is the default"); + opts.optflag("f", "", "omit the line of column headings in short format"); + opts.optflag("w", "", "omit the user's full name in short format"); + opts.optflag( + "i", + "", + "omit the user's full name and remote host in short format", + ); + opts.optflag( + "q", + "", + "omit the user's full name, remote host and idle time in short format", + ); + opts.optflag("", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + let matches = opts.parse(args); + + // If true, display the hours:minutes since each user has touched + // the keyboard, or blank if within the last minute, or days followed + // by a 'd' if not within the last day. + let mut include_idle = true; + + // If true, display a line at the top describing each field. + let include_heading = !matches.opt_present("f"); + + // if true, display the user's full name from pw_gecos. + let mut include_fullname = true; + + // if true, display the user's ~/.project file when doing long format. + let include_project = !matches.opt_present("h"); + + // if true, display the user's ~/.plan file when doing long format. + let include_plan = !matches.opt_present("p"); + + // if true, display the user's home directory and shell + // when doing long format. + let include_home_and_shell = !matches.opt_present("b"); + + // if true, use the "short" output format. + let do_short_format = !matches.opt_present("l"); + + /* if true, display the ut_host field. */ + let mut include_where = true; + + if matches.opt_present("w") { + include_fullname = false; + } + if matches.opt_present("i") { + include_fullname = false; + include_where = false; + } + if matches.opt_present("q") { + include_fullname = false; + include_idle = false; + include_where = false; + } + + if !do_short_format && matches.free.is_empty() { + disp_err!("no username specified; at least one must be specified when using -l"); + return 1; + } + + let pk = Pinky { + include_idle: include_idle, + include_heading: include_heading, + include_fullname: include_fullname, + include_project: include_project, + include_plan: include_plan, + include_home_and_shell: include_home_and_shell, + include_where: include_where, + names: matches.free, + }; + + if do_short_format { + if let Err(e) = pk.short_pinky() { + disp_err!("{}", e); + 1 + } else { + 0 + } + } else { + pk.long_pinky() + } +} + +struct Pinky { + include_idle: bool, + include_heading: bool, + include_fullname: bool, + include_project: bool, + include_plan: bool, + include_where: bool, + include_home_and_shell: bool, + names: Vec, +} + +pub trait Capitalize { + fn capitalize(&self) -> String; +} + +impl Capitalize for str { + fn capitalize(&self) -> String { + self.char_indices() + .fold(String::with_capacity(self.len()), |mut acc, x| { + if x.0 != 0 { + acc.push(x.1) + } else { + acc.push(x.1.to_ascii_uppercase()) + } + acc + }) + } +} + +fn idle_string(when: i64) -> String { + thread_local! { + static NOW: time::Tm = time::now() + } + NOW.with(|n| { + let duration = n.to_timespec().sec - when; + if duration < 60 { + // less than 1min + " ".to_owned() + } else if duration < 24 * 3600 { + // less than 1day + let hours = duration / (60 * 60); + let minutes = (duration % (60 * 60)) / 60; + format!("{:02}:{:02}", hours, minutes) + } else { + // more than 1day + let days = duration / (24 * 3600); + format!("{}d", days) + } + }) +} + +fn time_string(ut: &Utmpx) -> String { + time::strftime("%Y-%m-%d %H:%M", &ut.login_time()).unwrap() +} + +impl Pinky { + fn print_entry(&self, ut: &Utmpx) { + let mut pts_path = PathBuf::from("/dev"); + pts_path.push(ut.tty_device().as_str()); + + let mesg; + let last_change; + match pts_path.metadata() { + Ok(meta) => { + mesg = if meta.mode() & (S_IWGRP as u32) != 0 { + ' ' + } else { + '*' + }; + last_change = meta.atime(); + } + _ => { + mesg = '?'; + last_change = 0; + } + } + + print!("{1:<8.0$}", utmpx::UT_NAMESIZE, ut.user()); + + if self.include_fullname { + if let Ok(pw) = Passwd::locate(ut.user().as_ref()) { + let mut gecos = pw.user_info().into_owned(); + if let Some(n) = gecos.find(',') { + gecos.truncate(n + 1); + } + print!(" {:<19.19}", gecos.replace("&", &pw.name().capitalize())); + } else { + print!(" {:19}", " ???"); + } + } + + print!(" {}{:<8.*}", mesg, utmpx::UT_LINESIZE, ut.tty_device()); + + if self.include_idle { + if last_change != 0 { + print!(" {:<6}", idle_string(last_change)); + } else { + print!(" {:<6}", "?????"); + } + } + + print!(" {}", time_string(&ut)); + + if self.include_where && !ut.host().is_empty() { + let ut_host = ut.host(); + let mut res = ut_host.splitn(2, ':'); + let host = match res.next() { + Some(_) => ut.canon_host().unwrap_or(ut_host.clone()), + None => ut_host.clone(), + }; + match res.next() { + Some(d) => print!(" {}:{}", host, d), + None => print!(" {}", host), + } + } + + println!(""); + } + + fn print_heading(&self) { + print!("{:<8}", "Login"); + if self.include_fullname { + print!(" {:<19}", "Name"); + } + print!(" {:<9}", " TTY"); + if self.include_idle { + print!(" {:<6}", "Idle"); + } + print!(" {:<16}", "When"); + if self.include_where { + print!(" Where"); + } + println!(""); + } + + fn short_pinky(&self) -> IOResult<()> { + if self.include_heading { + self.print_heading(); + } + for ut in Utmpx::iter_all_records() { + if ut.is_user_process() { + if self.names.is_empty() { + self.print_entry(&ut) + } else { + if self.names.iter().any(|n| n.as_str() == ut.user()) { + self.print_entry(&ut); + } + } + } + } + Ok(()) + } + + fn long_pinky(&self) -> i32 { + for u in &self.names { + print!("Login name: {:<28}In real life: ", u); + if let Ok(pw) = Passwd::locate(u.as_str()) { + println!(" {}", pw.user_info().replace("&", &pw.name().capitalize())); + if self.include_home_and_shell { + print!("Directory: {:<29}", pw.user_dir()); + println!("Shell: {}", pw.user_shell()); + } + if self.include_project { + let mut p = PathBuf::from(pw.user_dir().as_ref()); + p.push(".project"); + if let Ok(f) = File::open(p) { + print!("Project: "); + read_to_console(f); + } + } + if self.include_plan { + let mut p = PathBuf::from(pw.user_dir().as_ref()); + p.push(".plan"); + if let Ok(f) = File::open(p) { + println!("Plan:"); + read_to_console(f); + } + } + println!(""); + } else { + println!(" ???"); + } + } + 0 + } +} + +fn read_to_console(f: F) { + let mut reader = BufReader::new(f); + let mut iobuf = [0_u8; BUFSIZE]; + while let Ok(n) = reader.read(&mut iobuf) { + if n == 0 { + break; + } + let s = String::from_utf8_lossy(&iobuf); + print!("{}", s); + } +} diff --git a/coreutils/src/printenv/Cargo.toml b/coreutils/src/printenv/Cargo.toml new file mode 100644 index 000000000..36476dbd4 --- /dev/null +++ b/coreutils/src/printenv/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "printenv" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_printenv" +path = "printenv.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "printenv" +path = "../../uumain.rs" diff --git a/coreutils/src/printenv/printenv.rs b/coreutils/src/printenv/printenv.rs new file mode 100644 index 000000000..56ca0a653 --- /dev/null +++ b/coreutils/src/printenv/printenv.rs @@ -0,0 +1,77 @@ +#![crate_name = "uu_printenv"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* last synced with: printenv (GNU coreutils) 8.13 */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::env; + +static NAME: &str = "printenv"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + opts.optflag( + "0", + "null", + "end each output line with 0 byte rather than newline", + ); + 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, "Invalid options\n{}", f), + }; + if matches.opt_present("help") { + let msg = format!( + "{0} {1} + +Usage: + {0} [VARIABLE]... [OPTION]... + +Prints the given environment VARIABLE(s), otherwise prints them all.", + NAME, VERSION + ); + print!("{}", opts.usage(&msg)); + return 0; + } + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + let mut separator = "\n"; + if matches.opt_present("null") { + separator = "\x00"; + }; + + exec(matches.free, separator); + + 0 +} + +pub fn exec(args: Vec, separator: &str) { + if args.is_empty() { + for (env_var, value) in env::vars() { + print!("{}={}{}", env_var, value, separator); + } + return; + } + + for env_var in &args { + if let Ok(var) = env::var(env_var) { + print!("{}{}", var, separator); + } + } +} diff --git a/coreutils/src/printf/Cargo.toml b/coreutils/src/printf/Cargo.toml new file mode 100644 index 000000000..bf1ab0b8f --- /dev/null +++ b/coreutils/src/printf/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "printf" +version = "0.0.1" +authors = ["Nathan Ross"] +build = "../../mkmain.rs" + +[lib] +name = "uu_printf" +path = "printf.rs" + +[dependencies] +itertools = "0.8.0" +uucore = "0.0.1" + +[[bin]] +name = "printf" +path = "../../uumain.rs" diff --git a/coreutils/src/printf/cli.rs b/coreutils/src/printf/cli.rs new file mode 100644 index 000000000..e69cdf3c9 --- /dev/null +++ b/coreutils/src/printf/cli.rs @@ -0,0 +1,34 @@ +//! stdio convenience fns +#[allow(unused_must_use)] + +use std::io::{stderr, stdout, Write}; +use std::env; + +pub const EXIT_OK: i32 = 0; +pub const EXIT_ERR: i32 = 1; + +pub fn err_msg(msg: &str) { + let exe_path = match env::current_exe() { + Ok(p) => p.to_string_lossy().into_owned(), + _ => String::from(""), + }; + writeln!(&mut stderr(), "{}: {}", exe_path, msg).unwrap(); +} + +// by default stdout only flushes +// to console when a newline is passed. +#[allow(unused_must_use)] +pub fn flush_char(c: &char) { + print!("{}", c); + stdout().flush(); +} +#[allow(unused_must_use)] +pub fn flush_str(s: &str) { + print!("{}", s); + stdout().flush(); +} +#[allow(unused_must_use)] +pub fn flush_bytes(bslice: &[u8]) { + stdout().write(bslice); + stdout().flush(); +} diff --git a/coreutils/src/printf/memo.rs b/coreutils/src/printf/memo.rs new file mode 100644 index 000000000..e5a05cf0e --- /dev/null +++ b/coreutils/src/printf/memo.rs @@ -0,0 +1,87 @@ +//! Memo runner of printf +//! Takes a format string and arguments +//! 1. tokenizes format string into tokens, consuming +//! any subst. arguments along the way. +//! 2. feeds remaining arguments into function +//! that prints tokens. + +use std::iter::Peekable; +use std::slice::Iter; +use itertools::put_back_n; +use cli; +use tokenize::token::{Token, Tokenizer}; +use tokenize::unescaped_text::UnescapedText; +use tokenize::sub::Sub; + +pub struct Memo { + tokens: Vec>, +} + +fn warn_excess_args(first_arg: &str) { + cli::err_msg(&format!( + "warning: ignoring excess arguments, starting with '{}'", + first_arg + )); +} + +impl Memo { + pub fn new(pf_string: &String, pf_args_it: &mut Peekable>) -> Memo { + let mut pm = Memo { tokens: Vec::new() }; + let mut tmp_token: Option>; + let mut it = put_back_n(pf_string.chars()); + let mut has_sub = false; + loop { + tmp_token = UnescapedText::from_it(&mut it, pf_args_it); + match tmp_token { + Some(x) => pm.tokens.push(x), + None => {} + } + tmp_token = Sub::from_it(&mut it, pf_args_it); + match tmp_token { + Some(x) => { + if !has_sub { + has_sub = true; + } + pm.tokens.push(x); + } + None => {} + } + if let Some(x) = it.next() { + it.put_back(x); + } else { + break; + } + } + if !has_sub { + let mut drain = false; + if let Some(first_arg) = pf_args_it.peek() { + warn_excess_args(first_arg); + drain = true; + } + if drain { + loop { + // drain remaining args; + if pf_args_it.next().is_none() { + break; + } + } + } + } + pm + } + pub fn apply(&self, pf_args_it: &mut Peekable>) { + for tkn in self.tokens.iter() { + tkn.print(pf_args_it); + } + } + pub fn run_all(pf_string: &String, pf_args: &[String]) { + let mut arg_it = pf_args.iter().peekable(); + let pm = Memo::new(pf_string, &mut arg_it); + loop { + if arg_it.peek().is_none() { + break; + } + pm.apply(&mut arg_it); + } + } +} diff --git a/coreutils/src/printf/mod.rs b/coreutils/src/printf/mod.rs new file mode 100644 index 000000000..5ed2ecea8 --- /dev/null +++ b/coreutils/src/printf/mod.rs @@ -0,0 +1,3 @@ +mod cli; +mod memo; +mod tokenize; diff --git a/coreutils/src/printf/printf.rs b/coreutils/src/printf/printf.rs new file mode 100644 index 000000000..1842f8831 --- /dev/null +++ b/coreutils/src/printf/printf.rs @@ -0,0 +1,296 @@ +#![crate_name = "uu_printf"] +#![allow(dead_code)] + +extern crate itertools; +extern crate uucore; + +mod cli; +mod memo; +mod tokenize; + +static NAME: &str = "printf"; +static VERSION: &str = "0.0.1"; +static SHORT_USAGE: &str = "printf: usage: printf [-v var] format [arguments]"; +static LONGHELP_LEAD: &str = "printf + + USAGE: printf FORMATSTRING [ARGUMENT]... + + basic anonymous string templating: + + prints format string at least once, repeating as long as there are remaining arguments + output prints escaped literals in the format string as character literals + output replaces anonymous fields with the next unused argument, formatted according to the field. + +Options: + --help display this help and exit + --version output version information and exit + +"; +static LONGHELP_BODY: &str = " + Prints the , replacing escaped character sequences with character literals + and substitution field sequences with passed arguments + + literally, with the exception of the below + escaped character sequences, and the substitution sequences described further down. + + ESCAPE SEQUENCES + + The following escape sequences, organized here in alphabetical order, + will print the corresponding character literal: + + \" double quote + + \\\\ backslash + + \\a alert (BEL) + + \\b backspace + + \\c End-of-Input + + \\e escape + + \\f form feed + + \\n new line + + \\r carriage return + + \\t horizontal tab + + \\v vertical tab + + \\NNN byte with value expressed in octal value NNN (1 to 3 digits) + values greater than 256 will be treated + + \\xHH byte with value expressed in hexadecimal value NN (1 to 2 digits) + + \\uHHHH Unicode (IEC 10646) character with value expressed in hexadecimal value HHHH (4 digits) + + \\uHHHH Unicode character with value expressed in hexadecimal value HHHH (8 digits) + + %% a single % + + SUBSTITUTIONS + + SUBSTITUTION QUICK REFERENCE + + Fields + + %s - string + %b - string parsed for literals + second parameter is max length + + %c - char + no second parameter + + %i or %d - 64-bit integer + %u - 64 bit unsigned integer + %x or %X - 64-bit unsigned integer as hex + %o - 64-bit unsigned integer as octal + second parameter is min-width, integer + output below that width is padded with leading zeroes + + %f or %F - decimal floating point value + %e or %E - scientific notation floating point value + %g or %G - shorter of specially interpreted decimal or SciNote floating point value. + second parameter is + -max places after decimal point for floating point output + -max number of significant digits for scientific notation output + + parameterizing fields + + examples: + + printf '%4.3i' 7 + has a first parameter of 4 + and a second parameter of 3 + will result in ' 007' + + printf '%.1s' abcde + has no first parameter + and a second parameter of 1 + will result in 'a' + + printf '%4c' q + has a first parameter of 4 + and no second parameter + will result in ' q' + + The first parameter of a field is the minimum width to pad the output to + if the output is less than this absolute value of this width, + it will be padded with leading spaces, or, if the argument is negative, + with trailing spaces. the default is zero. + + The second parameter of a field is particular to the output field type. + defaults can be found in the full substitution help below + + special prefixes to numeric arguments + 0 (e.g. 010) - interpret argument as octal (integer output fields only) + 0x (e.g. 0xABC) - interpret argument as hex (numeric output fields only) + \' (e.g. \'a) - interpret argument as a character constant + + HOW TO USE SUBSTITUTIONS + + Substitutions are used to pass additional argument(s) into the FORMAT string, to be formatted a + particular way. E.g. + + printf 'the letter %X comes before the letter %X' 10 11 + + will print + + 'the letter A comes before the letter B' + + because the substitution field %X means + 'take an integer argument and write it as a hexadecimal number' + + Passing more arguments than are in the format string will cause the format string to be + repeated for the remaining substitutions + + printf 'it is %i F in %s \n' 22 Portland 25 Boston 27 New York + + will print + + 'it is 22 F in Portland + it is 25 F in Boston + it is 27 F in Boston + ' + If a format string is printed but there are less arguments remaining + than there are substitution fields, substitution fields without + an argument will default to empty strings, or for numeric fields + the value 0 + + AVAILABLE SUBSTITUTIONS + + This program, like GNU coreutils printf, + interprets a modified subset of the POSIX C printf spec, + a quick reference to substitutions is below. + + STRING SUBSTITUTIONS + All string fields have a 'max width' parameter + %.3s means 'print no more than three characters of the original input' + + %s - string + + %b - escaped string - the string will be checked for any escaped literals from + the escaped literal list above, and translate them to literal charcters. + e.g. \\n will be transformed into a newline character. + + One special rule about %b mode is that octal literals are intepreted differently + In arguments passed by %b, pass octal-interpreted literals must be in the form of \\0NNN + instead of \\NNN. (Although, for legacy reasons, octal literals in the form of \\NNN will + still be interpreted and not throw a warning, you will have problems if you use this for a + literal whose code begins with zero, as it will be viewed as in \\0NNN form.) + + CHAR SUBSTITUTIONS + The character field does not have a secondary parameter. + + %c - a single character + + INTEGER SUBSTITUTIONS + All integer fields have a 'pad with zero' parameter + %.4i means an integer which if it is less than 4 digits in length, + is padded with leading zeros until it is 4 digits in length. + + %d or %i - 64-bit integer + + %u - 64 bit unsigned integer + + %x or %X - 64 bit unsigned integer printed in Hexadecimal (base 16) + %X instead of %x means to use uppercase letters for 'a' through 'f' + + %o - 64 bit unsigned integer printed in octal (base 8) + + FLOATING POINT SUBSTITUTIONS + + All floating point fields have a 'max decimal places / max significant digits' parameter + %.10f means a decimal floating point with 7 decimal places past 0 + %.10e means a scientific notation number with 10 significant digits + %.10g means the same behavior for decimal and Sci. Note, respectively, and provides the shorter + of each's output. + + Like with GNU coreutils, the value after the decimal point is these outputs is parsed as a + double first before being rendered to text. For both implementations do not expect meaningful + precision past the 18th decimal place. When using a number of decimal places that is 18 or + higher, you can expect variation in output between GNU coreutils printf and this printf at the + 18th decimal place of +/- 1 + + %f - floating point value presented in decimal, truncated and displayed to 6 decimal places by + default. There is not past-double behavior parity with Coreutils printf, values are not + estimated or adjusted beyond input values. + + %e or %E - floating point value presented in scientific notation + 7 significant digits by default + %E means use to use uppercase E for the mantissa. + + %g or %G - floating point value presented in the shorter of decimal and scientific notation + behaves differently from %f and %E, please see posix printf spec for full details, + some examples of different behavior: + + Sci Note has 6 significant digits by default + Trailing zeroes are removed + Instead of being truncated, digit after last is rounded + + Like other behavior in this utility, the design choices of floating point + behavior in this utility is selected to reproduce in exact + the behavior of GNU coreutils' printf from an inputs and outputs standpoint. + + USING PARAMETERS + Most substitution fields can be parameterized using up to 2 numbers that can + be passed to the field, between the % sign and the field letter. + + The 1st parameter always indicates the minimum width of output, it is useful for creating + columnar output. Any output that would be less than this minimum width is padded with + leading spaces + The 2nd parameter is proceeded by a dot. + You do not have to use parameters + + SPECIAL FORMS OF INPUT + For numeric input, the following additional forms of input are accepted besides decimal: + + Octal (only with integer): if the argument begins with a 0 the proceeding characters + will be interpreted as octal (base 8) for integer fields + + Hexadecimal: if the argument begins with 0x the proceeding characters will be interpreted + will be interpreted as hex (base 16) for any numeric fields + for float fields, hexadecimal input results in a precision + limit (in converting input past the decimal point) of 10^-15 + + Character Constant: if the argument begins with a single quote character, the first byte + of the next character will be interpreted as an 8-bit unsigned integer. If there are + additional bytes, they will throw an error (unless the environment variable POSIXLY_CORRECT + is set) + +WRITTEN BY : + Nathan E. Ross, et al. for the uutils project + +MORE INFO : + https://github.com/uutils/coreutils + +COPYRIGHT : + Copyright 2015 uutils project. + Licensed under the MIT License, please see LICENSE file for details + +"; + +pub fn uumain(args: Vec) -> i32 { + let location = &args[0]; + if args.len() <= 1 { + println!( + "{0}: missing operand\nTry '{0} --help' for more information.", + location + ); + return 1; + } + let ref formatstr = args[1]; + + if formatstr == "--help" { + print!("{} {}", LONGHELP_LEAD, LONGHELP_BODY); + } else if formatstr == "--version" { + println!("{} {}", NAME, VERSION); + } else { + let printf_args = &args[2..]; + memo::Memo::run_all(formatstr, printf_args); + } + return 0; +} diff --git a/coreutils/src/printf/tokenize/mod.rs b/coreutils/src/printf/tokenize/mod.rs new file mode 100644 index 000000000..0570b7489 --- /dev/null +++ b/coreutils/src/printf/tokenize/mod.rs @@ -0,0 +1,4 @@ +pub mod token; +pub mod sub; +pub mod unescaped_text; +mod num_format; diff --git a/coreutils/src/printf/tokenize/num_format/format_field.rs b/coreutils/src/printf/tokenize/num_format/format_field.rs new file mode 100644 index 000000000..eb59fde05 --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/format_field.rs @@ -0,0 +1,41 @@ +//! Primitievs used by Sub Tokenizer +//! and num_format modules +#[derive(Clone)] +pub enum FieldType { + Strf, + Floatf, + CninetyNineHexFloatf, + Scif, + Decf, + Intf, + Charf, +} + +// #[allow(non_camel_case_types)] +// pub enum FChar { +// d, +// e, +// E, +// i, +// f, +// F, +// g, +// G, +// u, +// x, +// X, +// o +// } +// + +// a Sub Tokens' fields are stored +// as a single object so they can be more simply +// passed by ref to num_format in a Sub method +#[derive(Clone)] +pub struct FormatField<'a> { + pub min_width: Option, + pub second_field: Option, + pub field_char: &'a char, + pub field_type: &'a FieldType, + pub orig: &'a String, +} diff --git a/coreutils/src/printf/tokenize/num_format/formatter.rs b/coreutils/src/printf/tokenize/num_format/formatter.rs new file mode 100644 index 000000000..aa5cf18a7 --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/formatter.rs @@ -0,0 +1,68 @@ +//! Primitives used by num_format and sub_modules. +//! never dealt with above (e.g. Sub Tokenizer never uses these) + +use std::str::Chars; +use itertools::{put_back_n, PutBackN}; +use cli; +use super::format_field::FormatField; + +// contains the rough ingredients to final +// output for a number, organized together +// to allow for easy generalization of output manipulation +// (e.g. max number of digits after decimal) +pub struct FormatPrimitive { + pub prefix: Option, + pub pre_decimal: Option, + pub post_decimal: Option, + pub suffix: Option, +} + +impl Default for FormatPrimitive { + fn default() -> FormatPrimitive { + FormatPrimitive { + prefix: None, + pre_decimal: None, + post_decimal: None, + suffix: None, + } + } +} + +#[derive(Clone, PartialEq)] +pub enum Base { + Ten = 10, + Hex = 16, + Octal = 8, +} + +// information from the beginning of a numeric argument +// the precedes the beginning of a numeric value +pub struct InPrefix { + pub radix_in: Base, + pub sign: i8, + pub offset: usize, +} + +pub trait Formatter { + // return a FormatPrimitive for + // particular field char(s), given the argument + // string and prefix information (sign, radix) + fn get_primitive( + &self, + field: &FormatField, + inprefix: &InPrefix, + str_in: &str, + ) -> Option; + // return a string from a formatprimitive, + // given information about the field + fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String; +} +pub fn get_it_at(offset: usize, str_in: &str) -> PutBackN { + put_back_n(str_in[offset..].chars()) +} + +// TODO: put this somewhere better +pub fn warn_incomplete_conv(pf_arg: &str) { + // important: keep println here not print + cli::err_msg(&format!("{}: value not completely converted", pf_arg)) +} diff --git a/coreutils/src/printf/tokenize/num_format/formatters/base_conv/mod.rs b/coreutils/src/printf/tokenize/num_format/formatters/base_conv/mod.rs new file mode 100644 index 000000000..5e46a6c51 --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/formatters/base_conv/mod.rs @@ -0,0 +1,330 @@ +pub fn arrnum_int_mult(arr_num: &Vec, basenum: u8, base_ten_int_fact: u8) -> Vec { + let mut carry: u16 = 0; + let mut rem: u16; + let mut new_amount: u16; + let fact: u16 = base_ten_int_fact as u16; + let base: u16 = basenum as u16; + + let mut ret_rev: Vec = Vec::new(); + let mut it = arr_num.iter().rev(); + loop { + let i = it.next(); + match i { + Some(u) => { + new_amount = ((u.clone() as u16) * fact) + carry; + rem = new_amount % base; + carry = (new_amount - rem) / base; + ret_rev.push(rem as u8) + } + None => { + while carry != 0 { + rem = carry % base; + carry = (carry - rem) / base; + ret_rev.push(rem as u8); + } + break; + } + } + } + let ret: Vec = ret_rev.iter().rev().map(|x| x.clone()).collect(); + ret +} + +pub struct Remainder<'a> { + pub position: usize, + pub replace: Vec, + pub arr_num: &'a Vec, +} + +pub struct DivOut<'a> { + pub quotient: u8, + pub remainder: Remainder<'a>, +} + +pub fn arrnum_int_div_step<'a>( + rem_in: Remainder<'a>, + radix_in: u8, + base_ten_int_divisor: u8, + after_decimal: bool, +) -> DivOut<'a> { + let mut rem_out = Remainder { + position: rem_in.position, + replace: Vec::new(), + arr_num: rem_in.arr_num, + }; + + let mut bufferval: u16 = 0; + let base: u16 = radix_in as u16; + let divisor: u16 = base_ten_int_divisor as u16; + let mut traversed = 0; + + let mut quotient = 0; + let refd_vals = &rem_in.arr_num[rem_in.position + rem_in.replace.len()..]; + let mut it_replace = rem_in.replace.iter(); + let mut it_f = refd_vals.iter(); + loop { + let u = match it_replace.next() { + Some(u_rep) => u_rep.clone() as u16, + None => match it_f.next() { + Some(u_orig) => u_orig.clone() as u16, + None => { + if !after_decimal { + break; + } + 0 + } + }, + }; + traversed += 1; + bufferval += u; + if bufferval > divisor { + while bufferval >= divisor { + quotient += 1; + bufferval -= divisor; + } + rem_out.replace = if bufferval == 0 { + Vec::new() + } else { + let remainder_as_arrnum = unsigned_to_arrnum(bufferval); + let remainder_as_base_arrnum = base_conv_vec(&remainder_as_arrnum, 10, radix_in); + remainder_as_base_arrnum + }; + rem_out.position += 1 + (traversed - rem_out.replace.len()); + break; + } else { + bufferval *= base; + } + } + DivOut { + quotient: quotient, + remainder: rem_out, + } +} +// pub struct ArrFloat { +// pub leading_zeros: u8, +// pub values: Vec, +// pub basenum: u8 +// } +// +// pub struct ArrFloatDivOut { +// pub quotient: u8, +// pub remainder: ArrFloat +// } +// +// pub fn arrfloat_int_div( +// arrfloat_in : &ArrFloat, +// base_ten_int_divisor : u8, +// precision : u16 +// ) -> DivOut { +// +// let mut remainder = ArrFloat { +// basenum: arrfloat_in.basenum, +// leading_zeros: arrfloat_in.leading_zeroes, +// values: Vec::new() +// } +// let mut quotient = 0; +// +// let mut bufferval : u16 = 0; +// let base : u16 = arrfloat_in.basenum as u16; +// let divisor : u16 = base_ten_int_divisor as u16; +// +// let mut it_f = arrfloat_in.values.iter(); +// let mut position = 0 + arrfloat_in.leading_zeroes as u16; +// let mut at_end = false; +// while position< precision { +// let next_digit = match it_f.next() { +// Some(c) => {} +// None => { 0 } +// } +// match u_cur { +// Some(u) => { +// bufferval += u.clone() as u16; +// if bufferval > divisor { +// while bufferval >= divisor { +// quotient+=1; +// bufferval -= divisor; +// } +// if bufferval == 0 { +// rem_out.position +=1; +// } else { +// rem_out.replace = Some(bufferval as u8); +// } +// break; +// } else { +// bufferval *= base; +// } +// }, +// None => { +// break; +// } +// } +// u_cur = it_f.next().clone(); +// rem_out.position+=1; +// } +// ArrFloatDivOut { quotient: quotient, remainder: remainder } +// } +// +pub fn arrnum_int_add(arrnum: &Vec, basenum: u8, base_ten_int_term: u8) -> Vec { + let mut carry: u16 = base_ten_int_term as u16; + let mut rem: u16; + let mut new_amount: u16; + let base: u16 = basenum as u16; + + let mut ret_rev: Vec = Vec::new(); + let mut it = arrnum.iter().rev(); + loop { + let i = it.next(); + match i { + Some(u) => { + new_amount = (u.clone() as u16) + carry; + rem = new_amount % base; + carry = (new_amount - rem) / base; + ret_rev.push(rem as u8) + } + None => { + while carry != 0 { + rem = carry % base; + carry = (carry - rem) / base; + ret_rev.push(rem as u8); + } + break; + } + } + } + let ret: Vec = ret_rev.iter().rev().map(|x| x.clone()).collect(); + ret +} + +pub fn base_conv_vec(src: &Vec, radix_src: u8, radix_dest: u8) -> Vec { + let mut result: Vec = Vec::new(); + result.push(0); + for i in src { + result = arrnum_int_mult(&result, radix_dest, radix_src); + result = arrnum_int_add(&result, radix_dest, i.clone()); + } + result +} + +pub fn unsigned_to_arrnum(src: u16) -> Vec { + let mut result: Vec = Vec::new(); + let mut src_tmp: u16 = src.clone(); + while src_tmp > 0 { + result.push((src_tmp % 10) as u8); + src_tmp /= 10; + } + result.reverse(); + result +} + +// temporary needs-improvement-function +#[allow(unused_variables)] +pub fn base_conv_float(src: &Vec, radix_src: u8, radix_dest: u8) -> f64 { + // it would require a lot of addl code + // to implement this for arbitrary string input. + // until then, the below operates as an outline + // of how it would work. + let mut result: Vec = Vec::new(); + result.push(0); + let mut factor: f64 = 1.; + let radix_src_float: f64 = radix_src as f64; + let mut i = 0; + let mut r: f64 = 0 as f64; + for u in src { + if i > 15 { + break; + } + i += 1; + factor /= radix_src_float; + r += factor * (u.clone() as f64) + } + r +} + +pub fn str_to_arrnum(src: &str, radix_def_src: &RadixDef) -> Vec { + let mut intermed_in: Vec = Vec::new(); + for c in src.chars() { + match radix_def_src.from_char(c) { + Some(u) => { + intermed_in.push(u); + } + None => {} //todo err msg on incorrect + } + } + intermed_in +} + +pub fn arrnum_to_str(src: &Vec, radix_def_dest: &RadixDef) -> String { + let mut str_out = String::new(); + for u in src.iter() { + match radix_def_dest.from_u8(u.clone()) { + Some(c) => { + str_out.push(c); + } + None => {} //todo + } + } + str_out +} + +#[allow(unused_variables)] +pub fn base_conv_str(src: &str, radix_def_src: &RadixDef, radix_def_dest: &RadixDef) -> String { + let intermed_in: Vec = str_to_arrnum(src, radix_def_src); + let intermed_out = base_conv_vec( + &intermed_in, + radix_def_src.get_max(), + radix_def_dest.get_max(), + ); + arrnum_to_str(&intermed_out, radix_def_dest) +} + +pub trait RadixDef { + fn get_max(&self) -> u8; + fn from_char(&self, x: char) -> Option; + fn from_u8(&self, x: u8) -> Option; +} +pub struct RadixTen; + +const ZERO_ASC: u8 = '0' as u8; +const UPPER_A_ASC: u8 = 'A' as u8; +const LOWER_A_ASC: u8 = 'a' as u8; + +impl RadixDef for RadixTen { + fn get_max(&self) -> u8 { + 10 + } + fn from_char(&self, c: char) -> Option { + match c { + '0'...'9' => Some(c as u8 - ZERO_ASC), + _ => None, + } + } + fn from_u8(&self, u: u8) -> Option { + match u { + 0...9 => Some((ZERO_ASC + u) as char), + _ => None, + } + } +} +pub struct RadixHex; +impl RadixDef for RadixHex { + fn get_max(&self) -> u8 { + 16 + } + fn from_char(&self, c: char) -> Option { + match c { + '0'...'9' => Some(c as u8 - ZERO_ASC), + 'A'...'F' => Some(c as u8 + 10 - UPPER_A_ASC), + 'a'...'f' => Some(c as u8 + 10 - LOWER_A_ASC), + _ => None, + } + } + fn from_u8(&self, u: u8) -> Option { + match u { + 0...9 => Some((ZERO_ASC + u) as char), + 10...15 => Some((UPPER_A_ASC + (u - 10)) as char), + _ => None, + } + } +} + +mod tests; diff --git a/coreutils/src/printf/tokenize/num_format/formatters/base_conv/tests.rs b/coreutils/src/printf/tokenize/num_format/formatters/base_conv/tests.rs new file mode 100644 index 000000000..b69dcdb65 --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/formatters/base_conv/tests.rs @@ -0,0 +1,55 @@ +#[cfg(test)] + +use super::*; + +#[test] +fn test_arrnum_int_mult() { + // (in base 10) 12 * 4 = 48 + let factor: Vec = vec![1, 2]; + let base_num = 10; + let base_ten_int_fact: u8 = 4; + let should_output: Vec = vec![4, 8]; + + let product = arrnum_int_mult(&factor, base_num, base_ten_int_fact); + assert!(product == should_output); +} + +#[test] +fn test_arrnum_int_non_base_10() { + // (in base 3) + // 5 * 4 = 20 + let factor: Vec = vec![1, 2]; + let base_num = 3; + let base_ten_int_fact: u8 = 4; + let should_output: Vec = vec![2, 0, 2]; + + let product = arrnum_int_mult(&factor, base_num, base_ten_int_fact); + assert!(product == should_output); +} + +#[test] +fn test_arrnum_int_div_shortcircuit() { + // ( + let arrnum: Vec = vec![5, 5, 5, 5, 0]; + let base_num = 10; + let base_ten_int_divisor: u8 = 41; + let remainder_passed_in = Remainder { + position: 1, + replace: vec![1, 3], + arr_num: &arrnum, + }; + + // the "replace" should mean the number being divided + // is 1350, the first time you can get 41 to go into + // 1350, its at 135, where you can get a quotient of + // 3 and a remainder of 12; + + let quotient_should_be: u8 = 3; + let remainder_position_should_be: usize = 3; + let remainder_replace_should_be = vec![1, 2]; + + let result = arrnum_int_div_step(remainder_passed_in, base_num, base_ten_int_divisor, false); + assert!(quotient_should_be == result.quotient); + assert!(remainder_position_should_be == result.remainder.position); + assert!(remainder_replace_should_be == result.remainder.replace); +} diff --git a/coreutils/src/printf/tokenize/num_format/formatters/cninetyninehexfloatf.rs b/coreutils/src/printf/tokenize/num_format/formatters/cninetyninehexfloatf.rs new file mode 100644 index 000000000..9d226818c --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/formatters/cninetyninehexfloatf.rs @@ -0,0 +1,127 @@ +//! formatter for %a %F C99 Hex-floating-point subs +use super::super::format_field::FormatField; +use super::super::formatter::{FormatPrimitive, Formatter, InPrefix}; +use super::float_common::{primitive_to_str_common, FloatAnalysis}; +use super::base_conv; +use super::base_conv::RadixDef; + +pub struct CninetyNineHexFloatf { + as_num: f64, +} +impl CninetyNineHexFloatf { + pub fn new() -> CninetyNineHexFloatf { + CninetyNineHexFloatf { as_num: 0.0 } + } +} + +impl Formatter for CninetyNineHexFloatf { + fn get_primitive( + &self, + field: &FormatField, + inprefix: &InPrefix, + str_in: &str, + ) -> Option { + let second_field = field.second_field.unwrap_or(6) + 1; + let analysis = + FloatAnalysis::analyze(&str_in, inprefix, Some(second_field as usize), None, true); + let f = get_primitive_hex( + inprefix, + &str_in[inprefix.offset..], + &analysis, + second_field as usize, + *field.field_char == 'A', + ); + Some(f) + } + fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { + primitive_to_str_common(prim, &field) + } +} + +// c99 hex has unique requirements of all floating point subs in pretty much every part of building a primitive, from prefix and suffix to need for base conversion (in all other cases if you don't have decimal you must have decimal, here it's the other way around) + +// on the todo list is to have a trait for get_primitive that is implemented by each float formatter and can override a default. when that happens we can take the parts of get_primitive_dec specific to dec and spin them out to their own functions that can be overridden. +#[allow(unused_variables)] +#[allow(unused_assignments)] +fn get_primitive_hex( + inprefix: &InPrefix, + str_in: &str, + analysis: &FloatAnalysis, + last_dec_place: usize, + capitalized: bool, +) -> FormatPrimitive { + let mut f: FormatPrimitive = Default::default(); + f.prefix = Some(String::from(if inprefix.sign == -1 { "-0x" } else { "0x" })); + + // assign the digits before and after the decimal points + // to separate slices. If no digits after decimal point, + // assign 0 + let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos { + Some(pos) => (&str_in[..pos], &str_in[pos + 1..]), + None => (&str_in[..], "0"), + }; + if first_segment_raw.len() == 0 { + first_segment_raw = "0"; + } + // convert to string, hexifying if input is in dec. + // let (first_segment, second_segment) = + // match inprefix.radix_in { + // Base::Ten => { + // (to_hex(first_segment_raw, true), + // to_hex(second_segment_raw, false)) + // } + // _ => { + // (String::from(first_segment_raw), + // String::from(second_segment_raw)) + // } + // }; + // + // + // f.pre_decimal = Some(first_segment); + // f.post_decimal = Some(second_segment); + // + + // TODO actual conversion, make sure to get back mantissa. + // for hex to hex, it's really just a matter of moving the + // decimal point and calculating the mantissa by its initial + // position and its moves, with every position counting for + // the addition or subtraction of 4 (2**4, because 4 bits in a hex digit) + // to the exponent. + // decimal's going to be a little more complicated. correct simulation + // of glibc will require after-decimal division to a specified precision. + // the difficult part of this (arrnum_int_div_step) is already implemented. + + // the hex float name may be a bit misleading in terms of how to go about the + // conversion. The best way to do it is to just convert the floatnum + // directly to base 2 and then at the end translate back to hex. + let mantissa = 0; + f.suffix = Some({ + let ind = if capitalized { "P" } else { "p" }; + if mantissa >= 0 { + format!("{}+{}", ind, mantissa) + } else { + format!("{}{}", ind, mantissa) + } + }); + f +} + +fn to_hex(src: &str, before_decimal: bool) -> String { + let rten = base_conv::RadixTen; + let rhex = base_conv::RadixHex; + if before_decimal { + base_conv::base_conv_str(src, &rten, &rhex) + } else { + let as_arrnum_ten = base_conv::str_to_arrnum(src, &rten); + let s = format!( + "{}", + base_conv::base_conv_float(&as_arrnum_ten, rten.get_max(), rhex.get_max()) + ); + if s.len() > 2 { + String::from(&s[2..]) + } else { + // zero + s + } + } +} diff --git a/coreutils/src/printf/tokenize/num_format/formatters/decf.rs b/coreutils/src/printf/tokenize/num_format/formatters/decf.rs new file mode 100644 index 000000000..46de17290 --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/formatters/decf.rs @@ -0,0 +1,90 @@ +//! formatter for %g %G decimal subs +use super::super::format_field::FormatField; +use super::super::formatter::{FormatPrimitive, Formatter, InPrefix}; +use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; + +fn get_len_fprim(fprim: &FormatPrimitive) -> usize { + let mut len = 0; + if let Some(ref s) = fprim.prefix { + len += s.len(); + } + if let Some(ref s) = fprim.pre_decimal { + len += s.len(); + } + if let Some(ref s) = fprim.post_decimal { + len += s.len(); + } + if let Some(ref s) = fprim.suffix { + len += s.len(); + } + len +} + +pub struct Decf { + as_num: f64, +} +impl Decf { + pub fn new() -> Decf { + Decf { as_num: 0.0 } + } +} +impl Formatter for Decf { + fn get_primitive( + &self, + field: &FormatField, + inprefix: &InPrefix, + str_in: &str, + ) -> Option { + let second_field = field.second_field.unwrap_or(6) + 1; + // default to scif interp. so as to not truncate input vals + // (that would be displayed in scif) based on relation to decimal place + let analysis = FloatAnalysis::analyze( + str_in, + inprefix, + Some(second_field as usize + 1), + None, + false, + ); + let mut f_sci = get_primitive_dec( + inprefix, + &str_in[inprefix.offset..], + &analysis, + second_field as usize, + Some(*field.field_char == 'G'), + ); + // strip trailing zeroes + match f_sci.post_decimal.clone() { + Some(ref post_dec) => { + let mut i = post_dec.len(); + { + let mut it = post_dec.chars(); + while let Some(c) = it.next_back() { + if c != '0' { + break; + } + i -= 1; + } + } + if i != post_dec.len() { + f_sci.post_decimal = Some(String::from(&post_dec[0..i])); + } + } + None => {} + } + let f_fl = get_primitive_dec( + inprefix, + &str_in[inprefix.offset..], + &analysis, + second_field as usize, + None, + ); + Some(if get_len_fprim(&f_fl) >= get_len_fprim(&f_sci) { + f_sci + } else { + f_fl + }) + } + fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { + primitive_to_str_common(prim, &field) + } +} diff --git a/coreutils/src/printf/tokenize/num_format/formatters/float_common.rs b/coreutils/src/printf/tokenize/num_format/formatters/float_common.rs new file mode 100644 index 000000000..0b4a54b30 --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/formatters/float_common.rs @@ -0,0 +1,357 @@ +use super::super::format_field::FormatField; +use super::super::formatter::{get_it_at, warn_incomplete_conv, Base, FormatPrimitive, InPrefix}; +use super::base_conv; +use super::base_conv::RadixDef; + +// if the memory, copy, and comparison cost of chars +// becomes an issue, we can always operate in vec here +// rather than just at de_hex + +pub struct FloatAnalysis { + pub len_important: usize, + // none means no decimal point. + pub decimal_pos: Option, + pub follow: Option, +} +fn has_enough_digits( + hex_input: bool, + hex_output: bool, + string_position: usize, + starting_position: usize, + limit: usize, +) -> bool { + // -1s are for rounding + if hex_output { + if hex_input { + ((string_position - 1) - starting_position >= limit) + } else { + false //undecidable without converting + } + } else { + if hex_input { + ((((string_position - 1) - starting_position) * 9) / 8 >= limit) + } else { + ((string_position - 1) - starting_position >= limit) + } + } +} + +impl FloatAnalysis { + pub fn analyze( + str_in: &str, + inprefix: &InPrefix, + max_sd_opt: Option, + max_after_dec_opt: Option, + hex_output: bool, + ) -> FloatAnalysis { + // this fn assumes + // the input string + // has no leading spaces or 0s + let mut str_it = get_it_at(inprefix.offset, str_in); + let mut ret = FloatAnalysis { + len_important: 0, + decimal_pos: None, + follow: None, + }; + let hex_input = match inprefix.radix_in { + Base::Hex => true, + Base::Ten => false, + Base::Octal => { + panic!("this should never happen: floats should never receive octal input"); + } + }; + let mut i = 0; + let mut pos_before_first_nonzero_after_decimal: Option = None; + while let Some(c) = str_it.next() { + match c { + e @ '0'...'9' | e @ 'A'...'F' | e @ 'a'...'f' => { + if !hex_input { + match e { + '0'...'9' => {} + _ => { + warn_incomplete_conv(str_in); + break; + } + } + } + if ret.decimal_pos.is_some() && pos_before_first_nonzero_after_decimal.is_none() + && e != '0' + { + pos_before_first_nonzero_after_decimal = Some(i - 1); + } + if let Some(max_sd) = max_sd_opt { + if i == max_sd { + // follow is used in cases of %g + // where the character right after the last + // sd is considered is rounded affecting + // the previous digit in 1/2 of instances + ret.follow = Some(e); + } else if ret.decimal_pos.is_some() && i > max_sd { + break; + } + } + if let Some(max_after_dec) = max_after_dec_opt { + if let Some(p) = ret.decimal_pos { + if has_enough_digits(hex_input, hex_output, i, p, max_after_dec) { + break; + } + } + } else if let Some(max_sd) = max_sd_opt { + if let Some(p) = pos_before_first_nonzero_after_decimal { + if has_enough_digits(hex_input, hex_output, i, p, max_sd) { + break; + } + } + } + } + '.' => { + if ret.decimal_pos.is_none() { + ret.decimal_pos = Some(i); + } else { + warn_incomplete_conv(str_in); + break; + } + } + _ => { + warn_incomplete_conv(str_in); + break; + } + }; + i += 1; + } + ret.len_important = i; + ret + } +} + +fn de_hex(src: &str, before_decimal: bool) -> String { + let rten = base_conv::RadixTen; + let rhex = base_conv::RadixHex; + if before_decimal { + base_conv::base_conv_str(src, &rhex, &rten) + } else { + let as_arrnum_hex = base_conv::str_to_arrnum(src, &rhex); + let s = format!( + "{}", + base_conv::base_conv_float(&as_arrnum_hex, rhex.get_max(), rten.get_max()) + ); + if s.len() > 2 { + String::from(&s[2..]) + } else { + // zero + s + } + } +} + +// takes a string in, +// truncates to a position, +// bumps the last digit up one, +// and if the digit was nine +// propagate to the next, etc. +fn _round_str_from(in_str: &str, position: usize) -> (String, bool) { + let mut it = in_str[0..position].chars(); + let mut rev = String::new(); + let mut i = position; + let mut finished_in_dec = false; + while let Some(c) = it.next_back() { + i -= 1; + match c { + '9' => { + rev.push('0'); + } + e @ _ => { + rev.push(((e as u8) + 1) as char); + finished_in_dec = true; + break; + } + } + } + let mut fwd = String::from(&in_str[0..i]); + for ch in rev.chars().rev() { + fwd.push(ch); + } + (fwd, finished_in_dec) +} + +fn round_terminal_digit( + before_dec: String, + after_dec: String, + position: usize, +) -> (String, String) { + if position < after_dec.len() { + let digit_at_pos: char; + { + digit_at_pos = (&after_dec[position..position + 1]) + .chars() + .next() + .expect(""); + } + match digit_at_pos { + '5'...'9' => { + let (new_after_dec, finished_in_dec) = _round_str_from(&after_dec, position); + if finished_in_dec { + return (before_dec, new_after_dec); + } else { + let (new_before_dec, _) = _round_str_from(&before_dec, before_dec.len()); + return (new_before_dec, new_after_dec); + } + // TODO + } + _ => {} + } + } + (before_dec, after_dec) +} + +pub fn get_primitive_dec( + inprefix: &InPrefix, + str_in: &str, + analysis: &FloatAnalysis, + last_dec_place: usize, + sci_mode: Option, +) -> FormatPrimitive { + let mut f: FormatPrimitive = Default::default(); + + // add negative sign section + if inprefix.sign == -1 { + f.prefix = Some(String::from("-")); + } + + // assign the digits before and after the decimal points + // to separate slices. If no digits after decimal point, + // assign 0 + let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos { + Some(pos) => (&str_in[..pos], &str_in[pos + 1..]), + None => (&str_in[..], "0"), + }; + if first_segment_raw.len() == 0 { + first_segment_raw = "0"; + } + // convert to string, de_hexifying if input is in hex. + let (first_segment, second_segment) = match inprefix.radix_in { + Base::Hex => ( + de_hex(first_segment_raw, true), + de_hex(second_segment_raw, false), + ), + _ => ( + String::from(first_segment_raw), + String::from(second_segment_raw), + ), + }; + let (pre_dec_unrounded, post_dec_unrounded, mantissa) = if sci_mode.is_some() { + if first_segment.len() > 1 { + let mut post_dec = String::from(&first_segment[1..]); + post_dec.push_str(&second_segment); + ( + String::from(&first_segment[0..1]), + post_dec, + first_segment.len() as isize - 1, + ) + } else { + match first_segment.chars().next() { + Some('0') => { + let mut it = second_segment.chars().enumerate(); + let mut m: isize = 0; + let mut pre = String::from("0"); + let mut post = String::from("0"); + while let Some((i, c)) = it.next() { + match c { + '0' => {} + _ => { + m = ((i as isize) + 1) * -1; + pre = String::from(&second_segment[i..i + 1]); + post = String::from(&second_segment[i + 1..]); + break; + } + } + } + (pre, post, m) + } + Some(_) => (first_segment, second_segment, 0), + None => { + panic!("float_common: no chars in first segment."); + } + } + } + } else { + (first_segment, second_segment, 0) + }; + + let (pre_dec_draft, post_dec_draft) = + round_terminal_digit(pre_dec_unrounded, post_dec_unrounded, last_dec_place - 1); + + f.pre_decimal = Some(pre_dec_draft); + f.post_decimal = Some(post_dec_draft); + if let Some(capitalized) = sci_mode { + let si_ind = if capitalized { 'E' } else { 'e' }; + f.suffix = Some(if mantissa >= 0 { + format!("{}+{:02}", si_ind, mantissa) + } else { + // negative sign is considered in format!s + // leading zeroes + format!("{}{:03}", si_ind, mantissa) + }); + } + + f +} + +pub fn primitive_to_str_common(prim: &FormatPrimitive, field: &FormatField) -> String { + let mut final_str = String::new(); + match prim.prefix { + Some(ref prefix) => { + final_str.push_str(&prefix); + } + None => {} + } + match prim.pre_decimal { + Some(ref pre_decimal) => { + final_str.push_str(&pre_decimal); + } + None => { + panic!( + "error, format primitives provided to int, will, incidentally under correct \ + behavior, always have a pre_dec value." + ); + } + } + let decimal_places = field.second_field.unwrap_or(6); + match prim.post_decimal { + Some(ref post_decimal) => { + if post_decimal.len() > 0 && decimal_places > 0 { + final_str.push('.'); + let len_avail = post_decimal.len() as u32; + + if decimal_places >= len_avail { + // println!("dec {}, len avail {}", decimal_places, len_avail); + final_str.push_str(post_decimal); + + if *field.field_char != 'g' && *field.field_char != 'G' { + let diff = decimal_places - len_avail; + for _ in 0..diff { + final_str.push('0'); + } + } + } else { + // println!("printing to only {}", decimal_places); + final_str.push_str(&post_decimal[0..decimal_places as usize]); + } + } + } + None => { + panic!( + "error, format primitives provided to int, will, incidentally under correct \ + behavior, always have a pre_dec value." + ); + } + } + match prim.suffix { + Some(ref suffix) => { + final_str.push_str(suffix); + } + None => {} + } + + final_str +} diff --git a/coreutils/src/printf/tokenize/num_format/formatters/floatf.rs b/coreutils/src/printf/tokenize/num_format/formatters/floatf.rs new file mode 100644 index 000000000..46d7c9682 --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/formatters/floatf.rs @@ -0,0 +1,36 @@ +//! formatter for %f %F common-notation floating-point subs +use super::super::format_field::FormatField; +use super::super::formatter::{FormatPrimitive, Formatter, InPrefix}; +use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; + +pub struct Floatf { + as_num: f64, +} +impl Floatf { + pub fn new() -> Floatf { + Floatf { as_num: 0.0 } + } +} +impl Formatter for Floatf { + fn get_primitive( + &self, + field: &FormatField, + inprefix: &InPrefix, + str_in: &str, + ) -> Option { + let second_field = field.second_field.unwrap_or(6) + 1; + let analysis = + FloatAnalysis::analyze(&str_in, inprefix, None, Some(second_field as usize), false); + let f = get_primitive_dec( + inprefix, + &str_in[inprefix.offset..], + &analysis, + second_field as usize, + None, + ); + Some(f) + } + fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { + primitive_to_str_common(prim, &field) + } +} diff --git a/coreutils/src/printf/tokenize/num_format/formatters/intf.rs b/coreutils/src/printf/tokenize/num_format/formatters/intf.rs new file mode 100644 index 000000000..498df3fc2 --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/formatters/intf.rs @@ -0,0 +1,277 @@ +//! formatter for unsigned and signed int subs +//! unsigned ints: %X %x (hex u64) %o (octal u64) %u (base ten u64) +//! signed ints: %i %d (both base ten i64) +use std::u64; +use std::i64; +use super::super::format_field::FormatField; +use super::super::formatter::{get_it_at, warn_incomplete_conv, Base, FormatPrimitive, Formatter, + InPrefix}; + +pub struct Intf { + a: u32, +} + +// see the Intf::analyze() function below +struct IntAnalysis { + check_past_max: bool, + past_max: bool, + is_zero: bool, + len_digits: u8, +} + +impl Intf { + pub fn new() -> Intf { + Intf { a: 0 } + } + // take a ref to argument string, and basic information + // about prefix (offset, radix, sign), and analyze string + // to gain the IntAnalysis information above + // check_past_max: true if the number *may* be above max, + // but we don't know either way. One of several reasons + // we may have to parse as int. + // past_max: true if the object is past max, false if not + // in the future we should probably combine these into an + // Option + // is_zero: true if number is zero, false otherwise + // len_digits: length of digits used to create the int + // important, for example, if we run into a non-valid character + fn analyze(str_in: &str, signed_out: bool, inprefix: &InPrefix) -> IntAnalysis { + // the maximum number of digits we could conceivably + // have before the decimal point without exceeding the + // max + let mut str_it = get_it_at(inprefix.offset, str_in); + let max_sd_in = if signed_out { + match inprefix.radix_in { + Base::Ten => 19, + Base::Octal => 21, + Base::Hex => 16, + } + } else { + match inprefix.radix_in { + Base::Ten => 20, + Base::Octal => 22, + Base::Hex => 16, + } + }; + let mut ret = IntAnalysis { + check_past_max: false, + past_max: false, + is_zero: false, + len_digits: 0, + }; + + // todo turn this to a while let now that we know + // no special behavior on EOI break + loop { + let c_opt = str_it.next(); + if let Some(c) = c_opt { + match c { + '0'...'9' | 'a'...'f' | 'A'...'F' => { + if ret.len_digits == 0 && c == '0' { + ret.is_zero = true; + } else if ret.is_zero { + ret.is_zero = false; + } + ret.len_digits += 1; + if ret.len_digits == max_sd_in { + if let Some(next_ch) = str_it.next() { + match next_ch { + '0'...'9' => { + ret.past_max = true; + } + _ => { + // force conversion + // to check if its above max. + // todo: spin out convert + // into fn, call it here to try + // read val, on Ok() + // save val for reuse later + // that way on same-base in and out + // we don't needlessly convert int + // to str, we can just copy it over. + ret.check_past_max = true; + str_it.put_back(next_ch); + } + } + if ret.past_max { + break; + } + } else { + ret.check_past_max = true; + } + } + } + _ => { + warn_incomplete_conv(str_in); + break; + } + } + } else { + // breaks on EOL + break; + } + } + ret + } + // get a FormatPrimitive of the maximum value for the field char + // and given sign + fn get_max(fchar: char, sign: i8) -> FormatPrimitive { + let mut fmt_prim: FormatPrimitive = Default::default(); + fmt_prim.pre_decimal = Some(String::from(match fchar { + 'd' | 'i' => match sign { + 1 => "9223372036854775807", + _ => { + fmt_prim.prefix = Some(String::from("-")); + "9223372036854775808" + } + }, + 'x' | 'X' => "ffffffffffffffff", + 'o' => "1777777777777777777777", + 'u' | _ => "18446744073709551615", + })); + fmt_prim + } + // conv_from_segment contract: + // 1. takes + // - a string that begins with a non-zero digit, and proceeds + // with zero or more following digits until the end of the string + // - a radix to interpret those digits as + // - a char that communicates: + // whether to interpret+output the string as an i64 or u64 + // what radix to write the parsed number as. + // 2. parses it as a rust integral type + // 3. outputs FormatPrimitive with: + // - if the string falls within bounds: + // number parsed and written in the correct radix + // - if the string falls outside bounds: + // for i64 output, the int minimum or int max (depending on sign) + // for u64 output, the u64 max in the output radix + fn conv_from_segment(segment: &str, radix_in: Base, fchar: char, sign: i8) -> FormatPrimitive { + match fchar { + 'i' | 'd' => match i64::from_str_radix(segment, radix_in as u32) { + Ok(i) => { + let mut fmt_prim: FormatPrimitive = Default::default(); + if sign == -1 { + fmt_prim.prefix = Some(String::from("-")); + } + fmt_prim.pre_decimal = Some(format!("{}", i)); + fmt_prim + } + Err(_) => Intf::get_max(fchar, sign), + }, + _ => match u64::from_str_radix(segment, radix_in as u32) { + Ok(u) => { + let mut fmt_prim: FormatPrimitive = Default::default(); + let u_f = if sign == -1 { u64::MAX - (u - 1) } else { u }; + fmt_prim.pre_decimal = Some(match fchar { + 'X' => format!("{:X}", u_f), + 'x' => format!("{:x}", u_f), + 'o' => format!("{:o}", u_f), + _ => format!("{}", u_f), + }); + fmt_prim + } + Err(_) => Intf::get_max(fchar, sign), + }, + } + } +} +impl Formatter for Intf { + fn get_primitive( + &self, + field: &FormatField, + inprefix: &InPrefix, + str_in: &str, + ) -> Option { + let begin = inprefix.offset; + + // get information about the string. see Intf::Analyze + // def above. + let convert_hints = Intf::analyze( + str_in, + *field.field_char == 'i' || *field.field_char == 'd', + inprefix, + ); + // We always will have a formatprimitive to return + Some(if convert_hints.len_digits == 0 || convert_hints.is_zero { + // if non-digit or end is reached before a non-zero digit + let mut fmt_prim: FormatPrimitive = Default::default(); + fmt_prim.pre_decimal = Some(String::from("0")); + fmt_prim + } else if !convert_hints.past_max { + // if the number is or may be below the bounds limit + let radix_out = match *field.field_char { + 'd' | 'i' | 'u' => Base::Ten, + 'x' | 'X' => Base::Hex, + 'o' | _ => Base::Octal, + }; + let radix_mismatch = !radix_out.eq(&inprefix.radix_in); + let decr_from_max: bool = inprefix.sign == -1 && *field.field_char != 'i'; + let end = begin + convert_hints.len_digits as usize; + + // convert to int if any one of these is true: + // - number of digits in int indicates it may be past max + // - we're subtracting from the max + // - we're converting the base + if convert_hints.check_past_max || decr_from_max || radix_mismatch { + // radix of in and out is the same. + let segment = String::from(&str_in[begin..end]); + let m = Intf::conv_from_segment( + &segment, + inprefix.radix_in.clone(), + *field.field_char, + inprefix.sign, + ); + m + } else { + // otherwise just do a straight string copy. + let mut fmt_prim: FormatPrimitive = Default::default(); + + // this is here and not earlier because + // zero doesn't get a sign, and conv_from_segment + // creates its format primitive separately + if inprefix.sign == -1 && *field.field_char == 'i' { + fmt_prim.prefix = Some(String::from("-")); + } + fmt_prim.pre_decimal = Some(String::from(&str_in[begin..end])); + fmt_prim + } + } else { + Intf::get_max(*field.field_char, inprefix.sign) + }) + } + fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { + let mut finalstr: String = String::new(); + match prim.prefix { + Some(ref prefix) => { + finalstr.push_str(&prefix); + } + None => {} + } + // integral second fields is zero-padded minimum-width + // which gets handled before general minimum-width + match prim.pre_decimal { + Some(ref pre_decimal) => { + match field.second_field { + Some(min) => { + let mut i = min; + let len = pre_decimal.len() as u32; + while i > len { + finalstr.push('0'); + i -= 1; + } + } + None => {} + } + finalstr.push_str(&pre_decimal); + } + None => { + panic!( + "error, format primitives provided to int, will, incidentally under \ + correct behavior, always have a pre_dec value." + ); + } + } + finalstr + } +} diff --git a/coreutils/src/printf/tokenize/num_format/formatters/mod.rs b/coreutils/src/printf/tokenize/num_format/formatters/mod.rs new file mode 100644 index 000000000..329e36d87 --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/formatters/mod.rs @@ -0,0 +1,7 @@ +pub mod intf; +pub mod floatf; +pub mod cninetyninehexfloatf; +pub mod scif; +pub mod decf; +mod float_common; +mod base_conv; diff --git a/coreutils/src/printf/tokenize/num_format/formatters/scif.rs b/coreutils/src/printf/tokenize/num_format/formatters/scif.rs new file mode 100644 index 000000000..f9092d1ca --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/formatters/scif.rs @@ -0,0 +1,41 @@ +//! formatter for %e %E scientific notation subs +use super::super::format_field::FormatField; +use super::super::formatter::{FormatPrimitive, Formatter, InPrefix}; +use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; + +pub struct Scif { + as_num: f64, +} +impl Scif { + pub fn new() -> Scif { + Scif { as_num: 0.0 } + } +} +impl Formatter for Scif { + fn get_primitive( + &self, + field: &FormatField, + inprefix: &InPrefix, + str_in: &str, + ) -> Option { + let second_field = field.second_field.unwrap_or(6) + 1; + let analysis = FloatAnalysis::analyze( + str_in, + inprefix, + Some(second_field as usize + 1), + None, + false, + ); + let f = get_primitive_dec( + inprefix, + &str_in[inprefix.offset..], + &analysis, + second_field as usize, + Some(*field.field_char == 'E'), + ); + Some(f) + } + fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { + primitive_to_str_common(prim, &field) + } +} diff --git a/coreutils/src/printf/tokenize/num_format/mod.rs b/coreutils/src/printf/tokenize/num_format/mod.rs new file mode 100644 index 000000000..d40cf92de --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/mod.rs @@ -0,0 +1,4 @@ +pub mod format_field; +mod formatter; +mod formatters; +pub mod num_format; diff --git a/coreutils/src/printf/tokenize/num_format/num_format.rs b/coreutils/src/printf/tokenize/num_format/num_format.rs new file mode 100644 index 000000000..911fabb6a --- /dev/null +++ b/coreutils/src/printf/tokenize/num_format/num_format.rs @@ -0,0 +1,280 @@ +//! handles creating printed output for numeric substitutions + +use std::env; +use std::vec::Vec; +use cli; +use super::format_field::{FieldType, FormatField}; +use super::formatter::{Base, FormatPrimitive, Formatter, InPrefix}; +use super::formatters::intf::Intf; +use super::formatters::floatf::Floatf; +use super::formatters::cninetyninehexfloatf::CninetyNineHexFloatf; +use super::formatters::scif::Scif; +use super::formatters::decf::Decf; + +pub fn warn_expected_numeric(pf_arg: &String) { + // important: keep println here not print + cli::err_msg(&format!("{}: expected a numeric value", pf_arg)); +} + +// when character constant arguments have excess characters +// issue a warning when POSIXLY_CORRECT is not set +fn warn_char_constant_ign(remaining_bytes: Vec) { + match env::var("POSIXLY_CORRECT") { + Ok(_) => {} + Err(e) => match e { + env::VarError::NotPresent => { + cli::err_msg(&format!( + "warning: {:?}: character(s) following character \ + constant have been ignored", + &*remaining_bytes + )); + } + _ => {} + }, + } +} + +// this function looks at the first few +// characters of an argument and returns a value if we can learn +// a value from that (e.g. no argument? return 0, char constant? ret value) +fn get_provided(str_in_opt: Option<&String>) -> Option { + const C_S_QUOTE: u8 = 39; + const C_D_QUOTE: u8 = 34; + match str_in_opt { + Some(str_in) => { + let mut byte_it = str_in.bytes(); + if let Some(qchar) = byte_it.next() { + match qchar { + C_S_QUOTE | C_D_QUOTE => { + return Some(match byte_it.next() { + Some(second_byte) => { + let mut ignored: Vec = Vec::new(); + while let Some(cont) = byte_it.next() { + ignored.push(cont); + } + if ignored.len() > 0 { + warn_char_constant_ign(ignored); + } + second_byte as u8 + } + // no byte after quote + None => { + let so_far = (qchar as u8 as char).to_string(); + warn_expected_numeric(&so_far); + 0 as u8 + } + }); + } + // first byte is not quote + _ => { + return None; + } // no first byte + } + } else { + Some(0 as u8) + } + } + None => Some(0), + } +} + +// takes a string and returns +// a sign, +// a base, +// and an offset for index after all +// initial spacing, sign, base prefix, and leading zeroes +fn get_inprefix(str_in: &String, field_type: &FieldType) -> InPrefix { + let mut str_it = str_in.chars(); + let mut ret = InPrefix { + radix_in: Base::Ten, + sign: 1, + offset: 0, + }; + let mut topchar = str_it.next().clone(); + // skip spaces and ensure topchar is the first non-space char + // (or None if none exists) + loop { + match topchar { + Some(' ') => { + ret.offset += 1; + topchar = str_it.next(); + } + _ => { + break; + } + } + } + // parse sign + match topchar { + Some('+') => { + ret.offset += 1; + topchar = str_it.next(); + } + Some('-') => { + ret.sign = -1; + ret.offset += 1; + topchar = str_it.next(); + } + _ => {} + } + // we want to exit with offset being + // the index of the first non-zero + // digit before the decimal point or + // if there is none, the zero before the + // decimal point, or, if there is none, + // the decimal point. + + // while we are determining the offset + // we will ensure as a convention + // the offset is always on the first character + // that we are yet unsure if it is the + // final offset. If the zero could be before + // a decimal point we don't move past the zero. + let mut is_hex = false; + if Some('0') == topchar { + if let Some(base) = str_it.next() { + // lead zeroes can only exist in + // octal and hex base + let mut do_clean_lead_zeroes = false; + match base { + 'x' | 'X' => { + is_hex = true; + ret.offset += 2; + ret.radix_in = Base::Hex; + do_clean_lead_zeroes = true; + } + e @ '0'...'9' => { + ret.offset += 1; + match *field_type { + FieldType::Intf => { + ret.radix_in = Base::Octal; + } + _ => {} + } + if e == '0' { + do_clean_lead_zeroes = true; + } + } + _ => {} + } + if do_clean_lead_zeroes { + let mut first = true; + while let Some(ch_zero) = str_it.next() { + // see notes on offset above: + // this is why the offset for octals and decimals + // that reach this branch is 1 even though + // they have already eaten the characters '00' + // this is also why when hex encounters its + // first zero it does not move its offset + // forward because it does not know for sure + // that it's current offset (of that zero) + // is not the final offset, + // whereas at that point octal knows its + // current offset is not the final offset. + match ch_zero { + '0' => { + if !(is_hex && first) { + ret.offset += 1; + } + } + // if decimal, keep last zero if one exists + // (it's possible for last zero to + // not exist at this branch if we're in hex input) + '.' => break, + // other digit, etc. + _ => { + if !(is_hex && first) { + ret.offset += 1; + } + break; + } + } + if first { + first = false; + } + } + } + } + } + ret +} + +// this is the function a Sub's print will delegate to +// if it is a numeric field, passing the field details +// and an iterator to the argument +pub fn num_format(field: &FormatField, in_str_opt: Option<&String>) -> Option { + let fchar = field.field_char.clone(); + + // num format mainly operates by further delegating to one of + // several Formatter structs depending on the field + // see formatter.rs for more details + + // to do switch to static dispatch + let fmtr: Box = match *field.field_type { + FieldType::Intf => Box::new(Intf::new()), + FieldType::Floatf => Box::new(Floatf::new()), + FieldType::CninetyNineHexFloatf => Box::new(CninetyNineHexFloatf::new()), + FieldType::Scif => Box::new(Scif::new()), + FieldType::Decf => Box::new(Decf::new()), + _ => { + panic!("asked to do num format with non-num fieldtype"); + } + }; + let prim_opt= + // if we can get an assumed value from looking at the first + // few characters, use that value to create the FormatPrimitive + if let Some(provided_num) = get_provided(in_str_opt) { + let mut tmp : FormatPrimitive = Default::default(); + match fchar { + 'u' | 'i' | 'd' => { + tmp.pre_decimal = Some( + format!("{}", provided_num)); + }, + 'x' | 'X' => { + tmp.pre_decimal = Some( + format!("{:x}", provided_num)); + }, + 'o' => { + tmp.pre_decimal = Some( + format!("{:o}", provided_num)); + }, + 'e' | 'E' | 'g' | 'G' => { + let as_str = format!("{}", provided_num); + let inprefix = get_inprefix( + &as_str, + &field.field_type + ); + tmp=fmtr.get_primitive(field, &inprefix, &as_str) + .expect("err during default provided num"); + }, + _ => { + tmp.pre_decimal = Some( + format!("{}", provided_num)); + tmp.post_decimal = Some(String::from("0")); + } + } + Some(tmp) + } else { + // otherwise we'll interpret the argument as a number + // using the appropriate Formatter + let in_str = in_str_opt.expect( + "please send the devs this message: + \n get_provided is failing to ret as Some(0) on no str "); + // first get information about the beginning of the + // numeric argument that would be useful for + // any formatter (int or float) + let inprefix = get_inprefix( + in_str, + &field.field_type + ); + // then get the FormatPrimitive from the Formatter + fmtr.get_primitive(field, &inprefix, in_str) + }; + // if we have a formatPrimitive, print its results + // according to the field-char appropriate Formatter + if let Some(prim) = prim_opt { + Some(fmtr.primitive_to_str(&prim, field.clone())) + } else { + None + } +} diff --git a/coreutils/src/printf/tokenize/sub.rs b/coreutils/src/printf/tokenize/sub.rs new file mode 100644 index 000000000..7e52757d4 --- /dev/null +++ b/coreutils/src/printf/tokenize/sub.rs @@ -0,0 +1,428 @@ +//! Sub is a token that represents a +//! segment of the format string that is a substitution +//! it is created by Sub's implementation of the Tokenizer trait +//! Subs which have numeric field chars make use of the num_format +//! submodule +use std::slice::Iter; +use std::iter::Peekable; +use std::str::Chars; +use std::process::exit; +use cli; +use itertools::{put_back_n, PutBackN}; +use super::token; +use super::unescaped_text::UnescapedText; +use super::num_format::format_field::{FieldType, FormatField}; +use super::num_format::num_format; +// use std::collections::HashSet; + +fn err_conv(sofar: &String) { + cli::err_msg(&format!("%{}: invalid conversion specification", sofar)); + exit(cli::EXIT_ERR); +} + +fn convert_asterisk_arg_int(asterisk_arg: &String) -> isize { + // this is a costly way to parse the + // args used for asterisk values into integers + // from various bases. Actually doing it correctly + // (going through the pipeline to intf, but returning + // the integer instead of writing it to string and then + // back) is on the refactoring TODO + let field_type = FieldType::Intf; + let field_char = 'i'; + let field_info = FormatField { + min_width: Some(0), + second_field: Some(0), + orig: asterisk_arg, + field_type: &field_type, + field_char: &field_char, + }; + num_format::num_format(&field_info, Some(asterisk_arg)) + .unwrap() + .parse::() + .unwrap() +} + +pub enum CanAsterisk { + Fixed(T), + Asterisk, +} + +// Sub is a tokenizer which creates tokens +// for substitution segments of a format string +pub struct Sub { + min_width: CanAsterisk>, + second_field: CanAsterisk>, + field_char: char, + field_type: FieldType, + orig: String, +} +impl Sub { + pub fn new( + min_width: CanAsterisk>, + second_field: CanAsterisk>, + field_char: char, + orig: String, + ) -> Sub { + // for more dry printing, field characters are grouped + // in initialization of token. + let field_type = match field_char { + 's' | 'b' => FieldType::Strf, + 'd' | 'i' | 'u' | 'o' | 'x' | 'X' => FieldType::Intf, + 'f' | 'F' => FieldType::Floatf, + 'a' | 'A' => FieldType::CninetyNineHexFloatf, + 'e' | 'E' => FieldType::Scif, + 'g' | 'G' => FieldType::Decf, + 'c' => FieldType::Charf, + _ => { + // should be unreachable. + println!("Invalid fieldtype"); + exit(cli::EXIT_ERR); + } + }; + Sub { + min_width: min_width, + second_field: second_field, + field_char: field_char, + field_type: field_type, + orig: orig, + } + } +} + +struct SubParser { + min_width_tmp: Option, + min_width_is_asterisk: bool, + past_decimal: bool, + second_field_tmp: Option, + second_field_is_asterisk: bool, + specifiers_found: bool, + field_char: Option, + text_so_far: String, +} + +impl SubParser { + fn new() -> SubParser { + SubParser { + min_width_tmp: None, + min_width_is_asterisk: false, + past_decimal: false, + second_field_tmp: None, + second_field_is_asterisk: false, + specifiers_found: false, + field_char: None, + text_so_far: String::new(), + } + } + fn from_it( + it: &mut PutBackN, + args: &mut Peekable>, + ) -> Option> { + let mut parser = SubParser::new(); + if parser.sub_vals_retrieved(it) { + let t: Box = SubParser::build_token(parser); + t.print(args); + Some(t) + } else { + None + } + } + fn build_token(parser: SubParser) -> Box { + // not a self method so as to allow move of subparser vals. + // return new Sub struct as token + let t: Box = Box::new(Sub::new( + if parser.min_width_is_asterisk { + CanAsterisk::Asterisk + } else { + CanAsterisk::Fixed(parser.min_width_tmp.map(|x| x.parse::().unwrap())) + }, + if parser.second_field_is_asterisk { + CanAsterisk::Asterisk + } else { + CanAsterisk::Fixed(parser.second_field_tmp.map(|x| x.parse::().unwrap())) + }, + parser.field_char.unwrap(), + parser.text_so_far, + )); + t + } + fn sub_vals_retrieved(&mut self, it: &mut PutBackN) -> bool { + if !SubParser::successfully_eat_prefix(it, &mut self.text_so_far) { + return false; + } + // this fn in particular is much longer than it needs to be + // .could get a lot + // of code savings just by cleaning it up. shouldn't use a regex + // though, as we want to mimic the original behavior of printing + // the field as interpreted up until the error in the field. + + let mut legal_fields = vec![// 'a', 'A', //c99 hex float implementation not yet complete + 'b', + 'c', + 'd', + 'e', + 'E', + 'f', + 'F', + 'g', + 'G', + 'i', + 'o', + 's', + 'u', + 'x', + 'X']; + let mut specifiers = vec!['h', 'j', 'l', 'L', 't', 'z']; + legal_fields.sort(); + specifiers.sort(); + + // divide substitution from %([0-9]+)?(.[0-9+])?([a-zA-Z]) + // into min_width, second_field, field_char + while let Some(ch) = it.next() { + self.text_so_far.push(ch); + match ch as char { + '-' | '*' | '0'...'9' => { + if !self.past_decimal { + if self.min_width_is_asterisk || self.specifiers_found { + err_conv(&self.text_so_far); + } + if self.min_width_tmp.is_none() { + self.min_width_tmp = Some(String::new()); + } + match self.min_width_tmp.as_mut() { + Some(x) => { + if (ch == '-' || ch == '*') && x.len() > 0 { + err_conv(&self.text_so_far); + } + if ch == '*' { + self.min_width_is_asterisk = true; + } + x.push(ch); + } + None => { + panic!("should be unreachable"); + } + } + } else { + // second field should never have a + // negative value + if self.second_field_is_asterisk || ch == '-' || self.specifiers_found { + err_conv(&self.text_so_far); + } + if self.second_field_tmp.is_none() { + self.second_field_tmp = Some(String::new()); + } + match self.second_field_tmp.as_mut() { + Some(x) => { + if ch == '*' && x.len() > 0 { + err_conv(&self.text_so_far); + } + if ch == '*' { + self.second_field_is_asterisk = true; + } + x.push(ch); + } + None => { + panic!("should be unreachable"); + } + } + } + } + '.' => { + if !self.past_decimal { + self.past_decimal = true; + } else { + err_conv(&self.text_so_far); + } + } + x if legal_fields.binary_search(&x).is_ok() => { + self.field_char = Some(ch); + self.text_so_far.push(ch); + break; + } + x if specifiers.binary_search(&x).is_ok() => { + if !self.past_decimal { + self.past_decimal = true; + } + if !self.specifiers_found { + self.specifiers_found = true; + } + } + _ => { + err_conv(&self.text_so_far); + } + } + } + if !self.field_char.is_some() { + err_conv(&self.text_so_far); + } + let field_char_retrieved = self.field_char.unwrap(); + if self.past_decimal && self.second_field_tmp.is_none() { + self.second_field_tmp = Some(String::from("0")); + } + self.validate_field_params(field_char_retrieved); + // if the dot is provided without a second field + // printf interprets it as 0. + match self.second_field_tmp.as_mut() { + Some(x) => { + if x.len() == 0 { + self.min_width_tmp = Some(String::from("0")); + } + } + _ => {} + } + + true + } + fn successfully_eat_prefix(it: &mut PutBackN, text_so_far: &mut String) -> bool { + // get next two chars, + // if they're '%%' we're not tokenizing it + // else put chars back + let preface = it.next(); + let n_ch = it.next(); + if preface == Some('%') && n_ch != Some('%') { + match n_ch { + Some(x) => { + it.put_back(x); + true + } + None => { + text_so_far.push('%'); + err_conv(&text_so_far); + false + } + } + } else { + n_ch.map(|x| it.put_back(x)); + preface.map(|x| it.put_back(x)); + false + } + } + fn validate_field_params(&self, field_char: char) { + // check for illegal combinations here when possible vs + // on each application so we check less per application + // to do: move these checks to Sub::new + if (field_char == 's' && self.min_width_tmp == Some(String::from("0"))) + || (field_char == 'c' + && (self.min_width_tmp == Some(String::from("0")) || self.past_decimal)) + || (field_char == 'b' + && (self.min_width_tmp.is_some() || self.past_decimal + || self.second_field_tmp.is_some())) + { + err_conv(&self.text_so_far); + } + } +} + +impl token::Tokenizer for Sub { + fn from_it( + it: &mut PutBackN, + args: &mut Peekable>, + ) -> Option> { + SubParser::from_it(it, args) + } +} +impl token::Token for Sub { + fn print(&self, pf_args_it: &mut Peekable>) { + let field = FormatField { + min_width: match self.min_width { + CanAsterisk::Fixed(x) => x, + CanAsterisk::Asterisk => { + match pf_args_it.next() { + // temporary, use intf.rs instead + Some(x) => Some(convert_asterisk_arg_int(x)), + None => Some(0), + } + } + }, + second_field: match self.second_field { + CanAsterisk::Fixed(x) => x, + CanAsterisk::Asterisk => { + match pf_args_it.next() { + // temporary, use intf.rs instead + Some(x) => { + let result = convert_asterisk_arg_int(x); + if result < 0 { + None + } else { + Some(result as u32) + } + } + None => Some(0), + } + } + }, + field_char: &self.field_char, + field_type: &self.field_type, + orig: &self.orig, + }; + let pf_arg = pf_args_it.next(); + + // minimum width is handled independently of actual + // field char + let pre_min_width_opt: Option = match *field.field_type { + // if %s just return arg + // if %b use UnescapedText module's unescaping-fn + // if %c return first char of arg + FieldType::Strf | FieldType::Charf => { + match pf_arg { + Some(arg_string) => { + match *field.field_char { + 's' => Some(match field.second_field { + Some(max) => String::from(&arg_string[..max as usize]), + None => arg_string.clone(), + }), + 'b' => { + let mut a_it = put_back_n(arg_string.chars()); + UnescapedText::from_it_core(&mut a_it, true); + None + } + // for 'c': get iter of string vals, + // get opt of first val + // and map it to opt + 'c' | _ => arg_string.chars().next().map(|x| x.to_string()), + } + } + None => None, + } + } + _ => { + // non string/char fields are delegated to num_format + num_format::num_format(&field, pf_arg) + } + }; + match pre_min_width_opt { + // if have a string, print it, ensuring minimum width is met. + Some(pre_min_width) => { + print!( + "{}", + match field.min_width { + Some(min_width) => { + let diff: isize = + min_width.abs() as isize - pre_min_width.len() as isize; + if diff > 0 { + let mut final_str = String::new(); + // definitely more efficient ways + // to do this. + let pad_before = min_width > 0; + if !pad_before { + final_str.push_str(&pre_min_width); + } + for _ in 0..diff { + final_str.push(' '); + } + if pad_before { + final_str.push_str(&pre_min_width); + } + final_str + } else { + pre_min_width + } + } + None => pre_min_width, + } + ); + } + None => {} + } + } +} diff --git a/coreutils/src/printf/tokenize/token.rs b/coreutils/src/printf/tokenize/token.rs new file mode 100644 index 000000000..378702682 --- /dev/null +++ b/coreutils/src/printf/tokenize/token.rs @@ -0,0 +1,30 @@ +//! Traits and enums dealing with Tokenization of printf Format String +#[allow(unused_must_use)] + +use std::iter::Peekable; +use std::str::Chars; +use std::slice::Iter; +use itertools::PutBackN; + +// A token object is an object that can print the expected output +// of a contiguous segment of the format string, and +// requires at most 1 argusegment +pub trait Token { + fn print(&self, args: &mut Peekable>); +} + +// A tokenizer object is an object that takes an iterator +// at a position in a format string, and sees whether +// it can return a token of a type it knows how to produce +// if so, return the token, move the iterator past the +// format string text the token represents, and if an +// argument is used move the argument iter forward one + +// creating token of a format string segment should also cause +// printing of that token's value. Essentially tokenizing +// a whole format string will print the format string and consume +// a number of arguments equal to the number of argument-using tokens + +pub trait Tokenizer { + fn from_it(it: &mut PutBackN, args: &mut Peekable>) -> Option>; +} diff --git a/coreutils/src/printf/tokenize/unescaped_text.rs b/coreutils/src/printf/tokenize/unescaped_text.rs new file mode 100644 index 000000000..3ea0038b1 --- /dev/null +++ b/coreutils/src/printf/tokenize/unescaped_text.rs @@ -0,0 +1,253 @@ +//! UnescapedText is a tokenizer impl +//! for tokenizing character literals, +//! and escaped character literals (of allowed escapes), +//! into an unescaped text byte array + +use std::iter::Peekable; +use std::slice::Iter; +use std::str::Chars; +use std::char::from_u32; +use std::process::exit; +use cli; +use itertools::PutBackN; +use super::token; + +pub struct UnescapedText(Vec); +impl UnescapedText { + fn new() -> UnescapedText { + UnescapedText(Vec::new()) + } + // take an iterator to the format string + // consume between min and max chars + // and return it as a base-X number + fn base_to_u32(min_chars: u8, max_chars: u8, base: u32, it: &mut PutBackN) -> u32 { + let mut retval: u32 = 0; + let mut found = 0; + while found < max_chars { + // if end of input break + let nc = it.next(); + match nc { + Some(digit) => { + // if end of hexchars break + match digit.to_digit(base) { + Some(d) => { + found += 1; + retval *= base; + retval += d; + } + None => { + it.put_back(digit); + break; + } + } + } + None => { + break; + } + } + } + if found < min_chars { + // only ever expected for hex + println!("missing hexadecimal number in escape"); //todo stderr + exit(cli::EXIT_ERR); + } + retval + } + // validates against valid + // IEC 10646 vals - these values + // are pinned against the more popular + // printf so as to not disrupt when + // dropped-in as a replacement. + fn validate_iec(val: u32, eight_word: bool) { + let mut preface = 'u'; + let mut leading_zeros = 4; + if eight_word { + preface = 'U'; + leading_zeros = 8; + } + let err_msg = format!( + "invalid universal character name {0}{1:02$x}", + preface, val, leading_zeros + ); + if (val < 159 && (val != 36 && val != 64 && val != 96)) || (val > 55296 && val < 57343) { + println!("{}", err_msg); //todo stderr + exit(cli::EXIT_ERR); + } + } + // pass an iterator that succeeds an '/', + // and process the remaining character + // adding the unescaped bytes + // to the passed byte_vec + // in subs_mode change octal behavior + fn handle_escaped(byte_vec: &mut Vec, it: &mut PutBackN, subs_mode: bool) { + let ch = match it.next() { + Some(x) => x, + None => '\\', + }; + match ch { + '0'...'9' | 'x' => { + let min_len = 1; + let mut max_len = 2; + let mut base = 16; + let ignore = false; + match ch { + 'x' => {} + e @ '0'...'9' => { + max_len = 3; + base = 8; + // in practice, gnu coreutils printf + // interprets octals without a + // leading zero in %b + // but it only skips leading zeros + // in %b mode. + // if we ever want to match gnu coreutil + // printf's docs instead of its behavior + // we'd set this to true. + // if subs_mode && e != '0' + // { ignore = true; } + if !subs_mode || e != '0' { + it.put_back(ch); + } + } + _ => {} + } + if !ignore { + let val = (UnescapedText::base_to_u32(min_len, max_len, base, it) % 256) as u8; + byte_vec.push(val); + let bvec = [val]; + cli::flush_bytes(&bvec); + } else { + byte_vec.push(ch as u8); + } + } + e @ _ => { + // only for hex and octal + // is byte encoding specified. + // otherwise, why not leave the door open + // for other encodings unless it turns out + // a bottleneck. + let mut s = String::new(); + let ch = match e { + '\\' => '\\', + '"' => '"', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + // bell + 'a' => '\x07', + // backspace + 'b' => '\x08', + // vertical tab + 'v' => '\x0B', + // form feed + 'f' => '\x0C', + // escape character + 'e' => '\x1B', + 'c' => exit(cli::EXIT_OK), + 'u' | 'U' => { + let len = match e { + 'u' => 4, + 'U' | _ => 8, + }; + let val = UnescapedText::base_to_u32(len, len, 16, it); + UnescapedText::validate_iec(val, false); + if let Some(c) = from_u32(val) { + c + } else { + '-' + } + } + _ => { + s.push('\\'); + ch + } + }; + s.push(ch); + cli::flush_str(&s); + byte_vec.extend(s.bytes()); + } + }; + } + + // take an iterator to a string, + // and return a wrapper around a Vec of unescaped bytes + // break on encounter of sub symbol ('%[^%]') unless called + // through %b subst. + pub fn from_it_core(it: &mut PutBackN, subs_mode: bool) -> Option> { + let mut addchar = false; + let mut new_text = UnescapedText::new(); + let mut tmp_str = String::new(); + { + let new_vec: &mut Vec = &mut (new_text.0); + while let Some(ch) = it.next() { + if !addchar { + addchar = true; + } + match ch as char { + x if x != '\\' && x != '%' => { + // lazy branch eval + // remember this fn could be called + // many times in a single exec through %b + cli::flush_char(&ch); + tmp_str.push(ch); + } + '\\' => { + // the literal may be a literal bytecode + // and not valid utf-8. Str only supports + // valid utf-8. + // if we find the unnecessary drain + // on non hex or octal escapes is costly + // then we can make it faster/more complex + // with as-necessary draining. + if tmp_str.len() > 0 { + new_vec.extend(tmp_str.bytes()); + tmp_str = String::new(); + } + UnescapedText::handle_escaped(new_vec, it, subs_mode) + } + x if x == '%' && !subs_mode => { + if let Some(follow) = it.next() { + if follow == '%' { + cli::flush_char(&ch); + tmp_str.push(ch); + } else { + it.put_back(follow); + it.put_back(ch); + break; + } + } else { + it.put_back(ch); + break; + } + } + _ => { + cli::flush_char(&ch); + tmp_str.push(ch); + } + } + } + if tmp_str.len() > 0 { + new_vec.extend(tmp_str.bytes()); + } + } + match addchar { + true => Some(Box::new(new_text)), + false => None, + } + } +} +#[allow(unused_variables)] +impl token::Tokenizer for UnescapedText { + fn from_it( + it: &mut PutBackN, + args: &mut Peekable>, + ) -> Option> { + UnescapedText::from_it_core(it, false) + } +} +#[allow(unused_variables)] +impl token::Token for UnescapedText { + fn print(&self, pf_args_it: &mut Peekable>) { + cli::flush_bytes(&self.0[..]); + } +} diff --git a/coreutils/src/ptx/Cargo.toml b/coreutils/src/ptx/Cargo.toml new file mode 100644 index 000000000..6a7ad255c --- /dev/null +++ b/coreutils/src/ptx/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ptx" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_ptx" +path = "ptx.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +aho-corasick = "0.7.3" +memchr = "2.2.0" +regex-syntax = "0.6.7" +regex = "1.0.1" +uucore = "0.0.1" + +[[bin]] +name = "ptx" +path = "../../uumain.rs" diff --git a/coreutils/src/ptx/ptx.rs b/coreutils/src/ptx/ptx.rs new file mode 100644 index 000000000..cc30242d5 --- /dev/null +++ b/coreutils/src/ptx/ptx.rs @@ -0,0 +1,597 @@ +#![crate_name = "uu_ptx"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Dorota Kapturkiewicz + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate aho_corasick; +extern crate getopts; +extern crate memchr; +extern crate regex; +extern crate regex_syntax; + +#[macro_use] +extern crate uucore; + +use getopts::{Matches, Options}; +use regex::Regex; +use std::cmp; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::default::Default; +use std::fs::File; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; + +static NAME: &str = "ptx"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(Debug)] +enum OutFormat { + Dumb, + Roff, + Tex, +} + +#[derive(Debug)] +struct Config { + format: OutFormat, + gnu_ext: bool, + auto_ref: bool, + input_ref: bool, + right_ref: bool, + ignore_case: bool, + macro_name: String, + trunc_str: String, + context_regex: String, + line_width: usize, + gap_size: usize, +} + +impl Default for Config { + fn default() -> Config { + Config { + format: OutFormat::Dumb, + gnu_ext: true, + auto_ref: false, + input_ref: false, + right_ref: false, + ignore_case: false, + macro_name: "xx".to_owned(), + trunc_str: "/".to_owned(), + context_regex: "\\w+".to_owned(), + line_width: 72, + gap_size: 3, + } + } +} + +fn read_word_filter_file(matches: &Matches, option: &str) -> HashSet { + let filename = matches.opt_str(option).expect("parsing options failed!"); + let reader = BufReader::new(crash_if_err!(1, File::open(filename))); + let mut words: HashSet = HashSet::new(); + for word in reader.lines() { + words.insert(crash_if_err!(1, word)); + } + words +} + +#[derive(Debug)] +struct WordFilter { + only_specified: bool, + ignore_specified: bool, + only_set: HashSet, + ignore_set: HashSet, + word_regex: String, +} + +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")) + } else { + (false, HashSet::new()) + }; + let (i, iset): (bool, HashSet) = if matches.opt_present("i") { + (true, read_word_filter_file(matches, "i")) + } else { + (false, HashSet::new()) + }; + if matches.opt_present("b") { + crash!(1, "-b not implemented yet"); + } + let reg = if matches.opt_present("W") { + matches.opt_str("W").expect("parsing options failed!") + } else if config.gnu_ext { + "\\w+".to_owned() + } else { + "[^ \t\n]+".to_owned() + }; + WordFilter { + only_specified: o, + ignore_specified: i, + only_set: oset, + ignore_set: iset, + word_regex: reg, + } + } +} + +#[derive(Debug, PartialOrd, PartialEq, Eq, Ord)] +struct WordRef { + word: String, + global_line_nr: usize, + local_line_nr: usize, + position: usize, + position_end: usize, + 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 explaination = "With no FILE, or when FILE is -, read standard input. \ + Default is '-F /'."; + println!("{}\n{}", opts.usage(&brief), explaination); +} + +fn get_config(matches: &Matches) -> Config { + let mut config: Config = Default::default(); + let err_msg = "parsing options failed"; + if matches.opt_present("G") { + 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") { + 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); + } + if matches.opt_present("F") { + config.trunc_str = matches.opt_str("F").expect(err_msg); + } + if matches.opt_present("w") { + let width_str = matches.opt_str("w").expect(err_msg); + 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); + config.gap_size = crash_if_err!(1, usize::from_str_radix(&gap_str, 10)); + } + if matches.opt_present("O") { + config.format = OutFormat::Roff; + } + if matches.opt_present("T") { + config.format = OutFormat::Tex; + } + config +} + +fn read_input(input_files: &[String], config: &Config) -> HashMap, usize)> { + let mut file_map: HashMap, usize)> = HashMap::new(); + let mut files = Vec::new(); + if input_files.is_empty() { + files.push("-"); + } else { + if config.gnu_ext { + for file in input_files { + files.push(&file); + } + } else { + files.push(&input_files[0]); + } + } + let mut lines_so_far: usize = 0; + for filename in files { + let reader: BufReader> = BufReader::new(if filename == "-" { + Box::new(stdin()) + } else { + let file = crash_if_err!(1, File::open(filename)); + Box::new(file) + }); + let lines: Vec = reader.lines().map(|x| crash_if_err!(1, x)).collect(); + let size = lines.len(); + file_map.insert(filename.to_owned(), (lines, lines_so_far)); + lines_so_far += size + } + file_map +} + +fn create_word_set( + config: &Config, + filter: &WordFilter, + file_map: &HashMap, usize)>, +) -> BTreeSet { + let reg = Regex::new(&filter.word_regex).unwrap(); + let ref_reg = Regex::new(&config.context_regex).unwrap(); + let mut word_set: BTreeSet = BTreeSet::new(); + for (file, lines) in file_map.iter() { + let mut count: usize = 0; + let offs = lines.1; + for line in &lines.0 { + // if -r, exclude reference from word set + let (ref_beg, ref_end) = match ref_reg.find(line) { + Some(x) => (x.start(), x.end()), + None => (0, 0), + }; + // match words with given regex + for mat in reg.find_iter(line) { + let (beg, end) = (mat.start(), mat.end()); + if config.input_ref && ((beg, end) == (ref_beg, ref_end)) { + continue; + } + let mut word = line[beg..end].to_owned(); + if filter.only_specified && !(filter.only_set.contains(&word)) { + continue; + } + if filter.ignore_specified && filter.ignore_set.contains(&word) { + continue; + } + if config.ignore_case { + word = word.to_lowercase(); + } + word_set.insert(WordRef { + word: word, + filename: String::from(file.clone()), + global_line_nr: offs + count, + local_line_nr: count, + position: beg, + position_end: end, + }); + } + count += 1; + } + } + word_set +} + +fn get_reference(config: &Config, word_ref: &WordRef, line: &str) -> String { + if config.auto_ref { + format!("{}:{}", word_ref.filename, word_ref.local_line_nr + 1) + } else if config.input_ref { + let reg = Regex::new(&config.context_regex).unwrap(); + let (beg, end) = match reg.find(line) { + Some(x) => (x.start(), x.end()), + None => (0, 0), + }; + format!("{}", &line[beg..end]) + } else { + String::new() + } +} + +fn assert_str_integrity(s: &[char], beg: usize, end: usize) { + assert!(beg <= end); + assert!(end <= s.len()); +} + +fn trim_broken_word_left(s: &[char], beg: usize, end: usize) -> usize { + assert_str_integrity(s, beg, end); + if beg == end || beg == 0 || s[beg].is_whitespace() || s[beg - 1].is_whitespace() { + return beg; + } + let mut b = beg; + while b < end && !s[b].is_whitespace() { + b += 1; + } + b +} + +fn trim_broken_word_right(s: &[char], beg: usize, end: usize) -> usize { + assert_str_integrity(s, beg, end); + if beg == end || end == s.len() || s[end - 1].is_whitespace() || s[end].is_whitespace() { + return end; + } + let mut e = end; + while beg < e && !s[e - 1].is_whitespace() { + e -= 1; + } + e +} + +fn trim_idx(s: &[char], beg: usize, end: usize) -> (usize, usize) { + assert_str_integrity(s, beg, end); + let mut b = beg; + let mut e = end; + while b < e && s[b].is_whitespace() { + b += 1; + } + while b < e && s[e - 1].is_whitespace() { + e -= 1; + } + (b, e) +} + +fn get_output_chunks( + all_before: &str, + keyword: &str, + all_after: &str, + config: &Config, +) -> (String, String, String, String) { + assert_eq!(all_before.trim(), all_before); + assert_eq!(keyword.trim(), keyword); + assert_eq!(all_after.trim(), all_after); + let mut head = String::new(); + let mut before = String::new(); + let mut after = String::new(); + let mut tail = String::new(); + + let half_line_size = cmp::max( + (config.line_width / 2) as isize - (2 * config.trunc_str.len()) as isize, + 0, + ) as usize; + let max_after_size = cmp::max(half_line_size as isize - keyword.len() as isize - 1, 0) as usize; + let max_before_size = half_line_size; + let all_before_vec: Vec = all_before.chars().collect(); + let all_after_vec: Vec = all_after.chars().collect(); + + // get before + let mut bb_tmp = cmp::max(all_before.len() as isize - max_before_size as isize, 0) as usize; + bb_tmp = trim_broken_word_left(&all_before_vec, bb_tmp, all_before.len()); + let (before_beg, before_end) = trim_idx(&all_before_vec, bb_tmp, all_before.len()); + before.push_str(&all_before[before_beg..before_end]); + assert!(max_before_size >= before.len()); + + // get after + let mut ae_tmp = cmp::min(max_after_size, all_after.len()); + ae_tmp = trim_broken_word_right(&all_after_vec, 0, ae_tmp); + let (after_beg, after_end) = trim_idx(&all_after_vec, 0, ae_tmp); + after.push_str(&all_after[after_beg..after_end]); + assert!(max_after_size >= after.len()); + + // get tail + let max_tail_size = max_before_size - before.len(); + let (tb, _) = trim_idx(&all_after_vec, after_end, all_after.len()); + let mut te_tmp = cmp::min(tb + max_tail_size, all_after.len()); + te_tmp = trim_broken_word_right(&all_after_vec, tb, te_tmp); + let (tail_beg, tail_end) = trim_idx(&all_after_vec, tb, te_tmp); + tail.push_str(&all_after[tail_beg..tail_end]); + + // get head + let max_head_size = max_after_size - after.len(); + let (_, he) = trim_idx(&all_before_vec, 0, before_beg); + let mut hb_tmp = cmp::max(he as isize - max_head_size as isize, 0) as usize; + hb_tmp = trim_broken_word_left(&all_before_vec, hb_tmp, he); + let (head_beg, head_end) = trim_idx(&all_before_vec, hb_tmp, he); + head.push_str(&all_before[head_beg..head_end]); + + // put right context truncation string if needed + if after_end != all_after.len() && tail_beg == tail_end { + after.push_str(&config.trunc_str); + } else if after_end != all_after.len() && tail_end != all_after.len() { + tail.push_str(&config.trunc_str); + } + + // put left context truncation string if needed + if before_beg != 0 && head_beg == head_end { + before = format!("{}{}", config.trunc_str, before); + } else if before_beg != 0 && head_beg != 0 { + head = format!("{}{}", config.trunc_str, head); + } + + // add space before "after" if needed + if !after.is_empty() { + after = format!(" {}", after); + } + + (tail, before, after, head) +} + +fn tex_mapper(x: char) -> String { + match x { + '\\' => "\\backslash{}".to_owned(), + '$' | '%' | '#' | '&' | '_' => format!("\\{}", x), + '}' | '{' => format!("$\\{}$", x), + _ => x.to_string(), + } +} + +fn adjust_tex_str(context: &str) -> String { + let ws_reg = Regex::new(r"[\t\n\v\f\r ]").unwrap(); + let mut fix: String = ws_reg.replace_all(context, " ").trim().to_owned(); + let mapped_chunks: Vec = fix.chars().map(tex_mapper).collect(); + fix = mapped_chunks.join(""); + fix +} + +fn format_tex_line(config: &Config, word_ref: &WordRef, line: &str, reference: &str) -> String { + let mut output = String::new(); + output.push_str(&format!("\\{} ", config.macro_name)); + let all_before = if config.input_ref { + let before = &line[0..word_ref.position]; + adjust_tex_str(before.trim().trim_start_matches(reference)) + } else { + adjust_tex_str(&line[0..word_ref.position]) + }; + let keyword = adjust_tex_str(&line[word_ref.position..word_ref.position_end]); + let all_after = adjust_tex_str(&line[word_ref.position_end..line.len()]); + let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config); + output.push_str(&format!( + "{5}{0}{6}{5}{1}{6}{5}{2}{6}{5}{3}{6}{5}{4}{6}", + tail, before, keyword, after, head, "{", "}" + )); + if config.auto_ref || config.input_ref { + output.push_str(&format!("{}{}{}", "{", adjust_tex_str(&reference), "}")); + } + output +} + +fn adjust_roff_str(context: &str) -> String { + let ws_reg = Regex::new(r"[\t\n\v\f\r]").unwrap(); + ws_reg + .replace_all(context, " ") + .replace("\"", "\"\"") + .trim() + .to_owned() +} + +fn format_roff_line(config: &Config, word_ref: &WordRef, line: &str, reference: &str) -> String { + let mut output = String::new(); + output.push_str(&format!(".{}", config.macro_name)); + let all_before = if config.input_ref { + let before = &line[0..word_ref.position]; + adjust_roff_str(before.trim().trim_start_matches(reference)) + } else { + adjust_roff_str(&line[0..word_ref.position]) + }; + let keyword = adjust_roff_str(&line[word_ref.position..word_ref.position_end]); + let all_after = adjust_roff_str(&line[word_ref.position_end..line.len()]); + let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config); + output.push_str(&format!( + " \"{}\" \"{}\" \"{}{}\" \"{}\"", + tail, before, keyword, after, head + )); + if config.auto_ref || config.input_ref { + output.push_str(&format!(" \"{}\"", adjust_roff_str(&reference))); + } + output +} + +fn write_traditional_output( + config: &Config, + file_map: &HashMap, usize)>, + words: &BTreeSet, + output_filename: &str, +) { + let mut writer: BufWriter> = BufWriter::new(if output_filename == "-" { + Box::new(stdout()) + } else { + let file = crash_if_err!(1, File::create(output_filename)); + Box::new(file) + }); + for word_ref in words.iter() { + let file_map_value: &(Vec, usize) = file_map + .get(&(word_ref.filename)) + .expect("Missing file in file map"); + let (ref lines, _) = *(file_map_value); + let reference = get_reference(config, word_ref, &lines[word_ref.local_line_nr]); + let output_line: String = match config.format { + OutFormat::Tex => { + format_tex_line(config, word_ref, &lines[word_ref.local_line_nr], &reference) + } + OutFormat::Roff => { + format_roff_line(config, word_ref, &lines[word_ref.local_line_nr], &reference) + } + OutFormat::Dumb => crash!(1, "There is no dumb format with GNU extensions disabled"), + }; + crash_if_err!(1, writeln!(writer, "{}", output_line)); + } +} + +pub fn uumain(args: Vec) -> i32 { + 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 matches = return_if_err!(1, opts.parse(&args[1..])); + + 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 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() + } else { + "-".to_owned() + }; + write_traditional_output(&config, &file_map, &word_set, &output_file); + 0 +} diff --git a/coreutils/src/pwd/Cargo.toml b/coreutils/src/pwd/Cargo.toml new file mode 100644 index 000000000..a6e29640b --- /dev/null +++ b/coreutils/src/pwd/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "pwd" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_pwd" +path = "pwd.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "pwd" +path = "../../uumain.rs" diff --git a/coreutils/src/pwd/pwd.rs b/coreutils/src/pwd/pwd.rs new file mode 100644 index 000000000..e4658989d --- /dev/null +++ b/coreutils/src/pwd/pwd.rs @@ -0,0 +1,85 @@ +#![crate_name = "uu_pwd"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Derek Chiang + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::env; +use std::path::{Path, PathBuf}; +use std::io; + +static NAME: &str = "pwd"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn absolute_path(path: &Path) -> io::Result { + let path_buf = path.canonicalize()?; + + #[cfg(windows)] + let path_buf = Path::new( + path_buf + .as_path() + .to_string_lossy() + .trim_left_matches(r"\\?\"), + ).to_path_buf(); + + Ok(path_buf) +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag("", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + opts.optflag( + "L", + "logical", + "use PWD from environment, even if it contains symlinks", + ); + opts.optflag("P", "physical", "avoid all symlinks"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "Invalid options\n{}", f), + }; + + if matches.opt_present("help") { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTION]... + +Print the full filename of the current working directory.", + NAME, VERSION + ); + print!("{}", opts.usage(&msg)); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else { + match env::current_dir() { + Ok(logical_path) => { + if matches.opt_present("logical") { + println!("{}", logical_path.display()); + } else { + match absolute_path(&logical_path) { + Ok(physical_path) => println!("{}", physical_path.display()), + Err(e) => crash!(1, "failed to get absolute path {}", e), + }; + } + } + Err(e) => crash!(1, "failed to get current directory {}", e), + }; + } + + 0 +} diff --git a/coreutils/src/readlink/Cargo.toml b/coreutils/src/readlink/Cargo.toml new file mode 100644 index 000000000..580d0b805 --- /dev/null +++ b/coreutils/src/readlink/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "readlink" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_readlink" +path = "readlink.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" + +[dependencies.uucore] +version = "0.0.1" +features = ["fs"] + +[[bin]] +name = "readlink" +path = "../../uumain.rs" diff --git a/coreutils/src/readlink/readlink.rs b/coreutils/src/readlink/readlink.rs new file mode 100644 index 000000000..b4971e27f --- /dev/null +++ b/coreutils/src/readlink/readlink.rs @@ -0,0 +1,146 @@ +#![crate_name = "uu_readlink"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Haitao Li + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::fs; +use std::io::{stdout, Write}; +use std::path::PathBuf; +use uucore::fs::{canonicalize, CanonicalizeMode}; + +const NAME: &str = "readlink"; +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag( + "f", + "canonicalize", + "canonicalize by following every symlink in every component of the \ + given name recursively; all but the last component must exist", + ); + opts.optflag( + "e", + "canonicalize-existing", + "canonicalize by following every symlink in every component of the \ + given name recursively, all components must exist", + ); + opts.optflag( + "m", + "canonicalize-missing", + "canonicalize by following every symlink in every component of the \ + given name recursively, without requirements on components existence", + ); + opts.optflag("n", "no-newline", "do not output the trailing delimiter"); + opts.optflag("q", "quiet", "suppress most error messages"); + opts.optflag("s", "silent", "suppress most error messages"); + opts.optflag("v", "verbose", "report error message"); + opts.optflag("z", "zero", "separate output with NUL rather than newline"); + opts.optflag("", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "Invalid options\n{}", f), + }; + if matches.opt_present("help") { + show_usage(&opts); + return 0; + } + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + let mut no_newline = matches.opt_present("no-newline"); + let use_zero = matches.opt_present("zero"); + let silent = matches.opt_present("silent") || matches.opt_present("quiet"); + let verbose = matches.opt_present("verbose"); + + let mut can_mode = CanonicalizeMode::None; + if matches.opt_present("canonicalize") { + can_mode = CanonicalizeMode::Normal; + } + + if matches.opt_present("canonicalize-existing") { + can_mode = CanonicalizeMode::Existing; + } + + if matches.opt_present("canonicalize-missing") { + can_mode = CanonicalizeMode::Missing; + } + + let files = matches.free; + if files.is_empty() { + crash!( + 1, + "missing operand\nTry {} --help for more information", + NAME + ); + } + + if no_newline && files.len() > 1 && !silent { + eprintln!("{}: ignoring --no-newline with multiple arguments", NAME); + no_newline = false; + } + + for f in &files { + let p = PathBuf::from(f); + if can_mode == CanonicalizeMode::None { + match fs::read_link(&p) { + Ok(path) => show(&path, no_newline, use_zero), + Err(err) => { + if verbose { + eprintln!("{}: {}: errno {}", NAME, f, err.raw_os_error().unwrap()); + } + return 1; + } + } + } else { + match canonicalize(&p, can_mode) { + Ok(path) => show(&path, no_newline, use_zero), + Err(err) => { + if verbose { + eprintln!("{}: {}: errno {:?}", NAME, f, err.raw_os_error().unwrap()); + } + return 1; + } + } + } + } + + 0 +} + +fn show(path: &PathBuf, no_newline: bool, use_zero: bool) { + let path = path.as_path().to_str().unwrap(); + if use_zero { + print!("{}\0", path); + } else if no_newline { + print!("{}", path); + } else { + println!("{}", path); + } + crash_if_err!(1, stdout().flush()); +} + +fn show_usage(opts: &getopts::Options) { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage: {0} [OPTION]... [FILE]...", NAME); + print!("Print value of a symbolic link or canonical file name"); + print!("{}", opts.usage("")); +} diff --git a/coreutils/src/realpath/Cargo.toml b/coreutils/src/realpath/Cargo.toml new file mode 100644 index 000000000..2b1c16a45 --- /dev/null +++ b/coreutils/src/realpath/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "realpath" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_realpath" +path = "realpath.rs" + +[dependencies] +getopts = "0.2.18" + +[dependencies.uucore] +version = "0.0.1" +features = ["fs"] + +[[bin]] +name = "realpath" +path = "../../uumain.rs" diff --git a/coreutils/src/realpath/realpath.rs b/coreutils/src/realpath/realpath.rs new file mode 100644 index 000000000..80e6052da --- /dev/null +++ b/coreutils/src/realpath/realpath.rs @@ -0,0 +1,151 @@ +#![crate_name = "uu_realpath"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) 2014 Vsevolod Velichko + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::fs; +use std::path::{Path, PathBuf}; +use uucore::fs::{canonicalize, CanonicalizeMode}; + +static NAME: &str = "realpath"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag("h", "help", "Show help and exit"); + opts.optflag("V", "version", "Show version and exit"); + opts.optflag( + "s", + "strip", + "Only strip '.' and '..' components, but don't resolve symbolic links", + ); + opts.optflag( + "z", + "zero", + "Separate output filenames with \\0 rather than newline", + ); + opts.optflag("q", "quiet", "Do not print warnings for invalid paths"); + + 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") { + version(); + return 0; + } + if matches.opt_present("h") { + show_usage(&opts); + return 0; + } + + if matches.free.is_empty() { + show_error!("Missing operand: FILENAME, at least one is required"); + println!("Try `{} --help` for more information.", NAME); + return 1; + } + + let strip = matches.opt_present("s"); + let zero = matches.opt_present("z"); + let quiet = matches.opt_present("q"); + let mut retcode = 0; + for path in &matches.free { + if !resolve_path(path, strip, zero, quiet) { + retcode = 1 + }; + } + retcode +} + +fn resolve_path(path: &str, strip: bool, zero: bool, quiet: bool) -> bool { + let p = Path::new(path).to_path_buf(); + let abs = canonicalize(p, CanonicalizeMode::Normal).unwrap(); + + if strip { + if zero { + print!("{}\0", abs.display()); + } else { + println!("{}", abs.display()) + } + return true; + } + + let mut result = PathBuf::new(); + let mut links_left = 256; + + for part in abs.components() { + result.push(part.as_os_str()); + loop { + if links_left == 0 { + if !quiet { + show_error!("Too many symbolic links: {}", path) + }; + return false; + } + match fs::metadata(result.as_path()) { + Err(_) => break, + Ok(ref m) if !m.file_type().is_symlink() => break, + Ok(_) => { + links_left -= 1; + match fs::read_link(result.as_path()) { + Ok(x) => { + result.pop(); + result.push(x.as_path()); + } + _ => { + if !quiet { + show_error!("Invalid path: {}", path) + }; + return false; + } + } + } + } + } + } + + if zero { + print!("{}\0", result.display()); + } else { + println!("{}", result.display()); + } + + true +} + +fn version() { + println!("{} {}", NAME, VERSION) +} + +fn show_usage(opts: &getopts::Options) { + version(); + println!(""); + println!("Usage:"); + println!(" {} [-s|--strip] [-z|--zero] FILENAME...", NAME); + println!(" {} -V|--version", NAME); + println!(" {} -h|--help", NAME); + println!(""); + print!("{}", opts.usage( + "Convert each FILENAME to the absolute path.\n\ + All the symbolic links will be resolved, resulting path will contain no special components like '.' or '..'.\n\ + Each path component must exist or resolution will fail and non-zero exit status returned.\n\ + Each resolved FILENAME will be written to the standard output, one per line.") + ); +} diff --git a/coreutils/src/relpath/Cargo.toml b/coreutils/src/relpath/Cargo.toml new file mode 100644 index 000000000..0855e0c0f --- /dev/null +++ b/coreutils/src/relpath/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "relpath" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_relpath" +path = "relpath.rs" + +[dependencies] +getopts = "0.2.18" + +[dependencies.uucore] +version = "0.0.1" +features = ["fs"] + +[[bin]] +name = "relpath" +path = "../../uumain.rs" diff --git a/coreutils/src/relpath/relpath.rs b/coreutils/src/relpath/relpath.rs new file mode 100644 index 000000000..8e0bb4012 --- /dev/null +++ b/coreutils/src/relpath/relpath.rs @@ -0,0 +1,124 @@ +#![crate_name = "uu_relpath"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) 2014 Vsevolod Velichko + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::env; +use std::path::{Path, PathBuf}; +use uucore::fs::{canonicalize, CanonicalizeMode}; + +static NAME: &str = "relpath"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag("h", "help", "Show help and exit"); + opts.optflag("V", "version", "Show version and exit"); + opts.optopt( + "d", + "", + "If any of FROM and TO is not subpath of DIR, output absolute path instead of relative", + "DIR", + ); + + 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") { + version(); + return 0; + } + if matches.opt_present("h") { + show_usage(&opts); + return 0; + } + + if matches.free.is_empty() { + show_error!("Missing operand: TO"); + println!("Try `{} --help` for more information.", NAME); + return 1; + } + + let to = Path::new(&matches.free[0]); + let from = if matches.free.len() > 1 { + Path::new(&matches.free[1]).to_path_buf() + } else { + env::current_dir().unwrap() + }; + let absto = canonicalize(to, CanonicalizeMode::Normal).unwrap(); + let absfrom = canonicalize(from, CanonicalizeMode::Normal).unwrap(); + + if matches.opt_present("d") { + let base = Path::new(&matches.opt_str("d").unwrap()).to_path_buf(); + let absbase = canonicalize(base, CanonicalizeMode::Normal).unwrap(); + if !absto.as_path().starts_with(absbase.as_path()) + || !absfrom.as_path().starts_with(absbase.as_path()) + { + println!("{}", absto.display()); + return 0; + } + } + + let mut suffix_pos = 0; + for (f, t) in absfrom.components().zip(absto.components()) { + if f == t { + suffix_pos += 1; + } else { + break; + } + } + + let mut result = PathBuf::new(); + absfrom + .components() + .skip(suffix_pos) + .map(|_| result.push("..")) + .last(); + absto + .components() + .skip(suffix_pos) + .map(|x| result.push(x.as_os_str())) + .last(); + + println!("{}", result.display()); + 0 +} + +fn version() { + println!("{} {}", NAME, VERSION) +} + +fn show_usage(opts: &getopts::Options) { + version(); + println!(""); + println!("Usage:"); + println!(" {} [-d DIR] TO [FROM]", NAME); + println!(" {} -V|--version", NAME); + println!(" {} -h|--help", NAME); + println!(""); + print!( + "{}", + opts.usage( + "Convert TO destination to the relative path from the FROM dir.\n\ + If FROM path is omitted, current working dir will be used." + ) + ); +} diff --git a/coreutils/src/rm/Cargo.toml b/coreutils/src/rm/Cargo.toml new file mode 100644 index 000000000..45c9b72b9 --- /dev/null +++ b/coreutils/src/rm/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rm" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_rm" +path = "rm.rs" + +[dependencies] +getopts = "0.2.18" +walkdir = "2.2.8" +remove_dir_all = "0.5.1" +uucore = "0.0.1" + +[[bin]] +name = "rm" +path = "../../uumain.rs" diff --git a/coreutils/src/rm/rm.rs b/coreutils/src/rm/rm.rs new file mode 100644 index 000000000..8c2fc08ba --- /dev/null +++ b/coreutils/src/rm/rm.rs @@ -0,0 +1,326 @@ +#![crate_name = "uu_rm"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate remove_dir_all; +extern crate walkdir; + +#[macro_use] +extern crate uucore; + +use std::collections::VecDeque; +use std::fs; +use std::io::{stderr, stdin, BufRead, Write}; +use std::ops::BitOr; +use std::path::Path; +use remove_dir_all::remove_dir_all; +use walkdir::{DirEntry, WalkDir}; + +#[derive(Eq, PartialEq, Clone, Copy)] +enum InteractiveMode { + InteractiveNone, + InteractiveOnce, + InteractiveAlways, +} + +struct Options { + force: bool, + interactive: InteractiveMode, + #[allow(dead_code)] + one_fs: bool, + preserve_root: bool, + recursive: bool, + dir: bool, + verbose: bool, +} + +static NAME: &str = "rm"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + // TODO: make getopts support -R in addition to -r + let mut opts = getopts::Options::new(); + + opts.optflag( + "f", + "force", + "ignore nonexistent files and arguments, never prompt", + ); + opts.optflag("i", "", "prompt before every removal"); + opts.optflag("I", "", "prompt once before removing more than three files, or when removing recursively. Less intrusive than -i, while still giving some protection against most mistakes"); + opts.optflagopt( + "", + "interactive", + "prompt according to WHEN: never, once (-I), or always (-i). Without WHEN, prompts always", + "WHEN", + ); + opts.optflag("", "one-file-system", "when removing a hierarchy recursively, skip any directory that is on a file system different from that of the corresponding command line argument (NOT IMPLEMENTED)"); + opts.optflag("", "no-preserve-root", "do not treat '/' specially"); + opts.optflag("", "preserve-root", "do not remove '/' (default)"); + opts.optflag( + "r", + "recursive", + "remove directories and their contents recursively", + ); + opts.optflag("d", "dir", "remove empty directories"); + opts.optflag("v", "verbose", "explain what is being done"); + 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), + }; + + let force = matches.opt_present("force"); + + if matches.opt_present("help") { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {0} [OPTION]... [FILE]...", NAME); + println!(""); + println!("{}", opts.usage("Remove (unlink) the FILE(s).")); + println!("By default, rm does not remove directories. Use the --recursive (-r)"); + println!("option to remove each listed directory, too, along with all of its contents"); + println!(""); + println!("To remove a file whose name starts with a '-', for example '-foo',"); + println!("use one of these commands:"); + println!("rm -- -foo"); + println!(""); + println!("rm ./-foo"); + println!(""); + println!("Note that if you use rm to remove a file, it might be possible to recover"); + println!("some of its contents, given sufficient expertise and/or time. For greater"); + println!("assurance that the contents are truly unrecoverable, consider using shred."); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else if matches.free.is_empty() && !force { + show_error!("missing an argument"); + show_error!("for help, try '{0} --help'", NAME); + return 1; + } else { + let options = Options { + force: force, + interactive: { + if matches.opt_present("i") { + InteractiveMode::InteractiveAlways + } else if matches.opt_present("I") { + InteractiveMode::InteractiveOnce + } else if matches.opt_present("interactive") { + match &matches.opt_str("interactive").unwrap()[..] { + "none" => InteractiveMode::InteractiveNone, + "once" => InteractiveMode::InteractiveOnce, + "always" => InteractiveMode::InteractiveAlways, + val => crash!(1, "Invalid argument to interactive ({})", val), + } + } else { + InteractiveMode::InteractiveNone + } + }, + one_fs: matches.opt_present("one-file-system"), + preserve_root: !matches.opt_present("no-preserve-root"), + recursive: matches.opt_present("recursive"), + dir: matches.opt_present("dir"), + verbose: matches.opt_present("verbose"), + }; + if options.interactive == InteractiveMode::InteractiveOnce + && (options.recursive || matches.free.len() > 3) + { + let msg = if options.recursive { + "Remove all arguments recursively? " + } else { + "Remove all arguments? " + }; + if !prompt(msg) { + return 0; + } + } + + if remove(matches.free, options) { + return 1; + } + } + + 0 +} + +// TODO: implement one-file-system (this may get partially implemented in walkdir) +fn remove(files: Vec, options: Options) -> bool { + let mut had_err = false; + + for filename in &files { + let file = Path::new(filename); + had_err = match file.symlink_metadata() { + Ok(metadata) => { + if metadata.is_dir() { + handle_dir(file, &options) + } else if is_symlink_dir(&metadata) { + remove_dir(file, &options) + } else { + remove_file(file, &options) + } + } + Err(_e) => { + // TODO: actually print out the specific error + // TODO: When the error is not about missing files + // (e.g., permission), even rm -f should fail with + // outputting the error, but there's no easy eay. + if !options.force { + show_error!("no such file or directory '{}'", filename); + true + } else { + false + } + } + }.bitor(had_err); + } + + had_err +} + +fn handle_dir(path: &Path, options: &Options) -> bool { + let mut had_err = false; + + let is_root = path.has_root() && path.parent().is_none(); + if options.recursive && (!is_root || !options.preserve_root) { + if options.interactive != InteractiveMode::InteractiveAlways { + // we need the extra crate because apparently fs::remove_dir_all() does not function + // correctly on Windows + if let Err(e) = remove_dir_all(path) { + had_err = true; + show_error!("could not remove '{}': {}", path.display(), e); + } + } else { + let mut dirs: VecDeque = VecDeque::new(); + + for entry in WalkDir::new(path) { + match entry { + Ok(entry) => { + let file_type = entry.file_type(); + if file_type.is_dir() { + dirs.push_back(entry); + } else { + had_err = remove_file(entry.path(), options).bitor(had_err); + } + } + Err(e) => { + had_err = true; + show_error!("recursing in '{}': {}", path.display(), e); + } + } + } + + for dir in dirs.iter().rev() { + had_err = remove_dir(dir.path(), options).bitor(had_err); + } + } + } else if options.dir && (!is_root || !options.preserve_root) { + had_err = remove_dir(path, options).bitor(had_err); + } else { + if options.recursive { + show_error!("could not remove directory '{}'", path.display()); + had_err = true; + } else { + show_error!( + "could not remove directory '{}' (did you mean to pass '-r'?)", + path.display() + ); + had_err = true; + } + } + + had_err +} + +fn remove_dir(path: &Path, options: &Options) -> bool { + let response = if options.interactive == InteractiveMode::InteractiveAlways { + prompt_file(path, true) + } else { + true + }; + if response { + match fs::remove_dir(path) { + Ok(_) => if options.verbose { + println!("removed '{}'", path.display()); + }, + Err(e) => { + show_error!("removing '{}': {}", path.display(), e); + return true; + } + } + } + + false +} + +fn remove_file(path: &Path, options: &Options) -> bool { + let response = if options.interactive == InteractiveMode::InteractiveAlways { + prompt_file(path, false) + } else { + true + }; + if response { + match fs::remove_file(path) { + Ok(_) => if options.verbose { + println!("removed '{}'", path.display()); + }, + Err(e) => { + show_error!("removing '{}': {}", path.display(), e); + return true; + } + } + } + + false +} + +fn prompt_file(path: &Path, is_dir: bool) -> bool { + if is_dir { + prompt(&(format!("rm: remove directory '{}'? ", path.display()))) + } else { + prompt(&(format!("rm: remove file '{}'? ", path.display()))) + } +} + +fn prompt(msg: &str) -> bool { + let _ = stderr().write_all(msg.as_bytes()); + let _ = stderr().flush(); + + let mut buf = Vec::new(); + let stdin = stdin(); + let mut stdin = stdin.lock(); + + match stdin.read_until('\n' as u8, &mut buf) { + Ok(x) if x > 0 => match buf[0] { + b'y' | b'Y' => true, + _ => false, + }, + _ => false, + } +} + +#[cfg(not(windows))] +fn is_symlink_dir(_metadata: &fs::Metadata) -> bool { + false +} + +#[cfg(windows)] +use std::os::windows::prelude::MetadataExt; + +#[cfg(windows)] +fn is_symlink_dir(metadata: &fs::Metadata) -> bool { + use std::os::raw::c_ulong; + pub type DWORD = c_ulong; + pub const FILE_ATTRIBUTE_DIRECTORY: DWORD = 0x10; + + metadata.file_type().is_symlink() && ((metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY) != 0) +} diff --git a/coreutils/src/rmdir/Cargo.toml b/coreutils/src/rmdir/Cargo.toml new file mode 100644 index 000000000..6f74aae90 --- /dev/null +++ b/coreutils/src/rmdir/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rmdir" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_rmdir" +path = "rmdir.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "rmdir" +path = "../../uumain.rs" diff --git a/coreutils/src/rmdir/rmdir.rs b/coreutils/src/rmdir/rmdir.rs new file mode 100644 index 000000000..2ef50e5bd --- /dev/null +++ b/coreutils/src/rmdir/rmdir.rs @@ -0,0 +1,129 @@ +#![crate_name = "uu_rmdir"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::fs; +use std::path::Path; + +static NAME: &str = "rmdir"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag( + "", + "ignore-fail-on-non-empty", + "ignore each failure that is solely because a directory is non-empty", + ); + opts.optflag("p", "parents", "remove DIRECTORY and its ancestors; e.g., 'rmdir -p a/b/c' is similar to rmdir a/b/c a/b a"); + opts.optflag( + "v", + "verbose", + "output a diagnostic for every directory processed", + ); + opts.optflag("h", "help", "print 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) => { + show_error!("{}", f); + return 1; + } + }; + + if matches.opt_present("help") { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTION]... DIRECTORY... + +Remove the DIRECTORY(ies), if they are empty.", + NAME, VERSION + ); + print!("{}", opts.usage(&msg)); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else if matches.free.is_empty() { + show_error!("missing an argument"); + show_error!("for help, try '{0} --help'", NAME); + return 1; + } else { + let ignore = matches.opt_present("ignore-fail-on-non-empty"); + let parents = matches.opt_present("parents"); + let verbose = matches.opt_present("verbose"); + match remove(matches.free, ignore, parents, verbose) { + Ok(()) => ( /* pass */ ), + Err(e) => return e, + } + } + + 0 +} + +fn remove(dirs: Vec, ignore: bool, parents: bool, verbose: bool) -> Result<(), i32> { + let mut r = Ok(()); + + for dir in &dirs { + let path = Path::new(&dir[..]); + r = remove_dir(&path, ignore, verbose).and(r); + if parents { + let mut p = path; + while let Some(new_p) = p.parent() { + p = new_p; + match p.as_os_str().to_str() { + None => break, + Some(s) => match s { + "" | "." | "/" => break, + _ => (), + }, + }; + r = remove_dir(p, ignore, verbose).and(r); + } + } + } + + r +} + +fn remove_dir(path: &Path, ignore: bool, verbose: bool) -> Result<(), i32> { + let mut read_dir = match fs::read_dir(path) { + Ok(m) => m, + Err(e) => { + show_error!("reading directory '{}': {}", path.display(), e); + return Err(1); + } + }; + + let mut r = Ok(()); + + if read_dir.next().is_none() { + match fs::remove_dir(path) { + Err(e) => { + show_error!("removing directory '{}': {}", path.display(), e); + r = Err(1); + } + Ok(_) if verbose => println!("Removed directory '{}'", path.display()), + _ => (), + } + } else if !ignore { + show_error!("failed to remove '{}': Directory not empty", path.display()); + r = Err(1); + } + + r +} diff --git a/coreutils/src/seq/Cargo.toml b/coreutils/src/seq/Cargo.toml new file mode 100644 index 000000000..43ab40aba --- /dev/null +++ b/coreutils/src/seq/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "seq" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_seq" +path = "seq.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "seq" +path = "../../uumain.rs" diff --git a/coreutils/src/seq/seq.rs b/coreutils/src/seq/seq.rs new file mode 100644 index 000000000..4bdbbf370 --- /dev/null +++ b/coreutils/src/seq/seq.rs @@ -0,0 +1,307 @@ +#![crate_name = "uu_seq"] + +// TODO: Make -w flag work with decimals +// TODO: Support -f flag + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::cmp; +use std::io::{stdout, Write}; + +static NAME: &str = "seq"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(Clone)] +struct SeqOptions { + separator: String, + terminator: Option, + widths: bool, +} + +fn parse_float(mut s: &str) -> Result { + if s.starts_with("+") { + s = &s[1..]; + } + match s.parse() { + Ok(n) => Ok(n), + Err(e) => Err(format!( + "seq: invalid floating point argument `{}`: {}", + s, e + )), + } +} + +fn escape_sequences(s: &str) -> String { + s.replace("\\n", "\n").replace("\\t", "\t") +} + +fn parse_options(args: Vec, options: &mut SeqOptions) -> Result, i32> { + let mut seq_args = vec![]; + let mut iter = args.into_iter().skip(1); + loop { + match iter.next() { + Some(arg) => match &arg[..] { + "--help" | "-h" => { + print_help(); + return Err(0); + } + "--version" | "-V" => { + print_version(); + return Err(0); + } + "-s" | "--separator" => match iter.next() { + Some(sep) => options.separator = sep, + None => { + show_error!("expected a separator after {}", arg); + return Err(1); + } + }, + "-t" | "--terminator" => match iter.next() { + Some(term) => options.terminator = Some(term), + None => { + show_error!("expected a terminator after '{}'", arg); + return Err(1); + } + }, + "-w" | "--widths" => options.widths = true, + "--" => { + seq_args.extend(iter); + break; + } + _ => { + if arg.len() > 1 && arg.chars().next().unwrap() == '-' { + let argptr: *const String = &arg; // escape from the borrow checker + let mut chiter = unsafe { &(*argptr)[..] }.chars().skip(1); + let mut ch = ' '; + while match chiter.next() { + Some(m) => { + ch = m; + true + } + None => false, + } { + match ch { + 'h' => { + print_help(); + return Err(0); + } + 'V' => { + print_version(); + return Err(0); + } + 's' => match iter.next() { + Some(sep) => { + options.separator = sep; + let next = chiter.next(); + if next.is_some() { + show_error!( + "unexpected character ('{}')", + next.unwrap() + ); + return Err(1); + } + } + None => { + show_error!("expected a separator after {}", arg); + return Err(1); + } + }, + 't' => match iter.next() { + Some(term) => { + options.terminator = Some(term); + let next = chiter.next(); + if next.is_some() { + show_error!( + "unexpected character ('{}')", + next.unwrap() + ); + return Err(1); + } + } + None => { + show_error!("expected a terminator after {}", arg); + return Err(1); + } + }, + 'w' => options.widths = true, + _ => { + seq_args.push(arg); + break; + } + } + } + } else { + seq_args.push(arg); + } + } + }, + None => break, + } + } + Ok(seq_args) +} + +fn print_help() { + let mut opts = getopts::Options::new(); + + opts.optopt( + "s", + "separator", + "Separator character (defaults to \\n)", + "", + ); + opts.optopt( + "t", + "terminator", + "Terminator character (defaults to separator)", + "", + ); + opts.optflag( + "w", + "widths", + "Equalize widths of all numbers by padding with zeros", + ); + opts.optflag("h", "help", "print this help text and exit"); + opts.optflag("V", "version", "print version and exit"); + + println!("{} {}\n", NAME, VERSION); + println!( + "Usage:\n {} [-w] [-s string] [-t string] [first [step]] last\n", + NAME + ); + println!("{}", opts.usage("Print sequences of numbers")); +} + +fn print_version() { + println!("{} {}", NAME, VERSION); +} + +pub fn uumain(args: Vec) -> i32 { + let mut options = SeqOptions { + separator: "\n".to_owned(), + terminator: None, + widths: false, + }; + let free = match parse_options(args, &mut options) { + Ok(m) => m, + Err(f) => return f, + }; + if free.len() < 1 || free.len() > 3 { + crash!( + 1, + "too {} operands.\nTry '{} --help' for more information.", + if free.len() < 1 { "few" } else { "many" }, + NAME + ); + } + let mut largest_dec = 0; + let mut padding = 0; + let first = if free.len() > 1 { + let slice = &free[0][..]; + let len = slice.len(); + let dec = slice.find('.').unwrap_or(len); + largest_dec = len - dec; + padding = dec; + match parse_float(slice) { + Ok(n) => n, + Err(s) => { + show_error!("{}", s); + return 1; + } + } + } else { + 1.0 + }; + let step = if free.len() > 2 { + let slice = &free[1][..]; + let len = slice.len(); + let dec = slice.find('.').unwrap_or(len); + largest_dec = cmp::max(largest_dec, len - dec); + padding = cmp::max(padding, dec); + match parse_float(slice) { + Ok(n) => n, + Err(s) => { + show_error!("{}", s); + return 1; + } + } + } else { + 1.0 + }; + let last = { + let slice = &free[free.len() - 1][..]; + padding = cmp::max(padding, slice.find('.').unwrap_or(slice.len())); + match parse_float(slice) { + Ok(n) => n, + Err(s) => { + show_error!("{}", s); + return 1; + } + } + }; + if largest_dec > 0 { + largest_dec -= 1; + } + let separator = escape_sequences(&options.separator[..]); + let terminator = match options.terminator { + Some(term) => escape_sequences(&term[..]), + None => separator.clone(), + }; + print_seq( + first, + step, + last, + largest_dec, + separator, + terminator, + options.widths, + padding, + ); + + 0 +} + +fn done_printing(next: f64, step: f64, last: f64) -> bool { + if step >= 0f64 { + next > last + } else { + next < last + } +} + +fn print_seq( + first: f64, + step: f64, + last: f64, + largest_dec: usize, + separator: String, + terminator: String, + pad: bool, + padding: usize, +) { + let mut i = 0isize; + let mut value = first + i as f64 * step; + while !done_printing(value, step, last) { + let istr = format!("{:.*}", largest_dec, value); + let ilen = istr.len(); + let before_dec = istr.find('.').unwrap_or(ilen); + if pad && before_dec < padding { + for _ in 0..(padding - before_dec) { + print!("0"); + } + } + print!("{}", istr); + i += 1; + value = first + i as f64 * step; + if !done_printing(value, step, last) { + print!("{}", separator); + } + } + if (first >= last && step < 0f64) || (first <= last && step > 0f64) { + print!("{}", terminator); + } + crash_if_err!(1, stdout().flush()); +} diff --git a/coreutils/src/shred/Cargo.toml b/coreutils/src/shred/Cargo.toml new file mode 100644 index 000000000..634c3ee63 --- /dev/null +++ b/coreutils/src/shred/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "shred" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_shred" +path = "shred.rs" + +[dependencies] +rand = "0.5" +filetime = "0.2.1" +getopts = "0.2.18" +libc = "0.2.42" +time = "0.1.40" +uucore = "0.0.1" + +[[bin]] +name = "shred" +path = "../../uumain.rs" diff --git a/coreutils/src/shred/shred.rs b/coreutils/src/shred/shred.rs new file mode 100644 index 000000000..65005b916 --- /dev/null +++ b/coreutils/src/shred/shred.rs @@ -0,0 +1,587 @@ +#![crate_name = "uu_shred"] + +/* +* This file is part of the uutils coreutils package. +* +* (c) Michael Rosenberg <42micro@gmail.com> +* (c) Fort +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +extern crate getopts; +extern crate rand; + +use rand::{Rng, ThreadRng}; +use std::cell::{Cell, RefCell}; +use std::fs; +use std::fs::{File, OpenOptions}; +use std::io; +use std::io::SeekFrom; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; + +#[macro_use] +extern crate uucore; + +static NAME: &str = "shred"; +static VERSION_STR: &str = "1.0.0"; +const BLOCK_SIZE: usize = 512; +const NAMESET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_."; + +// Patterns as shown in the GNU coreutils shred implementation +const PATTERNS: [&'static [u8]; 22] = [ + b"\x00", + b"\xFF", + b"\x55", + b"\xAA", + b"\x24\x92\x49", + b"\x49\x24\x92", + b"\x6D\xB6\xDB", + b"\x92\x49\x24", + b"\xB6\xDB\x6D", + b"\xDB\x6D\xB6", + b"\x11", + b"\x22", + b"\x33", + b"\x44", + b"\x66", + b"\x77", + b"\x88", + b"\x99", + b"\xBB", + b"\xCC", + b"\xDD", + b"\xEE", +]; + +#[derive(Clone, Copy)] +enum PassType<'a> { + Pattern(&'a [u8]), + Random, +} + +// Used to generate all possible filenames of a certain length using NAMESET as an alphabet +struct FilenameGenerator { + name_len: usize, + nameset_indices: RefCell>, // Store the indices of the letters of our filename in NAMESET + exhausted: Cell, +} + +impl FilenameGenerator { + fn new(name_len: usize) -> FilenameGenerator { + let mut indices: Vec = Vec::new(); + for _ in 0..name_len { + indices.push(0); + } + FilenameGenerator { + name_len: name_len, + nameset_indices: RefCell::new(indices), + exhausted: Cell::new(false), + } + } +} + +impl Iterator for FilenameGenerator { + type Item = String; + + fn next(&mut self) -> Option { + if self.exhausted.get() { + return None; + } + + let mut nameset_indices = self.nameset_indices.borrow_mut(); + + // Make the return value, then increment + let mut ret = String::new(); + for i in nameset_indices.iter() { + let c: char = NAMESET.chars().nth(*i).unwrap(); + ret.push(c); + } + + if nameset_indices[0] == NAMESET.len() - 1 { + self.exhausted.set(true) + } + // Now increment the least significant index + for i in (0..self.name_len).rev() { + if nameset_indices[i] == NAMESET.len() - 1 { + nameset_indices[i] = 0; // Carry the 1 + continue; + } else { + nameset_indices[i] += 1; + break; + } + } + + Some(ret) + } +} + +// Used to generate blocks of bytes of size <= BLOCK_SIZE based on either a give pattern +// or randomness +struct BytesGenerator<'a> { + total_bytes: u64, + bytes_generated: Cell, + block_size: usize, + exact: bool, // if false, every block's size is block_size + gen_type: PassType<'a>, + rng: Option>, +} + +impl<'a> BytesGenerator<'a> { + fn new(total_bytes: u64, gen_type: PassType<'a>, exact: bool) -> BytesGenerator { + let rng = match gen_type { + PassType::Random => Some(RefCell::new(rand::thread_rng())), + _ => None, + }; + + BytesGenerator { + total_bytes: total_bytes, + bytes_generated: Cell::new(0u64), + block_size: BLOCK_SIZE, + exact: exact, + gen_type: gen_type, + rng: rng, + } + } +} + +impl<'a> Iterator for BytesGenerator<'a> { + type Item = Box<[u8]>; + + fn next(&mut self) -> Option> { + // We go over the total_bytes limit when !self.exact and total_bytes isn't a multiple + // of self.block_size + if self.bytes_generated.get() >= self.total_bytes { + return None; + } + + let this_block_size: usize = { + if !self.exact { + self.block_size + } else { + let bytes_left: u64 = self.total_bytes - self.bytes_generated.get(); + if bytes_left >= self.block_size as u64 { + self.block_size + } else { + (bytes_left % self.block_size as u64) as usize + } + } + }; + + let mut bytes: Vec = Vec::with_capacity(this_block_size); + + match self.gen_type { + PassType::Random => { + // This is ok because the vector was + // allocated with the same capacity + unsafe { + bytes.set_len(this_block_size); + } + let mut rng = self.rng.as_ref().unwrap().borrow_mut(); + rng.fill(&mut bytes[..]); + } + PassType::Pattern(pattern) => { + let skip = { + if self.bytes_generated.get() == 0 { + 0 + } else { + (pattern.len() as u64 % self.bytes_generated.get()) as usize + } + }; + // Same range as 0..this_block_size but we start with the right index + for i in skip..this_block_size + skip { + let index = i % pattern.len(); + bytes.push(pattern[index]); + } + } + }; + + let new_bytes_generated = self.bytes_generated.get() + this_block_size as u64; + self.bytes_generated.set(new_bytes_generated); + + Some(bytes.into_boxed_slice()) + } +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + // TODO: Add force option + opts.optopt( + "n", + "iterations", + "overwrite N times instead of the default (3)", + "N", + ); + opts.optopt( + "s", + "size", + "shred this many bytes (suffixes like K, M, G accepted)", + "FILESIZE", + ); + opts.optflag( + "u", + "remove", + "truncate and remove the file after overwriting; See below", + ); + opts.optflag("v", "verbose", "show progress"); + opts.optflag( + "x", + "exact", + "do not round file sizes up to the next full block; \ + this is the default for non-regular files", + ); + opts.optflag( + "z", + "zero", + "add a final overwrite with zeros to hide shredding", + ); + opts.optflag("", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(e) => panic!("Invalid options\n{}", e), + }; + + if matches.opt_present("help") { + show_help(&opts); + return 0; + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION_STR); + return 0; + } else if matches.free.is_empty() { + println!("{}: Missing an argument", NAME); + println!("For help, try '{} --help'", NAME); + return 0; + } else { + let iterations = match matches.opt_str("iterations") { + Some(s) => match s.parse::() { + Ok(u) => u, + Err(_) => { + println!("{}: Invalid number of passes", NAME); + return 1; + } + }, + None => 3, + }; + let remove = matches.opt_present("remove"); + let size = get_size(matches.opt_str("size")); + let exact = matches.opt_present("exact") && size.is_none(); // if -s is given, ignore -x + let zero = matches.opt_present("zero"); + let verbose = matches.opt_present("verbose"); + for path_str in matches.free.into_iter() { + wipe_file(&path_str, iterations, remove, size, exact, zero, verbose); + } + } + + 0 +} + +fn show_help(opts: &getopts::Options) { + println!("Usage: {} [OPTION]... FILE...", NAME); + println!( + "Overwrite the specified FILE(s) repeatedly, in order to make it harder \ + for even very expensive hardware probing to recover the data." + ); + println!("{}", opts.usage("")); + println!("Delete FILE(s) if --remove (-u) is specified. The default is not to remove"); + println!("the files because it is common to operate on device files like /dev/hda,"); + println!("and those files usually should not be removed."); + println!(""); + println!( + "CAUTION: Note that {} relies on a very important assumption:", + NAME + ); + println!("that the file system overwrites data in place. This is the traditional"); + println!("way to do things, but many modern file system designs do not satisfy this"); + println!( + "assumption. The following are examples of file systems on which {} is", + NAME + ); + println!("not effective, or is not guaranteed to be effective in all file system modes:"); + println!(""); + println!("* log-structured or journaled file systems, such as those supplied with"); + println!("AIX and Solaris (and JFS, ReiserFS, XFS, Ext3, etc.)"); + println!(""); + println!("* file systems that write redundant data and carry on even if some writes"); + println!("fail, such as RAID-based file systems"); + println!(""); + println!("* file systems that make snapshots, such as Network Appliance's NFS server"); + println!(""); + println!("* file systems that cache in temporary locations, such as NFS"); + println!("version 3 clients"); + println!(""); + println!("* compressed file systems"); + println!(""); + println!("In the case of ext3 file systems, the above disclaimer applies"); + println!( + "(and {} is thus of limited effectiveness) only in data=journal mode,", + NAME + ); + println!("which journals file data in addition to just metadata. In both the"); + println!( + "data=ordered (default) and data=writeback modes, {} works as usual.", + NAME + ); + println!("Ext3 journaling modes can be changed by adding the data=something option"); + println!("to the mount options for a particular file system in the /etc/fstab file,"); + println!("as documented in the mount man page (man mount)."); + println!(""); + println!("In addition, file system backups and remote mirrors may contain copies"); + println!("of the file that cannot be removed, and that will allow a shredded file"); + println!("to be recovered later."); +} + +// TODO: Add support for all postfixes here up to and including EiB +// http://www.gnu.org/software/coreutils/manual/coreutils.html#Block-size +fn get_size(size_str_opt: Option) -> Option { + if size_str_opt.is_none() { + return None; + } + + let mut size_str = size_str_opt.as_ref().unwrap().clone(); + // Immutably look at last character of size string + let unit = match size_str.chars().last().unwrap() { + 'K' => { + size_str.pop(); + 1024u64 + } + 'M' => { + size_str.pop(); + (1024 * 1024) as u64 + } + 'G' => { + size_str.pop(); + (1024 * 1024 * 1024) as u64 + } + _ => 1u64, + }; + + let coeff = match size_str.parse::() { + Ok(u) => u, + Err(_) => { + println!("{}: {}: Invalid file size", NAME, size_str_opt.unwrap()); + exit!(1); + } + }; + + Some(coeff * unit) +} + +fn pass_name(pass_type: &PassType) -> String { + match *pass_type { + PassType::Random => String::from("random"), + PassType::Pattern(bytes) => { + let mut s: String = String::new(); + while s.len() < 6 { + for b in bytes { + let readable: String = format!("{:x}", b); + s.push_str(&readable); + } + } + s + } + } +} + +fn wipe_file( + path_str: &str, + n_passes: usize, + remove: bool, + size: Option, + exact: bool, + zero: bool, + verbose: bool, +) { + // Get these potential errors out of the way first + let path: &Path = Path::new(path_str); + if !path.exists() { + println!("{}: {}: No such file or directory", NAME, path.display()); + return; + } + if !path.is_file() { + println!("{}: {}: Not a file", NAME, path.display()); + return; + } + + // Fill up our pass sequence + let mut pass_sequence: Vec = Vec::new(); + + if n_passes <= 3 { + // Only random passes if n_passes <= 3 + for _ in 0..n_passes { + pass_sequence.push(PassType::Random) + } + } + // First fill it with Patterns, shuffle it, then evenly distribute Random + else { + let n_full_arrays = n_passes / PATTERNS.len(); // How many times can we go through all the patterns? + let remainder = n_passes % PATTERNS.len(); // How many do we get through on our last time through? + + for _ in 0..n_full_arrays { + for p in &PATTERNS { + pass_sequence.push(PassType::Pattern(*p)); + } + } + for i in 0..remainder { + pass_sequence.push(PassType::Pattern(PATTERNS[i])); + } + rand::thread_rng().shuffle(&mut pass_sequence[..]); // randomize the order of application + + let n_random = 3 + n_passes / 10; // Minimum 3 random passes; ratio of 10 after + // Evenly space random passes; ensures one at the beginning and end + for i in 0..n_random { + pass_sequence[i * (n_passes - 1) / (n_random - 1)] = PassType::Random; + } + } + + // --zero specifies whether we want one final pass of 0x00 on our file + if zero { + pass_sequence.push(PassType::Pattern(b"\x00")); + } + + { + let total_passes: usize = pass_sequence.len(); + let mut file: File = OpenOptions::new() + .write(true) + .truncate(false) + .open(path) + .expect("Failed to open file for writing"); + + for (i, pass_type) in pass_sequence.iter().enumerate() { + if verbose { + let pass_name: String = pass_name(pass_type); + if total_passes.to_string().len() == 1 { + println!( + "{}: {}: pass {}/{} ({})... ", + NAME, + path.display(), + i + 1, + total_passes, + pass_name + ); + } else { + println!( + "{}: {}: pass {:2.0}/{:2.0} ({})... ", + NAME, + path.display(), + i + 1, + total_passes, + pass_name + ); + } + } + // size is an optional argument for exactly how many bytes we want to shred + do_pass(&mut file, path, *pass_type, size, exact).expect("File write pass failed"); // Ignore failed writes; just keep trying + } + } + + if remove { + do_remove(path, path_str, verbose).expect("Failed to remove file"); + } +} + +fn do_pass( + file: &mut File, + path: &Path, + generator_type: PassType, + given_file_size: Option, + exact: bool, +) -> Result<(), io::Error> { + file.seek(SeekFrom::Start(0))?; + + // Use the given size or the whole file if not specified + let size: u64 = given_file_size.unwrap_or(get_file_size(path)?); + + let generator = BytesGenerator::new(size, generator_type, exact); + + for block in generator { + file.write_all(&*block)?; + } + + file.sync_data()?; + + Ok(()) +} + +fn get_file_size(path: &Path) -> Result { + let size: u64 = fs::metadata(path)?.len(); + + Ok(size) +} + +// Repeatedly renames the file with strings of decreasing length (most likely all 0s) +// Return the path of the file after its last renaming or None if error +fn wipe_name(orig_path: &Path, verbose: bool) -> Option { + let file_name_len: usize = orig_path.file_name().unwrap().to_str().unwrap().len(); + + let mut last_path: PathBuf = PathBuf::from(orig_path); + + for length in (1..file_name_len + 1).rev() { + for name in FilenameGenerator::new(length) { + let new_path: PathBuf = orig_path.with_file_name(name); + // We don't want the filename to already exist (don't overwrite) + // If it does, find another name that doesn't + if new_path.exists() { + continue; + } + match fs::rename(&last_path, &new_path) { + Ok(()) => { + if verbose { + println!( + "{}: {}: renamed to {}", + NAME, + last_path.display(), + new_path.display() + ); + } + + // Sync every file rename + { + let new_file: File = File::open(new_path.clone()) + .expect("Failed to open renamed file for syncing"); + new_file.sync_all().expect("Failed to sync renamed file"); + } + + last_path = new_path; + break; + } + Err(e) => { + println!( + "{}: {}: Couldn't rename to {}: {}", + NAME, + last_path.display(), + new_path.display(), + e + ); + return None; + } + } + } // If every possible filename already exists, just reduce the length and try again + } + + Some(last_path) +} + +fn do_remove(path: &Path, orig_filename: &str, verbose: bool) -> Result<(), io::Error> { + if verbose { + println!("{}: {}: removing", NAME, orig_filename); + } + + let renamed_path: Option = wipe_name(&path, verbose); + match renamed_path { + Some(rp) => { + fs::remove_file(rp)?; + } + None => (), + } + + if verbose { + println!("{}: {}: removed", NAME, orig_filename); + } + + Ok(()) +} diff --git a/coreutils/src/shuf/Cargo.toml b/coreutils/src/shuf/Cargo.toml new file mode 100644 index 000000000..9e04c2046 --- /dev/null +++ b/coreutils/src/shuf/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "shuf" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_shuf" +path = "shuf.rs" + +[dependencies] +getopts = "0.2.18" +rand = "0.5" +uucore = "0.0.1" + +[[bin]] +name = "shuf" +path = "../../uumain.rs" diff --git a/coreutils/src/shuf/shuf.rs b/coreutils/src/shuf/shuf.rs new file mode 100644 index 000000000..6f5e0ee28 --- /dev/null +++ b/coreutils/src/shuf/shuf.rs @@ -0,0 +1,290 @@ +#![crate_name = "uu_shuf"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate rand; + +#[macro_use] +extern crate uucore; + +use rand::Rng; +use std::fs::File; +use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; +use std::usize::MAX as MAX_USIZE; + +enum Mode { + Default, + Echo, + InputRange((usize, usize)), +} + +static NAME: &str = "shuf"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + opts.optflag("e", "echo", "treat each ARG as an input line"); + opts.optopt( + "i", + "input-range", + "treat each number LO through HI as an input line", + "LO-HI", + ); + opts.optopt("n", "head-count", "output at most COUNT lines", "COUNT"); + opts.optopt( + "o", + "output", + "write result to FILE instead of standard output", + "FILE", + ); + opts.optopt("", "random-source", "get random bytes from FILE", "FILE"); + opts.optflag("r", "repeat", "output lines can be repeated"); + opts.optflag("z", "zero-terminated", "end lines with 0 byte, not newline"); + opts.optflag("h", "help", "display this help and exit"); + opts.optflag("V", "version", "output version information and exit"); + let mut matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "{}", f), + }; + if matches.opt_present("help") { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTION]... [FILE] + {0} -e [OPTION]... [ARG]... + {0} -i LO-HI [OPTION]... + +Write a random permutation of the input lines to standard output. +With no FILE, or when FILE is -, read standard input.", + NAME, VERSION + ); + print!("{}", opts.usage(&msg)); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else { + let echo = matches.opt_present("echo"); + let mode = match matches.opt_str("input-range") { + Some(range) => { + if echo { + show_error!("cannot specify more than one mode"); + return 1; + } + match parse_range(range) { + Ok(m) => Mode::InputRange(m), + Err(msg) => { + crash!(1, "{}", msg); + } + } + } + None => { + if echo { + Mode::Echo + } else { + if matches.free.is_empty() { + matches.free.push("-".to_owned()); + } else if matches.free.len() > 1 { + show_error!("extra operand '{}'", &matches.free[1][..]); + } + Mode::Default + } + } + }; + let repeat = matches.opt_present("repeat"); + let sep = if matches.opt_present("zero-terminated") { + 0x00 as u8 + } else { + 0x0a as u8 + }; + let count = match matches.opt_str("head-count") { + Some(cnt) => match cnt.parse::() { + Ok(val) => val, + Err(e) => { + show_error!("'{}' is not a valid count: {}", cnt, e); + return 1; + } + }, + None => MAX_USIZE, + }; + let output = matches.opt_str("output"); + let random = matches.opt_str("random-source"); + + match mode { + Mode::Echo => { + // XXX: this doesn't correctly handle non-UTF-8 cmdline args + let mut evec = matches + .free + .iter() + .map(|a| a.as_bytes()) + .collect::>(); + find_seps(&mut evec, sep); + shuf_bytes(&mut evec, repeat, count, sep, output, random); + } + Mode::InputRange((b, e)) => { + let rvec = (b..e).map(|x| format!("{}", x)).collect::>(); + let mut rvec = rvec.iter().map(|a| a.as_bytes()).collect::>(); + shuf_bytes(&mut rvec, repeat, count, sep, output, random); + } + Mode::Default => { + let fdata = read_input_file(&matches.free[0][..]); + let mut fdata = vec![&fdata[..]]; + find_seps(&mut fdata, sep); + shuf_bytes(&mut fdata, repeat, count, sep, output, random); + } + } + } + + 0 +} + +fn read_input_file(filename: &str) -> Vec { + let mut file = BufReader::new(if filename == "-" { + Box::new(stdin()) as Box + } else { + match File::open(filename) { + Ok(f) => Box::new(f) as Box, + Err(e) => crash!(1, "failed to open '{}': {}", filename, e), + } + }); + + let mut data = Vec::new(); + match file.read_to_end(&mut data) { + Err(e) => crash!(1, "failed reading '{}': {}", filename, e), + Ok(_) => (), + }; + + data +} + +fn find_seps(data: &mut Vec<&[u8]>, sep: u8) { + // need to use for loop so we don't borrow the vector as we modify it in place + // basic idea: + // * We don't care about the order of the result. This lets us slice the slices + // without making a new vector. + // * Starting from the end of the vector, we examine each element. + // * If that element contains the separator, we remove it from the vector, + // and then sub-slice it into slices that do not contain the separator. + // * We maintain the invariant throughout that each element in the vector past + // the ith element does not have any separators remaining. + for i in (0..data.len()).rev() { + if data[i].contains(&sep) { + let this = data.swap_remove(i); + let mut p = 0; + let mut i = 1; + loop { + if i == this.len() { + break; + } + + if this[i] == sep { + data.push(&this[p..i]); + p = i + 1; + } + i += 1; + } + if p < this.len() { + data.push(&this[p..i]); + } + } + } +} + +fn shuf_bytes( + input: &mut Vec<&[u8]>, + repeat: bool, + count: usize, + sep: u8, + output: Option, + random: Option, +) { + let mut output = BufWriter::new(match output { + None => Box::new(stdout()) as Box, + Some(s) => match File::create(&s[..]) { + Ok(f) => Box::new(f) as Box, + Err(e) => crash!(1, "failed to open '{}' for writing: {}", &s[..], e), + }, + }); + + let mut rng = match random { + Some(r) => WrappedRng::RngFile(rand::read::ReadRng::new(match File::open(&r[..]) { + Ok(f) => f, + Err(e) => crash!(1, "failed to open random source '{}': {}", &r[..], e), + })), + None => WrappedRng::RngDefault(rand::thread_rng()), + }; + + // we're generating a random usize. To keep things fair, we take this number mod ceil(log2(length+1)) + let mut len_mod = 1; + let mut len = input.len(); + while len > 0 { + len >>= 1; + len_mod <<= 1; + } + drop(len); + + let mut count = count; + while count > 0 && !input.is_empty() { + let mut r = input.len(); + while r >= input.len() { + r = rng.next_usize() % len_mod; + } + + // write the randomly chosen value and the separator + output + .write_all(input[r]) + .unwrap_or_else(|e| crash!(1, "write failed: {}", e)); + output + .write_all(&[sep]) + .unwrap_or_else(|e| crash!(1, "write failed: {}", e)); + + // if we do not allow repeats, remove the chosen value from the input vector + if !repeat { + // shrink the mask if we will drop below a power of 2 + if input.len() % 2 == 0 && len_mod > 2 { + len_mod >>= 1; + } + input.swap_remove(r); + } + + count -= 1; + } +} + +fn parse_range(input_range: String) -> Result<(usize, usize), String> { + let split: Vec<&str> = input_range.split('-').collect(); + if split.len() != 2 { + Err("invalid range format".to_owned()) + } else { + let begin = match split[0].parse::() { + Ok(m) => m, + Err(e) => return Err(format!("{} is not a valid number: {}", split[0], e)), + }; + let end = match split[1].parse::() { + Ok(m) => m, + Err(e) => return Err(format!("{} is not a valid number: {}", split[1], e)), + }; + Ok((begin, end + 1)) + } +} + +enum WrappedRng { + RngFile(rand::read::ReadRng), + RngDefault(rand::ThreadRng), +} + +impl WrappedRng { + fn next_usize(&mut self) -> usize { + match *self { + WrappedRng::RngFile(ref mut r) => r.gen(), + WrappedRng::RngDefault(ref mut r) => r.gen(), + } + } +} diff --git a/coreutils/src/sleep/Cargo.toml b/coreutils/src/sleep/Cargo.toml new file mode 100644 index 000000000..5dff8703a --- /dev/null +++ b/coreutils/src/sleep/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "sleep" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_sleep" +path = "sleep.rs" + +[dependencies] +getopts = "0.2.18" + +[dependencies.uucore] +version = "0.0.1" +features = ["parse_time"] + +[[bin]] +name = "sleep" +path = "../../uumain.rs" diff --git a/coreutils/src/sleep/sleep.rs b/coreutils/src/sleep/sleep.rs new file mode 100644 index 000000000..f27c42be5 --- /dev/null +++ b/coreutils/src/sleep/sleep.rs @@ -0,0 +1,74 @@ +#![crate_name = "uu_sleep"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::thread; +use std::time::Duration; + +static NAME: &str = "sleep"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + 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) => { + show_error!("{}", f); + return 1; + } + }; + + if matches.opt_present("help") { + let msg = format!( + "{0} {1} + +Usage: + {0} NUMBER[SUFFIX] +or + {0} OPTION + +Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default), +'m' for minutes, 'h' for hours or 'd' for days. Unlike most implementations +that require NUMBER be an integer, here NUMBER may be an arbitrary floating +point number. Given two or more arguments, pause for the amount of time +specified by the sum of their values.", + NAME, VERSION + ); + print!("{}", opts.usage(&msg)); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else if matches.free.is_empty() { + show_error!("missing an argument"); + show_error!("for help, try '{0} --help'", NAME); + return 1; + } else { + sleep(matches.free); + } + + 0 +} + +fn sleep(args: Vec) { + let sleep_dur = args.iter().fold(Duration::new(0, 0), |result, arg| { + match uucore::parse_time::from_str(&arg[..]) { + Ok(m) => m + result, + Err(f) => crash!(1, "{}", f), + } + }); + + thread::sleep(sleep_dur); +} diff --git a/coreutils/src/sort/Cargo.toml b/coreutils/src/sort/Cargo.toml new file mode 100644 index 000000000..f221ec054 --- /dev/null +++ b/coreutils/src/sort/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "sort" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_sort" +path = "sort.rs" + +[dependencies] +getopts = "0.2.18" +semver = "0.9.0" +itertools = "0.8.0" + +[dependencies.uucore] +version = "0.0.1" +features = ["fs"] + +[[bin]] +name = "sort" +path = "../../uumain.rs" diff --git a/coreutils/src/sort/sort.rs b/coreutils/src/sort/sort.rs new file mode 100644 index 000000000..c21802d49 --- /dev/null +++ b/coreutils/src/sort/sort.rs @@ -0,0 +1,547 @@ +#![crate_name = "uu_sort"] +/* + * This file is part of the uutils coreutils package. + * + * (c) Michael Yin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +#![allow(dead_code)] + +extern crate getopts; +extern crate semver; + +extern crate itertools; +#[macro_use] +extern crate uucore; + +use std::cmp::Ordering; +use std::collections::BinaryHeap; +use std::fs::File; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write}; +use std::mem::replace; +use std::path::Path; +use uucore::fs::is_stdin_interactive; +use semver::Version; +use itertools::Itertools; // for Iterator::dedup() + +static NAME: &str = "sort"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +const DECIMAL_PT: char = '.'; +const THOUSANDS_SEP: char = ','; + +enum SortMode { + Numeric, + HumanNumeric, + Month, + Version, + Default, +} + +struct Settings { + mode: SortMode, + merge: bool, + reverse: bool, + outfile: Option, + stable: bool, + unique: bool, + check: bool, + ignore_case: bool, + compare_fns: Vec Ordering>, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + mode: SortMode::Default, + merge: false, + reverse: false, + outfile: None, + stable: false, + unique: false, + check: false, + ignore_case: false, + compare_fns: Vec::new(), + } + } +} + +struct MergeableFile<'a> { + lines: Lines>>, + current_line: String, + settings: &'a Settings, +} + +// BinaryHeap depends on `Ord`. Note that we want to pop smallest items +// from the heap first, and BinaryHeap.pop() returns the largest, so we +// trick it into the right order by calling reverse() here. +impl<'a> Ord for MergeableFile<'a> { + fn cmp(&self, other: &MergeableFile) -> Ordering { + compare_by(&self.current_line, &other.current_line, &self.settings).reverse() + } +} + +impl<'a> PartialOrd for MergeableFile<'a> { + fn partial_cmp(&self, other: &MergeableFile) -> Option { + Some(self.cmp(other)) + } +} + +impl<'a> PartialEq for MergeableFile<'a> { + fn eq(&self, other: &MergeableFile) -> bool { + Ordering::Equal == compare_by(&self.current_line, &other.current_line, &self.settings) + } +} + +impl<'a> Eq for MergeableFile<'a> {} + +struct FileMerger<'a> { + heap: BinaryHeap>, + settings: &'a Settings, +} + +impl<'a> FileMerger<'a> { + fn new(settings: &'a Settings) -> FileMerger<'a> { + FileMerger { + heap: BinaryHeap::new(), + settings: settings, + } + } + fn push_file(&mut self, mut lines: Lines>>) { + match lines.next() { + Some(Ok(next_line)) => { + let mergeable_file = MergeableFile { + lines: lines, + current_line: next_line, + settings: &self.settings, + }; + self.heap.push(mergeable_file); + } + _ => {} + } + } +} + +impl<'a> Iterator for FileMerger<'a> { + type Item = String; + fn next(&mut self) -> Option { + match self.heap.pop() { + Some(mut current) => { + match current.lines.next() { + Some(Ok(next_line)) => { + let ret = replace(&mut current.current_line, next_line); + self.heap.push(current); + Some(ret) + } + _ => { + // Don't put it back in the heap (it's empty/erroring) + // but its first line is still valid. + Some(current.current_line) + } + } + } + None => None, + } + } +} + +pub fn uumain(args: Vec) -> i32 { + let mut settings: Settings = Default::default(); + let mut opts = getopts::Options::new(); + + opts.optflag( + "f", + "ignore-case", + "fold lower case to upper case characters", + ); + opts.optflag( + "n", + "numeric-sort", + "compare according to string numerical value", + ); + opts.optflag( + "h", + "human-numeric-sort", + "compare according to human readable sizes, eg 1M > 100k", + ); + opts.optflag( + "M", + "month-sort", + "compare according to month name abbreviation", + ); + opts.optflag("r", "reverse", "reverse the output"); + opts.optflag("h", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + opts.optflag("m", "merge", "merge already sorted files; do not sort"); + opts.optopt( + "o", + "output", + "write output to FILENAME instead of stdout", + "FILENAME", + ); + opts.optflag( + "s", + "stable", + "stabilize sort by disabling last-resort comparison", + ); + opts.optflag("u", "unique", "output only the first of an equal run"); + opts.optflag( + "V", + "version-sort", + "Sort by SemVer version number, eg 1.12.2 > 1.1.2", + ); + opts.optflag("c", "check", "check for sorted input; do not sort"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "Invalid options\n{}", f), + }; + if matches.opt_present("help") { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTION]... [FILE]... + +Write the sorted concatenation of all FILE(s) to standard output. + +Mandatory arguments for long options are mandatory for short options too. + +With no FILE, or when FILE is -, read standard input.", + NAME, VERSION + ); + print!("{}", opts.usage(&msg)); + return 0; + } + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + settings.mode = if matches.opt_present("numeric-sort") { + SortMode::Numeric + } else if matches.opt_present("human-numeric-sort") { + SortMode::HumanNumeric + } else if matches.opt_present("month-sort") { + SortMode::Month + } else if matches.opt_present("version-sort") { + SortMode::Version + } else { + SortMode::Default + }; + + settings.merge = matches.opt_present("merge"); + settings.reverse = matches.opt_present("reverse"); + settings.outfile = matches.opt_str("output"); + settings.stable = matches.opt_present("stable"); + settings.unique = matches.opt_present("unique"); + settings.check = matches.opt_present("check"); + settings.ignore_case = matches.opt_present("ignore-case"); + + let mut files = matches.free; + if files.is_empty() { + /* if no file, default to stdin */ + files.push("-".to_owned()); + } else if settings.check && files.len() != 1 { + crash!(1, "sort: extra operand `{}' not allowed with -c", files[1]) + } + + settings.compare_fns.push(match settings.mode { + SortMode::Numeric => numeric_compare, + SortMode::HumanNumeric => human_numeric_size_compare, + SortMode::Month => month_compare, + SortMode::Version => version_compare, + SortMode::Default => String::cmp, + }); + + if !settings.stable { + match settings.mode { + SortMode::Default => {} + _ => settings.compare_fns.push(String::cmp), + } + } + + exec(files, &settings) +} + +fn exec(files: Vec, settings: &Settings) -> i32 { + let mut lines = Vec::new(); + let mut file_merger = FileMerger::new(&settings); + + for path in &files { + let (reader, _) = match open(path) { + Some(x) => x, + None => continue, + }; + + let buf_reader = BufReader::new(reader); + + if settings.merge { + file_merger.push_file(buf_reader.lines()); + } else if settings.check { + return exec_check_file(buf_reader.lines(), &settings); + } else { + for line in buf_reader.lines() { + if let Ok(n) = line { + lines.push(n); + } else { + break; + } + } + } + } + + sort_by(&mut lines, &settings); + + if settings.merge { + if settings.unique { + print_sorted(file_merger.dedup(), &settings.outfile) + } else { + print_sorted(file_merger, &settings.outfile) + } + } else { + if settings.unique { + print_sorted(lines.iter().dedup(), &settings.outfile) + } else { + print_sorted(lines.iter(), &settings.outfile) + } + } + + 0 +} + +fn exec_check_file(lines: Lines>>, settings: &Settings) -> i32 { + // errors yields the line before each disorder, + // plus the last line (quirk of .coalesce()) + let unwrapped_lines = lines.filter_map(|maybe_line| { + if let Ok(line) = maybe_line { + Some(line) + } else { + None + } + }); + let mut errors = unwrapped_lines + .enumerate() + .coalesce(|(last_i, last_line), (i, line)| { + if compare_by(&last_line, &line, &settings) == Ordering::Greater { + Err(((last_i, last_line), (i, line))) + } else { + Ok((i, line)) + } + }); + if let Some((first_error_index, _line)) = errors.next() { + // Check for a second "error", as .coalesce() always returns the last + // line, no matter what our merging function does. + if let Some(_last_line_or_next_error) = errors.next() { + println!("sort: disorder in line {}", first_error_index); + return 1; + } else { + // first "error" was actually the last line. + return 0; + } + } else { + // unwrapped_lines was empty. Empty files are defined to be sorted. + return 0; + } +} + +fn sort_by(lines: &mut Vec, settings: &Settings) { + lines.sort_by(|a, b| compare_by(a, b, &settings)) +} + +fn compare_by(a: &String, b: &String, settings: &Settings) -> Ordering { + // Convert to uppercase if necessary + let (a_upper, b_upper): (String, String); + let (a, b) = if settings.ignore_case { + a_upper = a.to_uppercase(); + b_upper = b.to_uppercase(); + (&a_upper, &b_upper) + } else { + (a, b) + }; + + for compare_fn in &settings.compare_fns { + let cmp = compare_fn(a, b); + if cmp != Ordering::Equal { + if settings.reverse { + return cmp.reverse(); + } else { + return cmp; + } + } + } + return Ordering::Equal; +} + +/// Parse the beginning string into an f64, returning -inf instead of NaN on errors. +fn permissive_f64_parse(a: &str) -> f64 { + // Maybe should be split on non-digit, but then 10e100 won't parse properly. + // On the flip side, this will give NEG_INFINITY for "1,234", which might be OK + // because there's no way to handle both CSV and thousands separators without a new flag. + // GNU sort treats "1,234" as "1" in numeric, so maybe it's fine. + // GNU sort treats "NaN" as non-number in numeric, so it needs special care. + match a.split_whitespace().next() { + None => std::f64::NEG_INFINITY, + Some(sa) => { + match sa.parse::() { + Ok(a) if a.is_nan() => std::f64::NEG_INFINITY, + Ok(a) => a, + Err(_) => std::f64::NEG_INFINITY, + } + } + } +} + +/// Compares two floating point numbers, with errors being assumed to be -inf. +/// Stops coercing at the first whitespace char, so 1e2 will parse as 100 but +/// 1,000 will parse as -inf. +fn numeric_compare(a: &String, b: &String) -> Ordering { + let fa = permissive_f64_parse(a); + let fb = permissive_f64_parse(b); + // f64::cmp isn't implemented because NaN messes with it + // but we sidestep that with permissive_f64_parse so just fake it + if fa > fb { + Ordering::Greater + } else if fa < fb { + Ordering::Less + } else { + Ordering::Equal + } +} + +fn human_numeric_convert(a: &String) -> f64 { + let int_iter = a.chars(); + let suffix_iter = a.chars(); + let int_str: String = int_iter.take_while(|c| c.is_numeric()).collect(); + let suffix = suffix_iter.skip_while(|c| c.is_numeric()).next(); + let int_part = match int_str.parse::() { + Ok(i) => i, + Err(_) => -1f64, + } as f64; + let suffix: f64 = match suffix.unwrap_or('\0') { + 'K' => 1000f64, + 'M' => 1E6, + 'G' => 1E9, + 'T' => 1E12, + 'P' => 1E15, + _ => 1f64, + }; + int_part * suffix +} + +/// Compare two strings as if they are human readable sizes. +/// AKA 1M > 100k +fn human_numeric_size_compare(a: &String, b: &String) -> Ordering { + let fa = human_numeric_convert(a); + let fb = human_numeric_convert(b); + if fa > fb { + Ordering::Greater + } else if fa < fb { + Ordering::Less + } else { + Ordering::Equal + } +} + +#[derive(Eq, Ord, PartialEq, PartialOrd)] +enum Month { + Unknown, + January, + February, + March, + April, + May, + June, + July, + August, + September, + October, + November, + December, +} + +/// Parse the beginning string into a Month, returning Month::Unknown on errors. +fn month_parse(line: &String) -> Month { + match line.split_whitespace() + .next() + .unwrap() + .to_uppercase() + .as_ref() + { + "JAN" => Month::January, + "FEB" => Month::February, + "MAR" => Month::March, + "APR" => Month::April, + "MAY" => Month::May, + "JUN" => Month::June, + "JUL" => Month::July, + "AUG" => Month::August, + "SEP" => Month::September, + "OCT" => Month::October, + "NOV" => Month::November, + "DEC" => Month::December, + _ => Month::Unknown, + } +} + +fn month_compare(a: &String, b: &String) -> Ordering { + month_parse(a).cmp(&month_parse(b)) +} + +fn version_compare(a: &String, b: &String) -> Ordering { + let ver_a = Version::parse(a); + let ver_b = Version::parse(b); + if ver_a > ver_b { + Ordering::Greater + } else if ver_a < ver_b { + Ordering::Less + } else { + Ordering::Equal + } +} + +fn print_sorted>(iter: T, outfile: &Option) +where + S: std::fmt::Display, +{ + let mut file: Box = match *outfile { + Some(ref filename) => match File::create(Path::new(&filename)) { + Ok(f) => Box::new(BufWriter::new(f)) as Box, + Err(e) => { + show_error!("sort: {0}: {1}", filename, e.to_string()); + panic!("Could not open output file"); + } + }, + None => Box::new(stdout()) as Box, + }; + + for line in iter { + let str = format!("{}\n", line); + match file.write_all(str.as_bytes()) { + Err(e) => { + show_error!("sort: {0}", e.to_string()); + panic!("Write failed"); + } + Ok(_) => (), + } + } +} + +// from cat.rs +fn open(path: &str) -> Option<(Box, bool)> { + if path == "-" { + let stdin = stdin(); + return Some((Box::new(stdin) as Box, is_stdin_interactive())); + } + + match File::open(Path::new(path)) { + Ok(f) => Some((Box::new(f) as Box, false)), + Err(e) => { + show_error!("sort: {0}: {1}", path, e.to_string()); + None + } + } +} diff --git a/coreutils/src/split/Cargo.toml b/coreutils/src/split/Cargo.toml new file mode 100644 index 000000000..8bf0d2022 --- /dev/null +++ b/coreutils/src/split/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "split" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_split" +path = "split.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "split" +path = "../../uumain.rs" diff --git a/coreutils/src/split/README.md b/coreutils/src/split/README.md new file mode 100644 index 000000000..582c9970a --- /dev/null +++ b/coreutils/src/split/README.md @@ -0,0 +1,9 @@ +# Rudimentary "split" Implementation + +## Missing Features + +### Flags +* `--verbose` - created file printing is implemented, don't know if there is anything else + +## Possible Optimizations +* Use slice (`[u8]`) directly as the `control.current_line`. diff --git a/coreutils/src/split/split.rs b/coreutils/src/split/split.rs new file mode 100644 index 000000000..e32467bb2 --- /dev/null +++ b/coreutils/src/split/split.rs @@ -0,0 +1,352 @@ +#![crate_name = "uu_split"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Akira Hayakawa + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::char; +use std::fs::{File, OpenOptions}; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; +use std::path::Path; + +static NAME: &str = "split"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optopt( + "a", + "suffix-length", + "use suffixes of length N (default 2)", + "N", + ); + opts.optopt("b", "bytes", "put SIZE bytes per output file", "SIZE"); + opts.optopt( + "C", + "line-bytes", + "put at most SIZE bytes of lines per output file", + "SIZE", + ); + opts.optflag( + "d", + "numeric-suffixes", + "use numeric suffixes instead of alphabetic", + ); + opts.optopt("l", "lines", "put NUMBER lines per output file", "NUMBER"); + opts.optflag( + "", + "verbose", + "print a diagnostic just before each output file is opened", + ); + opts.optflag("h", "help", "display 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") { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTION]... [INPUT [PREFIX]] + +Output fixed-size pieces of INPUT to PREFIXaa, PREFIX ab, ...; default +size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is +-, read standard input.", + NAME, VERSION + ); + + println!( + "{}\nSIZE may have a multiplier suffix: b for 512, k for 1K, m for 1 Meg.", + opts.usage(&msg) + ); + return 0; + } + + if matches.opt_present("V") { + println!("{} {}", NAME, VERSION); + return 0; + } + + let mut settings = Settings { + prefix: "".to_owned(), + numeric_suffix: false, + suffix_length: 0, + input: "".to_owned(), + strategy: "".to_owned(), + strategy_param: "".to_owned(), + verbose: false, + }; + + settings.numeric_suffix = matches.opt_present("d"); + + settings.suffix_length = match matches.opt_str("a") { + Some(n) => match n.parse() { + Ok(m) => m, + Err(e) => crash!(1, "cannot parse num: {}", e), + }, + None => 2, + }; + + settings.verbose = matches.opt_present("verbose"); + + settings.strategy = "l".to_owned(); + settings.strategy_param = "1000".to_owned(); + let strategies = vec!["b", "C", "l"]; + for e in &strategies { + match matches.opt_str(*e) { + Some(a) => { + if settings.strategy == "l" { + settings.strategy = (*e).to_owned(); + settings.strategy_param = a; + } else { + crash!(1, "{}: cannot split in more than one way", NAME) + } + } + None => {} + } + } + + let mut v = matches.free.iter(); + let (input, prefix) = match (v.next(), v.next()) { + (Some(a), None) => (a.to_owned(), "x".to_owned()), + (Some(a), Some(b)) => (a.clone(), b.clone()), + (None, _) => ("-".to_owned(), "x".to_owned()), + }; + settings.input = input; + settings.prefix = prefix; + + split(&settings) +} + +struct Settings { + prefix: String, + numeric_suffix: bool, + suffix_length: usize, + input: String, + strategy: String, + strategy_param: String, + verbose: bool, +} + +struct SplitControl { + current_line: String, // Don't touch + request_new_file: bool, // Splitter implementation requests new file +} + +trait Splitter { + // Consume the current_line and return the consumed string + fn consume(&mut self, &mut SplitControl) -> String; +} + +struct LineSplitter { + saved_lines_to_write: usize, + lines_to_write: usize, +} + +impl LineSplitter { + fn new(settings: &Settings) -> Box { + let n = match settings.strategy_param.parse() { + Ok(a) => a, + Err(e) => crash!(1, "invalid number of lines: {}", e), + }; + Box::new(LineSplitter { + saved_lines_to_write: n, + lines_to_write: n, + }) as Box + } +} + +impl Splitter for LineSplitter { + fn consume(&mut self, control: &mut SplitControl) -> String { + self.lines_to_write -= 1; + if self.lines_to_write == 0 { + self.lines_to_write = self.saved_lines_to_write; + control.request_new_file = true; + } + control.current_line.clone() + } +} + +struct ByteSplitter { + saved_bytes_to_write: usize, + bytes_to_write: usize, + break_on_line_end: bool, + require_whole_line: bool, +} + +impl ByteSplitter { + fn new(settings: &Settings) -> Box { + let mut strategy_param: Vec = settings.strategy_param.chars().collect(); + let suffix = strategy_param.pop().unwrap(); + let multiplier = match suffix { + '0'...'9' => 1usize, + 'b' => 512usize, + 'k' => 1024usize, + 'm' => 1024usize * 1024usize, + _ => crash!(1, "invalid number of bytes"), + }; + let n = if suffix.is_alphabetic() { + match strategy_param + .iter() + .cloned() + .collect::() + .parse::() + { + Ok(a) => a, + Err(e) => crash!(1, "invalid number of bytes: {}", e), + } + } else { + match settings.strategy_param.parse::() { + Ok(a) => a, + Err(e) => crash!(1, "invalid number of bytes: {}", e), + } + }; + Box::new(ByteSplitter { + saved_bytes_to_write: n * multiplier, + bytes_to_write: n * multiplier, + break_on_line_end: settings.strategy == "b", + require_whole_line: false, + }) as Box + } +} + +impl Splitter for ByteSplitter { + fn consume(&mut self, control: &mut SplitControl) -> String { + let line = control.current_line.clone(); + let n = std::cmp::min(line.chars().count(), self.bytes_to_write); + if self.require_whole_line && n < line.chars().count() { + self.bytes_to_write = self.saved_bytes_to_write; + control.request_new_file = true; + self.require_whole_line = false; + return line[0..0].to_owned(); + } + self.bytes_to_write -= n; + if n == 0 { + self.bytes_to_write = self.saved_bytes_to_write; + control.request_new_file = true; + } + if self.break_on_line_end && n == line.chars().count() { + self.require_whole_line = self.break_on_line_end; + } + line[..n].to_owned() + } +} + +// (1, 3) -> "aab" +fn str_prefix(i: usize, width: usize) -> String { + let mut c = "".to_owned(); + let mut n = i; + let mut w = width; + while w > 0 { + w -= 1; + let div = 26usize.pow(w as u32); + let r = n / div; + n -= r * div; + c.push(char::from_u32((r as u32) + 97).unwrap()); + } + c +} + +// (1, 3) -> "001" +fn num_prefix(i: usize, width: usize) -> String { + let mut c = "".to_owned(); + let mut n = i; + let mut w = width; + while w > 0 { + w -= 1; + let div = 10usize.pow(w as u32); + let r = n / div; + n -= r * div; + c.push(char::from_digit(r as u32, 10).unwrap()); + } + c +} + +fn split(settings: &Settings) -> i32 { + let mut reader = BufReader::new(if settings.input == "-" { + Box::new(stdin()) as Box + } else { + let r = match File::open(Path::new(&settings.input)) { + Ok(a) => a, + Err(_) => crash!( + 1, + "cannot open '{}' for reading: No such file or directory", + settings.input + ), + }; + Box::new(r) as Box + }); + + let mut splitter: Box = match settings.strategy.as_ref() { + "l" => LineSplitter::new(settings), + "b" | "C" => ByteSplitter::new(settings), + a => crash!(1, "strategy {} not supported", a), + }; + + let mut control = SplitControl { + current_line: "".to_owned(), // Request new line + request_new_file: true, // Request new file + }; + + let mut writer = BufWriter::new(Box::new(stdout()) as Box); + let mut fileno = 0; + loop { + if control.current_line.chars().count() == 0 { + match reader.read_line(&mut control.current_line) { + Ok(0) | Err(_) => break, + _ => {} + } + } + + if control.request_new_file { + let mut filename = settings.prefix.clone(); + filename.push_str( + if settings.numeric_suffix { + num_prefix(fileno, settings.suffix_length) + } else { + str_prefix(fileno, settings.suffix_length) + }.as_ref(), + ); + + if fileno != 0 { + crash_if_err!(1, writer.flush()); + } + fileno += 1; + writer = BufWriter::new(Box::new( + OpenOptions::new() + .write(true) + .create(true) + .open(Path::new(&filename)) + .unwrap(), + ) as Box); + control.request_new_file = false; + if settings.verbose { + println!("creating file '{}'", filename); + } + } + + let consumed = splitter.consume(&mut control); + crash_if_err!(1, writer.write_all(consumed.as_bytes())); + + let advance = consumed.chars().count(); + let clone = control.current_line.clone(); + let sl = clone; + control.current_line = sl[advance..sl.chars().count()].to_owned(); + } + 0 +} diff --git a/coreutils/src/stat/Cargo.toml b/coreutils/src/stat/Cargo.toml new file mode 100644 index 000000000..992ca7f88 --- /dev/null +++ b/coreutils/src/stat/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "stat" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_stat" +path = "stat.rs" + +[dependencies] +getopts = "0.2.18" +time = "0.1.40" + +[dependencies.uucore] +version = "0.0.1" +features = ["entries"] + +[[bin]] +name = "stat" +path = "../../uumain.rs" diff --git a/coreutils/src/stat/fsext.rs b/coreutils/src/stat/fsext.rs new file mode 100644 index 000000000..b76451740 --- /dev/null +++ b/coreutils/src/stat/fsext.rs @@ -0,0 +1,388 @@ +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +pub use super::uucore::libc; +extern crate time; + +use self::time::Timespec; +pub use libc::{c_int, mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, + S_IFREG, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, + S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR}; + +pub trait BirthTime { + fn pretty_birth(&self) -> String; + fn birth(&self) -> String; +} + +use std::fs::Metadata; +impl BirthTime for Metadata { + fn pretty_birth(&self) -> String { + self.created() + .ok() + .and_then(|t| t.elapsed().ok()) + .map(|e| pretty_time(e.as_secs() as i64, e.subsec_nanos() as i64)) + .unwrap_or("-".to_owned()) + } + + fn birth(&self) -> String { + self.created() + .ok() + .and_then(|t| t.elapsed().ok()) + .map(|e| format!("{}", e.as_secs())) + .unwrap_or("0".to_owned()) + } +} + +#[macro_export] +macro_rules! has { + ($mode:expr, $perm:expr) => ( + $mode & $perm != 0 + ) +} + +pub fn pretty_time(sec: i64, nsec: i64) -> String { + let tm = time::at(Timespec::new(sec, nsec as i32)); + let res = time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap(); + if res.ends_with(" -0000") { + res.replace(" -0000", " +0000") + } else { + res + } +} + +pub fn pretty_filetype<'a>(mode: mode_t, size: u64) -> &'a str { + match mode & S_IFMT { + S_IFREG => { + if size != 0 { + "regular file" + } else { + "regular empty file" + } + } + S_IFDIR => "directory", + S_IFLNK => "symbolic link", + S_IFCHR => "character special file", + S_IFBLK => "block special file", + S_IFIFO => "fifo", + S_IFSOCK => "socket", + // TODO: Other file types + // See coreutils/gnulib/lib/file-type.c + _ => "weird file", + } +} + +pub fn pretty_access(mode: mode_t) -> String { + let mut result = String::with_capacity(10); + result.push(match mode & S_IFMT { + S_IFDIR => 'd', + S_IFCHR => 'c', + S_IFBLK => 'b', + S_IFREG => '-', + S_IFIFO => 'p', + S_IFLNK => 'l', + S_IFSOCK => 's', + // TODO: Other file types + _ => '?', + }); + + result.push(if has!(mode, S_IRUSR) { 'r' } else { '-' }); + result.push(if has!(mode, S_IWUSR) { 'w' } else { '-' }); + result.push(if has!(mode, S_ISUID as mode_t) { + if has!(mode, S_IXUSR) { + 's' + } else { + 'S' + } + } else if has!(mode, S_IXUSR) { + 'x' + } else { + '-' + }); + + result.push(if has!(mode, S_IRGRP) { 'r' } else { '-' }); + result.push(if has!(mode, S_IWGRP) { 'w' } else { '-' }); + result.push(if has!(mode, S_ISGID as mode_t) { + if has!(mode, S_IXGRP) { + 's' + } else { + 'S' + } + } else if has!(mode, S_IXGRP) { + 'x' + } else { + '-' + }); + + result.push(if has!(mode, S_IROTH) { 'r' } else { '-' }); + result.push(if has!(mode, S_IWOTH) { 'w' } else { '-' }); + result.push(if has!(mode, S_ISVTX as mode_t) { + if has!(mode, S_IXOTH) { + 't' + } else { + 'T' + } + } else if has!(mode, S_IXOTH) { + 'x' + } else { + '-' + }); + + result +} + +use std::mem::{self, transmute}; +use std::path::Path; +use std::borrow::Cow; +use std::ffi::CString; +use std::convert::{AsRef, From}; +use std::error::Error; +use std::io::Error as IOError; + +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android", target_os = "freebsd"))] +use libc::statfs as Sstatfs; +#[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "openbsd", target_os = "bitrig", target_os = "dragonfly"))] +use libc::statvfs as Sstatfs; + +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android", target_os = "freebsd"))] +use libc::statfs as statfs_fn; +#[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "openbsd", target_os = "bitrig", target_os = "dragonfly"))] +use libc::statvfs as statfs_fn; + +pub trait FsMeta { + fn fs_type(&self) -> i64; + fn iosize(&self) -> u64; + fn blksize(&self) -> i64; + fn total_blocks(&self) -> u64; + fn free_blocks(&self) -> u64; + fn avail_blocks(&self) -> u64; + fn total_fnodes(&self) -> u64; + fn free_fnodes(&self) -> u64; + fn fsid(&self) -> u64; + fn namelen(&self) -> u64; +} + +impl FsMeta for Sstatfs { + fn blksize(&self) -> i64 { + self.f_bsize as i64 + } + fn total_blocks(&self) -> u64 { + self.f_blocks as u64 + } + fn free_blocks(&self) -> u64 { + self.f_bfree as u64 + } + fn avail_blocks(&self) -> u64 { + self.f_bavail as u64 + } + fn total_fnodes(&self) -> u64 { + self.f_files as u64 + } + fn free_fnodes(&self) -> u64 { + self.f_ffree as u64 + } + #[cfg(any(target_os = "linux", target_os = "macos", 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")))] + fn fs_type(&self) -> i64 { + // FIXME: statvfs doesn't have an equivalent, so we need to do something else + unimplemented!() + } + + #[cfg(target_os = "linux")] + fn iosize(&self) -> u64 { + self.f_frsize as u64 + } + #[cfg(any(target_os = "macos", 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")))] + fn iosize(&self) -> u64 { + self.f_bsize as u64 + } + + // Linux, SunOS, HP-UX, 4.4BSD, FreeBSD have a system call statfs() that returns + // a struct statfs, containing a fsid_t f_fsid, where fsid_t is defined + // as struct { int val[2]; } + // + // 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"))] + fn fsid(&self) -> u64 { + let f_fsid: &[u32; 2] = unsafe { transmute(&self.f_fsid) }; + (f_fsid[0] as u64) << 32 | f_fsid[1] as u64 + } + #[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "linux")))] + fn fsid(&self) -> u64 { + self.f_fsid as u64 + } + + #[cfg(target_os = "linux")] + fn namelen(&self) -> u64 { + self.f_namelen as u64 + } + #[cfg(target_os = "macos")] + fn namelen(&self) -> u64 { + 1024 + } + #[cfg(target_os = "freebsd")] + fn namelen(&self) -> u64 { + self.f_namemax as u64 + } + // XXX: should everything just use statvfs? + #[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "linux")))] + fn namelen(&self) -> u64 { + self.f_namemax as u64 + } +} + +pub fn statfs>(path: P) -> Result +where + Vec: From

, +{ + match CString::new(path) { + Ok(p) => { + let mut buffer: Sstatfs = unsafe { mem::zeroed() }; + unsafe { + match statfs_fn(p.as_ptr(), &mut buffer) { + 0 => Ok(buffer), + _ => { + let errno = IOError::last_os_error().raw_os_error().unwrap_or(0); + Err(CString::from_raw(strerror(errno)) + .into_string() + .unwrap_or("Unknown Error".to_owned())) + } + } + } + } + Err(e) => Err(e.description().to_owned()), + } +} + +pub fn pretty_fstype<'a>(fstype: i64) -> Cow<'a, str> { + match fstype { + 0x61636673 => "acfs".into(), + 0xADF5 => "adfs".into(), + 0xADFF => "affs".into(), + 0x5346414F => "afs".into(), + 0x09041934 => "anon-inode FS".into(), + 0x61756673 => "aufs".into(), + 0x0187 => "autofs".into(), + 0x42465331 => "befs".into(), + 0x62646576 => "bdevfs".into(), + 0x1BADFACE => "bfs".into(), + 0xCAFE4A11 => "bpf_fs".into(), + 0x42494E4D => "binfmt_misc".into(), + 0x9123683E => "btrfs".into(), + 0x73727279 => "btrfs_test".into(), + 0x00C36400 => "ceph".into(), + 0x0027E0EB => "cgroupfs".into(), + 0xFF534D42 => "cifs".into(), + 0x73757245 => "coda".into(), + 0x012FF7B7 => "coh".into(), + 0x62656570 => "configfs".into(), + 0x28CD3D45 => "cramfs".into(), + 0x453DCD28 => "cramfs-wend".into(), + 0x64626720 => "debugfs".into(), + 0x1373 => "devfs".into(), + 0x1CD1 => "devpts".into(), + 0xF15F => "ecryptfs".into(), + 0xDE5E81E4 => "efivarfs".into(), + 0x00414A53 => "efs".into(), + 0x5DF5 => "exofs".into(), + 0x137D => "ext".into(), + 0xEF53 => "ext2/ext3".into(), + 0xEF51 => "ext2".into(), + 0xF2F52010 => "f2fs".into(), + 0x4006 => "fat".into(), + 0x19830326 => "fhgfs".into(), + 0x65735546 => "fuseblk".into(), + 0x65735543 => "fusectl".into(), + 0x0BAD1DEA => "futexfs".into(), + 0x01161970 => "gfs/gfs2".into(), + 0x47504653 => "gpfs".into(), + 0x4244 => "hfs".into(), + 0x482B => "hfs+".into(), + 0x4858 => "hfsx".into(), + 0x00C0FFEE => "hostfs".into(), + 0xF995E849 => "hpfs".into(), + 0x958458F6 => "hugetlbfs".into(), + 0x11307854 => "inodefs".into(), + 0x013111A8 => "ibrix".into(), + 0x2BAD1DEA => "inotifyfs".into(), + 0x9660 => "isofs".into(), + 0x4004 => "isofs".into(), + 0x4000 => "isofs".into(), + 0x07C0 => "jffs".into(), + 0x72B6 => "jffs2".into(), + 0x3153464A => "jfs".into(), + 0x6B414653 => "k-afs".into(), + 0xC97E8168 => "logfs".into(), + 0x0BD00BD0 => "lustre".into(), + 0x5346314D => "m1fs".into(), + 0x137F => "minix".into(), + 0x138F => "minix (30 char.)".into(), + 0x2468 => "minix v2".into(), + 0x2478 => "minix v2 (30 char.)".into(), + 0x4D5A => "minix3".into(), + 0x19800202 => "mqueue".into(), + 0x4D44 => "msdos".into(), + 0x564C => "novell".into(), + 0x6969 => "nfs".into(), + 0x6E667364 => "nfsd".into(), + 0x3434 => "nilfs".into(), + 0x6E736673 => "nsfs".into(), + 0x5346544E => "ntfs".into(), + 0x9FA1 => "openprom".into(), + 0x7461636F => "ocfs2".into(), + 0x794C7630 => "overlayfs".into(), + 0xAAD7AAEA => "panfs".into(), + 0x50495045 => "pipefs".into(), + 0x7C7C6673 => "prl_fs".into(), + 0x9FA0 => "proc".into(), + 0x6165676C => "pstorefs".into(), + 0x002F => "qnx4".into(), + 0x68191122 => "qnx6".into(), + 0x858458F6 => "ramfs".into(), + 0x52654973 => "reiserfs".into(), + 0x7275 => "romfs".into(), + 0x67596969 => "rpc_pipefs".into(), + 0x73636673 => "securityfs".into(), + 0xF97CFF8C => "selinux".into(), + 0x43415D53 => "smackfs".into(), + 0x517B => "smb".into(), + 0xFE534D42 => "smb2".into(), + 0xBEEFDEAD => "snfs".into(), + 0x534F434B => "sockfs".into(), + 0x73717368 => "squashfs".into(), + 0x62656572 => "sysfs".into(), + 0x012FF7B6 => "sysv2".into(), + 0x012FF7B5 => "sysv4".into(), + 0x01021994 => "tmpfs".into(), + 0x74726163 => "tracefs".into(), + 0x24051905 => "ubifs".into(), + 0x15013346 => "udf".into(), + 0x00011954 => "ufs".into(), + 0x54190100 => "ufs".into(), + 0x9FA2 => "usbdevfs".into(), + 0x01021997 => "v9fs".into(), + 0xBACBACBC => "vmhgfs".into(), + 0xA501FCF5 => "vxfs".into(), + 0x565A4653 => "vzfs".into(), + 0x53464846 => "wslfs".into(), + 0xABBA1974 => "xenfs".into(), + 0x012FF7B4 => "xenix".into(), + 0x58465342 => "xfs".into(), + 0x012FD16D => "xia".into(), + 0x2FC12FC1 => "zfs".into(), + other => format!("UNKNOWN ({:#x})", other).into(), + } +} diff --git a/coreutils/src/stat/stat.rs b/coreutils/src/stat/stat.rs new file mode 100644 index 000000000..7b65c7a24 --- /dev/null +++ b/coreutils/src/stat/stat.rs @@ -0,0 +1,999 @@ +#![crate_name = "uu_stat"] + +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +extern crate getopts; +use getopts::Options; + +#[macro_use] +mod fsext; +pub use fsext::*; + +#[macro_use] +extern crate uucore; +use uucore::entries; + +use std::{cmp, fs, iter}; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::borrow::Cow; +use std::os::unix::fs::{FileTypeExt, MetadataExt}; +use std::path::Path; +use std::convert::AsRef; + +macro_rules! check_bound { + ($str: ident, $bound:expr, $beg: expr, $end: expr) => ( + if $end >= $bound { + return Err(format!("‘{}’: invalid directive", &$str[$beg..$end])); + } + + ) +} +macro_rules! fill_string { + ($str: ident, $c: expr, $cnt: expr) => ( + iter::repeat($c).take($cnt).map(|c| $str.push(c)).all(|_| true) + ) +} +macro_rules! extend_digits { + ($str: expr, $min: expr) => ( + if $min > $str.len() { + let mut pad = String::with_capacity($min); + fill_string!(pad, '0', $min - $str.len()); + pad.push_str($str); + pad.into() + } else { + $str.into() + } + ) +} +macro_rules! pad_and_print { + ($result: ident, $str: ident, $left: expr, $width: expr, $padding: expr) => ( + if $str.len() < $width { + if $left { + $result.push_str($str.as_ref()); + fill_string!($result, $padding, $width - $str.len()); + } else { + fill_string!($result, $padding, $width - $str.len()); + $result.push_str($str.as_ref()); + } + } else { + $result.push_str($str.as_ref()); + } + print!("{}", $result); + ) +} +macro_rules! print_adjusted { + ($str: ident, $left: expr, $width: expr, $padding: expr) => { + let field_width = cmp::max($width, $str.len()); + let mut result = String::with_capacity(field_width); + pad_and_print!(result, $str, $left, field_width, $padding); + }; + ($str: ident, $left: expr, $need_prefix: expr, $prefix: expr, $width: expr, $padding: expr) => { + let mut field_width = cmp::max($width, $str.len()); + let mut result = String::with_capacity(field_width + $prefix.len()); + if $need_prefix { + result.push_str($prefix); + field_width -= $prefix.len(); + } + pad_and_print!(result, $str, $left, field_width, $padding); + } +} + +static NAME: &'static str = "stat"; +static VERSION: &'static str = env!("CARGO_PKG_VERSION"); + +const MOUNT_INFO: &'static str = "/etc/mtab"; +pub const F_ALTER: u8 = 1; +pub const F_ZERO: u8 = 1 << 1; +pub const F_LEFT: u8 = 1 << 2; +pub const F_SPACE: u8 = 1 << 3; +pub const F_SIGN: u8 = 1 << 4; +// unused at present +pub const F_GROUP: u8 = 1 << 5; + +#[derive(Debug, PartialEq)] +pub enum OutputType { + Str, + Integer, + Unsigned, + UnsignedHex, + UnsignedOct, + Unknown, +} + +#[derive(Debug, PartialEq)] +pub enum Token { + Char(char), + Directive { + flag: u8, + width: usize, + precision: i32, + format: char, + }, +} + +pub trait ScanUtil { + fn scan_num(&self) -> Option<(F, usize)> + where + F: std::str::FromStr; + fn scan_char(&self, radix: u32) -> Option<(char, usize)>; +} + +impl ScanUtil for str { + fn scan_num(&self) -> Option<(F, usize)> + where + F: std::str::FromStr, + { + let mut chars = self.chars(); + let mut i = 0; + match chars.next() { + Some('-') | Some('+') | Some('0'...'9') => i += 1, + _ => return None, + } + while let Some(c) = chars.next() { + match c { + '0'...'9' => i += 1, + _ => break, + } + } + if i > 0 { + F::from_str(&self[..i]).ok().map(|x| (x, i)) + } else { + None + } + } + + fn scan_char(&self, radix: u32) -> Option<(char, usize)> { + let count = match radix { + 8 => 3_usize, + 16 => 2, + _ => return None, + }; + let mut chars = self.chars().enumerate(); + let mut res = 0_u32; + let mut offset = 0_usize; + while let Some((i, c)) = chars.next() { + if i >= count { + break; + } + match c.to_digit(radix) { + Some(digit) => { + let tmp = res * radix + digit; + if tmp < 256 { + res = tmp; + } else { + break; + } + } + None => break, + } + offset = i + 1; + } + if offset > 0 { + Some((res as u8 as char, offset)) + } else { + None + } + } +} + +pub fn group_num<'a>(s: &'a str) -> Cow<'a, str> { + assert!(s.chars().all(char::is_numeric)); + if s.len() < 4 { + return s.into(); + } + let mut res = String::with_capacity((s.len() - 1) / 3); + let mut alone = (s.len() - 1) % 3 + 1; + res.push_str(&s[..alone]); + while alone != s.len() { + res.push(','); + res.push_str(&s[alone..alone + 3]); + alone += 3; + } + res.into() +} + +pub struct Stater { + follow: bool, + showfs: bool, + from_user: bool, + files: Vec, + mount_list: Option>, + default_tokens: Vec, + default_dev_tokens: Vec, +} + +fn print_it(arg: &str, otype: OutputType, flag: u8, width: usize, precision: i32) { + // If the precision is given as just '.', the precision is taken to be zero. + // A negative precision is taken as if the precision were omitted. + // This gives the minimum number of digits to appear for d, i, o, u, x, and X conversions, + // the maximum number of characters to be printed from a string for s and S conversions. + + // # + // The value should be converted to an "alternate form". + // For o conversions, the first character of the output string is made zero (by prefixing a 0 if it was not zero already). + // For x and X conversions, a nonzero result has the string "0x" (or "0X" for X conversions) prepended to it. + + // 0 + // The value should be zero padded. + // For d, i, o, u, x, X, a, A, e, E, f, F, g, and G conversions, the converted value is padded on the left with zeros rather than blanks. + // If the 0 and - flags both appear, the 0 flag is ignored. + // If a precision is given with a numeric conversion (d, i, o, u, x, and X), the 0 flag is ignored. + // For other conversions, the behavior is undefined. + + // - + // The converted value is to be left adjusted on the field boundary. (The default is right justification.) + // The converted value is padded on the right with blanks, rather than on the left with blanks or zeros. + // A - overrides a 0 if both are given. + + // ' ' (a space) + // A blank should be left before a positive number (or empty string) produced by a signed conversion. + + // + + // A sign (+ or -) should always be placed before a number produced by a signed conversion. + // By default, a sign is used only for negative numbers. + // A + overrides a space if both are used. + + if otype == OutputType::Unknown { + return print!("?"); + } + + let left_align = has!(flag, F_LEFT); + let padding_char = if has!(flag, F_ZERO) && !left_align && precision == -1 { + '0' + } else { + ' ' + }; + + let has_sign = has!(flag, F_SIGN) || has!(flag, F_SPACE); + + let should_alter = has!(flag, F_ALTER); + let prefix = match otype { + OutputType::UnsignedOct => "0", + OutputType::UnsignedHex => "0x", + OutputType::Integer => { + if has!(flag, F_SIGN) { + "+" + } else { + " " + } + } + _ => "", + }; + + match otype { + OutputType::Str => { + let limit = cmp::min(precision, arg.len() as i32); + let s: &str = if limit >= 0 { + &arg[..limit as usize] + } else { + arg + }; + print_adjusted!(s, left_align, width, ' '); + } + OutputType::Integer => { + let arg = if has!(flag, F_GROUP) { + group_num(arg) + } else { + Cow::Borrowed(arg) + }; + let min_digits = cmp::max(precision, arg.len() as i32) as usize; + let extended: Cow = extend_digits!(arg.as_ref(), min_digits); + print_adjusted!(extended, left_align, has_sign, prefix, width, padding_char); + } + OutputType::Unsigned => { + let arg = if has!(flag, F_GROUP) { + group_num(arg) + } else { + Cow::Borrowed(arg) + }; + let min_digits = cmp::max(precision, arg.len() as i32) as usize; + let extended: Cow = extend_digits!(arg.as_ref(), min_digits); + print_adjusted!(extended, left_align, width, padding_char); + } + OutputType::UnsignedOct => { + let min_digits = cmp::max(precision, arg.len() as i32) as usize; + let extended: Cow = extend_digits!(arg, min_digits); + print_adjusted!( + extended, + left_align, + should_alter, + prefix, + width, + padding_char + ); + } + OutputType::UnsignedHex => { + let min_digits = cmp::max(precision, arg.len() as i32) as usize; + let extended: Cow = extend_digits!(arg, min_digits); + print_adjusted!( + extended, + left_align, + should_alter, + prefix, + width, + padding_char + ); + } + _ => unreachable!(), + } +} + +impl Stater { + pub fn generate_tokens(fmtstr: &str, use_printf: bool) -> Result, String> { + let mut tokens = Vec::new(); + let bound = fmtstr.len(); + let chars = fmtstr.chars().collect::>(); + + let mut i = 0_usize; + while i < bound { + match chars[i] { + '%' => { + let old = i; + + i += 1; + if i >= bound { + tokens.push(Token::Char('%')); + continue; + } + if chars[i] == '%' { + tokens.push(Token::Char('%')); + i += 1; + continue; + } + + let mut flag: u8 = 0; + + while i < bound { + match chars[i] { + '#' => flag |= F_ALTER, + '0' => flag |= F_ZERO, + '-' => flag |= F_LEFT, + ' ' => flag |= F_SPACE, + '+' => flag |= F_SIGN, + '\'' => flag |= F_GROUP, + 'I' => unimplemented!(), + _ => break, + } + i += 1; + } + check_bound!(fmtstr, bound, old, i); + + let mut width = 0_usize; + let mut precision = -1_i32; + let mut j = i; + + match fmtstr[j..].scan_num::() { + Some((field_width, offset)) => { + width = field_width; + j += offset; + } + None => (), + } + check_bound!(fmtstr, bound, old, j); + + if chars[j] == '.' { + j += 1; + check_bound!(fmtstr, bound, old, j); + + match fmtstr[j..].scan_num::() { + Some((prec, offset)) => { + if prec >= 0 { + precision = prec; + } + j += offset; + } + None => precision = 0, + } + check_bound!(fmtstr, bound, old, j); + } + + i = j; + tokens.push(Token::Directive { + width: width, + flag: flag, + precision: precision, + format: chars[i], + }) + } + '\\' => { + if !use_printf { + tokens.push(Token::Char('\\')); + } else { + i += 1; + if i >= bound { + show_warning!("backslash at end of format"); + tokens.push(Token::Char('\\')); + continue; + } + match chars[i] { + 'x' if i + 1 < bound => { + if let Some((c, offset)) = fmtstr[i + 1..].scan_char(16) { + tokens.push(Token::Char(c)); + i += offset; + } else { + show_warning!("unrecognized escape '\\x'"); + tokens.push(Token::Char('x')); + } + } + '0'...'7' => { + let (c, offset) = fmtstr[i..].scan_char(8).unwrap(); + tokens.push(Token::Char(c)); + i += offset - 1; + } + '"' => tokens.push(Token::Char('"')), + '\\' => tokens.push(Token::Char('\\')), + 'a' => tokens.push(Token::Char('\x07')), + 'b' => tokens.push(Token::Char('\x08')), + 'e' => tokens.push(Token::Char('\x1B')), + 'f' => tokens.push(Token::Char('\x0C')), + 'n' => tokens.push(Token::Char('\n')), + 'r' => tokens.push(Token::Char('\r')), + 'v' => tokens.push(Token::Char('\x0B')), + c => { + show_warning!("unrecognized escape '\\{}'", c); + tokens.push(Token::Char(c)); + } + } + } + } + + c => tokens.push(Token::Char(c)), + } + i += 1; + } + if !use_printf && !fmtstr.ends_with('\n') { + tokens.push(Token::Char('\n')); + } + Ok(tokens) + } + + fn new(matches: getopts::Matches) -> Result { + let fmtstr = if matches.opt_present("printf") { + matches.opt_str("printf").expect("Invalid format string") + } else { + matches.opt_str("format").unwrap_or("".to_owned()) + }; + + let use_printf = matches.opt_present("printf"); + let terse = matches.opt_present("terse"); + let showfs = matches.opt_present("file-system"); + + let default_tokens = if fmtstr.is_empty() { + Stater::generate_tokens(&Stater::default_fmt(showfs, terse, false), use_printf).unwrap() + } else { + Stater::generate_tokens(&fmtstr, use_printf)? + }; + let default_dev_tokens = + Stater::generate_tokens(&Stater::default_fmt(showfs, terse, true), use_printf).unwrap(); + + let mount_list = if showfs { + // mount points aren't displayed when showing filesystem information + None + } else { + let reader = BufReader::new( + File::open(MOUNT_INFO).expect(&format!("Failed to read {}", MOUNT_INFO)), + ); + let mut mount_list = reader + .lines() + .filter_map(|s| s.ok()) + .filter_map(|line| line.split_whitespace().nth(1).map(|s| s.to_owned())) + .collect::>(); + // Reverse sort. The longer comes first. + mount_list.sort_by(|a, b| b.cmp(a)); + Some(mount_list) + }; + + Ok(Stater { + follow: matches.opt_present("dereference"), + showfs: showfs, + from_user: !fmtstr.is_empty(), + files: matches.free, + default_tokens: default_tokens, + default_dev_tokens: default_dev_tokens, + mount_list: mount_list, + }) + } + + fn find_mount_point>(&self, p: P) -> Option { + let path = match p.as_ref().canonicalize() { + Ok(s) => s, + Err(_) => return None, + }; + if let Some(ref mount_list) = self.mount_list { + for root in mount_list.into_iter() { + if path.starts_with(root) { + return Some(root.clone()); + } + } + } + None + } + + fn exec(&self) -> i32 { + let mut ret = 0; + for f in &self.files { + ret |= self.do_stat(f.as_str()); + } + ret + } + + fn do_stat(&self, file: &str) -> i32 { + if !self.showfs { + let result = if self.follow { + fs::metadata(file) + } else { + fs::symlink_metadata(file) + }; + match result { + Ok(meta) => { + let ftype = meta.file_type(); + let tokens = + if self.from_user || !(ftype.is_char_device() || ftype.is_block_device()) { + &self.default_tokens + } else { + &self.default_dev_tokens + }; + + for t in tokens.into_iter() { + match t { + &Token::Char(c) => print!("{}", c), + &Token::Directive { + flag, + width, + precision, + format, + } => { + let arg: String; + let otype: OutputType; + + match format { + // access rights in octal + 'a' => { + arg = format!("{:o}", 0o7777 & meta.mode()); + otype = OutputType::UnsignedOct; + } + // access rights in human readable form + 'A' => { + arg = pretty_access(meta.mode() as mode_t); + otype = OutputType::Str; + } + // number of blocks allocated (see %B) + 'b' => { + arg = format!("{}", meta.blocks()); + otype = OutputType::Unsigned; + } + + // the size in bytes of each block reported by %b + // FIXME: blocksize differs on various platform + // See coreutils/gnulib/lib/stat-size.h ST_NBLOCKSIZE + 'B' => { + // the size in bytes of each block reported by %b + arg = format!("{}", 512); + otype = OutputType::Unsigned; + } + + // device number in decimal + 'd' => { + arg = format!("{}", meta.dev()); + otype = OutputType::Unsigned; + } + // device number in hex + 'D' => { + arg = format!("{:x}", meta.dev()); + otype = OutputType::UnsignedHex; + } + // raw mode in hex + 'f' => { + arg = format!("{:x}", meta.mode()); + otype = OutputType::UnsignedHex; + } + // file type + 'F' => { + arg = pretty_filetype(meta.mode() as mode_t, meta.len()) + .to_owned(); + otype = OutputType::Str; + } + // group ID of owner + 'g' => { + arg = format!("{}", meta.gid()); + otype = OutputType::Unsigned; + } + // group name of owner + 'G' => { + arg = entries::gid2grp(meta.gid()) + .unwrap_or("UNKNOWN".to_owned()); + otype = OutputType::Str; + } + // number of hard links + 'h' => { + arg = format!("{}", meta.nlink()); + otype = OutputType::Unsigned; + } + // inode number + 'i' => { + arg = format!("{}", meta.ino()); + otype = OutputType::Unsigned; + } + + // mount point + 'm' => { + arg = self.find_mount_point(file).unwrap(); + otype = OutputType::Str; + } + + // file name + 'n' => { + arg = file.to_owned(); + otype = OutputType::Str; + } + // quoted file name with dereference if symbolic link + 'N' => { + if ftype.is_symlink() { + let dst = match fs::read_link(file) { + Ok(path) => path, + Err(e) => { + println!("{}", e); + return 1; + } + }; + arg = format!( + "`{}' -> `{}'", + file, + dst.to_string_lossy() + ); + } else { + arg = format!("`{}'", file); + } + otype = OutputType::Str; + } + // optimal I/O transfer size hint + 'o' => { + arg = format!("{}", meta.blksize()); + otype = OutputType::Unsigned; + } + // total size, in bytes + 's' => { + arg = format!("{}", meta.len()); + otype = OutputType::Integer; + } + // major device type in hex, for character/block device special + // files + 't' => { + arg = format!("{:x}", meta.rdev() >> 8); + otype = OutputType::UnsignedHex; + } + // minor device type in hex, for character/block device special + // files + 'T' => { + arg = format!("{:x}", meta.rdev() & 0xff); + otype = OutputType::UnsignedHex; + } + // user ID of owner + 'u' => { + arg = format!("{}", meta.uid()); + otype = OutputType::Unsigned; + } + // user name of owner + 'U' => { + arg = entries::uid2usr(meta.uid()) + .unwrap_or("UNKNOWN".to_owned()); + otype = OutputType::Str; + } + + // time of file birth, human-readable; - if unknown + 'w' => { + arg = meta.pretty_birth(); + otype = OutputType::Str; + } + + // time of file birth, seconds since Epoch; 0 if unknown + 'W' => { + arg = meta.birth(); + otype = OutputType::Integer; + } + + // time of last access, human-readable + 'x' => { + arg = pretty_time(meta.atime(), meta.atime_nsec()); + otype = OutputType::Str; + } + // time of last access, seconds since Epoch + 'X' => { + arg = format!("{}", meta.atime()); + otype = OutputType::Integer; + } + // time of last data modification, human-readable + 'y' => { + arg = pretty_time(meta.mtime(), meta.mtime_nsec()); + otype = OutputType::Str; + } + // time of last data modification, seconds since Epoch + 'Y' => { + arg = format!("{}", meta.mtime()); + otype = OutputType::Str; + } + // time of last status change, human-readable + 'z' => { + arg = pretty_time(meta.ctime(), meta.ctime_nsec()); + otype = OutputType::Str; + } + // time of last status change, seconds since Epoch + 'Z' => { + arg = format!("{}", meta.ctime()); + otype = OutputType::Integer; + } + + _ => { + arg = "?".to_owned(); + otype = OutputType::Unknown; + } + } + print_it(&arg, otype, flag, width, precision); + } + } + } + } + Err(e) => { + show_info!("cannot stat '{}': {}", file, e); + return 1; + } + } + } else { + match statfs(file) { + Ok(meta) => { + let tokens = &self.default_tokens; + + for t in tokens.into_iter() { + match t { + &Token::Char(c) => print!("{}", c), + &Token::Directive { + flag, + width, + precision, + format, + } => { + let arg: String; + let otype: OutputType; + match format { + // free blocks available to non-superuser + 'a' => { + arg = format!("{}", meta.avail_blocks()); + otype = OutputType::Integer; + } + // total data blocks in file system + 'b' => { + arg = format!("{}", meta.total_blocks()); + otype = OutputType::Integer; + } + // total file nodes in file system + 'c' => { + arg = format!("{}", meta.total_fnodes()); + otype = OutputType::Unsigned; + } + // free file nodes in file system + 'd' => { + arg = format!("{}", meta.free_fnodes()); + otype = OutputType::Integer; + } + // free blocks in file system + 'f' => { + arg = format!("{}", meta.free_blocks()); + otype = OutputType::Integer; + } + // file system ID in hex + 'i' => { + arg = format!("{:x}", meta.fsid()); + otype = OutputType::UnsignedHex; + } + // maximum length of filenames + 'l' => { + arg = format!("{}", meta.namelen()); + otype = OutputType::Unsigned; + } + // file name + 'n' => { + arg = file.to_owned(); + otype = OutputType::Str; + } + // block size (for faster transfers) + 's' => { + arg = format!("{}", meta.iosize()); + otype = OutputType::Unsigned; + } + // fundamental block size (for block counts) + 'S' => { + arg = format!("{}", meta.blksize()); + otype = OutputType::Unsigned; + } + // file system type in hex + 't' => { + arg = format!("{:x}", meta.fs_type()); + otype = OutputType::UnsignedHex; + } + // file system type in human readable form + 'T' => { + arg = pretty_fstype(meta.fs_type()).into_owned(); + otype = OutputType::Str; + } + _ => { + arg = "?".to_owned(); + otype = OutputType::Unknown; + } + } + + print_it(&arg, otype, flag, width, precision); + } + } + } + } + Err(e) => { + show_info!("cannot read file system information for '{}': {}", file, e); + return 1; + } + } + } + 0 + } + + // taken from coreutils/src/stat.c + fn default_fmt(showfs: bool, terse: bool, dev: bool) -> String { + // SELinux related format is *ignored* + + let mut fmtstr = String::with_capacity(36); + if showfs { + if terse { + fmtstr.push_str("%n %i %l %t %s %S %b %f %a %c %d\n"); + } else { + fmtstr.push_str( + " File: \"%n\"\n ID: %-8i Namelen: %-7l Type: %T\nBlock \ + size: %-10s Fundamental block size: %S\nBlocks: Total: %-10b \ + Free: %-10f Available: %a\nInodes: Total: %-10c Free: %d\n", + ); + } + } else if terse { + fmtstr.push_str("%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o\n"); + } else { + fmtstr.push_str(" File: %N\n Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"); + if dev { + fmtstr.push_str("Device: %Dh/%dd\tInode: %-10i Links: %-5h Device type: %t,%T\n"); + } else { + fmtstr.push_str("Device: %Dh/%dd\tInode: %-10i Links: %h\n"); + } + fmtstr.push_str("Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n"); + fmtstr.push_str("Access: %x\nModify: %y\nChange: %z\n Birth: %w\n"); + } + fmtstr + } +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + opts.optflag("h", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + opts.optflag("L", "dereference", "follow links"); + opts.optflag( + "f", + "file-system", + "display file system status instead of file status", + ); + opts.optflag("t", "terse", "print the information in terse form"); + + // Omit the unused description as they are too long + opts.optopt("c", "format", "", "FORMAT"); + opts.optopt("", "printf", "", "FORMAT"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => { + disp_err!("{}", f); + return 1; + } + }; + + if matches.opt_present("help") { + return help(); + } else if matches.opt_present("version") { + return version(); + } + + if matches.free.is_empty() { + disp_err!("missing operand"); + return 1; + } + + match Stater::new(matches) { + Ok(stater) => stater.exec(), + Err(e) => { + show_info!("{}", e); + 1 + } + } +} + +fn version() -> i32 { + println!("{} {}", NAME, VERSION); + 0 +} + +fn help() -> i32 { + let msg = format!( + r#"Usage: {} [OPTION]... FILE... +Display file or file system status. + +Mandatory arguments to long options are mandatory for short options too. + -L, --dereference follow links + -f, --file-system display file system status instead of file status + -c --format=FORMAT use the specified FORMAT instead of the default; + output a newline after each use of FORMAT + --printf=FORMAT like --format, but interpret backslash escapes, + and do not output a mandatory trailing newline; + if you want a newline, include \n in FORMAT + -t, --terse print the information in terse form + --help display this help and exit + --version output version information and exit + +The valid format sequences for files (without --file-system): + + %a access rights in octal (note '#' and '0' printf flags) + %A access rights in human readable form + %b number of blocks allocated (see %B) + %B the size in bytes of each block reported by %b + %C SELinux security context string + %d device number in decimal + %D device number in hex + %f raw mode in hex + %F file type + %g group ID of owner + %G group name of owner + %h number of hard links + %i inode number + %m mount point + %n file name + %N quoted file name with dereference if symbolic link + %o optimal I/O transfer size hint + %s total size, in bytes + %t major device type in hex, for character/block device special files + %T minor device type in hex, for character/block device special files + %u user ID of owner + %U user name of owner + %w time of file birth, human-readable; - if unknown + %W time of file birth, seconds since Epoch; 0 if unknown + %x time of last access, human-readable + %X time of last access, seconds since Epoch + %y time of last data modification, human-readable + %Y time of last data modification, seconds since Epoch + %z time of last status change, human-readable + %Z time of last status change, seconds since Epoch + +Valid format sequences for file systems: + + %a free blocks available to non-superuser + %b total data blocks in file system + %c total file nodes in file system + %d free file nodes in file system + %f free blocks in file system + %i file system ID in hex + %l maximum length of filenames + %n file name + %s block size (for faster transfers) + %S fundamental block size (for block counts) + %t file system type in hex + %T file system type in human readable form + +NOTE: your shell may have its own version of stat, which usually supersedes +the version described here. Please refer to your shell's documentation +for details about the options it supports."#, + NAME + ); + println!("{}", msg); + 0 +} diff --git a/coreutils/src/stat/test_stat.rs b/coreutils/src/stat/test_stat.rs new file mode 100644 index 000000000..816ad680a --- /dev/null +++ b/coreutils/src/stat/test_stat.rs @@ -0,0 +1,70 @@ +pub use super::*; + +#[test] +fn test_scanutil() { + assert_eq!(Some((-5, 2)), "-5zxc".scan_num::()); + assert_eq!(Some((51, 2)), "51zxc".scan_num::()); + assert_eq!(Some((192, 4)), "+192zxc".scan_num::()); + assert_eq!(None, "z192zxc".scan_num::()); + + assert_eq!(Some(('a', 3)), "141zxc".scan_char(8)); + assert_eq!(Some(('\n', 2)), "12qzxc".scan_char(8)); + assert_eq!(Some(('\r', 1)), "dqzxc".scan_char(16)); + assert_eq!(None, "z2qzxc".scan_char(8)); +} + +#[cfg(test)] +mod test_generate_tokens { + use super::*; + + #[test] + fn test_normal_format() { + let s = "%10.2ac%-5.w\n"; + let expected = vec![Token::Directive { + flag: 0, + width: 10, + precision: 2, + format: 'a', + }, + Token::Char('c'), + Token::Directive { + flag: F_LEFT, + width: 5, + precision: 0, + format: 'w', + }, + Token::Char('\n')]; + assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap()); + } + + #[test] + fn test_printf_format() { + let s = "%-# 15a\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.-23w\\x12\\167\\132\\112\\n"; + let expected = vec![Token::Directive { + flag: F_LEFT | F_ALTER | F_SPACE, + width: 15, + precision: -1, + format: 'a', + }, + Token::Char('\r'), + Token::Char('"'), + Token::Char('\\'), + Token::Char('\x07'), + Token::Char('\x08'), + Token::Char('\x1B'), + Token::Char('\x0C'), + Token::Char('\x0B'), + Token::Directive { + flag: F_SIGN | F_ZERO, + width: 20, + precision: -1, + format: 'w', + }, + Token::Char('\x12'), + Token::Char('w'), + Token::Char('Z'), + Token::Char('J'), + Token::Char('\n')]; + assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap()); + } +} diff --git a/coreutils/src/stdbuf/Cargo.toml b/coreutils/src/stdbuf/Cargo.toml new file mode 100644 index 000000000..d31e175b1 --- /dev/null +++ b/coreutils/src/stdbuf/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "stdbuf" +version = "0.0.1" +authors = [] +build = "build.rs" + +[lib] +name = "uu_stdbuf" +path = "stdbuf.rs" + +[dependencies] +getopts = "0.2.18" +tempdir = "0.3.7" +uucore = "0.0.1" + +[build-dependencies] +libstdbuf = { path="libstdbuf" } + +[[bin]] +name = "stdbuf" +path = "../../uumain.rs" diff --git a/coreutils/src/stdbuf/build.rs b/coreutils/src/stdbuf/build.rs new file mode 100644 index 000000000..256592301 --- /dev/null +++ b/coreutils/src/stdbuf/build.rs @@ -0,0 +1,39 @@ +use std::env; +use std::fs; +use std::path::Path; + +#[path = "../../mkmain.rs"] +mod mkmain; + +#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows")))] +mod platform { + pub const DYLIB_EXT: &str = ".so"; +} + +#[cfg(any(target_os = "macos", target_os = "ios"))] +mod platform { + pub const DYLIB_EXT: &str = ".dylib"; +} + +#[cfg(target_os = "windows")] +mod platform { + pub const DYLIB_EXT: &str = ".dll"; +} + +fn main() { + mkmain::main(); + + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("Could not find manifest dir"); + let profile = env::var("PROFILE").expect("Could not determine profile"); + + let out_dir = env::var("OUT_DIR").unwrap(); + let libstdbuf = format!( + "{}/../../{}/{}/deps/liblibstdbuf{}", + manifest_dir, + env::var("CARGO_TARGET_DIR").unwrap_or("target".to_string()), + profile, + platform::DYLIB_EXT + ); + + fs::copy(libstdbuf, Path::new(&out_dir).join("libstdbuf.so")).unwrap(); +} diff --git a/coreutils/src/stdbuf/libstdbuf/Cargo.toml b/coreutils/src/stdbuf/libstdbuf/Cargo.toml new file mode 100644 index 000000000..3ee7ef0bd --- /dev/null +++ b/coreutils/src/stdbuf/libstdbuf/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "libstdbuf" +version = "0.0.1" +authors = [] + +[lib] +name = "libstdbuf" +path = "libstdbuf.rs" +# XXX: the rlib is just to prevent Cargo from spitting out a warning +crate-type = ["cdylib", "rlib"] + +[dependencies] +uucore = "0.0.1" +libc = "0.2" +cpp = "0.4" + +[build-dependencies] +cpp_build = "0.4" diff --git a/coreutils/src/stdbuf/libstdbuf/build.rs b/coreutils/src/stdbuf/libstdbuf/build.rs new file mode 100644 index 000000000..c4bb007b9 --- /dev/null +++ b/coreutils/src/stdbuf/libstdbuf/build.rs @@ -0,0 +1,7 @@ +extern crate cpp_build; + +use cpp_build::Config; + +fn main() { + Config::new().pic(true).build("libstdbuf.rs"); +} diff --git a/coreutils/src/stdbuf/libstdbuf/libstdbuf.rs b/coreutils/src/stdbuf/libstdbuf/libstdbuf.rs new file mode 100644 index 000000000..1e290d005 --- /dev/null +++ b/coreutils/src/stdbuf/libstdbuf/libstdbuf.rs @@ -0,0 +1,69 @@ +#[macro_use] +extern crate cpp; +extern crate libc; + +#[macro_use] +extern crate uucore; + +use libc::{c_char, c_int, size_t, FILE, _IOFBF, _IOLBF, _IONBF}; +use std::env; +use std::ptr; + +cpp!{{ + #include + + extern "C" { + void __stdbuf(void); + + void __attribute((constructor)) + __stdbuf_init(void) { + __stdbuf(); + } + + FILE *__stdbuf_get_stdin() { return stdin; } + FILE *__stdbuf_get_stdout() { return stdout; } + FILE *__stdbuf_get_stderr() { return stderr; } + } +}} + +extern "C" { + fn __stdbuf_get_stdin() -> *mut FILE; + fn __stdbuf_get_stdout() -> *mut FILE; + fn __stdbuf_get_stderr() -> *mut FILE; +} + +fn set_buffer(stream: *mut FILE, value: &str) { + let (mode, size): (c_int, size_t) = match value { + "0" => (_IONBF, 0 as size_t), + "L" => (_IOLBF, 0 as size_t), + input => { + let buff_size: usize = match input.parse() { + Ok(num) => num, + Err(e) => crash!(1, "incorrect size of buffer!: {}", e), + }; + (_IOFBF, buff_size as size_t) + } + }; + let res: c_int; + unsafe { + let buffer: *mut c_char = ptr::null_mut(); + assert!(buffer.is_null()); + res = libc::setvbuf(stream, buffer, mode, size); + } + if res != 0 { + crash!(res, "error while calling setvbuf!"); + } +} + +#[no_mangle] +pub unsafe extern "C" fn __stdbuf() { + if let Ok(val) = env::var("_STDBUF_E") { + set_buffer(__stdbuf_get_stderr(), &val); + } + if let Ok(val) = env::var("_STDBUF_I") { + set_buffer(__stdbuf_get_stdin(), &val); + } + if let Ok(val) = env::var("_STDBUF_O") { + set_buffer(__stdbuf_get_stdout(), &val); + } +} diff --git a/coreutils/src/stdbuf/stdbuf.rs b/coreutils/src/stdbuf/stdbuf.rs new file mode 100644 index 000000000..1b58ad5a2 --- /dev/null +++ b/coreutils/src/stdbuf/stdbuf.rs @@ -0,0 +1,287 @@ +#![crate_name = "uu_stdbuf"] + +/* +* This file is part of the uutils coreutils package. +* +* (c) Dorota Kapturkiewicz +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +extern crate getopts; +extern crate tempdir; + +#[macro_use] +extern crate uucore; + +use getopts::{Matches, Options}; +use tempdir::TempDir; +use std::fs::File; +use std::io::{self, Write}; +use std::os::unix::process::ExitStatusExt; +use std::path::PathBuf; +use std::process::Command; + +static NAME: &str = "stdbuf"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.so")); + +enum BufferType { + Default, + Line, + Size(u64), +} + +struct ProgramOptions { + stdin: BufferType, + stdout: BufferType, + stderr: BufferType, +} + +enum ErrMsg { + Retry, + Fatal, +} + +enum OkMsg { + Buffering, + Help, + Version, +} + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "dragonflybsd"))] +fn preload_strings() -> (&'static str, &'static str) { + ("LD_PRELOAD", "so") +} + +#[cfg(target_os = "macos")] +fn preload_strings() -> (&'static str, &'static str) { + ("DYLD_LIBRARY_PATH", "dylib") +} + +#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "dragonflybsd", target_os = "macos")))] +fn preload_strings() -> (&'static str, &'static str) { + crash!(1, "Command not supported for this operating system!") +} + +fn print_version() { + println!("{} {}", NAME, VERSION); +} + +fn print_usage(opts: &Options) { + let brief = "Run COMMAND, with modified buffering operations for its standard streams\n \ + Mandatory arguments to long options are mandatory for short options too."; + let explanation = + "If MODE is 'L' the corresponding stream will be line buffered.\n \ + This option is invalid with standard input.\n\n \ + If MODE is '0' the corresponding stream will be unbuffered.\n\n \ + Otherwise MODE is a number which may be followed by one of the following:\n\n \ + KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n \ + In this case the corresponding stream will be fully buffered with the buffer size set to \ + MODE bytes.\n\n \ + NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for e.g.) then \ + that will override corresponding settings changed by 'stdbuf'.\n \ + Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O, \ + and are thus unaffected by 'stdbuf' settings.\n"; + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage: stdbuf OPTION... COMMAND"); + println!(""); + println!("{}\n{}", opts.usage(brief), explanation); +} + +fn parse_size(size: &str) -> Option { + let ext = size.trim_start_matches(|c: char| c.is_digit(10)); + let num = size.trim_end_matches(|c: char| c.is_alphabetic()); + let mut recovered = num.to_owned(); + recovered.push_str(ext); + if recovered != size { + return None; + } + let buf_size: u64 = match num.parse().ok() { + Some(m) => m, + None => return None, + }; + let (power, base): (u32, u64) = match ext { + "" => (0, 0), + "KB" => (1, 1024), + "K" => (1, 1000), + "MB" => (2, 1024), + "M" => (2, 1000), + "GB" => (3, 1024), + "G" => (3, 1000), + "TB" => (4, 1024), + "T" => (4, 1000), + "PB" => (5, 1024), + "P" => (5, 1000), + "EB" => (6, 1024), + "E" => (6, 1000), + "ZB" => (7, 1024), + "Z" => (7, 1000), + "YB" => (8, 1024), + "Y" => (8, 1000), + _ => return None, + }; + Some(buf_size * base.pow(power)) +} + +fn check_option(matches: &Matches, name: &str, modified: &mut bool) -> Option { + match matches.opt_str(name) { + Some(value) => { + *modified = true; + match &value[..] { + "L" => { + if name == "input" { + show_info!("line buffering stdin is meaningless"); + None + } else { + Some(BufferType::Line) + } + } + x => { + let size = match parse_size(x) { + Some(m) => m, + None => { + show_error!("Invalid mode {}", x); + return None; + } + }; + Some(BufferType::Size(size)) + } + } + } + None => Some(BufferType::Default), + } +} + +fn parse_options( + args: &[String], + options: &mut ProgramOptions, + optgrps: &Options, +) -> Result { + let matches = match optgrps.parse(args) { + Ok(m) => m, + Err(_) => return Err(ErrMsg::Retry), + }; + if matches.opt_present("help") { + return Ok(OkMsg::Help); + } + if matches.opt_present("version") { + return Ok(OkMsg::Version); + } + let mut modified = false; + options.stdin = check_option(&matches, "input", &mut modified).ok_or(ErrMsg::Fatal)?; + options.stdout = check_option(&matches, "output", &mut modified).ok_or(ErrMsg::Fatal)?; + options.stderr = check_option(&matches, "error", &mut modified).ok_or(ErrMsg::Fatal)?; + + if matches.free.len() != 1 { + return Err(ErrMsg::Retry); + } + if !modified { + show_error!("you must specify a buffering mode option"); + return Err(ErrMsg::Fatal); + } + Ok(OkMsg::Buffering) +} + +fn set_command_env(command: &mut Command, buffer_name: &str, buffer_type: BufferType) { + match buffer_type { + BufferType::Size(m) => { + command.env(buffer_name, m.to_string()); + } + BufferType::Line => { + command.env(buffer_name, "L"); + } + BufferType::Default => {} + } +} + +fn get_preload_env(tmp_dir: &mut TempDir) -> io::Result<(String, PathBuf)> { + let (preload, extension) = preload_strings(); + let inject_path = tmp_dir.path().join("libstdbuf").with_extension(extension); + + let mut file = File::create(&inject_path)?; + file.write_all(STDBUF_INJECT)?; + + Ok((preload.to_owned(), inject_path)) +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + opts.optopt( + "i", + "input", + "adjust standard input stream buffering", + "MODE", + ); + opts.optopt( + "o", + "output", + "adjust standard output stream buffering", + "MODE", + ); + opts.optopt( + "e", + "error", + "adjust standard error stream buffering", + "MODE", + ); + opts.optflag("", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + let mut options = ProgramOptions { + stdin: BufferType::Default, + stdout: BufferType::Default, + stderr: BufferType::Default, + }; + let mut command_idx: i32 = -1; + for i in 1..args.len() + 1 { + match parse_options(&args[1..i], &mut options, &opts) { + Ok(OkMsg::Buffering) => { + command_idx = (i as i32) - 1; + break; + } + Ok(OkMsg::Help) => { + print_usage(&opts); + return 0; + } + Ok(OkMsg::Version) => { + print_version(); + return 0; + } + Err(ErrMsg::Fatal) => break, + Err(ErrMsg::Retry) => continue, + } + } + if command_idx == -1 { + crash!( + 125, + "Invalid options\nTry 'stdbuf --help' for more information." + ); + } + let command_name = &args[command_idx as usize]; + let mut command = Command::new(command_name); + + let mut tmp_dir = return_if_err!(1, TempDir::new("stdbuf")); + let (preload_env, libstdbuf) = return_if_err!(1, get_preload_env(&mut tmp_dir)); + command + .args(&args[(command_idx as usize) + 1..]) + .env(preload_env, libstdbuf); + set_command_env(&mut command, "_STDBUF_I", options.stdin); + set_command_env(&mut command, "_STDBUF_O", options.stdout); + set_command_env(&mut command, "_STDBUF_E", options.stderr); + let mut process = match command.spawn() { + Ok(p) => p, + Err(e) => crash!(1, "failed to execute process: {}", e), + }; + match process.wait() { + Ok(status) => match status.code() { + Some(i) => return i, + None => crash!(1, "process killed by signal {}", status.signal().unwrap()), + }, + Err(e) => crash!(1, "{}", e), + }; +} diff --git a/coreutils/src/sum/Cargo.toml b/coreutils/src/sum/Cargo.toml new file mode 100644 index 000000000..572f49e2b --- /dev/null +++ b/coreutils/src/sum/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "sum" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_sum" +path = "sum.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "sum" +path = "../../uumain.rs" diff --git a/coreutils/src/sum/sum.rs b/coreutils/src/sum/sum.rs new file mode 100644 index 000000000..3f506ab40 --- /dev/null +++ b/coreutils/src/sum/sum.rs @@ -0,0 +1,144 @@ +#![crate_name = "uu_sum"] + +/* +* This file is part of the uutils coreutils package. +* +* (c) T. Jameson Little +* +* For the full copyright and license information, please view the LICENSE file +* that was distributed with this source code. +*/ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::fs::File; +use std::io::{stdin, Read, Result}; +use std::path::Path; + +static NAME: &str = "sum"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn bsd_sum(mut reader: Box) -> (usize, u16) { + let mut buf = [0; 1024]; + let mut blocks_read = 0; + let mut checksum: u16 = 0; + loop { + match reader.read(&mut buf) { + Ok(n) if n != 0 => { + blocks_read += 1; + for &byte in buf[..n].iter() { + checksum = (checksum >> 1) + ((checksum & 1) << 15); + checksum = checksum.wrapping_add(byte as u16); + } + } + _ => break, + } + } + + (blocks_read, checksum) +} + +fn sysv_sum(mut reader: Box) -> (usize, u16) { + let mut buf = [0; 512]; + let mut blocks_read = 0; + let mut ret = 0u32; + + loop { + match reader.read(&mut buf) { + Ok(n) if n != 0 => { + blocks_read += 1; + for &byte in buf[..n].iter() { + ret = ret.wrapping_add(byte as u32); + } + } + _ => break, + } + } + + ret = (ret & 0xffff) + (ret >> 16); + ret = (ret & 0xffff) + (ret >> 16); + + (blocks_read, ret as u16) +} + +fn open(name: &str) -> Result> { + match name { + "-" => Ok(Box::new(stdin()) as Box), + _ => { + let f = File::open(&Path::new(name))?; + Ok(Box::new(f) as Box) + } + } +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag("r", "", "use the BSD compatible algorithm (default)"); + opts.optflag("s", "sysv", "use System V compatible algorithm"); + opts.optflag("h", "help", "show this help message"); + opts.optflag("v", "version", "print the version and exit"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "Invalid options\n{}", f), + }; + + if matches.opt_present("help") { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTION]... [FILE]... + +Checksum and count the blocks in a file.", + NAME, VERSION + ); + println!( + "{}\nWith no FILE, or when FILE is -, read standard input.", + opts.usage(&msg) + ); + return 0; + } + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + let sysv = matches.opt_present("sysv"); + + let files = if matches.free.is_empty() { + vec!["-".to_owned()] + } else { + matches.free + }; + + let print_names = if sysv { + files.len() > 1 || files[0] != "-" + } else { + files.len() > 1 + }; + + for file in &files { + let reader = match open(file) { + Ok(f) => f, + _ => crash!(1, "unable to open file"), + }; + let (blocks, sum) = if sysv { + sysv_sum(reader) + } else { + bsd_sum(reader) + }; + + if print_names { + println!("{} {} {}", sum, blocks, file); + } else { + println!("{} {}", sum, blocks); + } + } + + 0 +} diff --git a/coreutils/src/sync/Cargo.toml b/coreutils/src/sync/Cargo.toml new file mode 100644 index 000000000..361a570de --- /dev/null +++ b/coreutils/src/sync/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "sync" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_sync" +path = "sync.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +winapi = { version = "0.3", features = ["handleapi", "winerror"] } +kernel32-sys = "0.2.2" + +[dependencies.uucore] +version = "0.0.1" +features = ["wide"] + +[[bin]] +name = "sync" +path = "../../uumain.rs" diff --git a/coreutils/src/sync/sync.rs b/coreutils/src/sync/sync.rs new file mode 100644 index 000000000..c9608e519 --- /dev/null +++ b/coreutils/src/sync/sync.rs @@ -0,0 +1,170 @@ +#![crate_name = "uu_sync"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alexander Fomin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* Last synced with: sync (GNU coreutils) 8.13 */ + +extern crate getopts; +extern crate libc; + +#[cfg(windows)] +#[macro_use] +extern crate uucore; + +#[cfg(not(windows))] +extern crate uucore; + +static NAME: &str = "sync"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg(unix)] +mod platform { + use super::libc; + + extern "C" { + fn sync() -> libc::c_void; + } + + pub unsafe fn do_sync() -> isize { + sync(); + 0 + } +} + +#[cfg(windows)] +mod platform { + extern crate kernel32; + extern crate winapi; + use std::mem; + use std::fs::OpenOptions; + use std::os::windows::prelude::*; + use uucore::wide::{FromWide, ToWide}; + use self::winapi::um::winbase; + use self::winapi::um::winnt; + use self::winapi::shared::minwindef; + use self::winapi::um::handleapi; + use self::winapi::shared::winerror; + + unsafe fn flush_volume(name: &str) { + let name_wide = name.to_wide_null(); + if kernel32::GetDriveTypeW(name_wide.as_ptr()) == winbase::DRIVE_FIXED { + let sliced_name = &name[..name.len() - 1]; // eliminate trailing backslash + match OpenOptions::new().write(true).open(sliced_name) { + Ok(file) => if kernel32::FlushFileBuffers(file.as_raw_handle()) == 0 { + crash!( + kernel32::GetLastError() as i32, + "failed to flush file buffer" + ); + }, + Err(e) => crash!( + e.raw_os_error().unwrap_or(1), + "failed to create volume handle" + ), + } + } + } + + unsafe fn find_first_volume() -> (String, winnt::HANDLE) { + let mut name: [winnt::WCHAR; minwindef::MAX_PATH] = mem::uninitialized(); + let handle = kernel32::FindFirstVolumeW(name.as_mut_ptr(), name.len() as minwindef::DWORD); + if handle == handleapi::INVALID_HANDLE_VALUE { + crash!( + kernel32::GetLastError() as i32, + "failed to find first volume" + ); + } + (String::from_wide_null(&name), handle) + } + + unsafe fn find_all_volumes() -> Vec { + let (first_volume, next_volume_handle) = find_first_volume(); + let mut volumes = vec![first_volume]; + loop { + let mut name: [winnt::WCHAR; minwindef::MAX_PATH] = mem::uninitialized(); + if kernel32::FindNextVolumeW( + next_volume_handle, + name.as_mut_ptr(), + name.len() as minwindef::DWORD, + ) == 0 + { + match kernel32::GetLastError() { + winerror::ERROR_NO_MORE_FILES => { + kernel32::FindVolumeClose(next_volume_handle); + return volumes; + } + err => crash!(err as i32, "failed to find next volume"), + } + } else { + volumes.push(String::from_wide_null(&name)); + } + } + } + + pub unsafe fn do_sync() -> isize { + let volumes = find_all_volumes(); + for vol in &volumes { + flush_volume(vol); + } + 0 + } +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + 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, + _ => { + help(&opts); + return 1; + } + }; + + if matches.opt_present("h") { + help(&opts); + return 0; + } + + if matches.opt_present("V") { + version(); + return 0; + } + + sync(); + 0 +} + +fn version() { + println!("{} (uutils) {}", NAME, VERSION); + println!("The MIT License"); + println!(""); + println!("Author -- Alexander Fomin."); +} + +fn help(opts: &getopts::Options) { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTION] + +Force changed blocks to disk, update the super block.", + NAME, VERSION + ); + + print!("{}", opts.usage(&msg)); +} + +fn sync() -> isize { + unsafe { platform::do_sync() } +} diff --git a/coreutils/src/tac/Cargo.toml b/coreutils/src/tac/Cargo.toml new file mode 100644 index 000000000..0f09e1a9b --- /dev/null +++ b/coreutils/src/tac/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tac" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_tac" +path = "tac.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "tac" +path = "../../uumain.rs" diff --git a/coreutils/src/tac/tac.rs b/coreutils/src/tac/tac.rs new file mode 100644 index 000000000..bc14d926b --- /dev/null +++ b/coreutils/src/tac/tac.rs @@ -0,0 +1,165 @@ +#![crate_name = "uu_tac"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::fs::File; +use std::io::{stdin, stdout, BufReader, Read, Stdout, Write}; + +static NAME: &str = "tac"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag( + "b", + "before", + "attach the separator before instead of after", + ); + opts.optflag( + "r", + "regex", + "interpret the sequence as a regular expression (NOT IMPLEMENTED)", + ); + opts.optopt( + "s", + "separator", + "use STRING as the separator instead of newline", + "STRING", + ); + 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") { + let msg = format!( + "{0} {1} + +Usage: + {0} [OPTION]... [FILE]... + +Write each file to standard output, last line first.", + NAME, VERSION + ); + + print!("{}", opts.usage(&msg)); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else { + let before = matches.opt_present("b"); + let regex = matches.opt_present("r"); + let separator = match matches.opt_str("s") { + Some(m) => { + if m.is_empty() { + crash!(1, "separator cannot be empty") + } else { + m + } + } + None => "\n".to_owned(), + }; + let files = if matches.free.is_empty() { + vec!["-".to_owned()] + } else { + matches.free + }; + tac(files, before, regex, &separator[..]); + } + + 0 +} + +fn tac(filenames: Vec, before: bool, _: bool, separator: &str) { + let mut out = stdout(); + let sbytes = separator.as_bytes(); + let slen = sbytes.len(); + + for filename in &filenames { + let mut file = BufReader::new(if filename == "-" { + Box::new(stdin()) as Box + } else { + match File::open(filename) { + Ok(f) => Box::new(f) as Box, + Err(e) => { + show_warning!("failed to open '{}' for reading: {}", filename, e); + continue; + } + } + }); + + let mut data = Vec::new(); + match file.read_to_end(&mut data) { + Err(e) => { + show_warning!("failed to read '{}': {}", filename, e); + continue; + } + Ok(_) => (), + }; + + // find offsets in string of all separators + let mut offsets = Vec::new(); + let mut i = 0; + loop { + if i + slen > data.len() { + break; + } + + if &data[i..i + slen] == sbytes { + offsets.push(i); + i += slen; + } else { + i += 1; + } + } + drop(i); + + // if there isn't a separator at the end of the file, fake it + if offsets.is_empty() || *offsets.last().unwrap() < data.len() - slen { + offsets.push(data.len()); + } + + let mut prev = *offsets.last().unwrap(); + let mut start = true; + for off in offsets.iter().rev().skip(1) { + // correctly handle case of no final separator in file + if start && prev == data.len() { + show_line(&mut out, &[], &data[*off + slen..prev], before); + start = false; + } else { + show_line(&mut out, sbytes, &data[*off + slen..prev], before); + } + prev = *off; + } + show_line(&mut out, sbytes, &data[0..prev], before); + } +} + +fn show_line(out: &mut Stdout, sep: &[u8], dat: &[u8], before: bool) { + if before { + out.write_all(sep) + .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); + } + + out.write_all(dat) + .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); + + if !before { + out.write_all(sep) + .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); + } +} diff --git a/coreutils/src/tail/Cargo.toml b/coreutils/src/tail/Cargo.toml new file mode 100644 index 000000000..337ea3436 --- /dev/null +++ b/coreutils/src/tail/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "tail" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_tail" +path = "tail.rs" + +[dependencies] +getopts = "0.2.18" +kernel32-sys = "0.2.2" +libc = "0.2.42" +winapi = "0.3" +uucore = "0.0.1" + +[target.'cfg(target_os = "redox")'.dependencies] +redox_syscall = "0.1" + +[[bin]] +name = "tail" +path = "../../uumain.rs" diff --git a/coreutils/src/tail/README.md b/coreutils/src/tail/README.md new file mode 100644 index 000000000..6d227c66a --- /dev/null +++ b/coreutils/src/tail/README.md @@ -0,0 +1,13 @@ +Rudimentary tail implementation. + +## Missing features: + +### Flags with features +* `--max-unchanged-stats` : with `--follow=name`, reopen a FILE which has not changed size after N (default 5) iterations to see if it has been unlinked or renamed (this is the usual case of rotated log files). With inotify, this option is rarely useful. +* `--retry` : keep trying to open a file even when it is or becomes inaccessible; useful when follow‐ing by name, i.e., with `--follow=name` + +### Others +The current implementation does not handle `-` as an alias for stdin. + +## Possible optimizations: +* Don't read the whole file if not using `-f` and input is regular file. Read in chunks from the end going backwards, reading each individual chunk forward. diff --git a/coreutils/src/tail/platform/mod.rs b/coreutils/src/tail/platform/mod.rs new file mode 100644 index 000000000..32531baa2 --- /dev/null +++ b/coreutils/src/tail/platform/mod.rs @@ -0,0 +1,32 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Alexander Batischev + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[cfg(unix)] +pub use self::unix::{supports_pid_checks, Pid, ProcessChecker}; + +#[cfg(windows)] +pub use self::windows::{supports_pid_checks, Pid, ProcessChecker}; + +#[cfg(target_os = "redox")] +pub use self::redox::{supports_pid_checks, Pid, ProcessChecker}; + +#[cfg(target_os = "sunrise")] +pub use self::sunrise::{supports_pid_checks, Pid, ProcessChecker}; + +#[cfg(unix)] +mod unix; + +#[cfg(windows)] +mod windows; + +#[cfg(target_os = "redox")] +mod redox; + +#[cfg(target_os = "sunrise")] +mod sunrise; diff --git a/coreutils/src/tail/platform/redox.rs b/coreutils/src/tail/platform/redox.rs new file mode 100644 index 000000000..c0d43cb1c --- /dev/null +++ b/coreutils/src/tail/platform/redox.rs @@ -0,0 +1,25 @@ +extern crate syscall; + +use self::syscall::{Error, EPERM, ENOSYS}; + +pub type Pid = usize; + +pub struct ProcessChecker { + pid: self::Pid, +} + +impl ProcessChecker { + pub fn new(process_id: self::Pid) -> ProcessChecker { + ProcessChecker { pid: process_id } + } + + // Borrowing mutably to be aligned with Windows implementation + pub fn is_dead(&mut self) -> bool { + let res = syscall::kill(self.pid, 0); + res != Ok(0) && res != Err(Error::new(EPERM)) + } +} + +pub fn supports_pid_checks(pid: self::Pid) -> bool { + true +} diff --git a/coreutils/src/tail/platform/sunrise.rs b/coreutils/src/tail/platform/sunrise.rs new file mode 100644 index 000000000..9719de337 --- /dev/null +++ b/coreutils/src/tail/platform/sunrise.rs @@ -0,0 +1,21 @@ +pub type Pid = usize; + +pub struct ProcessChecker { + pid: self::Pid, +} + +impl ProcessChecker { + pub fn new(process_id: self::Pid) -> ProcessChecker { + ProcessChecker { pid: process_id } + } + + // Borrowing mutably to be aligned with Windows implementation + pub fn is_dead(&mut self) -> bool { + // not availaible on Sunrise + true + } +} + +pub fn supports_pid_checks(pid: self::Pid) -> bool { + false +} \ No newline at end of file diff --git a/coreutils/src/tail/platform/unix.rs b/coreutils/src/tail/platform/unix.rs new file mode 100644 index 000000000..7f64207f4 --- /dev/null +++ b/coreutils/src/tail/platform/unix.rs @@ -0,0 +1,42 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Alexander Batischev + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate libc; + +use std::io::Error; + +pub type Pid = libc::pid_t; + +pub struct ProcessChecker { + pid: self::Pid, +} + +impl ProcessChecker { + pub fn new(process_id: self::Pid) -> ProcessChecker { + ProcessChecker { pid: process_id } + } + + // Borrowing mutably to be aligned with Windows implementation + pub fn is_dead(&mut self) -> bool { + unsafe { libc::kill(self.pid, 0) != 0 && get_errno() != libc::EPERM } + } +} + +impl Drop for ProcessChecker { + fn drop(&mut self) {} +} + +pub fn supports_pid_checks(pid: self::Pid) -> bool { + unsafe { !(libc::kill(pid, 0) != 0 && get_errno() == libc::ENOSYS) } +} + +#[inline] +fn get_errno() -> i32 { + Error::last_os_error().raw_os_error().unwrap() +} diff --git a/coreutils/src/tail/platform/windows.rs b/coreutils/src/tail/platform/windows.rs new file mode 100644 index 000000000..ebab7d32b --- /dev/null +++ b/coreutils/src/tail/platform/windows.rs @@ -0,0 +1,58 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Alexander Batischev + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate kernel32; +extern crate winapi; + +use self::kernel32::{CloseHandle, OpenProcess, WaitForSingleObject}; +use self::winapi::shared::minwindef::DWORD; +use self::winapi::um::winbase::{WAIT_OBJECT_0, WAIT_FAILED}; +use self::winapi::um::winnt::{HANDLE, SYNCHRONIZE}; + +pub type Pid = DWORD; + +pub struct ProcessChecker { + dead: bool, + handle: HANDLE, +} + +impl ProcessChecker { + pub fn new(process_id: self::Pid) -> ProcessChecker { + #[allow(non_snake_case)] + let FALSE = 0i32; + let h = unsafe { OpenProcess(SYNCHRONIZE, FALSE, process_id as DWORD) }; + ProcessChecker { + dead: h.is_null(), + handle: h, + } + } + + pub fn is_dead(&mut self) -> bool { + if !self.dead { + self.dead = unsafe { + let status = WaitForSingleObject(self.handle, 0); + status == WAIT_OBJECT_0 || status == WAIT_FAILED + } + } + + self.dead + } +} + +impl Drop for ProcessChecker { + fn drop(&mut self) { + unsafe { + CloseHandle(self.handle); + } + } +} + +pub fn supports_pid_checks(_pid: self::Pid) -> bool { + true +} diff --git a/coreutils/src/tail/tail.rs b/coreutils/src/tail/tail.rs new file mode 100755 index 000000000..3eb068b6a --- /dev/null +++ b/coreutils/src/tail/tail.rs @@ -0,0 +1,577 @@ +#![crate_name = "uu_tail"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Morten Olsen Lysgaard + * (c) Alexander Batischev + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +extern crate getopts; +extern crate libc; + +#[macro_use] +extern crate uucore; + +mod platform; + +use std::collections::VecDeque; +use std::error::Error; +use std::fmt; +use std::fs::File; +use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write}; +use std::path::Path; +use std::str::from_utf8; +use std::thread::sleep; +use std::time::Duration; + +static NAME: &str = "tail"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +enum FilterMode { + Bytes(u64), + Lines(u64, u8), // (number of lines, delimiter) +} + +struct Settings { + mode: FilterMode, + sleep_msec: u32, + beginning: bool, + follow: bool, + pid: platform::Pid, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + mode: FilterMode::Lines(10, '\n' as u8), + sleep_msec: 1000, + beginning: false, + follow: false, + pid: 0, + } + } +} + +pub fn uumain(args: Vec) -> i32 { + let mut settings: Settings = Default::default(); + + // handle obsolete -number syntax + let options = match obsolete(&args[1..]) { + (args, Some(n)) => { + settings.mode = FilterMode::Lines(n, '\n' as u8); + args + } + (args, None) => args, + }; + + let args = options; + + let mut opts = getopts::Options::new(); + + opts.optopt("c", "bytes", "Number of bytes to print", "k"); + opts.optopt("n", "lines", "Number of lines to print", "k"); + opts.optflag("f", "follow", "Print the file as it grows"); + opts.optopt( + "s", + "sleep-interval", + "Number or seconds to sleep between polling the file when running with -f", + "n", + ); + opts.optopt( + "", + "pid", + "with -f, terminate after process ID, PID dies", + "PID", + ); + opts.optflag("z", "zero-terminated", "Line delimiter is NUL, not newline"); + opts.optflag("h", "help", "help"); + opts.optflag("V", "version", "version"); + opts.optflag("v", "verbose", "always output headers giving file names"); + opts.optflag("q", "quiet", "never output headers giving file names"); + opts.optflag("", "silent", "synonym of --quiet"); + + let given_options = match opts.parse(&args) { + Ok(m) => m, + Err(_) => { + println!("{}", opts.usage("")); + return 1; + } + }; + + if given_options.opt_present("h") { + println!("{}", opts.usage("")); + return 0; + } + if given_options.opt_present("V") { + version(); + return 0; + } + + settings.follow = given_options.opt_present("f"); + + if settings.follow { + match given_options.opt_str("s") { + Some(n) => { + let parsed: Option = n.parse().ok(); + match parsed { + Some(m) => settings.sleep_msec = m * 1000, + None => {} + } + } + None => {} + }; + } + + if let Some(pid_str) = given_options.opt_str("pid") { + if let Ok(pid) = pid_str.parse() { + settings.pid = pid; + if pid != 0 { + if !settings.follow { + show_warning!("PID ignored; --pid=PID is useful only when following"); + } + + if !platform::supports_pid_checks(pid) { + show_warning!("--pid=PID is not supported on this system"); + settings.pid = 0; + } + } + } + } + + match given_options.opt_str("n") { + Some(n) => { + let mut slice: &str = n.as_ref(); + if slice.chars().next().unwrap_or('_') == '+' { + settings.beginning = true; + slice = &slice[1..]; + } + match parse_size(slice) { + Ok(m) => settings.mode = FilterMode::Lines(m, '\n' as u8), + Err(e) => { + show_error!("{}", e.description()); + return 1; + } + } + } + None => match given_options.opt_str("c") { + Some(n) => { + let mut slice: &str = n.as_ref(); + if slice.chars().next().unwrap_or('_') == '+' { + settings.beginning = true; + slice = &slice[1..]; + } + match parse_size(slice) { + Ok(m) => settings.mode = FilterMode::Bytes(m), + Err(e) => { + show_error!("{}", e.description()); + return 1; + } + } + } + None => {} + }, + }; + + if given_options.opt_present("z") { + if let FilterMode::Lines(count, _) = settings.mode { + settings.mode = FilterMode::Lines(count, 0); + } + } + + let verbose = given_options.opt_present("v"); + let quiet = given_options.opt_present("q") || given_options.opt_present("silent"); + + let files = given_options.free; + + if files.is_empty() { + let mut buffer = BufReader::new(stdin()); + unbounded_tail(&mut buffer, &settings); + } else { + let multiple = files.len() > 1; + let mut first_header = true; + let mut readers = Vec::new(); + + for filename in &files { + if (multiple || verbose) && !quiet { + if !first_header { + println!(); + } + println!("==> {} <==", filename); + } + first_header = false; + + let path = Path::new(filename); + if path.is_dir() { + continue; + } + let mut file = File::open(&path).unwrap(); + if is_seekable(&mut file) { + bounded_tail(&file, &settings); + if settings.follow { + let reader = BufReader::new(file); + readers.push(reader); + } + } else { + let mut reader = BufReader::new(file); + unbounded_tail(&mut reader, &settings); + if settings.follow { + readers.push(reader); + } + } + } + + if settings.follow { + follow(&mut readers[..], &files[..], &settings); + } + } + + 0 +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseSizeErr { + ParseFailure(String), + SizeTooBig(String), +} + +impl Error for ParseSizeErr { + fn description(&self) -> &str { + match *self { + ParseSizeErr::ParseFailure(ref s) => &*s, + ParseSizeErr::SizeTooBig(ref s) => &*s, + } + } +} + +impl fmt::Display for ParseSizeErr { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", Error::description(self)) + } +} + +impl ParseSizeErr { + fn parse_failure(s: &str) -> ParseSizeErr { + ParseSizeErr::ParseFailure(format!("invalid size: '{}'", s)) + } + + fn size_too_big(s: &str) -> ParseSizeErr { + ParseSizeErr::SizeTooBig(format!( + "invalid size: '{}': Value too large to be stored in data type", + s + )) + } +} + +pub type ParseSizeResult = Result; + +pub fn parse_size(mut size_slice: &str) -> Result { + let mut base = if size_slice.chars().last().unwrap_or('_') == 'B' { + size_slice = &size_slice[..size_slice.len() - 1]; + 1000u64 + } else { + 1024u64 + }; + + let exponent = if size_slice.len() > 0 { + let mut has_suffix = true; + let exp = match size_slice.chars().last().unwrap_or('_') { + 'K' | 'k' => 1u64, + 'M' => 2u64, + 'G' => 3u64, + 'T' => 4u64, + 'P' => 5u64, + 'E' => 6u64, + 'Z' | 'Y' => { + return Err(ParseSizeErr::size_too_big(size_slice)); + } + 'b' => { + base = 512u64; + 1u64 + } + _ => { + has_suffix = false; + 0u64 + } + }; + if has_suffix { + size_slice = &size_slice[..size_slice.len() - 1]; + } + exp + } else { + 0u64 + }; + + let mut multiplier = 1u64; + for _ in 0u64..exponent { + multiplier *= base; + } + if base == 1000u64 && exponent == 0u64 { + // sole B is not a valid suffix + Err(ParseSizeErr::parse_failure(size_slice)) + } else { + let value: Option = size_slice.parse().ok(); + value + .map(|v| Ok(multiplier * v)) + .unwrap_or(Err(ParseSizeErr::parse_failure(size_slice))) + } +} + +// It searches for an option in the form of -123123 +// +// In case is found, the options vector will get rid of that object so that +// getopts works correctly. +fn obsolete(options: &[String]) -> (Vec, Option) { + let mut options: Vec = options.to_vec(); + let mut a = 0; + let b = options.len(); + + while a < b { + let current = options[a].clone(); + let current = current.as_bytes(); + + if current.len() > 1 && current[0] == '-' as u8 { + let len = current.len(); + for pos in 1..len { + // Ensure that the argument is only made out of digits + if !(current[pos] as char).is_numeric() { + break; + } + + // If this is the last number + if pos == len - 1 { + options.remove(a); + let number: Option = from_utf8(¤t[1..len]).unwrap().parse().ok(); + return (options, Some(number.unwrap())); + } + } + } + + a += 1; + } + + (options, None) +} + +/// When reading files in reverse in `bounded_tail`, this is the size of each +/// block read at a time. +const BLOCK_SIZE: u64 = 1 << 16; + +fn follow(readers: &mut [BufReader], filenames: &[String], settings: &Settings) { + assert!(settings.follow); + let mut last = readers.len() - 1; + let mut read_some = false; + let mut process = platform::ProcessChecker::new(settings.pid); + + loop { + sleep(Duration::new(0, settings.sleep_msec * 1000)); + + let pid_is_dead = !read_some && settings.pid != 0 && process.is_dead(); + read_some = false; + + for (i, reader) in readers.iter_mut().enumerate() { + // Print all new content since the last pass + loop { + let mut datum = String::new(); + match reader.read_line(&mut datum) { + Ok(0) => break, + Ok(_) => { + read_some = true; + if i != last { + println!("\n==> {} <==", filenames[i]); + last = i; + } + print!("{}", datum); + } + Err(err) => panic!(err), + } + } + } + + if pid_is_dead { + break; + } + } +} + +/// Iterate over bytes in the file, in reverse, until `should_stop` returns +/// true. The `file` is left seek'd to the position just after the byte that +/// `should_stop` returned true for. +fn backwards_thru_file( + mut file: &File, + size: u64, + buf: &mut Vec, + delimiter: u8, + should_stop: &mut F, +) where + F: FnMut(u8) -> bool, +{ + assert!(buf.len() >= BLOCK_SIZE as usize); + + let max_blocks_to_read = (size as f64 / BLOCK_SIZE as f64).ceil() as usize; + + for block_idx in 0..max_blocks_to_read { + let block_size = if block_idx == max_blocks_to_read - 1 { + size % BLOCK_SIZE + } else { + BLOCK_SIZE + }; + + // Seek backwards by the next block, read the full block into + // `buf`, and then seek back to the start of the block again. + let pos = file.seek(SeekFrom::Current(-(block_size as i64))).unwrap(); + file.read_exact(&mut buf[0..(block_size as usize)]).unwrap(); + let pos2 = file.seek(SeekFrom::Current(-(block_size as i64))).unwrap(); + assert_eq!(pos, pos2); + + // Iterate backwards through the bytes, calling `should_stop` on each + // one. + let slice = &buf[0..(block_size as usize)]; + for (i, ch) in slice.iter().enumerate().rev() { + // Ignore one trailing newline. + if block_idx == 0 && i as u64 == block_size - 1 && *ch == delimiter { + continue; + } + + if should_stop(*ch) { + file.seek(SeekFrom::Current((i + 1) as i64)).unwrap(); + return; + } + } + } +} + +/// When tail'ing a file, we do not need to read the whole file from start to +/// finish just to find the last n lines or bytes. Instead, we can seek to the +/// end of the file, and then read the file "backwards" in blocks of size +/// `BLOCK_SIZE` until we find the location of the first line/byte. This ends up +/// being a nice performance win for very large files. +fn bounded_tail(mut file: &File, settings: &Settings) { + let size = file.seek(SeekFrom::End(0)).unwrap(); + let mut buf = vec![0; BLOCK_SIZE as usize]; + + // Find the position in the file to start printing from. + match settings.mode { + FilterMode::Lines(mut count, delimiter) => { + backwards_thru_file(&mut file, size, &mut buf, delimiter, &mut |byte| { + if byte == delimiter { + count -= 1; + count == 0 + } else { + false + } + }); + } + FilterMode::Bytes(count) => { + file.seek(SeekFrom::End(-(count as i64))).unwrap(); + } + } + + // Print the target section of the file. + loop { + let bytes_read = file.read(&mut buf).unwrap(); + + let mut stdout = stdout(); + for b in &buf[0..bytes_read] { + print_byte(&mut stdout, b); + } + + if bytes_read == 0 { + break; + } + } +} + +fn unbounded_tail(reader: &mut BufReader, settings: &Settings) { + // Read through each line/char and store them in a ringbuffer that always + // contains count lines/chars. When reaching the end of file, output the + // data in the ringbuf. + match settings.mode { + FilterMode::Lines(mut count, _delimiter) => { + let mut ringbuf: VecDeque = VecDeque::new(); + let mut skip = if settings.beginning { + let temp = count; + count = ::std::u64::MAX; + temp - 1 + } else { + 0 + }; + loop { + let mut datum = String::new(); + match reader.read_line(&mut datum) { + Ok(0) => break, + Ok(_) => { + if skip > 0 { + skip -= 1; + } else { + if count <= ringbuf.len() as u64 { + ringbuf.pop_front(); + } + ringbuf.push_back(datum); + } + } + Err(err) => panic!(err), + } + } + let mut stdout = stdout(); + for datum in &ringbuf { + print_string(&mut stdout, datum); + } + } + FilterMode::Bytes(mut count) => { + let mut ringbuf: VecDeque = VecDeque::new(); + let mut skip = if settings.beginning { + let temp = count; + count = ::std::u64::MAX; + temp - 1 + } else { + 0 + }; + loop { + let mut datum = [0; 1]; + match reader.read(&mut datum) { + Ok(0) => break, + Ok(_) => { + if skip > 0 { + skip -= 1; + } else { + if count <= ringbuf.len() as u64 { + ringbuf.pop_front(); + } + ringbuf.push_back(datum[0]); + } + } + Err(err) => panic!(err), + } + } + let mut stdout = stdout(); + for datum in &ringbuf { + print_byte(&mut stdout, datum); + } + } + } +} + +fn is_seekable(file: &mut T) -> bool { + file.seek(SeekFrom::Current(0)).is_ok() +} + +#[inline] +fn print_byte(stdout: &mut T, ch: &u8) { + if let Err(err) = stdout.write(&[*ch]) { + crash!(1, "{}", err); + } +} + +#[inline] +fn print_string(_: &mut T, s: &str) { + print!("{}", s); +} + +fn version() { + println!("{} {}", NAME, VERSION); +} diff --git a/coreutils/src/tee/Cargo.toml b/coreutils/src/tee/Cargo.toml new file mode 100644 index 000000000..d1d86612a --- /dev/null +++ b/coreutils/src/tee/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tee" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_tee" +path = "tee.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "tee" +path = "../../uumain.rs" diff --git a/coreutils/src/tee/tee.rs b/coreutils/src/tee/tee.rs new file mode 100644 index 000000000..dfc72ef74 --- /dev/null +++ b/coreutils/src/tee/tee.rs @@ -0,0 +1,195 @@ +#![crate_name = "uu_tee"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Aleksander Bielawski + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +extern crate uucore; + +use std::fs::OpenOptions; +use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; +use std::path::{Path, PathBuf}; + +static NAME: &str = "tee"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + match options(&args).and_then(exec) { + Ok(_) => 0, + Err(_) => 1, + } +} + +#[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"); + 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))) + .and_then(|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 + }; + Ok(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 exec(options: Options) -> Result<()> { + match options.print_and_exit { + Some(text) => Ok(println!("{}", text)), + None => tee(options), + } +} + +fn tee(options: Options) -> Result<()> { + let mut writers: Vec> = options + .files + .clone() + .into_iter() + .map(|file| open(file, options.append)) + .collect(); + writers.push(Box::new(stdout())); + let output = &mut MultiWriter { writers }; + let input = &mut NamedReader { + inner: Box::new(stdin()) as Box, + }; + if copy(input, output).is_err() || output.flush().is_err() { + Err(Error::new(ErrorKind::Other, "")) + } else { + Ok(()) + } +} + +fn open(name: String, append: bool) -> Box { + let path = PathBuf::from(name); + let inner: Box = { + let mut options = OpenOptions::new(); + let mode = if append { + options.append(true) + } else { + options.truncate(true) + }; + match mode.write(true).create(true).open(path.as_path()) { + Ok(file) => Box::new(file), + Err(_) => Box::new(sink()), + } + }; + Box::new(NamedWriter { + inner: inner, + path: path, + }) as Box +} + +struct MultiWriter { + writers: Vec>, +} + +impl Write for MultiWriter { + fn write(&mut self, buf: &[u8]) -> Result { + for writer in &mut self.writers { + writer.write_all(buf)?; + } + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<()> { + for writer in &mut self.writers { + writer.flush()?; + } + Ok(()) + } +} + +struct NamedWriter { + inner: Box, + path: PathBuf, +} + +impl Write for NamedWriter { + fn write(&mut self, buf: &[u8]) -> Result { + match self.inner.write(buf) { + Err(f) => { + self.inner = Box::new(sink()) as Box; + warn(format!("{}: {}", self.path.display(), f.to_string()).as_ref()); + Err(f) + } + okay => okay, + } + } + + fn flush(&mut self) -> Result<()> { + match self.inner.flush() { + Err(f) => { + self.inner = Box::new(sink()) as Box; + warn(format!("{}: {}", self.path.display(), f.to_string()).as_ref()); + Err(f) + } + okay => okay, + } + } +} + +struct NamedReader { + inner: Box, +} + +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()); + Err(f) + } + okay => okay, + } + } +} + +fn warn(message: &str) -> Error { + eprintln!("{}: {}", NAME, message); + Error::new(ErrorKind::Other, format!("{}: {}", NAME, message)) +} diff --git a/coreutils/src/test/Cargo.toml b/coreutils/src/test/Cargo.toml new file mode 100644 index 000000000..923a952f4 --- /dev/null +++ b/coreutils/src/test/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "test" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_test" +path = "test.rs" + +[dependencies] +libc = "0.2.42" +atty = "0.2" +uucore = "0.0.1" + +[target.'cfg(target_os = "redox")'.dependencies] +redox_syscall = "0.1" + +[[bin]] +name = "test" +path = "../../uumain.rs" diff --git a/coreutils/src/test/test.rs b/coreutils/src/test/test.rs new file mode 100644 index 000000000..8d821c55e --- /dev/null +++ b/coreutils/src/test/test.rs @@ -0,0 +1,444 @@ +#![crate_name = "uu_test"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) mahkoh (ju.orth [at] gmail [dot] com) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate libc; +#[cfg(target_os = "redox")] +extern crate syscall; + +extern crate atty; + +use atty::Stream; +use std::collections::HashMap; +use std::ffi::OsString; +use std::env::args_os; +use std::str::from_utf8; + +static NAME: &str = "test"; + +// TODO: decide how to handle non-UTF8 input for all the utils +// Definitely don't use [u8], try keeping it as OsStr or OsString instead +pub fn uumain(_: Vec) -> i32 { + let args = args_os().collect::>(); + // This is completely disregarding valid windows paths that aren't valid unicode + let args = args.iter() + .map(|a| a.to_str().unwrap().as_bytes()) + .collect::>(); + if args.is_empty() { + return 2; + } + let args = if !args[0].ends_with(NAME.as_bytes()) { + &args[1..] + } else { + &args[..] + }; + let args = match args[0] { + b"[" => match args[args.len() - 1] { + b"]" => &args[1..args.len() - 1], + _ => return 2, + }, + _ => &args[1..args.len()], + }; + let mut error = false; + let retval = 1 - parse_expr(args, &mut error) as i32; + if error { + 2 + } else { + retval + } +} + +fn one(args: &[&[u8]]) -> bool { + args[0].len() > 0 +} + +fn two(args: &[&[u8]], error: &mut bool) -> bool { + match args[0] { + b"!" => !one(&args[1..]), + b"-b" => path(args[1], PathCondition::BlockSpecial), + b"-c" => path(args[1], PathCondition::CharacterSpecial), + b"-d" => path(args[1], PathCondition::Directory), + b"-e" => path(args[1], PathCondition::Exists), + b"-f" => path(args[1], PathCondition::Regular), + b"-g" => path(args[1], PathCondition::GroupIDFlag), + b"-h" => path(args[1], PathCondition::SymLink), + b"-L" => path(args[1], PathCondition::SymLink), + b"-n" => one(&args[1..]), + b"-p" => path(args[1], PathCondition::FIFO), + b"-r" => path(args[1], PathCondition::Readable), + b"-S" => path(args[1], PathCondition::Socket), + b"-s" => path(args[1], PathCondition::NonEmpty), + b"-t" => isatty(args[1]), + b"-u" => path(args[1], PathCondition::UserIDFlag), + b"-w" => path(args[1], PathCondition::Writable), + b"-x" => path(args[1], PathCondition::Executable), + b"-z" => !one(&args[1..]), + _ => { + *error = true; + false + } + } +} + +fn three(args: &[&[u8]], error: &mut bool) -> bool { + match args[1] { + b"=" => args[0] == args[2], + b"==" => args[0] == args[2], + b"!=" => args[0] != args[2], + b"-eq" => integers(args[0], args[2], IntegerCondition::Equal), + b"-ne" => integers(args[0], args[2], IntegerCondition::Unequal), + b"-gt" => integers(args[0], args[2], IntegerCondition::Greater), + b"-ge" => integers(args[0], args[2], IntegerCondition::GreaterEqual), + b"-lt" => integers(args[0], args[2], IntegerCondition::Less), + b"-le" => integers(args[0], args[2], IntegerCondition::LessEqual), + _ => match args[0] { + b"!" => !two(&args[1..], error), + _ => { + *error = true; + false + } + }, + } +} + +fn four(args: &[&[u8]], error: &mut bool) -> bool { + match args[0] { + b"!" => !three(&args[1..], error), + _ => { + *error = true; + false + } + } +} + +enum IntegerCondition { + Equal, + Unequal, + Greater, + GreaterEqual, + Less, + LessEqual, +} + +fn integers(a: &[u8], b: &[u8], cond: IntegerCondition) -> bool { + let (a, b): (&str, &str) = match (from_utf8(a), from_utf8(b)) { + (Ok(a), Ok(b)) => (a, b), + _ => return false, + }; + let (a, b): (i64, i64) = match (a.parse(), b.parse()) { + (Ok(a), Ok(b)) => (a, b), + _ => return false, + }; + match cond { + IntegerCondition::Equal => a == b, + IntegerCondition::Unequal => a != b, + IntegerCondition::Greater => a > b, + IntegerCondition::GreaterEqual => a >= b, + IntegerCondition::Less => a < b, + IntegerCondition::LessEqual => a <= b, + } +} + +fn isatty(fd: &[u8]) -> bool { + from_utf8(fd) + .ok() + .and_then(|s| s.parse().ok()) + .map_or(false, |i| { + #[cfg(not(any(target_os = "redox", target_os = "sunrise")))] + unsafe { libc::isatty(i) == 1 } + #[cfg(target_os = "sunrise")] + { + match i { + 0 => atty::is(Stream::Stdin), + 1 => atty::is(Stream::Stdout), + 2 => atty::is(Stream::Stderr), + _ => false + } + } + #[cfg(target_os = "redox")] + syscall::dup(i, b"termios").map(syscall::close).is_ok() + }) +} + +fn dispatch(args: &mut &[&[u8]], error: &mut bool) -> bool { + let (val, idx) = match args.len() { + 0 => { + *error = true; + (false, 0) + } + 1 => (one(*args), 1), + 2 => dispatch_two(args, error), + 3 => dispatch_three(args, error), + _ => dispatch_four(args, error), + }; + *args = &(*args)[idx..]; + val +} + +fn dispatch_two(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) { + let val = two(*args, error); + if *error { + *error = false; + (one(*args), 1) + } else { + (val, 2) + } +} + +fn dispatch_three(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) { + let val = three(*args, error); + if *error { + *error = false; + dispatch_two(args, error) + } else { + (val, 3) + } +} + +fn dispatch_four(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) { + let val = four(*args, error); + if *error { + *error = false; + dispatch_three(args, error) + } else { + (val, 4) + } +} + +#[derive(Clone, Copy)] +enum Precedence { + Unknown = 0, + Paren, // FIXME: this is useless (parentheses have not been implemented) + Or, + And, + BUnOp, + BinOp, + UnOp, +} + +fn parse_expr(mut args: &[&[u8]], error: &mut bool) -> bool { + if args.len() == 0 { + false + } else { + let hashmap = setup_hashmap(); + let lhs = dispatch(&mut args, error); + + if args.len() > 0 { + parse_expr_helper(&hashmap, &mut args, lhs, Precedence::Unknown, error) + } else { + lhs + } + } +} + +fn parse_expr_helper<'a>( + hashmap: &HashMap<&'a [u8], Precedence>, + args: &mut &[&'a [u8]], + mut lhs: bool, + min_prec: Precedence, + error: &mut bool, +) -> bool { + let mut prec = *hashmap.get(&args[0]).unwrap_or_else(|| { + *error = true; + &min_prec + }); + while !*error && args.len() > 0 && prec as usize >= min_prec as usize { + let op = args[0]; + *args = &(*args)[1..]; + let mut rhs = dispatch(args, error); + while args.len() > 0 { + let subprec = *hashmap.get(&args[0]).unwrap_or_else(|| { + *error = true; + &min_prec + }); + if subprec as usize <= prec as usize || *error { + break; + } + rhs = parse_expr_helper(hashmap, args, rhs, subprec, error); + } + lhs = match prec { + Precedence::UnOp | Precedence::BUnOp => { + *error = true; + false + } + Precedence::And => lhs && rhs, + Precedence::Or => lhs || rhs, + Precedence::BinOp => three( + &[ + if lhs { b" " } else { b"" }, + op, + if rhs { b" " } else { b"" }, + ], + error, + ), + Precedence::Paren => unimplemented!(), // TODO: implement parentheses + _ => unreachable!(), + }; + if args.len() > 0 { + prec = *hashmap.get(&args[0]).unwrap_or_else(|| { + *error = true; + &min_prec + }); + } + } + lhs +} + +#[inline] +fn setup_hashmap<'a>() -> HashMap<&'a [u8], Precedence> { + let mut hashmap = HashMap::<&'a [u8], Precedence>::new(); + + hashmap.insert(b"-b", Precedence::UnOp); + hashmap.insert(b"-c", Precedence::UnOp); + hashmap.insert(b"-d", Precedence::UnOp); + hashmap.insert(b"-e", Precedence::UnOp); + hashmap.insert(b"-f", Precedence::UnOp); + hashmap.insert(b"-g", Precedence::UnOp); + hashmap.insert(b"-h", Precedence::UnOp); + hashmap.insert(b"-L", Precedence::UnOp); + hashmap.insert(b"-n", Precedence::UnOp); + hashmap.insert(b"-p", Precedence::UnOp); + hashmap.insert(b"-r", Precedence::UnOp); + hashmap.insert(b"-S", Precedence::UnOp); + hashmap.insert(b"-s", Precedence::UnOp); + hashmap.insert(b"-t", Precedence::UnOp); + hashmap.insert(b"-u", Precedence::UnOp); + hashmap.insert(b"-w", Precedence::UnOp); + hashmap.insert(b"-x", Precedence::UnOp); + hashmap.insert(b"-z", Precedence::UnOp); + + hashmap.insert(b"=", Precedence::BinOp); + hashmap.insert(b"!=", Precedence::BinOp); + hashmap.insert(b"-eq", Precedence::BinOp); + hashmap.insert(b"-ne", Precedence::BinOp); + hashmap.insert(b"-gt", Precedence::BinOp); + hashmap.insert(b"-ge", Precedence::BinOp); + hashmap.insert(b"-lt", Precedence::BinOp); + hashmap.insert(b"-le", Precedence::BinOp); + + hashmap.insert(b"!", Precedence::BUnOp); + + hashmap.insert(b"-a", Precedence::And); + hashmap.insert(b"-o", Precedence::Or); + + hashmap.insert(b"(", Precedence::Paren); + hashmap.insert(b")", Precedence::Paren); + + hashmap +} + +#[derive(Eq, PartialEq)] +enum PathCondition { + BlockSpecial, + CharacterSpecial, + Directory, + Exists, + Regular, + GroupIDFlag, + SymLink, + FIFO, + Readable, + Socket, + NonEmpty, + UserIDFlag, + Writable, + Executable, +} + +#[cfg(not(any(windows, target_os = "sunrise")))] +fn path(path: &[u8], cond: PathCondition) -> bool { + use std::os::unix::fs::{MetadataExt, FileTypeExt}; + use std::os::unix::ffi::OsStrExt; + use std::fs::{self, Metadata}; + use std::ffi::OsStr; + + let path = OsStr::from_bytes(path); + + const S_ISUID: u32 = 0o4000; + const S_ISGID: u32 = 0o2000; + + enum Permission { + Read = 0o4, + Write = 0o2, + Execute = 0o1, + } + + let perm = |metadata: Metadata, p: Permission| { + #[cfg(not(target_os = "redox"))] + let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) }; + #[cfg(target_os = "redox")] + let (uid, gid) = (syscall::getuid().unwrap() as u32, + syscall::getgid().unwrap() as u32); + + if uid == metadata.uid() { + metadata.mode() & ((p as u32) << 6) != 0 + } else if gid == metadata.gid() { + metadata.mode() & ((p as u32) << 3) != 0 + } else { + metadata.mode() & ((p as u32)) != 0 + } + }; + + let metadata = if cond == PathCondition::SymLink { + fs::symlink_metadata(path) + } else { + fs::metadata(path) + }; + + let metadata = match metadata { + Ok(metadata) => metadata, + Err(_) => { return false; } + }; + + let file_type = metadata.file_type(); + + match cond { + PathCondition::BlockSpecial => file_type.is_block_device(), + PathCondition::CharacterSpecial => file_type.is_char_device(), + PathCondition::Directory => file_type.is_dir(), + PathCondition::Exists => true, + PathCondition::Regular => file_type.is_file(), + PathCondition::GroupIDFlag => metadata.mode() & S_ISGID != 0, + PathCondition::SymLink => metadata.file_type().is_symlink(), + PathCondition::FIFO => file_type.is_fifo(), + PathCondition::Readable => perm(metadata, Permission::Read), + PathCondition::Socket => file_type.is_socket(), + PathCondition::NonEmpty => metadata.size() > 0, + PathCondition::UserIDFlag => metadata.mode() & S_ISUID != 0, + PathCondition::Writable => perm(metadata, Permission::Write), + PathCondition::Executable => perm(metadata, Permission::Execute), + } +} + +#[cfg(any(windows, target_os = "sunrise"))] +fn path(path: &[u8], cond: PathCondition) -> bool { + use std::fs::metadata; + let path = from_utf8(path).unwrap(); + let stat = match metadata(path) { + Ok(s) => s, + _ => return false, + }; + match cond { + PathCondition::BlockSpecial => false, + PathCondition::CharacterSpecial => false, + PathCondition::Directory => stat.is_dir(), + PathCondition::Exists => true, + PathCondition::Regular => stat.is_file(), + PathCondition::GroupIDFlag => false, + PathCondition::SymLink => false, + PathCondition::FIFO => false, + PathCondition::Readable => false, // TODO + PathCondition::Socket => false, + PathCondition::NonEmpty => stat.len() > 0, + PathCondition::UserIDFlag => false, + PathCondition::Writable => false, // TODO + PathCondition::Executable => false, // TODO + } +} diff --git a/coreutils/src/timeout/Cargo.toml b/coreutils/src/timeout/Cargo.toml new file mode 100644 index 000000000..69fd52e4f --- /dev/null +++ b/coreutils/src/timeout/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "timeout" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_timeout" +path = "timeout.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +time = "0.1.40" + +[dependencies.uucore] +version = "0.0.1" +features = ["parse_time", "process"] + +[[bin]] +name = "timeout" +path = "../../uumain.rs" diff --git a/coreutils/src/timeout/timeout.rs b/coreutils/src/timeout/timeout.rs new file mode 100644 index 000000000..cd8965955 --- /dev/null +++ b/coreutils/src/timeout/timeout.rs @@ -0,0 +1,174 @@ +#![crate_name = "uu_timeout"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate libc; +extern crate time; + +#[macro_use] +extern crate uucore; + +use std::io::ErrorKind; +use std::process::{Command, Stdio}; +use std::time::Duration; +use uucore::process::ChildExt; + +static NAME: &str = "timeout"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +const ERR_EXIT_STATUS: i32 = 125; + +pub fn uumain(args: Vec) -> i32 { + let program = args[0].clone(); + + let mut opts = getopts::Options::new(); + opts.optflag( + "", + "preserve-status", + "exit with the same status as COMMAND, even when the command times out", + ); + opts.optflag("", "foreground", "when not running timeout directly from a shell prompt, allow COMMAND to read from the TTY and get TTY signals; in this mode, children of COMMAND will not be timed out"); + opts.optopt("k", "kill-after", "also send a KILL signal if COMMAND is still running this long after the initial signal was sent", "DURATION"); + opts.optflag("s", "signal", "specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals"); + 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!(ERR_EXIT_STATUS, "{}", f), + }; + if matches.opt_present("help") { + print!( + "{} {} + +Usage: + {} [OPTION] DURATION COMMAND [ARG]... + +{}", + NAME, + VERSION, + program, + &opts.usage("Start COMMAND, and kill it if still running after DURATION.") + ); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else if matches.free.len() < 2 { + show_error!("missing an argument"); + show_error!("for help, try '{0} --help'", program); + return ERR_EXIT_STATUS; + } else { + let status = matches.opt_present("preserve-status"); + let foreground = matches.opt_present("foreground"); + let kill_after = match matches.opt_str("kill-after") { + Some(tstr) => match uucore::parse_time::from_str(&tstr) { + Ok(time) => time, + Err(f) => { + show_error!("{}", f); + return ERR_EXIT_STATUS; + } + }, + None => Duration::new(0, 0), + }; + let signal = match matches.opt_str("signal") { + Some(sigstr) => match uucore::signals::signal_by_name_or_value(&sigstr) { + Some(sig) => sig, + None => { + show_error!("invalid signal '{}'", sigstr); + return ERR_EXIT_STATUS; + } + }, + None => uucore::signals::signal_by_name_or_value("TERM").unwrap(), + }; + let duration = match uucore::parse_time::from_str(&matches.free[0]) { + Ok(time) => time, + Err(f) => { + show_error!("{}", f); + return ERR_EXIT_STATUS; + } + }; + return timeout( + &matches.free[1], + &matches.free[2..], + duration, + signal, + kill_after, + foreground, + status, + ); + } + + 0 +} + +fn timeout( + cmdname: &str, + args: &[String], + duration: Duration, + signal: usize, + kill_after: Duration, + foreground: bool, + preserve_status: bool, +) -> i32 { + if !foreground { + unsafe { libc::setpgid(0, 0) }; + } + let mut process = match Command::new(cmdname) + .args(args) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + { + Ok(p) => p, + Err(err) => { + show_error!("failed to execute process: {}", err); + if err.kind() == ErrorKind::NotFound { + // XXX: not sure which to use + return 127; + } else { + // XXX: this may not be 100% correct... + return 126; + } + } + }; + match process.wait_or_timeout(duration) { + Ok(Some(status)) => status.code().unwrap_or_else(|| status.signal().unwrap()), + Ok(None) => { + return_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); + match process.wait_or_timeout(kill_after) { + Ok(Some(status)) => { + if preserve_status { + status.code().unwrap_or_else(|| status.signal().unwrap()) + } else { + 124 + } + } + Ok(None) => { + if kill_after == Duration::new(0, 0) { + // XXX: this may not be right + return 124; + } + return_if_err!( + ERR_EXIT_STATUS, + process + .send_signal(uucore::signals::signal_by_name_or_value("KILL").unwrap()) + ); + return_if_err!(ERR_EXIT_STATUS, process.wait()); + 137 + } + Err(_) => 124, + } + } + Err(_) => { + return_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); + ERR_EXIT_STATUS + } + } +} diff --git a/coreutils/src/touch/Cargo.toml b/coreutils/src/touch/Cargo.toml new file mode 100644 index 000000000..d29303780 --- /dev/null +++ b/coreutils/src/touch/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "touch" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_touch" +path = "touch.rs" + +[dependencies] +filetime = "0.2.1" +getopts = "0.2.18" +time = "0.1.40" + +[dependencies.uucore] +version = "0.0.1" +features = ["libc"] + +[[bin]] +name = "touch" +path = "../../uumain.rs" diff --git a/coreutils/src/touch/touch.rs b/coreutils/src/touch/touch.rs new file mode 100644 index 000000000..ed75a7676 --- /dev/null +++ b/coreutils/src/touch/touch.rs @@ -0,0 +1,243 @@ +#![crate_name = "uu_touch"] + +// This file is part of the uutils coreutils package. +// +// (c) Nick Platt +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +pub extern crate filetime; +extern crate getopts; +extern crate time; + +#[macro_use] +extern crate uucore; + +use filetime::*; +use std::fs::{self, File}; +use std::io::Error; +use std::path::Path; + +static NAME: &str = "touch"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +// 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 + }) +); + +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) + }) +); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag("a", "", "change only the access time"); + opts.optflag("c", "no-create", "do not create any files"); + opts.optopt( + "d", + "date", + "parse argument and use it instead of current time", + "STRING", + ); + opts.optflag( + "h", + "no-dereference", + "affect each symbolic link instead of any referenced file \ + (only for systems that can change the timestamps of a symlink)", + ); + opts.optflag("m", "", "change only the modification time"); + opts.optopt( + "r", + "reference", + "use this file's times instead of the current time", + "FILE", + ); + opts.optopt( + "t", + "", + "use [[CC]YY]MMDDhhmm[.ss] instead of the current time", + "STAMP", + ); + opts.optopt( + "", + "time", + "change only the specified time: \"access\", \"atime\", or \ + \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \ + equivalent to -m", + "WORD", + ); + 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(e) => panic!("Invalid options\n{}", e), + }; + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + if matches.opt_present("help") || matches.free.is_empty() { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage: {} [OPTION]... FILE...", NAME); + println!(""); + println!( + "{}", + opts.usage( + "Update the access and modification times of \ + each FILE to the current time." + ) + ); + if matches.free.is_empty() { + return 1; + } + return 0; + } + + if matches.opt_present("date") + && matches.opts_present(&["reference".to_owned(), "t".to_owned()]) + || matches.opt_present("reference") + && matches.opts_present(&["date".to_owned(), "t".to_owned()]) + || matches.opt_present("t") + && matches.opts_present(&["date".to_owned(), "reference".to_owned()]) + { + panic!("Invalid options: cannot specify reference time from more than one source"); + } + + let (mut atime, mut mtime) = if matches.opt_present("reference") { + stat( + &matches.opt_str("reference").unwrap()[..], + !matches.opt_present("no-dereference"), + ) + } else if matches.opts_present(&["date".to_owned(), "t".to_owned()]) { + let timestamp = if matches.opt_present("date") { + parse_date(matches.opt_str("date").unwrap().as_ref()) + } else { + parse_timestamp(matches.opt_str("t").unwrap().as_ref()) + }; + (timestamp, timestamp) + } else { + let now = local_tm_to_filetime!(time::now()); + (now, now) + }; + + for filename in &matches.free { + let path = &filename[..]; + + if !Path::new(path).exists() { + // no-dereference included here for compatibility + if matches.opts_present(&["no-create".to_owned(), "no-dereference".to_owned()]) { + continue; + } + + if let Err(e) = File::create(path) { + show_warning!("cannot touch '{}': {}", path, e); + continue; + }; + + // Minor optimization: if no reference time was specified, we're done. + if !matches.opts_present(&["date".to_owned(), "reference".to_owned(), "t".to_owned()]) { + continue; + } + } + + // If changing "only" atime or mtime, grab the existing value of the other. + // Note that "-a" and "-m" may be passed together; this is not an xor. + if matches.opts_present(&["a".to_owned(), "m".to_owned(), "time".to_owned()]) { + let st = stat(path, !matches.opt_present("no-dereference")); + let time = matches.opt_strs("time"); + + if !(matches.opt_present("a") || time.contains(&"access".to_owned()) + || time.contains(&"atime".to_owned()) + || time.contains(&"use".to_owned())) + { + atime = st.0; + } + + if !(matches.opt_present("m") || time.contains(&"modify".to_owned()) + || time.contains(&"mtime".to_owned())) + { + mtime = st.1; + } + } + + if matches.opt_present("h") { + if let Err(e) = set_symlink_file_times(path, atime, mtime) { + show_warning!("cannot touch '{}': {}", path, e); + } + } else { + if let Err(e) = filetime::set_file_times(path, atime, mtime) { + show_warning!("cannot touch '{}': {}", path, e); + } + } + } + + 0 +} + +fn stat(path: &str, follow: bool) -> (FileTime, FileTime) { + let metadata = if follow { + fs::symlink_metadata(path) + } else { + fs::metadata(path) + }; + + match metadata { + Ok(m) => ( + FileTime::from_last_access_time(&m), + FileTime::from_last_modification_time(&m), + ), + Err(_) => crash!( + 1, + "failed to get attributes of '{}': {}", + path, + Error::last_os_error() + ), + } +} + +fn parse_date(str: &str) -> FileTime { + // This isn't actually compatible with GNU touch, but there doesn't seem to + // be any simple specification for what format this parameter allows and I'm + // not about to implement GNU parse_datetime. + // http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y + match time::strptime(str, "%c") { + Ok(tm) => local_tm_to_filetime!(to_local!(tm)), + Err(e) => panic!("Unable to parse date\n{}", e), + } +} + +fn parse_timestamp(s: &str) -> FileTime { + let now = time::now(); + let (format, ts) = match s.chars().count() { + 15 => ("%Y%m%d%H%M.%S", s.to_owned()), + 12 => ("%Y%m%d%H%M", s.to_owned()), + 13 => ("%y%m%d%H%M.%S", s.to_owned()), + 10 => ("%y%m%d%H%M", s.to_owned()), + 11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)), + 8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)), + _ => panic!("Unknown timestamp format"), + }; + + match time::strptime(&ts, format) { + Ok(tm) => local_tm_to_filetime!(to_local!(tm)), + Err(e) => panic!("Unable to parse timestamp\n{}", e), + } +} diff --git a/coreutils/src/tr/Cargo.toml b/coreutils/src/tr/Cargo.toml new file mode 100644 index 000000000..4c3421b74 --- /dev/null +++ b/coreutils/src/tr/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "tr" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_tr" +path = "tr.rs" + +[dependencies] +getopts = "0.2.18" +bit-set = "0.5.0" +fnv = "1.0.5" +uucore = "0.0.1" + +[[bin]] +name = "tr" +path = "../../uumain.rs" diff --git a/coreutils/src/tr/expand.rs b/coreutils/src/tr/expand.rs new file mode 100644 index 000000000..d9d38688a --- /dev/null +++ b/coreutils/src/tr/expand.rs @@ -0,0 +1,116 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Michael Gehring + * (c) kwantam + * 20150428 created `expand` module to eliminate most allocs during setup + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use std::char::from_u32; +use std::cmp::min; +use std::iter::Peekable; +use std::ops::Range; + +#[inline] +fn unescape_char(c: char) -> char { + match c { + 'a' => 0x07u8 as char, + 'b' => 0x08u8 as char, + 'f' => 0x0cu8 as char, + 'v' => 0x0bu8 as char, + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + _ => c, + } +} + +struct Unescape<'a> { + string: &'a str, +} + +impl<'a> Iterator for Unescape<'a> { + type Item = char; + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let slen = self.string.len(); + (min(slen, 1), None) + } + + #[inline] + fn next(&mut self) -> Option { + if self.string.len() == 0 { + return None; + } + + // is the next character an escape? + let (ret, idx) = match self.string.chars().next().unwrap() { + '\\' if self.string.len() > 1 => { + // yes---it's \ and it's not the last char in a string + // we know that \ is 1 byte long so we can index into the string safely + let c = self.string[1..].chars().next().unwrap(); + (Some(unescape_char(c)), 1 + c.len_utf8()) + } + c => (Some(c), c.len_utf8()), // not an escape char + }; + + self.string = &self.string[idx..]; // advance the pointer to the next char + ret + } +} + +pub struct ExpandSet<'a> { + range: Range, + unesc: Peekable>, +} + +impl<'a> Iterator for ExpandSet<'a> { + type Item = char; + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.unesc.size_hint() + } + + #[inline] + fn next(&mut self) -> Option { + // while the Range has elements, try to return chars from it + // but make sure that they actually turn out to be Chars! + while let Some(n) = self.range.next() { + if let Some(c) = from_u32(n) { + return Some(c); + } + } + + if let Some(first) = self.unesc.next() { + // peek ahead + if self.unesc.peek() == Some(&'-') && match self.unesc.size_hint() { + (x, _) if x > 1 => true, // there's a range here; record it in our internal Range struct + _ => false, + } { + self.unesc.next(); // this is the '-' + let last = self.unesc.next().unwrap(); // this is the end of the range + + self.range = first as u32 + 1..last as u32 + 1; + } + + return Some(first); // in any case, return the next char + } + + None + } +} + +impl<'a> ExpandSet<'a> { + #[inline] + pub fn new(s: &'a str) -> ExpandSet<'a> { + ExpandSet { + range: 0..0, + unesc: Unescape { string: s }.peekable(), + } + } +} diff --git a/coreutils/src/tr/tr.rs b/coreutils/src/tr/tr.rs new file mode 100644 index 000000000..1fe08d454 --- /dev/null +++ b/coreutils/src/tr/tr.rs @@ -0,0 +1,259 @@ +#![crate_name = "uu_tr"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Michael Gehring + * (c) kwantam + * 20150428 created `expand` module to eliminate most allocs during setup + * (c) Sergey "Shnatsel" Davidoff + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate bit_set; +extern crate fnv; +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use bit_set::BitSet; +use getopts::Options; +use std::io::{stdin, stdout, BufRead, BufWriter, Write}; +use fnv::FnvHashMap; + +use expand::ExpandSet; + +mod expand; + +static NAME: &str = "tr"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +const BUFFER_LEN: usize = 1024; + +trait SymbolTranslator { + fn translate(&self, c: &char, prev_c: &char) -> Option; +} + +struct DeleteOperation { + bset: BitSet, + complement: bool, +} + +impl DeleteOperation { + fn new(set: ExpandSet, complement: bool) -> DeleteOperation { + DeleteOperation { + bset: set.map(|c| c as usize).collect(), + complement: complement, + } + } +} + +impl SymbolTranslator for DeleteOperation { + fn translate(&self, c: &char, _prev_c: &char) -> Option { + let uc = *c as usize; + if self.complement == self.bset.contains(uc) { + Some(*c) + } else { + None + } + } +} + +struct SqueezeOperation { + squeeze_set: BitSet, + complement: bool, +} + +impl SqueezeOperation { + fn new(squeeze_set: ExpandSet, complement: bool) -> SqueezeOperation { + SqueezeOperation { + squeeze_set: squeeze_set.map(|c| c as usize).collect(), + complement: complement, + } + } +} + +impl SymbolTranslator for SqueezeOperation { + fn translate(&self, c: &char, prev_c: &char) -> Option { + if *prev_c == *c && self.complement != self.squeeze_set.contains(*c as usize) { + None + } else { + Some(*c) + } + } +} + +struct DeleteAndSqueezeOperation { + delete_set: BitSet, + squeeze_set: BitSet, + complement: bool, +} + +impl DeleteAndSqueezeOperation { + fn new( + delete_set: ExpandSet, + squeeze_set: ExpandSet, + complement: bool, + ) -> DeleteAndSqueezeOperation { + DeleteAndSqueezeOperation { + delete_set: delete_set.map(|c| c as usize).collect(), + squeeze_set: squeeze_set.map(|c| c as usize).collect(), + complement: complement, + } + } +} + +impl SymbolTranslator for DeleteAndSqueezeOperation { + fn translate(&self, c: &char, prev_c: &char) -> Option { + if self.complement != self.delete_set.contains(*c as usize) + || *prev_c == *c && self.squeeze_set.contains(*c as usize) + { + None + } else { + Some(*c) + } + } +} + +struct TranslateOperation { + translate_map: FnvHashMap, +} + +impl TranslateOperation { + fn new(set1: ExpandSet, set2: &mut ExpandSet, truncate: bool) -> TranslateOperation { + let mut map = FnvHashMap::default(); + let mut s2_prev = '_'; + for i in set1 { + let s2_next = set2.next(); + + if s2_next.is_none() && truncate { + map.insert(i as usize, i); + } else { + s2_prev = s2_next.unwrap_or(s2_prev); + map.insert(i as usize, s2_prev); + } + } + TranslateOperation { translate_map: map } + } +} + +impl SymbolTranslator for TranslateOperation { + fn translate(&self, c: &char, _prev_c: &char) -> Option { + Some(*self.translate_map.get(&(*c as usize)).unwrap_or(c)) + } +} + +fn translate_input(input: &mut BufRead, output: &mut Write, translator: T) { + let mut buf = String::with_capacity(BUFFER_LEN + 4); + let mut output_buf = String::with_capacity(BUFFER_LEN + 4); + + while let Ok(length) = input.read_line(&mut buf) { + let mut prev_c = 0 as char; + if length == 0 { + break; + } + { + // isolation to make borrow checker happy + let filtered = buf.chars().filter_map(|c| { + let res = translator.translate(&c, &prev_c); + if res.is_some() { + prev_c = c; + } + res + }); + + output_buf.extend(filtered); + output.write_all(output_buf.as_bytes()).unwrap(); + } + buf.clear(); + output_buf.clear(); + } +} + +fn usage(opts: &Options) { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {} [OPTIONS] SET1 [SET2]", NAME); + println!(""); + println!("{}", opts.usage("Translate or delete characters.")); +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + opts.optflag("c", "complement", "use the complement of SET1"); + opts.optflag("C", "", "same as -c"); + opts.optflag("d", "delete", "delete characters in SET1"); + opts.optflag("h", "help", "display this help and exit"); + opts.optflag("s", "squeeze", "replace each sequence of a repeated character that is listed in the last specified SET, with a single occurrence of that character"); + opts.optflag( + "t", + "truncate-set1", + "first truncate SET1 to length of SET2", + ); + opts.optflag("V", "version", "output version information and exit"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(err) => { + show_error!("{}", err); + return 1; + } + }; + + if matches.opt_present("help") { + usage(&opts); + return 0; + } + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + if matches.free.is_empty() { + usage(&opts); + return 1; + } + + let dflag = matches.opt_present("d"); + let cflag = matches.opts_present(&["c".to_owned(), "C".to_owned()]); + let sflag = matches.opt_present("s"); + let tflag = matches.opt_present("t"); + let sets = matches.free; + + if cflag && !dflag && !sflag { + show_error!("-c is only supported with -d or -s"); + return 1; + } + + let stdin = stdin(); + let mut locked_stdin = stdin.lock(); + let stdout = stdout(); + let locked_stdout = stdout.lock(); + let mut buffered_stdout = BufWriter::new(locked_stdout); + + let set1 = ExpandSet::new(sets[0].as_ref()); + if dflag { + if sflag { + let set2 = ExpandSet::new(sets[1].as_ref()); + let op = DeleteAndSqueezeOperation::new(set1, set2, cflag); + translate_input(&mut locked_stdin, &mut buffered_stdout, op); + } else { + let op = DeleteOperation::new(set1, cflag); + translate_input(&mut locked_stdin, &mut buffered_stdout, op); + } + } else if sflag { + let op = SqueezeOperation::new(set1, cflag); + translate_input(&mut locked_stdin, &mut buffered_stdout, op); + } else { + let mut set2 = ExpandSet::new(sets[1].as_ref()); + let op = TranslateOperation::new(set1, &mut set2, tflag); + translate_input(&mut locked_stdin, &mut buffered_stdout, op) + } + + 0 +} diff --git a/coreutils/src/true/Cargo.toml b/coreutils/src/true/Cargo.toml new file mode 100644 index 000000000..41c95aec2 --- /dev/null +++ b/coreutils/src/true/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "true" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_true" +path = "true.rs" + +[dependencies] +uucore = "0.0.1" + +[[bin]] +name = "true" +path = "../../uumain.rs" diff --git a/coreutils/src/true/true.rs b/coreutils/src/true/true.rs new file mode 100644 index 000000000..d1d3d70b9 --- /dev/null +++ b/coreutils/src/true/true.rs @@ -0,0 +1,14 @@ +#![crate_name = "uu_true"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +pub fn uumain(_: Vec) -> i32 { + 0 +} diff --git a/coreutils/src/truncate/Cargo.toml b/coreutils/src/truncate/Cargo.toml new file mode 100644 index 000000000..a7a4cd539 --- /dev/null +++ b/coreutils/src/truncate/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "truncate" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_truncate" +path = "truncate.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "truncate" +path = "../../uumain.rs" diff --git a/coreutils/src/truncate/truncate.rs b/coreutils/src/truncate/truncate.rs new file mode 100644 index 000000000..47070d9a7 --- /dev/null +++ b/coreutils/src/truncate/truncate.rs @@ -0,0 +1,233 @@ +#![crate_name = "uu_truncate"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Alex Lyon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use std::fs::{metadata, File, OpenOptions}; +use std::io::Result; +use std::path::Path; + +#[derive(Eq, PartialEq)] +enum TruncateMode { + Reference, + Extend, + Reduce, + AtMost, + AtLeast, + RoundDown, + RoundUp, +} + +static NAME: &str = "truncate"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag("c", "no-create", "do not create files that do not exist"); + opts.optflag( + "o", + "io-blocks", + "treat SIZE as the number of I/O blocks of the file rather than bytes (NOT IMPLEMENTED)", + ); + opts.optopt( + "r", + "reference", + "base the size of each file on the size of RFILE", + "RFILE", + ); + opts.optopt("s", "size", "set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified", "SIZE"); + 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!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {} [OPTION]... FILE...", NAME); + println!(""); + print!( + "{}", + opts.usage("Shrink or extend the size of each file to the specified size.") + ); + print!( + " +SIZE is an integer with an optional prefix and optional unit. +The available units (K, M, G, T, P, E, Z, and Y) use the following format: + 'KB' => 1000 (kilobytes) + 'K' => 1024 (kibibytes) + 'MB' => 1000*1000 (megabytes) + 'M' => 1024*1024 (mebibytes) + 'GB' => 1000*1000*1000 (gigabytes) + 'G' => 1024*1024*1024 (gibibytes) +SIZE may also be prefixed by one of the following to adjust the size of each +file based on its current size: + '+' => extend by + '-' => reduce by + '<' => at most + '>' => at least + '/' => round down to multiple of + '%' => round up to multiple of +" + ); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else if matches.free.is_empty() { + show_error!("missing an argument"); + return 1; + } else { + let no_create = matches.opt_present("no-create"); + let io_blocks = matches.opt_present("io-blocks"); + let reference = matches.opt_str("reference"); + let size = matches.opt_str("size"); + if reference.is_none() && size.is_none() { + crash!(1, "you must specify either --reference or --size"); + } else { + match truncate(no_create, io_blocks, reference, size, matches.free) { + Ok(()) => ( /* pass */ ), + Err(_) => return 1, + } + } + } + + 0 +} + +fn truncate( + no_create: bool, + _: bool, + reference: Option, + size: Option, + filenames: Vec, +) -> Result<()> { + let (refsize, mode) = match reference { + Some(rfilename) => { + let _ = match File::open(Path::new(&rfilename)) { + Ok(m) => m, + Err(f) => crash!(1, "{}", f.to_string()), + }; + match metadata(rfilename) { + Ok(meta) => (meta.len(), TruncateMode::Reference), + Err(f) => crash!(1, "{}", f.to_string()), + } + } + None => parse_size(size.unwrap().as_ref()), + }; + for filename in &filenames { + let path = Path::new(filename); + match OpenOptions::new() + .read(true) + .write(true) + .create(!no_create) + .open(path) + { + Ok(file) => { + let fsize = match metadata(filename) { + Ok(meta) => meta.len(), + Err(f) => { + show_warning!("{}", f.to_string()); + continue; + } + }; + let tsize: u64 = match mode { + TruncateMode::Reference => refsize, + TruncateMode::Extend => fsize + refsize, + TruncateMode::Reduce => fsize - refsize, + TruncateMode::AtMost => if fsize > refsize { + refsize + } else { + fsize + }, + TruncateMode::AtLeast => if fsize < refsize { + refsize + } else { + fsize + }, + TruncateMode::RoundDown => fsize - fsize % refsize, + TruncateMode::RoundUp => fsize + fsize % refsize, + }; + match file.set_len(tsize) { + Ok(_) => {} + Err(f) => crash!(1, "{}", f.to_string()), + }; + } + Err(f) => crash!(1, "{}", f.to_string()), + } + } + Ok(()) +} + +fn parse_size(size: &str) -> (u64, TruncateMode) { + let mode = match size.chars().next().unwrap() { + '+' => TruncateMode::Extend, + '-' => TruncateMode::Reduce, + '<' => TruncateMode::AtMost, + '>' => TruncateMode::AtLeast, + '/' => TruncateMode::RoundDown, + '*' => TruncateMode::RoundUp, + _ => TruncateMode::Reference, /* assume that the size is just a number */ + }; + let bytes = { + let mut slice = if mode == TruncateMode::Reference { + size + } else { + &size[1..] + }; + if slice.chars().last().unwrap().is_alphabetic() { + slice = &slice[..slice.len() - 1]; + if slice.len() > 0 && slice.chars().last().unwrap().is_alphabetic() { + slice = &slice[..slice.len() - 1]; + } + } + slice + }.to_owned(); + let mut number: u64 = match bytes.parse() { + Ok(num) => num, + Err(e) => crash!(1, "'{}' is not a valid number: {}", size, e), + }; + if size.chars().last().unwrap().is_alphabetic() { + number *= match size.chars().last().unwrap().to_ascii_uppercase() { + 'B' => match size.chars() + .nth(size.len() - 2) + .unwrap() + .to_ascii_uppercase() + { + 'K' => 1000u64, + 'M' => 1000u64.pow(2), + 'G' => 1000u64.pow(3), + 'T' => 1000u64.pow(4), + 'P' => 1000u64.pow(5), + 'E' => 1000u64.pow(6), + 'Z' => 1000u64.pow(7), + 'Y' => 1000u64.pow(8), + letter => crash!(1, "'{}B' is not a valid suffix.", letter), + }, + 'K' => 1024u64, + 'M' => 1024u64.pow(2), + 'G' => 1024u64.pow(3), + 'T' => 1024u64.pow(4), + 'P' => 1024u64.pow(5), + 'E' => 1024u64.pow(6), + 'Z' => 1024u64.pow(7), + 'Y' => 1024u64.pow(8), + letter => crash!(1, "'{}' is not a valid suffix.", letter), + }; + } + (number, mode) +} diff --git a/coreutils/src/tsort/Cargo.toml b/coreutils/src/tsort/Cargo.toml new file mode 100644 index 000000000..b2c3fe4dc --- /dev/null +++ b/coreutils/src/tsort/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tsort" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_tsort" +path = "tsort.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "tsort" +path = "../../uumain.rs" diff --git a/coreutils/src/tsort/tsort.rs b/coreutils/src/tsort/tsort.rs new file mode 100644 index 000000000..f930e620d --- /dev/null +++ b/coreutils/src/tsort/tsort.rs @@ -0,0 +1,195 @@ +#![crate_name = "uu_tsort"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Ben Eggers + * (c) Akira Hayakawa + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +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"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + 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 mut stdin_buf; + let mut file_buf; + let mut reader = BufReader::new(if input == "-" { + stdin_buf = stdin(); + &mut stdin_buf as &mut Read + } else { + file_buf = match File::open(Path::new(&input)) { + Ok(a) => a, + _ => { + show_error!("{}: No such file or directory", input); + return 1; + } + }; + &mut file_buf as &mut Read + }); + + let mut g = Graph::new(); + loop { + let mut line = String::new(); + match reader.read_line(&mut line) { + Ok(_) => { + let tokens: Vec = line.trim_end() + .split_whitespace() + .map(|s| s.to_owned()) + .collect(); + if tokens.is_empty() { + break; + } + for ab in tokens.chunks(2) { + match ab.len() { + 2 => g.add_edge(&ab[0], &ab[1]), + _ => crash!(1, "{}: input contains an odd number of tokens", input), + } + } + } + _ => break, + } + } + + g.run_tsort(); + + if !g.is_acyclic() { + crash!(1, "{}, input contains a loop:", input); + } + + for x in &g.result { + println!("{}", x); + } + + 0 +} + +// We use String as a representation of node here +// but using integer may improve performance. +struct Graph { + in_edges: HashMap>, + out_edges: HashMap>, + result: Vec, +} + +impl Graph { + fn new() -> Graph { + Graph { + in_edges: HashMap::new(), + out_edges: HashMap::new(), + result: vec![], + } + } + + fn has_node(&self, n: &str) -> bool { + self.in_edges.contains_key(n) + } + + fn has_edge(&self, from: &str, to: &str) -> bool { + self.in_edges.get(to).unwrap().contains(from) + } + + fn init_node(&mut self, n: &String) { + self.in_edges.insert(n.clone(), HashSet::new()); + self.out_edges.insert(n.clone(), vec![]); + } + + fn add_edge(&mut self, from: &String, to: &String) { + if !self.has_node(to) { + self.init_node(to); + } + + if !self.has_node(from) { + self.init_node(from); + } + + if from != to && !self.has_edge(from, to) { + self.in_edges.get_mut(to).unwrap().insert(from.clone()); + self.out_edges.get_mut(from).unwrap().push(to.clone()); + } + } + + // Kahn's algorithm + // O(|V|+|E|) + fn run_tsort(&mut self) { + let mut start_nodes = vec![]; + for (n, edges) in &self.in_edges { + if edges.is_empty() { + start_nodes.push(n.clone()); + } + } + + while !start_nodes.is_empty() { + let n = start_nodes.remove(0); + + self.result.push(n.clone()); + + let n_out_edges = self.out_edges.get_mut(&n).unwrap(); + for m in n_out_edges.iter() { + let m_in_edges = self.in_edges.get_mut(m).unwrap(); + m_in_edges.remove(&n); + + // If m doesn't have other in-coming edges add it to start_nodes + if m_in_edges.is_empty() { + start_nodes.push(m.clone()); + } + } + n_out_edges.clear(); + } + } + + fn is_acyclic(&self) -> bool { + for (_, edges) in &self.out_edges { + if !edges.is_empty() { + return false; + } + } + true + } +} diff --git a/coreutils/src/tty/Cargo.toml b/coreutils/src/tty/Cargo.toml new file mode 100644 index 000000000..57185ce86 --- /dev/null +++ b/coreutils/src/tty/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "tty" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_tty" +path = "tty.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" + +[dependencies.uucore] +version = "0.0.1" +features = ["fs"] + +[[bin]] +name = "tty" +path = "../../uumain.rs" diff --git a/coreutils/src/tty/tty.rs b/coreutils/src/tty/tty.rs new file mode 100644 index 000000000..eaff78c96 --- /dev/null +++ b/coreutils/src/tty/tty.rs @@ -0,0 +1,82 @@ +#![crate_name = "uu_tty"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Synced with http://lingrok.org/xref/coreutils/src/tty.c + */ + +extern crate getopts; +extern crate libc; + +#[macro_use] +extern crate uucore; + +use std::ffi::CStr; +use uucore::fs::is_stdin_interactive; + +extern "C" { + fn ttyname(filedesc: libc::c_int) -> *const libc::c_char; +} + +static NAME: &str = "tty"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + + opts.optflag("s", "silent", "print nothing, only return an exit status"); + 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!(2, "{}", f), + }; + + if matches.opt_present("help") { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {} [OPTION]...", NAME); + println!(""); + print!( + "{}", + opts.usage("Print the file name of the terminal connected to standard input.") + ); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else { + let silent = matches.opt_present("s"); + + let tty = unsafe { + let ptr = ttyname(libc::STDIN_FILENO); + if !ptr.is_null() { + String::from_utf8_lossy(CStr::from_ptr(ptr).to_bytes()).to_string() + } else { + "".to_owned() + } + }; + + if !silent { + if !tty.chars().all(|c| c.is_whitespace()) { + println!("{}", tty); + } else { + println!("not a tty"); + } + } + + return if is_stdin_interactive() { + libc::EXIT_SUCCESS + } else { + libc::EXIT_FAILURE + }; + } + + 0 +} diff --git a/coreutils/src/uname/Cargo.toml b/coreutils/src/uname/Cargo.toml new file mode 100644 index 000000000..3129adac2 --- /dev/null +++ b/coreutils/src/uname/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "uname" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_uname" +path = "uname.rs" + +[dependencies] +clap = "2.32.0" +platform-info = "0.0.1" +uucore = "0.0.1" + +[[bin]] +name = "uname" +path = "../../uumain.rs" diff --git a/coreutils/src/uname/uname.rs b/coreutils/src/uname/uname.rs new file mode 100644 index 000000000..e9085db58 --- /dev/null +++ b/coreutils/src/uname/uname.rs @@ -0,0 +1,130 @@ +#![crate_name = "uu_uname"] + +// This file is part of the uutils coreutils package. +// +// (c) Joao Oliveira +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// + +// last synced with: uname (GNU coreutils) 8.21 + +extern crate clap; +extern crate platform_info; +#[macro_use] +extern crate uucore; + +use clap::{App, Arg}; +use platform_info::*; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); +const ABOUT: &str = "Print certain system information. With no OPTION, same as -s."; + +const OPT_ALL: &str = "all"; +const OPT_KERNELNAME: &str = "kernel-name"; +const OPT_NODENAME: &str = "nodename"; +const OPT_KERNELVERSION: &str = "kernel-version"; +const OPT_KERNELRELEASE: &str = "kernel-release"; +const OPT_MACHINE: &str = "machine"; + +//FIXME: unimplemented options +//const OPT_PROCESSOR: &'static str = "processor"; +//const OPT_HWPLATFORM: &'static str = "hardware-platform"; +const OPT_OS: &str = "operating-system"; + +#[cfg(target_os = "linux")] +const HOST_OS: &str = "GNU/Linux"; +#[cfg(target_os = "windows")] +const HOST_OS: &str = "Windows NT"; +#[cfg(target_os = "freebsd")] +const HOST_OS: &str = "FreeBSD"; +#[cfg(target_os = "openbsd")] +const HOST_OS: &str = "OpenBSD"; +#[cfg(target_os = "macos")] +const HOST_OS: &str = "Darwin"; +#[cfg(target_os = "fuchsia")] +const HOST_OS: &str = "Fuchsia"; +#[cfg(target_os = "redox")] +const HOST_OS: &str = "Redox"; +#[cfg(target_os = "sunrise")] +const HOST_OS: &str = "Sunrise"; + +pub fn uumain(args: Vec) -> i32 { + let usage = format!("{} [OPTION]...", executable!()); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .arg(Arg::with_name(OPT_ALL) + .short("a") + .long(OPT_ALL) + .help("Behave as though all of the options -mnrsv were specified.")) + .arg(Arg::with_name(OPT_KERNELNAME) + .short("s") + .long(OPT_KERNELNAME) + .alias("sysname") // Obsolescent option in GNU uname + .help("print the operating system name.")) + .arg(Arg::with_name(OPT_NODENAME) + .short("n") + .long(OPT_NODENAME) + .help("print the nodename (the nodename may be a name that the system is known by to a communications network).")) + .arg(Arg::with_name(OPT_KERNELRELEASE) + .short("r") + .long(OPT_KERNELRELEASE) + .alias("release") // Obsolescent option in GNU uname + .help("print the operating system release.")) + .arg(Arg::with_name(OPT_KERNELVERSION) + .short("v") + .long(OPT_KERNELVERSION) + .help("print the operating system version.")) + + //FIXME: unimplemented options + // .arg(Arg::with_name(OPT_PROCESSOR) + // .short("p") + // .long(OPT_PROCESSOR) + // .help("print the processor type (non-portable)")) + // .arg(Arg::with_name(OPT_HWPLATFORM) + // .short("i") + // .long(OPT_HWPLATFORM) + // .help("print the hardware platform (non-portable)")) + .arg(Arg::with_name(OPT_MACHINE) + .short("m") + .long(OPT_MACHINE) + .help("print the machine hardware name.")) + .get_matches_from(&args); + + let argc = args.len(); + let uname = return_if_err!(1, PlatformInfo::new()); + let mut output = String::new(); + + if matches.is_present(OPT_KERNELNAME) || matches.is_present(OPT_ALL) || argc == 1 { + output.push_str(&uname.sysname()); + output.push_str(" "); + } + + if matches.is_present(OPT_NODENAME) || matches.is_present(OPT_ALL) { + output.push_str(&uname.nodename()); + output.push_str(" "); + } + if matches.is_present(OPT_KERNELRELEASE) || matches.is_present(OPT_ALL) { + output.push_str(&uname.release()); + output.push_str(" "); + } + if matches.is_present(OPT_KERNELVERSION) || matches.is_present(OPT_ALL) { + output.push_str(&uname.version()); + output.push_str(" "); + } + if matches.is_present(OPT_MACHINE) || matches.is_present(OPT_ALL) { + output.push_str(&uname.machine()); + output.push_str(" "); + } + if matches.is_present(OPT_OS) || matches.is_present(OPT_ALL) { + output.push_str(HOST_OS); + output.push_str(" "); + } + println!("{}", output.trim_end()); + + 0 +} diff --git a/coreutils/src/unexpand/Cargo.toml b/coreutils/src/unexpand/Cargo.toml new file mode 100644 index 000000000..1521a9225 --- /dev/null +++ b/coreutils/src/unexpand/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "unexpand" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_unexpand" +path = "unexpand.rs" + +[dependencies] +getopts = "0.2.18" +unicode-width = "0.1.5" +uucore = "0.0.1" + +[[bin]] +name = "unexpand" +path = "../../uumain.rs" diff --git a/coreutils/src/unexpand/unexpand.rs b/coreutils/src/unexpand/unexpand.rs new file mode 100644 index 000000000..5df0dfda0 --- /dev/null +++ b/coreutils/src/unexpand/unexpand.rs @@ -0,0 +1,330 @@ +#![crate_name = "uu_unexpand"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Virgile Andreani + * (c) kwantam + * 20150428 updated to work with both UTF-8 and non-UTF-8 encodings + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; +extern crate unicode_width; + +#[macro_use] +extern crate uucore; + +use std::fs::File; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdout, Write}; +use std::str::from_utf8; +use unicode_width::UnicodeWidthChar; + +static NAME: &str = "unexpand"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +const DEFAULT_TABSTOP: usize = 8; + +fn tabstops_parse(s: String) -> Vec { + let words = s.split(',').collect::>(); + + let nums = words + .into_iter() + .map(|sn| { + sn.parse() + .unwrap_or_else(|_| crash!(1, "{}\n", "tab size contains invalid character(s)")) + }) + .collect::>(); + + if nums.iter().any(|&n| n == 0) { + crash!(1, "{}\n", "tab size cannot be 0"); + } + + if let (false, _) = nums.iter() + .fold((true, 0), |(acc, last), &n| (acc && last <= n, n)) + { + crash!(1, "{}\n", "tab sizes must be ascending"); + } + + nums +} + +struct Options { + files: Vec, + tabstops: Vec, + aflag: bool, + uflag: bool, +} + +impl Options { + fn new(matches: getopts::Matches) -> Options { + let tabstops = match matches.opt_str("t") { + None => vec![DEFAULT_TABSTOP], + Some(s) => tabstops_parse(s), + }; + + let aflag = (matches.opt_present("all") || matches.opt_present("tabs")) + && !matches.opt_present("first-only"); + let uflag = !matches.opt_present("U"); + + let files = if matches.free.is_empty() { + vec!["-".to_owned()] + } else { + matches.free + }; + + Options { + files: files, + tabstops: tabstops, + aflag: aflag, + uflag: uflag, + } + } +} + +pub fn uumain(args: Vec) -> i32 { + 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; + } + + unexpand(Options::new(matches)); + + 0 +} + +fn open(path: String) -> BufReader> { + let file_buf; + if path == "-" { + BufReader::new(Box::new(stdin()) as Box) + } else { + file_buf = match File::open(&path[..]) { + Ok(a) => a, + Err(e) => crash!(1, "{}: {}", &path[..], e), + }; + BufReader::new(Box::new(file_buf) as Box) + } +} + +fn next_tabstop(tabstops: &[usize], col: usize) -> Option { + if tabstops.len() == 1 { + Some(tabstops[0] - col % tabstops[0]) + } else { + // find next larger tab + match tabstops.iter().skip_while(|&&t| t <= col).next() { + Some(t) => Some(t - col), + None => None, // if there isn't one in the list, tab becomes a single space + } + } +} + +fn write_tabs( + output: &mut BufWriter, + tabstops: &[usize], + mut scol: usize, + col: usize, + prevtab: bool, + init: bool, + amode: bool, +) { + // This conditional establishes the following: + // We never turn a single space before a non-blank into + // a tab, unless it's at the start of the line. + let ai = init || amode; + if (ai && !prevtab && col > scol + 1) || (col > scol && (init || ai && prevtab)) { + while let Some(nts) = next_tabstop(tabstops, scol) { + if col < scol + nts { + break; + } + + safe_unwrap!(output.write_all("\t".as_bytes())); + scol += nts; + } + } + + while col > scol { + safe_unwrap!(output.write_all(" ".as_bytes())); + scol += 1; + } +} + +#[derive(PartialEq, Eq, Debug)] +enum CharType { + Backspace, + Space, + Tab, + Other, +} + +fn unexpand(options: Options) { + use self::CharType::*; + + let mut output = BufWriter::new(stdout()); + let ts = &options.tabstops[..]; + let mut buf = Vec::new(); + let lastcol = if ts.len() > 1 { *ts.last().unwrap() } else { 0 }; + + for file in options.files.into_iter() { + let mut fh = open(file); + + while match fh.read_until('\n' as u8, &mut buf) { + Ok(s) => s > 0, + Err(_) => !buf.is_empty(), + } { + let mut byte = 0; // offset into the buffer + let mut col = 0; // the current column + let mut scol = 0; // the start col for the current span, i.e., the already-printed width + let mut init = true; // are we at the start of the line? + let mut pctype = Other; + + while byte < buf.len() { + // when we have a finite number of columns, never convert past the last column + if lastcol > 0 && col >= lastcol { + write_tabs(&mut output, ts, scol, col, pctype == Tab, init, true); + safe_unwrap!(output.write_all(&buf[byte..])); + scol = col; + break; + } + + let (ctype, cwidth, nbytes) = if options.uflag { + let nbytes = char::from(buf[byte]).len_utf8(); + + // figure out how big the next char is, if it's UTF-8 + if byte + nbytes > buf.len() { + // make sure we don't overrun the buffer because of invalid UTF-8 + (Other, 1, 1) + } else if let Ok(t) = from_utf8(&buf[byte..byte + nbytes]) { + // Now that we think it's UTF-8, figure out what kind of char it is + match t.chars().next() { + Some(' ') => (Space, 0, 1), + Some('\t') => (Tab, 0, 1), + Some('\x08') => (Backspace, 0, 1), + Some(c) => (Other, UnicodeWidthChar::width(c).unwrap_or(0), nbytes), + None => { + // invalid char snuck past the utf8_validation_iterator somehow??? + (Other, 1, 1) + } + } + } else { + // otherwise, it's not valid + (Other, 1, 1) // implicit assumption: non-UTF8 char has display width 1 + } + } else { + ( + match buf[byte] { + // always take exactly 1 byte in strict ASCII mode + 0x20 => Space, + 0x09 => Tab, + 0x08 => Backspace, + _ => Other, + }, + 1, + 1, + ) + }; + + // now figure out how many columns this char takes up, and maybe print it + let tabs_buffered = init || options.aflag; + match ctype { + Space | Tab => { + // compute next col, but only write space or tab chars if not buffering + col += if ctype == Space { + 1 + } else { + next_tabstop(ts, col).unwrap_or(1) + }; + + if !tabs_buffered { + safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); + scol = col; // now printed up to this column + } + } + Other | Backspace => { + // always + write_tabs( + &mut output, + ts, + scol, + col, + pctype == Tab, + init, + options.aflag, + ); + init = false; // no longer at the start of a line + col = if ctype == Other { + // use computed width + col + cwidth + } else if col > 0 { + // Backspace case, but only if col > 0 + col - 1 + } else { + 0 + }; + safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); + scol = col; // we've now printed up to this column + } + } + + byte += nbytes; // move on to next char + pctype = ctype; // save the previous type + } + + // write out anything remaining + write_tabs(&mut output, ts, scol, col, pctype == Tab, init, true); + buf.truncate(0); // clear out the buffer + } + } + crash_if_err!(1, output.flush()) +} diff --git a/coreutils/src/uniq/Cargo.toml b/coreutils/src/uniq/Cargo.toml new file mode 100644 index 000000000..17b014f56 --- /dev/null +++ b/coreutils/src/uniq/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "uniq" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_uniq" +path = "uniq.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "uniq" +path = "../../uumain.rs" diff --git a/coreutils/src/uniq/uniq.rs b/coreutils/src/uniq/uniq.rs new file mode 100644 index 000000000..f82704b8b --- /dev/null +++ b/coreutils/src/uniq/uniq.rs @@ -0,0 +1,332 @@ +#![crate_name = "uu_uniq"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Chirag B Jadwani + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use getopts::{Matches, Options}; +use std::fs::File; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; +use std::path::Path; +use std::str::FromStr; + +static NAME: &str = "uniq"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(PartialEq)] +enum Delimiters { + Prepend, + Separate, + None, +} + +struct Uniq { + repeats_only: bool, + uniques_only: bool, + all_repeated: bool, + delimiters: Delimiters, + show_counts: bool, + skip_fields: Option, + slice_start: Option, + slice_stop: Option, + ignore_case: bool, + zero_terminated: bool, +} + +impl Uniq { + pub fn print_uniq( + &self, + reader: &mut BufReader, + writer: &mut BufWriter, + ) { + let mut lines: Vec = vec![]; + let mut first_line_printed = false; + let delimiters = &self.delimiters; + let line_terminator = self.get_line_terminator(); + + for io_line in reader.split(line_terminator) { + let line = String::from_utf8(crash_if_err!(1, io_line)).unwrap(); + if !lines.is_empty() && self.cmp_keys(&lines[0], &line) { + let print_delimiter = delimiters == &Delimiters::Prepend + || (delimiters == &Delimiters::Separate && first_line_printed); + first_line_printed |= self.print_lines(writer, &lines, print_delimiter); + lines.truncate(0); + } + lines.push(line); + } + if !lines.is_empty() { + let print_delimiter = delimiters == &Delimiters::Prepend + || (delimiters == &Delimiters::Separate && first_line_printed); + self.print_lines(writer, &lines, print_delimiter); + } + } + + fn skip_fields<'a>(&self, line: &'a str) -> &'a str { + if let Some(skip_fields) = self.skip_fields { + if line.split_whitespace().count() > skip_fields { + let mut field = 0; + let mut i = 0; + while field < skip_fields && i < line.len() { + while i < line.len() && line.chars().nth(i).unwrap().is_whitespace() { + i = i + 1; + } + while i < line.len() && !line.chars().nth(i).unwrap().is_whitespace() { + i = i + 1; + } + field = field + 1; + } + &line[i..] + } else { + "" + } + } else { + line + } + } + + fn get_line_terminator(&self) -> u8 { + if self.zero_terminated { + 0 + } else { + '\n' as u8 + } + } + + fn cmp_keys(&self, first: &str, second: &str) -> bool { + self.cmp_key(first, |first_iter| { + self.cmp_key(second, |second_iter| first_iter.ne(second_iter)) + }) + } + + fn cmp_key(&self, line: &str, mut closure: F) -> bool + where + F: FnMut(&mut Iterator) -> bool, + { + let fields_to_check = self.skip_fields(line); + let len = fields_to_check.len(); + let slice_start = self.slice_start.unwrap_or(0); + let slice_stop = self.slice_stop.unwrap_or(len); + if len > 0 { + // fast path: avoid doing any work if there is no need to skip or map to lower-case + if !self.ignore_case && slice_start == 0 && slice_stop == len { + return closure(&mut fields_to_check.chars()); + } + + // fast path: avoid skipping + if self.ignore_case && slice_start == 0 && slice_stop == len { + return closure(&mut fields_to_check.chars().map(|c| match c { + 'a'...'z' => ((c as u8) - 32) as char, + _ => c, + })); + } + + // fast path: we can avoid mapping chars to upper-case, if we don't want to ignore the case + if !self.ignore_case { + return closure(&mut fields_to_check.chars().skip(slice_start).take(slice_stop)); + } + + closure( + &mut fields_to_check + .chars() + .skip(slice_start) + .take(slice_stop) + .map(|c| match c { + 'a'...'z' => ((c as u8) - 32) as char, + _ => c, + }), + ) + } else { + closure(&mut fields_to_check.chars()) + } + } + + fn print_lines( + &self, + writer: &mut BufWriter, + lines: &[String], + print_delimiter: bool, + ) -> bool { + let mut first_line_printed = false; + let mut count = if self.all_repeated { 1 } else { lines.len() }; + if lines.len() == 1 && !self.repeats_only || lines.len() > 1 && !self.uniques_only { + self.print_line(writer, &lines[0], count, print_delimiter); + first_line_printed = true; + count += 1; + } + if self.all_repeated { + for line in lines[1..].iter() { + self.print_line(writer, line, count, print_delimiter && !first_line_printed); + first_line_printed = true; + count += 1; + } + } + first_line_printed + } + + fn print_line( + &self, + writer: &mut BufWriter, + line: &str, + count: usize, + print_delimiter: bool, + ) { + let line_terminator = self.get_line_terminator(); + + if print_delimiter { + crash_if_err!(1, writer.write_all(&[line_terminator])); + } + + crash_if_err!( + 1, + if self.show_counts { + writer.write_all(format!("{:7} {}", count, line).as_bytes()) + } else { + writer.write_all(line.as_bytes()) + } + ); + crash_if_err!(1, writer.write_all(&[line_terminator])); + } +} + +fn opt_parsed(opt_name: &str, matches: &Matches) -> Option { + matches.opt_str(opt_name).map(|arg_str| { + let opt_val: Option = arg_str.parse().ok(); + opt_val.unwrap_or_else(|| crash!(1, "Invalid argument for {}: {}", opt_name, arg_str)) + }) +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + opts.optflag("c", "count", "prefix lines by the number of occurrences"); + opts.optflag("d", "repeated", "only print duplicate lines"); + opts.optflagopt( + "D", + "all-repeated", + "print all duplicate lines delimit-method={none(default),prepend,separate} Delimiting is done with blank lines", + "delimit-method" + ); + opts.optopt( + "f", + "skip-fields", + "avoid comparing the first N fields", + "N", + ); + opts.optopt( + "s", + "skip-chars", + "avoid comparing the first N characters", + "N", + ); + opts.optopt( + "w", + "check-chars", + "compare no more than N characters in lines", + "N", + ); + opts.optflag( + "i", + "ignore-case", + "ignore differences in case when comparing", + ); + opts.optflag("u", "unique", "only print unique lines"); + opts.optflag("z", "zero-terminated", "end lines with 0 byte, not newline"); + 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!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {0} [OPTION]... [FILE]...", NAME); + println!(""); + print!( + "{}", + opts.usage( + "Filter adjacent matching lines from INPUT (or standard input),\n\ + writing to OUTPUT (or standard output)." + ) + ); + println!(""); + println!( + "Note: '{0}' does not detect repeated lines unless they are adjacent.\n\ + You may want to sort the input first, or use 'sort -u' without '{0}'.\n", + NAME + ); + } else if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + } else { + let (in_file_name, out_file_name) = match matches.free.len() { + 0 => ("-".to_owned(), "-".to_owned()), + 1 => (matches.free[0].clone(), "-".to_owned()), + 2 => (matches.free[0].clone(), matches.free[1].clone()), + _ => { + crash!(1, "Extra operand: {}", matches.free[2]); + } + }; + let uniq = Uniq { + repeats_only: matches.opt_present("repeated") || matches.opt_present("all-repeated"), + uniques_only: matches.opt_present("unique"), + all_repeated: matches.opt_present("all-repeated"), + delimiters: match matches.opt_default("all-repeated", "none") { + Some(ref opt_arg) if opt_arg != "none" => match &(*opt_arg.as_str()) { + "prepend" => Delimiters::Prepend, + "separate" => Delimiters::Separate, + _ => crash!(1, "Incorrect argument for all-repeated: {}", opt_arg), + }, + _ => Delimiters::None, + }, + show_counts: matches.opt_present("count"), + skip_fields: opt_parsed("skip-fields", &matches), + slice_start: opt_parsed("skip-chars", &matches), + slice_stop: opt_parsed("check-chars", &matches), + ignore_case: matches.opt_present("ignore-case"), + zero_terminated: matches.opt_present("zero-terminated"), + }; + uniq.print_uniq( + &mut open_input_file(in_file_name), + &mut open_output_file(out_file_name), + ); + } + 0 +} + +fn open_input_file(in_file_name: String) -> BufReader> { + let in_file = if in_file_name == "-" { + Box::new(stdin()) as Box + } else { + let path = Path::new(&in_file_name[..]); + let in_file = File::open(&path); + let r = crash_if_err!(1, in_file); + Box::new(r) as Box + }; + BufReader::new(in_file) +} + +fn open_output_file(out_file_name: String) -> BufWriter> { + let out_file = if out_file_name == "-" { + Box::new(stdout()) as Box + } else { + let path = Path::new(&out_file_name[..]); + let in_file = File::create(&path); + let w = crash_if_err!(1, in_file); + Box::new(w) as Box + }; + BufWriter::new(out_file) +} diff --git a/coreutils/src/unlink/Cargo.toml b/coreutils/src/unlink/Cargo.toml new file mode 100644 index 000000000..2606d66c7 --- /dev/null +++ b/coreutils/src/unlink/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "unlink" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_unlink" +path = "unlink.rs" + +[dependencies] +getopts = "0.2.18" +libc = "0.2.42" +uucore = "0.0.1" + +[[bin]] +name = "unlink" +path = "../../uumain.rs" diff --git a/coreutils/src/unlink/unlink.rs b/coreutils/src/unlink/unlink.rs new file mode 100644 index 000000000..9bca5e585 --- /dev/null +++ b/coreutils/src/unlink/unlink.rs @@ -0,0 +1,117 @@ +#![crate_name = "uu_unlink"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Colin Warren + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* last synced with: unlink (GNU coreutils) 8.21 */ + +extern crate getopts; +extern crate libc; + +#[macro_use] +extern crate uucore; + +use getopts::Options; +use libc::{S_IFLNK, S_IFMT, S_IFREG}; +use libc::{lstat, stat, unlink}; +use std::io::{Error, ErrorKind}; +use std::mem::uninitialized; +use std::ffi::CString; + +static NAME: &str = "unlink"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + 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, "invalid options\n{}", f), + }; + + if matches.opt_present("help") { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {} [FILE]... [OPTION]...", NAME); + println!(""); + println!("{}", opts.usage("Unlink the file at [FILE].")); + return 0; + } + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + if matches.free.is_empty() { + crash!( + 1, + "missing operand\nTry '{0} --help' for more information.", + NAME + ); + } else if matches.free.len() > 1 { + crash!( + 1, + "extra operand: '{1}'\nTry '{0} --help' for more information.", + NAME, + matches.free[1] + ); + } + + let c_string = CString::new(matches.free[0].clone()).unwrap(); // unwrap() cannot fail, the string comes from argv so it cannot contain a \0. + + let st_mode = { + let mut buf: stat = unsafe { uninitialized() }; + let result = unsafe { + lstat( + c_string.as_ptr(), + &mut buf as *mut stat, + ) + }; + + if result < 0 { + crash!( + 1, + "Cannot stat '{}': {}", + matches.free[0], + Error::last_os_error() + ); + } + + buf.st_mode & S_IFMT + }; + + let result = if st_mode != S_IFREG && st_mode != S_IFLNK { + Err(Error::new( + ErrorKind::Other, + "Not a regular file or symlink", + )) + } else { + let result = unsafe { unlink(c_string.as_ptr()) }; + + if result < 0 { + Err(Error::last_os_error()) + } else { + Ok(()) + } + }; + + match result { + Ok(_) => (), + Err(e) => { + crash!(1, "cannot unlink '{0}': {1}", matches.free[0], e); + } + } + + 0 +} diff --git a/coreutils/src/uptime/Cargo.toml b/coreutils/src/uptime/Cargo.toml new file mode 100644 index 000000000..cf86a2e66 --- /dev/null +++ b/coreutils/src/uptime/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "uptime" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_uptime" +path = "uptime.rs" + +[dependencies] +getopts = "0.2.18" +time = "0.1.40" + +[dependencies.uucore] +version = "0.0.1" +features = ["utmpx"] + +[[bin]] +name = "uptime" +path = "../../uumain.rs" diff --git a/coreutils/src/uptime/uptime.rs b/coreutils/src/uptime/uptime.rs new file mode 100644 index 000000000..8d9d48ab4 --- /dev/null +++ b/coreutils/src/uptime/uptime.rs @@ -0,0 +1,199 @@ +#![crate_name = "uu_uptime"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * (c) Jian Zeng + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* last synced with: cat (GNU coreutils) 8.13 */ + +extern crate getopts; +extern crate time; + +#[macro_use] +extern crate uucore; +// import crate time from utmpx +use uucore::libc::time_t; +pub use uucore::libc; + +use getopts::Options; + +static NAME: &str = "uptime"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg(unix)] +use libc::getloadavg; + +#[cfg(windows)] +extern "C" { + fn GetTickCount() -> libc::uint32_t; +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + opts.optflag("v", "version", "output version information and exit"); + opts.optflag("h", "help", "display this help and exit"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "Invalid options\n{}", f), + }; + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + if matches.opt_present("help") || !matches.free.is_empty() { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {0} [OPTION]", NAME); + println!(""); + println!( + "{}", + opts.usage( + "Print the current time, the length of time the system has been up,\n\ + the number of users on the system, and the average number of jobs\n\ + in the run queue over the last 1, 5 and 15 minutes." + ) + ); + return 0; + } + + print_time(); + let (boot_time, user_count) = process_utmpx(); + let uptime = get_uptime(boot_time); + if uptime < 0 { + show_error!("could not retrieve system uptime"); + + 1 + } else { + let upsecs = uptime / 100; + print_uptime(upsecs); + print_nusers(user_count); + print_loadavg(); + + 0 + } +} + +#[cfg(unix)] +fn print_loadavg() { + use libc::c_double; + use std::mem::transmute; + + let mut avg: [c_double; 3] = [0.0; 3]; + let loads: i32 = unsafe { transmute(getloadavg(avg.as_mut_ptr(), 3)) }; + + if loads == -1 { + print!("\n"); + } else { + print!("load average: "); + for n in 0..loads { + print!( + "{:.2}{}", + avg[n as usize], + if n == loads - 1 { "\n" } else { ", " } + ); + } + } +} + +#[cfg(windows)] +fn print_loadavg() { + // XXX: currently this is a noop as Windows does not seem to have anything comparable to + // getloadavg() +} + +#[cfg(unix)] +fn process_utmpx() -> (Option, usize) { + use uucore::utmpx::*; + + let mut nusers = 0; + let mut boot_time = None; + + for line in Utmpx::iter_all_records() { + match line.record_type() { + USER_PROCESS => nusers += 1, + BOOT_TIME => { + let t = line.login_time().to_timespec(); + if t.sec > 0 { + boot_time = Some(t.sec as time_t); + } + } + _ => continue, + } + } + (boot_time, nusers) +} + +#[cfg(windows)] +fn process_utmpx() -> (Option, usize) { + (None, 0) // TODO: change 0 to number of users +} + +fn print_nusers(nusers: usize) { + if nusers == 1 { + print!("1 user, "); + } else if nusers > 1 { + print!("{} users, ", nusers); + } +} + +fn print_time() { + let local_time = time::now(); + + print!( + " {:02}:{:02}:{:02} ", + local_time.tm_hour, local_time.tm_min, local_time.tm_sec + ); +} + +#[cfg(unix)] +fn get_uptime(boot_time: Option) -> i64 { + use std::fs::File; + use std::io::Read; + + let mut proc_uptime = String::new(); + + if let Some(n) = File::open("/proc/uptime") + .ok() + .and_then(|mut f| f.read_to_string(&mut proc_uptime).ok()) + .and_then(|_| proc_uptime.split_whitespace().next()) + .and_then(|s| s.replace(".", "").parse().ok()) + { + n + } else { + match boot_time { + Some(t) => { + let now = time::get_time().sec; + let boottime = t as i64; + ((now - boottime) * 100) + } + _ => -1, + } + } +} + +#[cfg(windows)] +fn get_uptime(_boot_time: Option) -> i64 { + unsafe { GetTickCount() as i64 } +} + +fn print_uptime(upsecs: i64) { + let updays = upsecs / 86400; + let uphours = (upsecs - (updays * 86400)) / 3600; + let upmins = (upsecs - (updays * 86400) - (uphours * 3600)) / 60; + if updays == 1 { + print!("up {:1} day, {:2}:{:02}, ", updays, uphours, upmins); + } else if updays > 1 { + print!("up {:1} days, {:2}:{:02}, ", updays, uphours, upmins); + } else { + print!("up {:2}:{:02}, ", uphours, upmins); + } +} diff --git a/coreutils/src/users/Cargo.toml b/coreutils/src/users/Cargo.toml new file mode 100644 index 000000000..4ef060373 --- /dev/null +++ b/coreutils/src/users/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "users" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_users" +path = "users.rs" + +[dependencies] +getopts = "0.2.18" + +[dependencies.uucore] +features = ["utmpx"] +version = "0.0.1" + +[[bin]] +name = "users" +path = "../../uumain.rs" diff --git a/coreutils/src/users/users.rs b/coreutils/src/users/users.rs new file mode 100644 index 000000000..10fb5eca5 --- /dev/null +++ b/coreutils/src/users/users.rs @@ -0,0 +1,78 @@ +#![crate_name = "uu_users"] +/* + * This file is part of the uutils coreutils package. + * + * (c) KokaKiwi + * (c) Jian Zeng + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* last synced with: whoami (GNU coreutils) 8.22 */ + +// Allow dead code here in order to keep all fields, constants here, for consistency. +#![allow(dead_code)] + +extern crate getopts; +extern crate uucore; + +use uucore::utmpx::*; + +use getopts::Options; + +static NAME: &str = "users"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + 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) => panic!("{}", f), + }; + + if matches.opt_present("help") { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {} [OPTION]... [FILE]", NAME); + println!(""); + println!( + "{}", + opts.usage("Output who is currently logged in according to FILE.") + ); + return 0; + } + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + let filename = if !matches.free.is_empty() { + matches.free[0].as_ref() + } else { + DEFAULT_FILE + }; + + exec(filename); + + 0 +} + +fn exec(filename: &str) { + let mut users = Utmpx::iter_all_records() + .read_from(filename) + .filter(|ut| ut.is_user_process()) + .map(|ut| ut.user()) + .collect::>(); + + if !users.is_empty() { + users.sort(); + println!("{}", users.join(" ")); + } +} diff --git a/coreutils/src/uutils/uutils.rs b/coreutils/src/uutils/uutils.rs new file mode 100644 index 000000000..a2481bcf7 --- /dev/null +++ b/coreutils/src/uutils/uutils.rs @@ -0,0 +1,110 @@ +#![crate_name = "uutils"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Michael Gehring + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +include!(concat!(env!("OUT_DIR"), "/uutils_crates.rs")); + +use std::collections::hash_map::HashMap; +use std::path::Path; +use std::io::Write; + +extern crate uucore; + +static NAME: &str = "uutils"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +include!(concat!(env!("OUT_DIR"), "/uutils_map.rs")); + +fn usage(cmap: &UtilityMap) { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {} [util [arguments...]]\n", NAME); + println!("Currently defined functions:"); + let mut utils: Vec<&str> = cmap.keys().map(|&s| s).collect(); + utils.sort(); + for util in utils { + println!("\t{}", util); + } +} + +fn main() { + uucore::panic::install_sigpipe_hook(); + + let umap = util_map(); + let mut args: Vec = uucore::args().collect(); + + // try binary name as util name. + let args0 = args[0].clone(); + let binary = Path::new(&args0[..]); + let binary_as_util = binary.file_stem().unwrap().to_str().unwrap(); + + if let Some(&uumain) = umap.get(binary_as_util) { + std::process::exit(uumain(args)); + } + + if binary_as_util.ends_with("uutils") || binary_as_util.starts_with("uutils") + || binary_as_util.ends_with("busybox") || binary_as_util.starts_with("busybox") + { + args.remove(0); + } else { + let mut found = false; + for util in umap.keys() { + if binary_as_util.ends_with(util) { + args[0] = (*util).to_owned(); + found = true; + break; + } + } + if !found { + println!("{}: applet not found", binary_as_util); + std::process::exit(1); + } + } + + // try first arg as util name. + if args.len() >= 1 { + let util = &args[0][..]; + + match umap.get(util) { + Some(&uumain) => { + std::process::exit(uumain(args.clone())); + } + None => { + if &args[0][..] == "--help" || &args[0][..] == "-h" { + // see if they want help on a specific util + if args.len() >= 2 { + let util = &args[1][..]; + match umap.get(util) { + Some(&uumain) => { + let code = uumain(vec![util.to_owned(), "--help".to_owned()]); + std::io::stdout().flush().expect("could not flush stdout"); + std::process::exit(code); + } + None => { + println!("{}: applet not found", util); + std::process::exit(1); + } + } + } + usage(&umap); + std::process::exit(0); + } else { + println!("{}: applet not found", util); + std::process::exit(1); + } + } + } + } else { + // no arguments provided + usage(&umap); + std::process::exit(0); + } +} diff --git a/coreutils/src/wc/Cargo.toml b/coreutils/src/wc/Cargo.toml new file mode 100644 index 000000000..51c59590f --- /dev/null +++ b/coreutils/src/wc/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wc" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_wc" +path = "wc.rs" + +[dependencies] +getopts = "0.2.18" +uucore = "0.0.1" + +[[bin]] +name = "wc" +path = "../../uumain.rs" diff --git a/coreutils/src/wc/wc.rs b/coreutils/src/wc/wc.rs new file mode 100644 index 000000000..db1ee0ebd --- /dev/null +++ b/coreutils/src/wc/wc.rs @@ -0,0 +1,281 @@ +#![crate_name = "uu_wc"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Boden Garman + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +#[macro_use] +extern crate uucore; + +use getopts::{Matches, Options}; + +use std::fs::File; +use std::io::{stdin, BufRead, BufReader, Read}; +use std::path::Path; +use std::result::Result as StdResult; +use std::str::from_utf8; + +struct Settings { + show_bytes: bool, + show_chars: bool, + show_lines: bool, + show_words: bool, + show_max_line_length: bool, +} + +impl Settings { + fn new(matches: &Matches) -> Settings { + let settings = Settings { + show_bytes: matches.opt_present("bytes"), + show_chars: matches.opt_present("chars"), + show_lines: matches.opt_present("lines"), + show_words: matches.opt_present("words"), + show_max_line_length: matches.opt_present("L"), + }; + + if settings.show_bytes || settings.show_chars || settings.show_lines || settings.show_words + || settings.show_max_line_length + { + return settings; + } + + Settings { + show_bytes: true, + show_chars: false, + show_lines: true, + show_words: true, + show_max_line_length: false, + } + } +} + +struct Result { + title: String, + bytes: usize, + chars: usize, + lines: usize, + words: usize, + max_line_length: usize, +} + +static NAME: &str = "wc"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + opts.optflag("c", "bytes", "print the byte counts"); + opts.optflag("m", "chars", "print the character counts"); + opts.optflag("l", "lines", "print the newline counts"); + opts.optflag( + "L", + "max-line-length", + "print the length of the longest line", + ); + opts.optflag("w", "words", "print the word counts"); + opts.optflag("h", "help", "display this help and exit"); + opts.optflag("V", "version", "output version information and exit"); + + let mut matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "Invalid options\n{}", f), + }; + + if matches.opt_present("help") { + println!("{} {}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {0} [OPTION]... [FILE]...", NAME); + println!(""); + println!( + "{}", + opts.usage("Print newline, word and byte counts for each FILE") + ); + println!("With no FILE, or when FILE is -, read standard input."); + return 0; + } + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + if matches.free.is_empty() { + matches.free.push("-".to_owned()); + } + + let settings = Settings::new(&matches); + + match wc(matches.free, &settings) { + Ok(()) => ( /* pass */ ), + Err(e) => return e, + } + + 0 +} + +const CR: u8 = '\r' as u8; +const LF: u8 = '\n' as u8; +const SPACE: u8 = ' ' as u8; +const TAB: u8 = '\t' as u8; +const SYN: u8 = 0x16 as u8; +const FF: u8 = 0x0C as u8; + +#[inline(always)] +fn is_word_seperator(byte: u8) -> bool { + byte == SPACE || byte == TAB || byte == CR || byte == SYN || byte == FF +} + +fn wc(files: Vec, settings: &Settings) -> StdResult<(), i32> { + let mut total_line_count: usize = 0; + let mut total_word_count: usize = 0; + let mut total_char_count: usize = 0; + let mut total_byte_count: usize = 0; + let mut total_longest_line_length: usize = 0; + + let mut results = vec![]; + let mut max_width: usize = 0; + + for path in &files { + let mut reader = open(&path[..])?; + + let mut line_count: usize = 0; + let mut word_count: usize = 0; + let mut byte_count: usize = 0; + let mut char_count: usize = 0; + let mut longest_line_length: usize = 0; + let mut raw_line = Vec::new(); + + // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. + // hence the option wrapped in a result here + while match reader.read_until(LF, &mut raw_line) { + Ok(n) if n > 0 => true, + Err(ref e) if !raw_line.is_empty() => { + show_warning!("Error while reading {}: {}", path, e); + !raw_line.is_empty() + } + _ => false, + } { + // GNU 'wc' only counts lines that end in LF as lines + if *raw_line.last().unwrap() == LF { + line_count += 1; + } + + byte_count += raw_line.len(); + + // try and convert the bytes to UTF-8 first + let current_char_count; + match from_utf8(&raw_line[..]) { + Ok(line) => { + word_count += line.split_whitespace().count(); + current_char_count = line.chars().count(); + } + Err(..) => { + word_count += raw_line.split(|&x| is_word_seperator(x)).count(); + current_char_count = raw_line.iter().filter(|c| c.is_ascii()).count() + } + } + char_count += current_char_count; + + if current_char_count > longest_line_length { + // we subtract one here because `line.len()` includes the LF + // matches GNU 'wc' behaviour + longest_line_length = current_char_count - 1; + } + + raw_line.truncate(0); + } + + results.push(Result { + title: path.clone(), + bytes: byte_count, + chars: char_count, + lines: line_count, + words: word_count, + max_line_length: longest_line_length, + }); + + total_line_count += line_count; + total_word_count += word_count; + total_char_count += char_count; + total_byte_count += byte_count; + + if longest_line_length > total_longest_line_length { + total_longest_line_length = longest_line_length; + } + + // used for formatting + max_width = total_byte_count.to_string().len() + 1; + } + + for result in &results { + print_stats(settings, &result, max_width); + } + + if files.len() > 1 { + let result = Result { + title: "total".to_owned(), + bytes: total_byte_count, + chars: total_char_count, + lines: total_line_count, + words: total_word_count, + max_line_length: total_longest_line_length, + }; + print_stats(settings, &result, max_width); + } + + Ok(()) +} + +fn print_stats(settings: &Settings, result: &Result, max_width: usize) { + if settings.show_lines { + print!("{:1$}", result.lines, max_width); + } + if settings.show_words { + print!("{:1$}", result.words, max_width); + } + if settings.show_bytes { + print!("{:1$}", result.bytes, max_width); + } + if settings.show_chars { + print!("{:1$}", result.chars, max_width); + } + if settings.show_max_line_length { + print!("{:1$}", result.max_line_length, max_width); + } + + if result.title != "-" { + println!(" {}", result.title); + } else { + println!(""); + } +} + +fn open(path: &str) -> StdResult>, i32> { + if "-" == path { + let reader = Box::new(stdin()) as Box; + return Ok(BufReader::new(reader)); + } + + let fpath = Path::new(path); + if fpath.is_dir() { + show_info!("{}: is a directory", path); + } + match File::open(&fpath) { + Ok(fd) => { + let reader = Box::new(fd) as Box; + Ok(BufReader::new(reader)) + } + Err(e) => { + show_error!("wc: {}: {}", path, e); + Err(1) + } + } +} diff --git a/coreutils/src/who/Cargo.toml b/coreutils/src/who/Cargo.toml new file mode 100644 index 000000000..036ba672b --- /dev/null +++ b/coreutils/src/who/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "who" +version = "0.0.1" +authors = [] +build = "../../mkmain.rs" + +[lib] +name = "uu_who" +path = "who.rs" + +[dependencies.uucore] +version = "0.0.1" +features = ["utmpx"] + +[dependencies.clippy] +version = "0.0.212" +optional = true + +[[bin]] +name = "who" +path = "../../uumain.rs" diff --git a/coreutils/src/who/who.rs b/coreutils/src/who/who.rs new file mode 100644 index 000000000..99f0cb9a0 --- /dev/null +++ b/coreutils/src/who/who.rs @@ -0,0 +1,541 @@ +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// +#![crate_name = "uu_who"] +#![cfg_attr(feature = "clippy", feature(plugin))] +#![cfg_attr(feature = "clippy", plugin(clippy))] + +#[macro_use] +extern crate uucore; +use uucore::utmpx::{self, time, Utmpx}; +use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP}; + +use std::borrow::Cow; +use std::ffi::CStr; +use std::path::PathBuf; +use std::os::unix::fs::MetadataExt; + +static SYNTAX: &str = "[OPTION]... [ FILE | ARG1 ARG2 ]"; +static SUMMARY: &str = "Print information about users who are currently logged in."; +static LONG_HELP: &str = " + -a, --all same as -b -d --login -p -r -t -T -u + -b, --boot time of last system boot + -d, --dead print dead processes + -H, --heading print line of column headings + -l, --login print system login processes + --lookup attempt to canonicalize hostnames via DNS + -m only hostname and user associated with stdin + -p, --process print active processes spawned by init + -q, --count all login names and number of users logged on + -r, --runlevel print current runlevel (not available on BSDs) + -s, --short print only name, line, and time (default) + -t, --time print last system clock change + -T, -w, --mesg add user's message status as +, - or ? + -u, --users list users logged in + --message same as -T + --writable same as -T + --help display this help and exit + --version output version information and exit + +If FILE is not specified, use /var/run/utmp. /var/log/wtmp as FILE is common. +If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual. +"; + +pub fn uumain(args: Vec) -> i32 { + let mut opts = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP); + opts.optflag("a", "all", "same as -b -d --login -p -r -t -T -u"); + opts.optflag("b", "boot", "time of last system boot"); + opts.optflag("d", "dead", "print dead processes"); + opts.optflag("H", "heading", "print line of column headings"); + opts.optflag("l", "login", "print system login processes"); + opts.optflag("", "lookup", "attempt to canonicalize hostnames via DNS"); + opts.optflag("m", "", "only hostname and user associated with stdin"); + opts.optflag("p", "process", "print active processes spawned by init"); + opts.optflag( + "q", + "count", + "all login names and number of users logged on", + ); + #[cfg(any(target_os = "macos", target_os = "ios", 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"); + opts.optflag("u", "users", "list users logged in"); + opts.optflag("w", "mesg", "add user's message status as +, - or ?"); + // --message, --writable are the same as --mesg + opts.optflag("T", "message", ""); + opts.optflag("T", "writable", ""); + + opts.optflag("", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + let matches = opts.parse(args); + + // If true, attempt to canonicalize hostnames via a DNS lookup. + let do_lookup = matches.opt_present("lookup"); + + // If true, display only a list of usernames and count of + // the users logged on. + // Ignored for 'who am i'. + let short_list = matches.opt_present("q"); + + // If true, display only name, line, and time fields. + let mut short_output = false; + + // If true, display the hours:minutes since each user has touched + // the keyboard, or "." if within the last minute, or "old" if + // not within the last day. + let mut include_idle = false; + + // If true, display a line at the top describing each field. + let include_heading = matches.opt_present("H"); + + // If true, display a '+' for each user if mesg y, a '-' if mesg n, + // or a '?' if their tty cannot be statted. + let include_mesg = + matches.opt_present("a") || matches.opt_present("T") || matches.opt_present("w"); + + // If true, display process termination & exit status. + let mut include_exit = false; + + // If true, display the last boot time. + let mut need_boottime = false; + + // If true, display dead processes. + let mut need_deadprocs = false; + + // If true, display processes waiting for user login. + let mut need_login = false; + + // If true, display processes started by init. + let mut need_initspawn = false; + + // If true, display the last clock change. + let mut need_clockchange = false; + + // If true, display the current runlevel. + let mut need_runlevel = false; + + // If true, display user processes. + let mut need_users = false; + + // If true, display info only for the controlling tty. + let mut my_line_only = false; + + let mut assumptions = true; + + if matches.opt_present("a") { + need_boottime = true; + need_deadprocs = true; + need_login = true; + need_initspawn = true; + need_runlevel = true; + need_clockchange = true; + need_users = true; + include_idle = true; + include_exit = true; + assumptions = false; + } + + if matches.opt_present("b") { + need_boottime = true; + assumptions = false; + } + + if matches.opt_present("d") { + need_deadprocs = true; + include_idle = true; + include_exit = true; + assumptions = false; + } + + if matches.opt_present("l") { + need_login = true; + include_idle = true; + assumptions = false; + } + + if matches.opt_present("m") || matches.free.len() == 2 { + my_line_only = true; + } + + if matches.opt_present("p") { + need_initspawn = true; + assumptions = false; + } + + if matches.opt_present("r") { + need_runlevel = true; + include_idle = true; + assumptions = false; + } + + if matches.opt_present("s") { + short_output = true; + } + + if matches.opt_present("t") { + need_clockchange = true; + assumptions = false; + } + + if matches.opt_present("u") { + need_users = true; + include_idle = true; + assumptions = false; + } + + if assumptions { + need_users = true; + short_output = true; + } + + if include_exit { + short_output = false; + } + + if matches.free.len() > 2 { + disp_err!("{}", msg_wrong_number_of_arguments!()); + exit!(1); + } + + let mut who = Who { + do_lookup: do_lookup, + short_list: short_list, + short_output: short_output, + include_idle: include_idle, + include_heading: include_heading, + include_mesg: include_mesg, + include_exit: include_exit, + need_boottime: need_boottime, + need_deadprocs: need_deadprocs, + need_login: need_login, + need_initspawn: need_initspawn, + need_clockchange: need_clockchange, + need_runlevel: need_runlevel, + need_users: need_users, + my_line_only: my_line_only, + has_records: false, + args: matches.free, + }; + + who.exec(); + + 0 +} + +struct Who { + do_lookup: bool, + short_list: bool, + short_output: bool, + include_idle: bool, + include_heading: bool, + include_mesg: bool, + include_exit: bool, + need_boottime: bool, + need_deadprocs: bool, + need_login: bool, + need_initspawn: bool, + need_clockchange: bool, + need_runlevel: bool, + need_users: bool, + my_line_only: bool, + has_records: bool, + args: Vec, +} + +fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> { + thread_local! { + static NOW: time::Tm = time::now() + } + NOW.with(|n| { + let now = n.to_timespec().sec; + if boottime < when && now - 24 * 3600 < when && when <= now { + let seconds_idle = now - when; + if seconds_idle < 60 { + " . ".into() + } else { + format!( + "{:02}:{:02}", + seconds_idle / 3600, + (seconds_idle % 3600) / 60 + ).into() + } + } else { + " old ".into() + } + }) +} + +fn time_string(ut: &Utmpx) -> String { + time::strftime("%Y-%m-%d %H:%M", &ut.login_time()).unwrap() +} + +#[inline] +fn current_tty() -> String { + unsafe { + let res = ttyname(STDIN_FILENO); + if !res.is_null() { + CStr::from_ptr(res as *const _) + .to_string_lossy() + .trim_start_matches("/dev/") + .to_owned() + } else { + "".to_owned() + } + } +} + +impl Who { + fn exec(&mut self) { + let run_level_chk = |record: i16| { + #[allow(unused_assignments)] + let mut res = false; + + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android"))] + { + res = record == utmpx::RUN_LVL; + } + res + }; + + let f = if self.args.len() == 1 { + self.args[0].as_ref() + } else { + utmpx::DEFAULT_FILE + }; + if self.short_list { + let users = Utmpx::iter_all_records() + .read_from(f) + .filter(|ut| ut.is_user_process()) + .map(|ut| ut.user()) + .collect::>(); + println!("{}", users.join(" ")); + println!("# users={}", users.len()); + } else { + let mut records = Utmpx::iter_all_records().read_from(f).peekable(); + self.has_records = records.peek().is_some(); + + if self.include_heading { + self.print_heading() + } + let cur_tty = if self.my_line_only { + current_tty() + } else { + "".to_owned() + }; + + for ut in records { + if !self.my_line_only || cur_tty == ut.tty_device() { + if self.need_users && ut.is_user_process() { + self.print_user(&ut); + } else if self.need_runlevel && run_level_chk(ut.record_type()) { + self.print_runlevel(&ut); + } else if self.need_boottime && ut.record_type() == utmpx::BOOT_TIME { + self.print_boottime(&ut); + } else if self.need_clockchange && ut.record_type() == utmpx::NEW_TIME { + self.print_clockchange(&ut); + } else if self.need_initspawn && ut.record_type() == utmpx::INIT_PROCESS { + self.print_initspawn(&ut); + } else if self.need_login && ut.record_type() == utmpx::LOGIN_PROCESS { + self.print_login(&ut); + } else if self.need_deadprocs && ut.record_type() == utmpx::DEAD_PROCESS { + self.print_deadprocs(&ut); + } + } + + if ut.record_type() == utmpx::BOOT_TIME {} + } + } + } + + #[inline] + fn print_runlevel(&self, ut: &Utmpx) { + let last = (ut.pid() / 256) as u8 as char; + let curr = (ut.pid() % 256) as u8 as char; + let runlvline = format!("run-level {}", curr); + let comment = format!("last={}", if last == 'N' { 'S' } else { 'N' }); + + self.print_line( + "", + ' ', + &runlvline, + &time_string(ut), + "", + "", + if !last.is_control() { &comment } else { "" }, + "", + ); + } + + #[inline] + fn print_clockchange(&self, ut: &Utmpx) { + self.print_line("", ' ', "clock change", &time_string(ut), "", "", "", ""); + } + + #[inline] + fn print_login(&self, ut: &Utmpx) { + let comment = format!("id={}", ut.terminal_suffix()); + let pidstr = format!("{}", ut.pid()); + self.print_line( + "LOGIN", + ' ', + &ut.tty_device(), + &time_string(ut), + "", + &pidstr, + &comment, + "", + ); + } + + #[inline] + fn print_deadprocs(&self, ut: &Utmpx) { + let comment = format!("id={}", ut.terminal_suffix()); + let pidstr = format!("{}", ut.pid()); + let e = ut.exit_status(); + let exitstr = format!("term={} exit={}", e.0, e.1); + self.print_line( + "", + ' ', + &ut.tty_device(), + &time_string(ut), + "", + &pidstr, + &comment, + &exitstr, + ); + } + + #[inline] + fn print_initspawn(&self, ut: &Utmpx) { + let comment = format!("id={}", ut.terminal_suffix()); + let pidstr = format!("{}", ut.pid()); + self.print_line( + "", + ' ', + &ut.tty_device(), + &time_string(ut), + "", + &pidstr, + &comment, + "", + ); + } + + #[inline] + fn print_boottime(&self, ut: &Utmpx) { + self.print_line("", ' ', "system boot", &time_string(ut), "", "", "", ""); + } + + fn print_user(&self, ut: &Utmpx) { + let mut p = PathBuf::from("/dev"); + p.push(ut.tty_device().as_str()); + let mesg; + let last_change; + match p.metadata() { + Ok(meta) => { + mesg = if meta.mode() & (S_IWGRP as u32) != 0 { + '+' + } else { + '-' + }; + last_change = meta.atime(); + } + _ => { + mesg = '?'; + last_change = 0; + } + } + + let idle = if last_change != 0 { + idle_string(last_change, 0) + } else { + " ?".into() + }; + + let mut buf = vec![]; + let ut_host = ut.host(); + let mut res = ut_host.splitn(2, ':'); + if let Some(h) = res.next() { + if self.do_lookup { + buf.push(ut.canon_host().unwrap_or(h.to_owned())); + } else { + buf.push(h.to_owned()); + } + } + if let Some(h) = res.next() { + buf.push(h.to_owned()); + } + let s = buf.join(":"); + let hoststr = if s.is_empty() { s } else { format!("({})", s) }; + + self.print_line( + ut.user().as_ref(), + mesg, + ut.tty_device().as_ref(), + time_string(ut).as_str(), + idle.as_ref(), + format!("{}", ut.pid()).as_str(), + hoststr.as_str(), + "", + ); + } + + fn print_line( + &self, + user: &str, + state: char, + line: &str, + time: &str, + idle: &str, + pid: &str, + comment: &str, + exit: &str, + ) { + let mut buf = String::with_capacity(64); + let msg = vec![' ', state].into_iter().collect::(); + + buf.push_str(&format!("{:<8}", user)); + if self.include_mesg { + buf.push_str(&msg); + } + buf.push_str(&format!(" {:<12}", line)); + // "%Y-%m-%d %H:%M" + let mut time_size = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2; + if !self.has_records { + time_size -= 4; + } + buf.push_str(&format!(" {:<1$}", time, time_size)); + + if !self.short_output { + if self.include_idle { + buf.push_str(&format!(" {:<6}", idle)); + } + buf.push_str(&format!(" {:>10}", pid)); + } + buf.push_str(&format!(" {:<8}", comment)); + if self.include_exit { + buf.push_str(&format!(" {:<12}", exit)); + } + println!("{}", buf.trim_end()); + } + + #[inline] + fn print_heading(&self) { + self.print_line( + "NAME", + ' ', + "LINE", + "TIME", + "IDLE", + "PID", + "COMMENT", + "EXIT", + ); + } +} diff --git a/coreutils/src/whoami/Cargo.toml b/coreutils/src/whoami/Cargo.toml new file mode 100644 index 000000000..32d7771b9 --- /dev/null +++ b/coreutils/src/whoami/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "whoami" +version = "0.0.1" +authors = [] +description = "Print effective user ID." +build = "../../mkmain.rs" + +[lib] +name = "uu_whoami" +path = "whoami.rs" + +[dependencies] +clap = "2.32" +winapi = { version = "0.3", features = ["lmcons"] } +advapi32-sys = "0.2.0" + +[dependencies.uucore] +version = "0.0.1" +features = ["entries", "wide"] + +[[bin]] +name = "whoami" +path = "../../uumain.rs" diff --git a/coreutils/src/whoami/platform/mod.rs b/coreutils/src/whoami/platform/mod.rs new file mode 100644 index 000000000..12d85ec08 --- /dev/null +++ b/coreutils/src/whoami/platform/mod.rs @@ -0,0 +1,20 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[cfg(unix)] +pub use self::unix::getusername; + +#[cfg(windows)] +pub use self::windows::getusername; + +#[cfg(unix)] +mod unix; + +#[cfg(windows)] +mod windows; diff --git a/coreutils/src/whoami/platform/unix.rs b/coreutils/src/whoami/platform/unix.rs new file mode 100644 index 000000000..ab7c42ff6 --- /dev/null +++ b/coreutils/src/whoami/platform/unix.rs @@ -0,0 +1,19 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * (c) Jian Zeng + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use std::io::Result; +use uucore::libc::geteuid; +use uucore::entries::uid2usr; + +pub unsafe fn getusername() -> Result { + // Get effective user id + let uid = geteuid(); + uid2usr(uid) +} diff --git a/coreutils/src/whoami/platform/windows.rs b/coreutils/src/whoami/platform/windows.rs new file mode 100644 index 000000000..b321b93be --- /dev/null +++ b/coreutils/src/whoami/platform/windows.rs @@ -0,0 +1,29 @@ +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate advapi32; +extern crate uucore; +extern crate winapi; + +use std::io::{Error, Result}; +use std::mem; +use uucore::wide::FromWide; +use self::winapi::um::winnt; +use self::winapi::shared::lmcons; +use self::winapi::shared::minwindef; + +pub unsafe fn getusername() -> Result { + let mut buffer: [winnt::WCHAR; lmcons::UNLEN as usize + 1] = mem::uninitialized(); + let mut len = buffer.len() as minwindef::DWORD; + if advapi32::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 { + return Err(Error::last_os_error()); + } + let username = String::from_wide(&buffer[..len as usize - 1]); + Ok(username) +} diff --git a/coreutils/src/whoami/whoami.rs b/coreutils/src/whoami/whoami.rs new file mode 100644 index 000000000..886126cbf --- /dev/null +++ b/coreutils/src/whoami/whoami.rs @@ -0,0 +1,54 @@ +#![crate_name = "uu_whoami"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* last synced with: whoami (GNU coreutils) 8.21 */ + +#[macro_use] +extern crate clap; +#[macro_use] +extern crate uucore; + +mod platform; + +// force a re-build whenever Cargo.toml changes +const _CARGO_TOML: &str = include_str!("Cargo.toml"); + +pub fn uumain(args: Vec) -> i32 { + let app = app_from_crate!(); + + if let Err(err) = app.get_matches_from_safe(args) { + if err.kind == clap::ErrorKind::HelpDisplayed + || err.kind == clap::ErrorKind::VersionDisplayed + { + println!("{}", err); + 0 + } else { + show_error!("{}", err); + 1 + } + } else { + exec(); + + 0 + } +} + +pub fn exec() { + unsafe { + match platform::getusername() { + Ok(username) => println!("{}", username), + Err(err) => match err.raw_os_error() { + Some(0) | None => crash!(1, "failed to get username"), + Some(_) => crash!(1, "failed to get username: {}", err), + }, + } + } +} diff --git a/coreutils/src/yes/Cargo.toml b/coreutils/src/yes/Cargo.toml new file mode 100644 index 000000000..736c5ee64 --- /dev/null +++ b/coreutils/src/yes/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "yes" +version = "0.0.1" +authors = [] +description = "Repeatedly output a line with all specified STRING(s), or 'y'." +build = "../../mkmain.rs" + +[lib] +name = "uu_yes" +path = "yes.rs" + +[dependencies] +clap = "2.32" +uucore = { version = "0.0.1", features = ["zero-copy"] } + +[features] +latency = [] +default = [] + +[[bin]] +name = "yes" +path = "../../uumain.rs" diff --git a/coreutils/src/yes/yes.rs b/coreutils/src/yes/yes.rs new file mode 100644 index 000000000..b8e7971fd --- /dev/null +++ b/coreutils/src/yes/yes.rs @@ -0,0 +1,93 @@ +#![crate_name = "uu_yes"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* last synced with: yes (GNU coreutils) 8.13 */ + +#[macro_use] +extern crate clap; +#[macro_use] +extern crate uucore; + +use clap::Arg; +use uucore::zero_copy::ZeroCopyWriter; +use std::borrow::Cow; +use std::io::{self, Write}; + +// force a re-build whenever Cargo.toml changes +const _CARGO_TOML: &str = include_str!("Cargo.toml"); + +// it's possible that using a smaller or larger buffer might provide better performance on some +// systems, but honestly this is good enough +const BUF_SIZE: usize = 16 * 1024; + +pub fn uumain(args: Vec) -> i32 { + let app = app_from_crate!().arg(Arg::with_name("STRING").index(1).multiple(true)); + + let matches = match app.get_matches_from_safe(args) { + Ok(m) => m, + Err(ref e) + if e.kind == clap::ErrorKind::HelpDisplayed + || e.kind == clap::ErrorKind::VersionDisplayed => + { + println!("{}", e); + return 0; + } + Err(f) => { + show_error!("{}", f); + return 1; + } + }; + + let string = if let Some(values) = matches.values_of("STRING") { + let mut result = values.fold(String::new(), |res, s| res + s + " "); + result.pop(); + result.push('\n'); + Cow::from(result) + } else { + Cow::from("y\n") + }; + + let mut buffer = [0; BUF_SIZE]; + let bytes = prepare_buffer(&string, &mut buffer); + + exec(bytes); + + 0 +} + +#[cfg(not(feature = "latency"))] +fn prepare_buffer<'a>(input: &'a str, buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8] { + if input.len() < BUF_SIZE / 2 { + let mut size = 0; + while size < BUF_SIZE - input.len() { + let (_, right) = buffer.split_at_mut(size); + right[..input.len()].copy_from_slice(input.as_bytes()); + size += input.len(); + } + &buffer[..size] + } else { + input.as_bytes() + } +} + +#[cfg(feature = "latency")] +fn prepare_buffer<'a>(input: &'a str, _buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8] { + input.as_bytes() +} + +pub fn exec(bytes: &[u8]) { + let mut stdin_raw = io::stdout(); + let mut writer = ZeroCopyWriter::with_default(&mut stdin_raw, |stdin| stdin.lock()); + loop { + // TODO: needs to check if pipe fails + writer.write_all(bytes).unwrap(); + } +} diff --git a/coreutils/tests/common/macros.rs b/coreutils/tests/common/macros.rs new file mode 100644 index 000000000..e36fdffdc --- /dev/null +++ b/coreutils/tests/common/macros.rs @@ -0,0 +1,66 @@ +#[macro_export] +macro_rules! assert_empty_stderr( + ($cond:expr) => ( + if $cond.stderr.len() > 0 { + panic!(format!("stderr: {}", $cond.stderr)) + } + ); +); + +#[macro_export] +macro_rules! assert_empty_stdout( + ($cond:expr) => ( + if $cond.stdout.len() > 0 { + panic!(format!("stdout: {}", $cond.stdout)) + } + ); +); + +#[macro_export] +macro_rules! assert_no_error( + ($cond:expr) => ( + assert!($cond.success); + if $cond.stderr.len() > 0 { + panic!(format!("stderr: {}", $cond.stderr)) + } + ); +); + +#[macro_export] +macro_rules! path_concat { + ($e:expr, ..$n:expr) => {{ + use std::path::PathBuf; + let n = $n; + let mut pb = PathBuf::new(); + for _ in 0..n { + pb.push($e); + } + pb.to_str().unwrap().to_owned() + }}; + ($($e:expr),*) => {{ + use std::path::PathBuf; + let mut pb = PathBuf::new(); + $( + pb.push($e); + )* + pb.to_str().unwrap().to_owned() + }}; +} + +#[macro_export] +macro_rules! util_name { + () => ( module_path!().split("_").nth(1).expect("no test name") ) +} + +#[macro_export] +macro_rules! new_ucmd { + () => ( TestScenario::new(util_name!()).ucmd() ) +} + +#[macro_export] +macro_rules! at_and_ucmd { + () => ({ + let ts = TestScenario::new(util_name!()); + (ts.fixtures.clone(), ts.ucmd()) + }) +} diff --git a/coreutils/tests/common/mod.rs b/coreutils/tests/common/mod.rs new file mode 100644 index 000000000..3fcd90441 --- /dev/null +++ b/coreutils/tests/common/mod.rs @@ -0,0 +1,3 @@ +#[macro_use] +pub mod macros; +pub mod util; diff --git a/coreutils/tests/common/util.rs b/coreutils/tests/common/util.rs new file mode 100644 index 000000000..43e83f1e6 --- /dev/null +++ b/coreutils/tests/common/util.rs @@ -0,0 +1,629 @@ +#![allow(dead_code)] +extern crate tempdir; + +use std::env; +use std::fs::{self, File, OpenOptions}; +use std::io::{Read, Result, Write}; +#[cfg(unix)] +use std::os::unix::fs::{symlink as symlink_dir, symlink as symlink_file}; +#[cfg(windows)] +use std::os::windows::fs::{symlink_dir, symlink_file}; +use std::path::{Path, PathBuf}; +use std::process::{Child, Command, Stdio}; +use std::str::from_utf8; +use std::ffi::OsStr; +use std::rc::Rc; +use std::thread::sleep; +use std::time::Duration; +use self::tempdir::TempDir; + +#[cfg(windows)] +static PROGNAME: &'static str = "uutils.exe"; +#[cfg(not(windows))] +static PROGNAME: &'static str = "uutils"; + +static TESTS_DIR: &'static str = "tests"; +static FIXTURES_DIR: &'static str = "fixtures"; + +static ALREADY_RUN: &'static str = + " you have already run this UCommand, if you want to run \ + another command in the same test, use TestScenario::new instead of \ + testing();"; +static MULTIPLE_STDIN_MEANINGLESS: &'static 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."; + +fn read_scenario_fixture>(tmpd: &Option>, file_rel_path: S) -> String { + let tmpdir_path = tmpd.as_ref().unwrap().as_ref().path(); + 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 +pub struct CmdResult { + //tmpd is used for convenience functions for asserts against fixtures + tmpd: Option>, + pub success: bool, + pub stdout: String, + pub stderr: String, +} + +impl CmdResult { + /// asserts that the command resulted in a success (zero) status code + pub fn success(&self) -> Box<&CmdResult> { + assert!(self.success); + Box::new(self) + } + + /// asserts that the command resulted in a failure (non-zero) status code + pub fn failure(&self) -> Box<&CmdResult> { + assert!(!self.success); + Box::new(self) + } + + /// asserts that the command resulted in empty (zero-length) stderr stream output + /// generally, it's better to use stdout_only() instead, + /// but you might find yourself using this function if + /// 1. you can not know exactly what stdout will be + /// or 2. you know that stdout will also be empty + pub fn no_stderr(&self) -> Box<&CmdResult> { + assert_eq!("", self.stderr); + Box::new(self) + } + + /// asserts that the command resulted in empty (zero-length) stderr stream output + /// unless asserting there was neither stdout or stderr, stderr_only is usually a better choice + /// generally, it's better to use stderr_only() instead, + /// but you might find yourself using this function if + /// 1. you can not know exactly what stderr will be + /// or 2. you know that stderr will also be empty + pub fn no_stdout(&self) -> Box<&CmdResult> { + assert_eq!("", self.stdout); + Box::new(self) + } + + /// asserts that the command resulted in stdout stream output that equals the + /// passed in value, trailing whitespace are kept to force strict comparison (#1235) + /// stdout_only is a better choice unless stderr may or will be non-empty + pub fn stdout_is>(&self, msg: T) -> Box<&CmdResult> { + assert_eq!( + String::from(msg.as_ref()), + self.stdout + ); + Box::new(self) + } + + /// like stdout_is(...), but expects the contents of the file at the provided relative path + pub fn stdout_is_fixture>(&self, file_rel_path: T) -> Box<&CmdResult> { + let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + self.stdout_is(contents) + } + + /// asserts that the command resulted in stderr stream output that equals the + /// passed in value, when both are trimmed of trailing whitespace + /// stderr_only is a better choice unless stdout may or will be non-empty + pub fn stderr_is>(&self, msg: T) -> Box<&CmdResult> { + assert_eq!( + String::from(msg.as_ref()).trim_end(), + self.stderr.trim_end() + ); + Box::new(self) + } + + /// like stderr_is(...), but expects the contents of the file at the provided relative path + pub fn stderr_is_fixture>(&self, file_rel_path: T) -> Box<&CmdResult> { + let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + self.stderr_is(contents) + } + + /// asserts that + /// 1. the command resulted in stdout stream output that equals the + /// passed in value, when both are trimmed of trailing whitespace + /// and 2. the command resulted in empty (zero-length) stderr stream output + pub fn stdout_only>(&self, msg: T) -> Box<&CmdResult> { + self.no_stderr().stdout_is(msg) + } + + /// like stdout_only(...), but expects the contents of the file at the provided relative path + pub fn stdout_only_fixture>(&self, file_rel_path: T) -> Box<&CmdResult> { + let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + self.stdout_only(contents) + } + + /// asserts that + /// 1. the command resulted in stderr stream output that equals the + /// passed in value, when both are trimmed of trailing whitespace + /// and 2. the command resulted in empty (zero-length) stdout stream output + pub fn stderr_only>(&self, msg: T) -> Box<&CmdResult> { + self.no_stdout().stderr_is(msg) + } + + /// like stderr_only(...), but expects the contents of the file at the provided relative path + pub fn stderr_only_fixture>(&self, file_rel_path: T) -> Box<&CmdResult> { + let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + self.stderr_only(contents) + } + + pub fn fails_silently(&self) -> Box<&CmdResult> { + assert!(!self.success); + assert_eq!("", self.stderr); + Box::new(self) + } +} + +pub fn log_info, U: AsRef>(msg: T, par: U) { + println!("{}: {}", msg.as_ref(), par.as_ref()); +} + +pub fn recursive_copy(src: &Path, dest: &Path) -> Result<()> { + if fs::metadata(src)?.is_dir() { + for entry in try!(fs::read_dir(src)) { + let entry = entry?; + let mut new_dest = PathBuf::from(dest); + new_dest.push(entry.file_name()); + if fs::metadata(entry.path())?.is_dir() { + fs::create_dir(&new_dest)?; + recursive_copy(&entry.path(), &new_dest)?; + } else { + fs::copy(&entry.path(), new_dest)?; + } + } + } + Ok(()) +} + +pub fn get_root_path() -> &'static str { + if cfg!(windows) { + "C:\\" + } else { + "/" + } +} + +/// Object-oriented path struct that represents and operates on +/// paths relative to the directory it was constructed for. +#[derive(Clone)] +pub struct AtPath { + pub subdir: PathBuf, +} + +impl AtPath { + pub fn new(subdir: &Path) -> AtPath { + AtPath { + subdir: PathBuf::from(subdir), + } + } + + pub fn as_string(&self) -> String { + self.subdir.to_str().unwrap().to_owned() + } + + pub fn plus(&self, name: &str) -> PathBuf { + let mut pathbuf = self.subdir.clone(); + pathbuf.push(name); + pathbuf + } + + pub fn plus_as_string(&self, name: &str) -> String { + String::from(self.plus(name).to_str().unwrap()) + } + + fn minus(&self, name: &str) -> PathBuf { + let prefixed = PathBuf::from(name); + if prefixed.starts_with(&self.subdir) { + let mut unprefixed = PathBuf::new(); + for component in prefixed.components().skip(self.subdir.components().count()) { + unprefixed.push(component.as_os_str().to_str().unwrap()); + } + unprefixed + } else { + prefixed + } + } + + pub fn minus_as_string(&self, name: &str) -> String { + String::from(self.minus(name).to_str().unwrap()) + } + + pub fn open(&self, name: &str) -> File { + log_info("open", self.plus_as_string(name)); + File::open(self.plus(name)).unwrap() + } + + pub fn read(&self, name: &str) -> String { + let mut f = self.open(name); + let mut contents = String::new(); + let _ = f.read_to_string(&mut contents); + contents + } + + pub fn write(&self, name: &str, contents: &str) { + let mut f = self.open(name); + let _ = f.write(contents.as_bytes()); + } + + pub fn append(&self, name: &str, contents: &str) { + log_info("open(append)", self.plus_as_string(name)); + let mut f = OpenOptions::new() + .write(true) + .append(true) + .open(self.plus(name)) + .unwrap(); + let _ = f.write(contents.as_bytes()); + } + + pub fn mkdir(&self, dir: &str) { + log_info("mkdir", self.plus_as_string(dir)); + fs::create_dir(&self.plus(dir)).unwrap(); + } + pub fn mkdir_all(&self, dir: &str) { + log_info("mkdir_all", self.plus_as_string(dir)); + fs::create_dir_all(self.plus(dir)).unwrap(); + } + + pub fn make_file(&self, name: &str) -> File { + match File::create(&self.plus(name)) { + Ok(f) => f, + Err(e) => panic!("{}", e), + } + } + + pub fn touch(&self, file: &str) { + log_info("touch", self.plus_as_string(file)); + File::create(&self.plus(file)).unwrap(); + } + + pub fn symlink_file(&self, src: &str, dst: &str) { + log_info( + "symlink", + &format!("{},{}", self.plus_as_string(src), self.plus_as_string(dst)), + ); + symlink_file(&self.plus(src), &self.plus(dst)).unwrap(); + } + + pub fn symlink_dir(&self, src: &str, dst: &str) { + log_info( + "symlink", + &format!("{},{}", self.plus_as_string(src), self.plus_as_string(dst)), + ); + symlink_dir(&self.plus(src), &self.plus(dst)).unwrap(); + } + + pub fn is_symlink(&self, path: &str) -> bool { + log_info("is_symlink", self.plus_as_string(path)); + match fs::symlink_metadata(&self.plus(path)) { + Ok(m) => m.file_type().is_symlink(), + Err(_) => false, + } + } + + pub fn resolve_link(&self, path: &str) -> String { + log_info("resolve_link", self.plus_as_string(path)); + match fs::read_link(&self.plus(path)) { + Ok(p) => self.minus_as_string(p.to_str().unwrap()), + Err(_) => "".to_string(), + } + } + + pub fn symlink_metadata(&self, path: &str) -> fs::Metadata { + match fs::symlink_metadata(&self.plus(path)) { + Ok(m) => m, + Err(e) => panic!("{}", e), + } + } + + pub fn metadata(&self, path: &str) -> fs::Metadata { + match fs::metadata(&self.plus(path)) { + Ok(m) => m, + Err(e) => panic!("{}", e), + } + } + + pub fn file_exists(&self, path: &str) -> bool { + match fs::metadata(&self.plus(path)) { + Ok(m) => m.is_file(), + Err(_) => false, + } + } + + pub fn dir_exists(&self, path: &str) -> bool { + match fs::metadata(&self.plus(path)) { + Ok(m) => m.is_dir(), + Err(_) => false, + } + } + + pub fn cleanup(&self, path: &'static str) { + let p = &self.plus(path); + if let Ok(m) = fs::metadata(p) { + if m.is_file() { + fs::remove_file(&p).unwrap(); + } else { + fs::remove_dir(&p).unwrap(); + } + } + } + + pub fn root_dir(&self) -> String { + log_info("current_directory", ""); + self.subdir.to_str().unwrap().to_owned() + } + + pub fn root_dir_resolved(&self) -> String { + log_info("current_directory_resolved", ""); + let s = self.subdir + .canonicalize() + .unwrap() + .to_str() + .unwrap() + .to_owned(); + + // Due to canonicalize()'s use of GetFinalPathNameByHandleW() on Windows, the resolved path + // starts with '\\?\' to extend the limit of a given path to 32,767 wide characters. + // + // To address this issue, we remove this prepended string if available. + // + // Source: + // http://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-without-prepended + let prefix = "\\\\?\\"; + if s.starts_with(prefix) { + String::from(&s[prefix.len()..]) + } else { + s + } + } +} + +/// 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 +/// 3. copies over fixtures for the utility to the temporary directory +pub struct TestScenario { + bin_path: PathBuf, + util_name: String, + pub fixtures: AtPath, + tmpd: Rc, +} + +impl TestScenario { + pub fn new(util_name: &str) -> TestScenario { + let tmpd = Rc::new(TempDir::new("uutils").unwrap()); + let ts = TestScenario { + bin_path: { + // Instead of hardcoding the path relative to the current + // directory, use Cargo's OUT_DIR to find path to executable. + // This allows tests to be run using profiles other than debug. + let target_dir = path_concat!(env!("OUT_DIR"), "..", "..", "..", PROGNAME); + PathBuf::from(AtPath::new(Path::new(&target_dir)).root_dir_resolved()) + }, + util_name: String::from(util_name), + fixtures: AtPath::new(tmpd.as_ref().path()), + tmpd: tmpd, + }; + let mut fixture_path_builder = env::current_dir().unwrap(); + fixture_path_builder.push(TESTS_DIR); + fixture_path_builder.push(FIXTURES_DIR); + fixture_path_builder.push(util_name); + if let Ok(m) = fs::metadata(&fixture_path_builder) { + if m.is_dir() { + recursive_copy(&fixture_path_builder, &ts.fixtures.subdir).unwrap(); + } + } + ts + } + + pub fn ucmd(&self) -> UCommand { + let mut cmd = self.cmd(&self.bin_path); + cmd.arg(&self.util_name); + cmd + } + + pub fn cmd>(&self, bin: S) -> UCommand { + UCommand::new_from_tmp(bin, self.tmpd.clone(), true) + } + + // different names are used rather than an argument + // because the need to keep the environment is exceedingly rare. + pub fn ucmd_keepenv(&self) -> UCommand { + let mut cmd = self.cmd_keepenv(&self.bin_path); + cmd.arg(&self.util_name); + cmd + } + + pub fn cmd_keepenv>(&self, bin: S) -> UCommand { + UCommand::new_from_tmp(bin, self.tmpd.clone(), false) + } +} + +/// A `UCommand` is a wrapper around an individual Command that provides several additional features +/// 1. it has convenience functions that are more ergonomic to use for piping in stdin, spawning the command +/// and asserting on the results. +/// 2. it tracks arguments provided so that in test cases which may provide variations of an arg in loops +/// the test failure can display the exact call which preceded an assertion failure. +/// 3. it provides convenience construction arguments to set the Command working directory and/or clear its environment. +#[derive(Debug)] +pub struct UCommand { + pub raw: Command, + comm_string: String, + tmpd: Option>, + has_run: bool, + stdin: Option>, +} + +impl UCommand { + pub fn new, U: AsRef>(arg: T, curdir: U, env_clear: bool) -> UCommand { + UCommand { + tmpd: None, + has_run: false, + raw: { + let mut cmd = Command::new(arg.as_ref()); + cmd.current_dir(curdir.as_ref()); + if env_clear { + if cfg!(windows) { + // %SYSTEMROOT% is required on Windows to initialize crypto provider + // ... and crypto provider is required for std::rand + // From procmon: RegQueryValue HKLM\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\Microsoft Strong Cryptographic Provider\Image Path + // SUCCESS Type: REG_SZ, Length: 66, Data: %SystemRoot%\system32\rsaenh.dll" + for (key, _) in env::vars_os() { + if key.as_os_str() != "SYSTEMROOT" { + cmd.env_remove(key); + } + } + } else { + cmd.env_clear(); + } + } + cmd + }, + comm_string: String::from(arg.as_ref().to_str().unwrap()), + stdin: None, + } + } + + pub fn new_from_tmp>(arg: T, tmpd: Rc, env_clear: bool) -> UCommand { + let tmpd_path_buf = String::from(&(*tmpd.as_ref().path().to_str().unwrap())); + let mut ucmd: UCommand = UCommand::new(arg.as_ref(), tmpd_path_buf, env_clear); + ucmd.tmpd = Some(tmpd); + ucmd + } + + pub fn arg>(&mut self, arg: S) -> Box<&mut UCommand> { + if self.has_run { + panic!(ALREADY_RUN); + } + self.comm_string.push_str(" "); + self.comm_string.push_str(arg.as_ref().to_str().unwrap()); + self.raw.arg(arg.as_ref()); + Box::new(self) + } + + /// like arg(...), but uses the contents of the file at the provided relative path as the argument + pub fn arg_fixture>(&mut self, file_rel_path: S) -> Box<&mut UCommand> { + let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + self.arg(contents) + } + + pub fn args>(&mut self, args: &[S]) -> Box<&mut UCommand> { + if self.has_run { + panic!(MULTIPLE_STDIN_MEANINGLESS); + } + for s in args { + self.comm_string.push_str(" "); + self.comm_string.push_str(s.as_ref().to_str().unwrap()); + } + + self.raw.args(args.as_ref()); + Box::new(self) + } + + /// provides stdinput to feed in to the command when spawned + pub fn pipe_in>>(&mut self, input: T) -> Box<&mut UCommand> { + if self.stdin.is_some() { + panic!(MULTIPLE_STDIN_MEANINGLESS); + } + self.stdin = Some(input.into()); + Box::new(self) + } + + /// like pipe_in(...), but uses the contents of the file at the provided relative path as the piped in data + pub fn pipe_in_fixture>(&mut self, file_rel_path: S) -> Box<&mut UCommand> { + let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + self.pipe_in(contents) + } + + pub fn env(&mut self, key: K, val: V) -> Box<&mut UCommand> + where + K: AsRef, + V: AsRef, + { + if self.has_run { + panic!(ALREADY_RUN); + } + self.raw.env(key, val); + Box::new(self) + } + + /// Spawns the command, feeds the stdin if any, and returns the + /// child process immediately. + pub fn run_no_wait(&mut self) -> Child { + if self.has_run { + panic!(ALREADY_RUN); + } + self.has_run = true; + log_info("run", &self.comm_string); + let mut result = self.raw + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + if let Some(ref input) = self.stdin { + result + .stdin + .take() + .unwrap_or_else(|| panic!("Could not take child process stdin")) + .write_all(input) + .unwrap_or_else(|e| panic!("{}", e)); + } + + result + } + + /// Spawns the command, feeds the stdin if any, waits for the result + /// and returns a command result. + /// It is recommended that you instead use succeeds() or fails() + pub fn run(&mut self) -> CmdResult { + let prog = self.run_no_wait().wait_with_output().unwrap(); + + CmdResult { + tmpd: self.tmpd.clone(), + success: prog.status.success(), + stdout: from_utf8(&prog.stdout).unwrap().to_string(), + stderr: from_utf8(&prog.stderr).unwrap().to_string(), + } + } + + /// Spawns the command, feeding the passed in stdin, waits for the result + /// and returns a command result. + /// It is recommended that, instead of this, you use a combination of pipe_in() + /// with succeeds() or fails() + pub fn run_piped_stdin>>(&mut self, input: T) -> CmdResult { + self.pipe_in(input).run() + } + + /// Spawns the command, feeds the stdin if any, waits for the result, + /// asserts success, and returns a command result. + pub fn succeeds(&mut self) -> CmdResult { + let cmd_result = self.run(); + cmd_result.success(); + cmd_result + } + + /// Spawns the command, feeds the stdin if any, waits for the result, + /// asserts failure, and returns a command result. + pub fn fails(&mut self) -> CmdResult { + let cmd_result = self.run(); + cmd_result.failure(); + cmd_result + } +} + +pub fn read_size(child: &mut Child, size: usize) -> String { + let mut output = Vec::new(); + output.resize(size, 0); + sleep(Duration::from_secs(1)); + child + .stdout + .as_mut() + .unwrap() + .read(output.as_mut_slice()) + .unwrap(); + String::from_utf8(output).unwrap() +} diff --git a/coreutils/tests/fixtures/.gitattributes b/coreutils/tests/fixtures/.gitattributes new file mode 100644 index 000000000..2156bf6bd --- /dev/null +++ b/coreutils/tests/fixtures/.gitattributes @@ -0,0 +1 @@ +* -text diff diff --git a/coreutils/tests/fixtures/cat/256.txt b/coreutils/tests/fixtures/cat/256.txt new file mode 100644 index 0000000000000000000000000000000000000000..c86626638e0bc8cf47ca49bb1525b40e9737ee64 GIT binary patch literal 256 zcmV+b0ssC00RjUA1qKHQ2?`4g4Gs?w5fT#=6&4p585$cL9UdPbAtECrB_<~*DJm;0 zEiNxGF)}kWH8wXmIXXK$Jw87`K|(`BMMg(RNlHshO-@fxQBqS>RaRG6Sz23MU0z>c zVPa!sWoBn+X=-b1ZEkOHadLBXb#`}nd3t+%eSUv{fr5jCg@%WSiHeJijgF6yk&=^? zm6n&7nVOrNot~edp`xRtrKYE-sj922t*)=Iv9hzYwYImoxw^Z&y}rM|!NSAD#m2|T z$;!*j&Cbuz(bCh@)z;V8+1lIO-QM5e;o{@u<>u$;>FVq3?e6dJ@$&QZ_4fDp`TG0( G{r>;0_J4r@ literal 0 HcmV?d00001 diff --git a/coreutils/tests/fixtures/cat/alpha.txt b/coreutils/tests/fixtures/cat/alpha.txt new file mode 100644 index 000000000..8351f4b20 --- /dev/null +++ b/coreutils/tests/fixtures/cat/alpha.txt @@ -0,0 +1,5 @@ +abcde +fghij +klmno +pqrst +uvwxyz diff --git a/coreutils/tests/fixtures/cat/nonewline.txt b/coreutils/tests/fixtures/cat/nonewline.txt new file mode 100644 index 000000000..320b95eaf --- /dev/null +++ b/coreutils/tests/fixtures/cat/nonewline.txt @@ -0,0 +1 @@ +text without a trailing newline \ No newline at end of file diff --git a/coreutils/tests/fixtures/cksum/alice_in_wonderland.txt b/coreutils/tests/fixtures/cksum/alice_in_wonderland.txt new file mode 100644 index 000000000..a95562a1c --- /dev/null +++ b/coreutils/tests/fixtures/cksum/alice_in_wonderland.txt @@ -0,0 +1,5 @@ +Alice was beginning to get very tired of sitting by +her sister on the bank, and of having nothing to do: once or twice +she had peeped into the book her sister was reading, but it had no +pictures or conversations in it, "and what is the use of a book," +thought Alice "without pictures or conversation?" diff --git a/coreutils/tests/fixtures/cksum/lorem_ipsum.txt b/coreutils/tests/fixtures/cksum/lorem_ipsum.txt new file mode 100644 index 000000000..16752446c --- /dev/null +++ b/coreutils/tests/fixtures/cksum/lorem_ipsum.txt @@ -0,0 +1,13 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing +elit. Nunc interdum suscipit sem vel ornare. Proin euismod, justo +sed mollis dictum, eros urna ultricies augue, eu pharetra mi ex id +ante. Duis convallis porttitor aliquam. Nunc vitae tincidunt ex. +Suspendisse iaculis ligula ac diam consectetur lacinia. Donec vel +velit dui. Etiam fringilla, dolor quis tempor vehicula, lacus +turpis bibendum velit, et pellentesque elit odio a magna. Cras +vulputate tortor non libero vehicula euismod. Aliquam tincidunt +nisl eget enim cursus, viverra sagittis magna commodo. Cras rhoncus +egestas leo nec blandit. Suspendisse potenti. Etiam ullamcorper +leo vel lacus vestibulum, cursus semper eros efficitur. In hac +habitasse platea dictumst. Phasellus scelerisque vehicula +fringilla. diff --git a/coreutils/tests/fixtures/cksum/multiple_files.expected b/coreutils/tests/fixtures/cksum/multiple_files.expected new file mode 100644 index 000000000..d7a4f5b4f --- /dev/null +++ b/coreutils/tests/fixtures/cksum/multiple_files.expected @@ -0,0 +1,2 @@ +378294376 772 lorem_ipsum.txt +3805907707 302 alice_in_wonderland.txt diff --git a/coreutils/tests/fixtures/cksum/single_file.expected b/coreutils/tests/fixtures/cksum/single_file.expected new file mode 100644 index 000000000..e9fc1ca7c --- /dev/null +++ b/coreutils/tests/fixtures/cksum/single_file.expected @@ -0,0 +1 @@ +378294376 772 lorem_ipsum.txt diff --git a/coreutils/tests/fixtures/cksum/stdin.expected b/coreutils/tests/fixtures/cksum/stdin.expected new file mode 100644 index 000000000..28b37d0be --- /dev/null +++ b/coreutils/tests/fixtures/cksum/stdin.expected @@ -0,0 +1 @@ +378294376 772 diff --git a/coreutils/tests/fixtures/comm/a b/coreutils/tests/fixtures/comm/a new file mode 100644 index 000000000..58f424cdb --- /dev/null +++ b/coreutils/tests/fixtures/comm/a @@ -0,0 +1,2 @@ +a +z diff --git a/coreutils/tests/fixtures/comm/ab.expected b/coreutils/tests/fixtures/comm/ab.expected new file mode 100644 index 000000000..0efac0666 --- /dev/null +++ b/coreutils/tests/fixtures/comm/ab.expected @@ -0,0 +1,3 @@ +a + b + z diff --git a/coreutils/tests/fixtures/comm/ab1.expected b/coreutils/tests/fixtures/comm/ab1.expected new file mode 100644 index 000000000..7c81c8ba7 --- /dev/null +++ b/coreutils/tests/fixtures/comm/ab1.expected @@ -0,0 +1,2 @@ +b + z diff --git a/coreutils/tests/fixtures/comm/ab2.expected b/coreutils/tests/fixtures/comm/ab2.expected new file mode 100644 index 000000000..c98cd0da6 --- /dev/null +++ b/coreutils/tests/fixtures/comm/ab2.expected @@ -0,0 +1,2 @@ +a + z diff --git a/coreutils/tests/fixtures/comm/ab3.expected b/coreutils/tests/fixtures/comm/ab3.expected new file mode 100644 index 000000000..1898bc804 --- /dev/null +++ b/coreutils/tests/fixtures/comm/ab3.expected @@ -0,0 +1,2 @@ +a + b diff --git a/coreutils/tests/fixtures/comm/ab_delimiter_word.expected b/coreutils/tests/fixtures/comm/ab_delimiter_word.expected new file mode 100644 index 000000000..a0c510e20 --- /dev/null +++ b/coreutils/tests/fixtures/comm/ab_delimiter_word.expected @@ -0,0 +1,3 @@ +a +wordb +wordwordz diff --git a/coreutils/tests/fixtures/comm/aempty.expected b/coreutils/tests/fixtures/comm/aempty.expected new file mode 100644 index 000000000..58f424cdb --- /dev/null +++ b/coreutils/tests/fixtures/comm/aempty.expected @@ -0,0 +1,2 @@ +a +z diff --git a/coreutils/tests/fixtures/comm/b b/coreutils/tests/fixtures/comm/b new file mode 100644 index 000000000..63d7d7058 --- /dev/null +++ b/coreutils/tests/fixtures/comm/b @@ -0,0 +1,2 @@ +b +z diff --git a/coreutils/tests/fixtures/comm/bad_order11.defaultcheck_order.expected b/coreutils/tests/fixtures/comm/bad_order11.defaultcheck_order.expected new file mode 100644 index 000000000..81aa48629 --- /dev/null +++ b/coreutils/tests/fixtures/comm/bad_order11.defaultcheck_order.expected @@ -0,0 +1,4 @@ + e + d + b + a diff --git a/coreutils/tests/fixtures/comm/bad_order12.check_order.expected b/coreutils/tests/fixtures/comm/bad_order12.check_order.expected new file mode 100644 index 000000000..3930ff036 --- /dev/null +++ b/coreutils/tests/fixtures/comm/bad_order12.check_order.expected @@ -0,0 +1 @@ + e diff --git a/coreutils/tests/fixtures/comm/bad_order12.nocheck_order.expected b/coreutils/tests/fixtures/comm/bad_order12.nocheck_order.expected new file mode 100644 index 000000000..b57118013 --- /dev/null +++ b/coreutils/tests/fixtures/comm/bad_order12.nocheck_order.expected @@ -0,0 +1,7 @@ + e + c + b + a +d +b +a diff --git a/coreutils/tests/fixtures/comm/bad_order_1 b/coreutils/tests/fixtures/comm/bad_order_1 new file mode 100644 index 000000000..2ebde65a6 --- /dev/null +++ b/coreutils/tests/fixtures/comm/bad_order_1 @@ -0,0 +1,4 @@ +e +d +b +a diff --git a/coreutils/tests/fixtures/comm/bad_order_2 b/coreutils/tests/fixtures/comm/bad_order_2 new file mode 100644 index 000000000..76603aede --- /dev/null +++ b/coreutils/tests/fixtures/comm/bad_order_2 @@ -0,0 +1,4 @@ +e +c +b +a diff --git a/coreutils/tests/fixtures/comm/defaultcheck_unintuitive.expected b/coreutils/tests/fixtures/comm/defaultcheck_unintuitive.expected new file mode 100644 index 000000000..132eab5b2 --- /dev/null +++ b/coreutils/tests/fixtures/comm/defaultcheck_unintuitive.expected @@ -0,0 +1,6 @@ + m + h + n + o +c + p diff --git a/coreutils/tests/fixtures/comm/defaultcheck_unintuitive_1 b/coreutils/tests/fixtures/comm/defaultcheck_unintuitive_1 new file mode 100644 index 000000000..f32fc1fd7 --- /dev/null +++ b/coreutils/tests/fixtures/comm/defaultcheck_unintuitive_1 @@ -0,0 +1,6 @@ +m +h +n +o +c +p diff --git a/coreutils/tests/fixtures/comm/defaultcheck_unintuitive_2 b/coreutils/tests/fixtures/comm/defaultcheck_unintuitive_2 new file mode 100644 index 000000000..212de88c4 --- /dev/null +++ b/coreutils/tests/fixtures/comm/defaultcheck_unintuitive_2 @@ -0,0 +1,5 @@ +m +h +n +o +p diff --git a/coreutils/tests/fixtures/comm/empty b/coreutils/tests/fixtures/comm/empty new file mode 100644 index 000000000..e69de29bb diff --git a/coreutils/tests/fixtures/comm/emptyempty.expected b/coreutils/tests/fixtures/comm/emptyempty.expected new file mode 100644 index 000000000..e69de29bb diff --git a/coreutils/tests/fixtures/comm/lowercase_uppercase b/coreutils/tests/fixtures/comm/lowercase_uppercase new file mode 100644 index 000000000..768ebc2b9 --- /dev/null +++ b/coreutils/tests/fixtures/comm/lowercase_uppercase @@ -0,0 +1,2 @@ +a +A \ No newline at end of file diff --git a/coreutils/tests/fixtures/comm/lowercase_uppercase.c.expected b/coreutils/tests/fixtures/comm/lowercase_uppercase.c.expected new file mode 100644 index 000000000..8458d7960 --- /dev/null +++ b/coreutils/tests/fixtures/comm/lowercase_uppercase.c.expected @@ -0,0 +1 @@ + a diff --git a/coreutils/tests/fixtures/comm/lowercase_uppercase.en_us.expected b/coreutils/tests/fixtures/comm/lowercase_uppercase.en_us.expected new file mode 100644 index 000000000..3992b34d0 --- /dev/null +++ b/coreutils/tests/fixtures/comm/lowercase_uppercase.en_us.expected @@ -0,0 +1,2 @@ + a + A diff --git a/coreutils/tests/fixtures/cp/existing_file.txt b/coreutils/tests/fixtures/cp/existing_file.txt new file mode 100644 index 000000000..651b4c7b0 --- /dev/null +++ b/coreutils/tests/fixtures/cp/existing_file.txt @@ -0,0 +1 @@ +Cogito ergo sum. diff --git a/coreutils/tests/fixtures/cp/hello_dir/hello.txt b/coreutils/tests/fixtures/cp/hello_dir/hello.txt new file mode 100644 index 000000000..e69de29bb diff --git a/coreutils/tests/fixtures/cp/hello_dir_with_file/hello_world.txt b/coreutils/tests/fixtures/cp/hello_dir_with_file/hello_world.txt new file mode 100644 index 000000000..8ab686eaf --- /dev/null +++ b/coreutils/tests/fixtures/cp/hello_dir_with_file/hello_world.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/coreutils/tests/fixtures/cp/hello_world.txt b/coreutils/tests/fixtures/cp/hello_world.txt new file mode 100644 index 000000000..8ab686eaf --- /dev/null +++ b/coreutils/tests/fixtures/cp/hello_world.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/coreutils/tests/fixtures/cp/how_are_you.txt b/coreutils/tests/fixtures/cp/how_are_you.txt new file mode 100644 index 000000000..d18c6b11f --- /dev/null +++ b/coreutils/tests/fixtures/cp/how_are_you.txt @@ -0,0 +1 @@ +How are you? diff --git a/coreutils/tests/fixtures/cut/delimiter_specified.expected b/coreutils/tests/fixtures/cut/delimiter_specified.expected new file mode 100644 index 000000000..ec022895e --- /dev/null +++ b/coreutils/tests/fixtures/cut/delimiter_specified.expected @@ -0,0 +1,5 @@ +foo:bar:qux +one:two:four:six:seven +alpha:beta:delta:zeta:eta:iota:kappa:lambda:mu +the quick brown fox jumps over the lazy dog +sally sells seashells down by the seashore where are the seashells sally sells diff --git a/coreutils/tests/fixtures/cut/lists.txt b/coreutils/tests/fixtures/cut/lists.txt new file mode 100644 index 000000000..3ecb99519 --- /dev/null +++ b/coreutils/tests/fixtures/cut/lists.txt @@ -0,0 +1,5 @@ +foo:bar:baz:qux:quux +one:two:three:four:five:six:seven +alpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu +the quick brown fox jumps over the lazy dog +sally sells seashells down by the seashore where are the seashells sally sells diff --git a/coreutils/tests/fixtures/cut/output_delimiter.expected b/coreutils/tests/fixtures/cut/output_delimiter.expected new file mode 100644 index 000000000..5be747a20 --- /dev/null +++ b/coreutils/tests/fixtures/cut/output_delimiter.expected @@ -0,0 +1,5 @@ +foo@bar@qux +one@two@four@six@seven +alpha@beta@delta@zeta@eta@iota@kappa@lambda@mu +the quick brown fox jumps over the lazy dog +sally sells seashells down by the seashore where are the seashells sally sells diff --git a/coreutils/tests/fixtures/cut/sequences/byte_aggregate.expected b/coreutils/tests/fixtures/cut/sequences/byte_aggregate.expected new file mode 100644 index 000000000..85891e06e --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/byte_aggregate.expected @@ -0,0 +1,5 @@ +fo:arbaz:qux:quux +on:wothree:four:five:six:seven +alh:bta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu +th uik brown fox jumps over the lazy dog +sal slls seashells down by the seashore where are the seashells sally sells diff --git a/coreutils/tests/fixtures/cut/sequences/byte_prefix.expected b/coreutils/tests/fixtures/cut/sequences/byte_prefix.expected new file mode 100644 index 000000000..e7df5a318 --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/byte_prefix.expected @@ -0,0 +1,5 @@ +fo +on +al +th +sa diff --git a/coreutils/tests/fixtures/cut/sequences/byte_range.expected b/coreutils/tests/fixtures/cut/sequences/byte_range.expected new file mode 100644 index 000000000..35314cf20 --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/byte_range.expected @@ -0,0 +1,5 @@ +oo: +ne: +lph +he +all diff --git a/coreutils/tests/fixtures/cut/sequences/byte_singular.expected b/coreutils/tests/fixtures/cut/sequences/byte_singular.expected new file mode 100644 index 000000000..7faa91b4e --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/byte_singular.expected @@ -0,0 +1,5 @@ +o +n +l +h +a diff --git a/coreutils/tests/fixtures/cut/sequences/byte_subsumed.expected b/coreutils/tests/fixtures/cut/sequences/byte_subsumed.expected new file mode 100644 index 000000000..914c8a6e1 --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/byte_subsumed.expected @@ -0,0 +1,5 @@ +oo:bar:baz:qux:quux +ne:two:three:four:five:six:seven +lpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu +he quick brown fox jumps over the lazy dog +ally sells seashells down by the seashore where are the seashells sally sells diff --git a/coreutils/tests/fixtures/cut/sequences/byte_suffix.expected b/coreutils/tests/fixtures/cut/sequences/byte_suffix.expected new file mode 100644 index 000000000..914c8a6e1 --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/byte_suffix.expected @@ -0,0 +1,5 @@ +oo:bar:baz:qux:quux +ne:two:three:four:five:six:seven +lpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu +he quick brown fox jumps over the lazy dog +ally sells seashells down by the seashore where are the seashells sally sells diff --git a/coreutils/tests/fixtures/cut/sequences/field_aggregate.expected b/coreutils/tests/fixtures/cut/sequences/field_aggregate.expected new file mode 100644 index 000000000..6be14fadb --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/field_aggregate.expected @@ -0,0 +1,5 @@ +foo:bar:baz:qux:quux +one:two:three:four:five:six:seven +alpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu +the quick brown fox jumps over the lazy dog +sally sells down the seashore are the seashells sally sells diff --git a/coreutils/tests/fixtures/cut/sequences/field_prefix.expected b/coreutils/tests/fixtures/cut/sequences/field_prefix.expected new file mode 100644 index 000000000..e5a018f3b --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/field_prefix.expected @@ -0,0 +1,5 @@ +foo:bar:baz:qux:quux +one:two:three:four:five:six:seven +alpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu +the quick brown fox jumps over the lazy dog +sally sells diff --git a/coreutils/tests/fixtures/cut/sequences/field_range.expected b/coreutils/tests/fixtures/cut/sequences/field_range.expected new file mode 100644 index 000000000..246b20827 --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/field_range.expected @@ -0,0 +1,5 @@ +foo:bar:baz:qux:quux +one:two:three:four:five:six:seven +alpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu +the quick brown fox jumps over the lazy dog +sells seashells down diff --git a/coreutils/tests/fixtures/cut/sequences/field_singular.expected b/coreutils/tests/fixtures/cut/sequences/field_singular.expected new file mode 100644 index 000000000..70dc5cc16 --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/field_singular.expected @@ -0,0 +1,5 @@ +foo:bar:baz:qux:quux +one:two:three:four:five:six:seven +alpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu +the quick brown fox jumps over the lazy dog +sells diff --git a/coreutils/tests/fixtures/cut/sequences/field_subsumed.expected b/coreutils/tests/fixtures/cut/sequences/field_subsumed.expected new file mode 100644 index 000000000..cff4d1061 --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/field_subsumed.expected @@ -0,0 +1,5 @@ +foo:bar:baz:qux:quux +one:two:three:four:five:six:seven +alpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu +the quick brown fox jumps over the lazy dog +sells seashells down by the seashore where are the seashells sally sells diff --git a/coreutils/tests/fixtures/cut/sequences/field_suffix.expected b/coreutils/tests/fixtures/cut/sequences/field_suffix.expected new file mode 100644 index 000000000..cff4d1061 --- /dev/null +++ b/coreutils/tests/fixtures/cut/sequences/field_suffix.expected @@ -0,0 +1,5 @@ +foo:bar:baz:qux:quux +one:two:three:four:five:six:seven +alpha:beta:gamma:delta:epsilon:zeta:eta:theta:iota:kappa:lambda:mu +the quick brown fox jumps over the lazy dog +sells seashells down by the seashore where are the seashells sally sells diff --git a/coreutils/tests/fixtures/dircolors/bash_def.expected b/coreutils/tests/fixtures/dircolors/bash_def.expected new file mode 100644 index 000000000..d0bd0d33b --- /dev/null +++ b/coreutils/tests/fixtures/dircolors/bash_def.expected @@ -0,0 +1,2 @@ +LS_COLORS='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:'; +export LS_COLORS diff --git a/coreutils/tests/fixtures/dircolors/csh_def.expected b/coreutils/tests/fixtures/dircolors/csh_def.expected new file mode 100644 index 000000000..2d1e1d374 --- /dev/null +++ b/coreutils/tests/fixtures/dircolors/csh_def.expected @@ -0,0 +1 @@ +setenv LS_COLORS '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:' diff --git a/coreutils/tests/fixtures/dircolors/internal.expected b/coreutils/tests/fixtures/dircolors/internal.expected new file mode 100644 index 000000000..8566587f4 --- /dev/null +++ b/coreutils/tests/fixtures/dircolors/internal.expected @@ -0,0 +1,191 @@ +# Configuration file for dircolors, a utility to help you set the +# LS_COLORS environment variable used by GNU ls with the --color option. +# Copyright (C) 1996-2016 Free Software Foundation, Inc. +# Copying and distribution of this file, with or without modification, +# are permitted provided the copyright notice and this notice are preserved. +# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the +# slackware version of dircolors) are recognized but ignored. +# Below are TERM entries, which can be a glob patterns, to match +# against the TERM environment variable to determine if it is colorizable. +TERM Eterm +TERM ansi +TERM color-xterm +TERM con[0-9]*x[0-9]* +TERM cons25 +TERM console +TERM cygwin +TERM dtterm +TERM eterm-color +TERM gnome +TERM gnome-256color +TERM hurd +TERM jfbterm +TERM konsole +TERM kterm +TERM linux +TERM linux-c +TERM mach-color +TERM mach-gnu-color +TERM mlterm +TERM putty +TERM putty-256color +TERM rxvt* +TERM screen* +TERM st +TERM st-256color +TERM terminator +TERM tmux* +TERM vt100 +TERM xterm* +# Below are the color init strings for the basic file types. A color init +# string consists of one or more of the following numeric codes: +# Attribute codes: +# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed +# Text color codes: +# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white +# Background color codes: +# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white +#NORMAL 00 # no color code at all +#FILE 00 # regular file: use no color at all +RESET 0 # reset to "normal" color +DIR 01;34 # directory +LINK 01;36 # symbolic link. (If you set this to 'target' instead of a + # numerical value, the color is as for the file pointed to.) +MULTIHARDLINK 00 # regular file with more than one link +FIFO 40;33 # pipe +SOCK 01;35 # socket +DOOR 01;35 # door +BLK 40;33;01 # block device driver +CHR 40;33;01 # character device driver +ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ... +MISSING 00 # ... and the files they point to +SETUID 37;41 # file that is setuid (u+s) +SETGID 30;43 # file that is setgid (g+s) +CAPABILITY 30;41 # file with capability +STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w) +OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky +STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable +# This is for files with execute permission: +EXEC 01;32 +# List any file extensions like '.gz' or '.tar' that you would like ls +# to colorize below. Put the extension, a space, and the color init string. +# (and any comments you want to add after a '#') +# If you use DOS-style suffixes, you may want to uncomment the following: +#.cmd 01;32 # executables (bright green) +#.exe 01;32 +#.com 01;32 +#.btm 01;32 +#.bat 01;32 +# Or if you want to colorize scripts even if they do not have the +# executable bit actually set. +#.sh 01;32 +#.csh 01;32 + # archives or compressed (bright red) +.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 +# image formats +.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 +# http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions +.ogv 01;35 +.ogx 01;35 +# audio formats +.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 +# http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions +.oga 00;36 +.opus 00;36 +.spx 00;36 +.xspf 00;36 diff --git a/coreutils/tests/fixtures/dircolors/keywords.csh.expected b/coreutils/tests/fixtures/dircolors/keywords.csh.expected new file mode 100644 index 000000000..f7f1f4ead --- /dev/null +++ b/coreutils/tests/fixtures/dircolors/keywords.csh.expected @@ -0,0 +1 @@ +setenv LS_COLORS 'no=00:fi=00: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:lc=\e[:rc=m:ec=\xff:' diff --git a/coreutils/tests/fixtures/dircolors/keywords.sh.expected b/coreutils/tests/fixtures/dircolors/keywords.sh.expected new file mode 100644 index 000000000..fdbf6871e --- /dev/null +++ b/coreutils/tests/fixtures/dircolors/keywords.sh.expected @@ -0,0 +1,2 @@ +LS_COLORS='no=00:fi=00: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:lc=\e[:rc=m:ec=\xff:'; +export LS_COLORS diff --git a/coreutils/tests/fixtures/dircolors/keywords.txt b/coreutils/tests/fixtures/dircolors/keywords.txt new file mode 100644 index 000000000..ac131d7ba --- /dev/null +++ b/coreutils/tests/fixtures/dircolors/keywords.txt @@ -0,0 +1,23 @@ +NORMAL 00 # no color code at all +FILE 00 # regular file: use no color at all +RESET 0 # reset to "normal" color +Dir 01;34 # directory +LINK 01;36 # symbolic link. (If you set this to 'target' instead of a +multihardlink 00 # regular file with more than one link +fifo 40;33 # pipe +SOCK 01;35 # socket +DOOR 01;35 # door +BLK 40;33;01 # block device driver +CHR 40;33;01 # character device driver +orphan 40;31;01 # symlink to nonexistent file, or non-stat'able file ... +MISSING 00 # ... and the files they point to +setuid 37;41 # file that is setuid (u+s) +SETGID 30;43 # file that is setgid (g+s) +CAPABILITY 30;41 # file with capability +STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w) +Other_writable 34;42 # dir that is other-writable (o+w) and not sticky +STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable +EXEC 01;32 +LEFTCODE \e[ +RIGHTCODE m +ENDCODE \xff diff --git a/coreutils/tests/fixtures/dircolors/test1.csh.expected b/coreutils/tests/fixtures/dircolors/test1.csh.expected new file mode 100644 index 000000000..5d7186935 --- /dev/null +++ b/coreutils/tests/fixtures/dircolors/test1.csh.expected @@ -0,0 +1 @@ +setenv LS_COLORS '*.xspf=00;36:no=00:fi=00:su=37;41:sg=30;43:ca=30;41:' diff --git a/coreutils/tests/fixtures/dircolors/test1.sh.expected b/coreutils/tests/fixtures/dircolors/test1.sh.expected new file mode 100644 index 000000000..c34901ea4 --- /dev/null +++ b/coreutils/tests/fixtures/dircolors/test1.sh.expected @@ -0,0 +1,2 @@ +LS_COLORS='*.xspf=00;36:no=00:fi=00:su=37;41:sg=30;43:ca=30;41:'; +export LS_COLORS diff --git a/coreutils/tests/fixtures/dircolors/test1.txt b/coreutils/tests/fixtures/dircolors/test1.txt new file mode 100644 index 000000000..855a642ba --- /dev/null +++ b/coreutils/tests/fixtures/dircolors/test1.txt @@ -0,0 +1,24 @@ +.xspf 00;36 +NORMAL 00 # no color code at all +FILE 00 # regular file: use no color at all +# Below are the color init strings for the basic file types. A color init +# string consists of one or more of the following numeric codes: +# Attribute codes: +# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed +# Text color codes: +# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white +# Background color codes: +# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white +TERM screen* +.rar 12;34 +.tar 01;31 +.tgz 01;31 +.arc 01;31 +ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ... +MISSING 00 # ... and the files they point to +.arj 01;31 +.taz 01;31 +TERM gnome +SETUID 37;41 # file that is setuid (u+s) +SETGID 30;43 # file that is setgid (g+s) +CAPABILITY 30;41 # file with capability diff --git a/coreutils/tests/fixtures/du/subdir/deeper/words.txt b/coreutils/tests/fixtures/du/subdir/deeper/words.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/coreutils/tests/fixtures/du/subdir/deeper/words.txt @@ -0,0 +1 @@ +hello diff --git a/coreutils/tests/fixtures/du/subdir/links/subwords.txt b/coreutils/tests/fixtures/du/subdir/links/subwords.txt new file mode 100644 index 0000000000000000000000000000000000000000..e7f3c2d40ecc31921643a456cda2de3a907b680a GIT binary patch literal 5120 scmeIu0Sy2E0K%a6Pi+qe5hx58Fkrxd0RsjM7%*VKfB^#r3>bJF7!&{i0RR91 literal 0 HcmV?d00001 diff --git a/coreutils/tests/fixtures/du/subdir/links/subwords2.txt b/coreutils/tests/fixtures/du/subdir/links/subwords2.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/coreutils/tests/fixtures/du/subdir/links/subwords2.txt @@ -0,0 +1 @@ +hello diff --git a/coreutils/tests/fixtures/du/words.txt b/coreutils/tests/fixtures/du/words.txt new file mode 100644 index 000000000..45b983be3 --- /dev/null +++ b/coreutils/tests/fixtures/du/words.txt @@ -0,0 +1 @@ +hi diff --git a/coreutils/tests/fixtures/env/vars.conf.txt b/coreutils/tests/fixtures/env/vars.conf.txt new file mode 100644 index 000000000..9364d6d12 --- /dev/null +++ b/coreutils/tests/fixtures/env/vars.conf.txt @@ -0,0 +1,4 @@ +# comment +FOO=bar + +BAR="bamf this" diff --git a/coreutils/tests/fixtures/fold/lorem_ipsum.txt b/coreutils/tests/fixtures/fold/lorem_ipsum.txt new file mode 100644 index 000000000..429fe2ef1 --- /dev/null +++ b/coreutils/tests/fixtures/fold/lorem_ipsum.txt @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc interdum suscipit sem vel ornare. Proin euismod, justo sed mollis dictum, eros urna ultricies augue, eu pharetra mi ex id ante. Duis convallis porttitor aliquam. Nunc vitae tincidunt ex. Suspendisse iaculis ligula ac diam consectetur lacinia. Donec vel velit dui. Etiam fringilla, dolor quis tempor vehicula, lacus turpis bibendum velit, et pellentesque elit odio a magna. Cras vulputate tortor non libero vehicula euismod. Aliquam tincidunt nisl eget enim cursus, viverra sagittis magna commodo. Cras rhoncus egestas leo nec blandit. Suspendisse potenti. Etiam ullamcorper leo vel lacus vestibulum, cursus semper eros efficitur. In hac habitasse platea dictumst. Phasellus scelerisque vehicula fringilla. diff --git a/coreutils/tests/fixtures/fold/lorem_ipsum_40_column_hard.expected b/coreutils/tests/fixtures/fold/lorem_ipsum_40_column_hard.expected new file mode 100644 index 000000000..dbc6095c1 --- /dev/null +++ b/coreutils/tests/fixtures/fold/lorem_ipsum_40_column_hard.expected @@ -0,0 +1,20 @@ +Lorem ipsum dolor sit amet, consectetur +adipiscing elit. Nunc interdum suscipit +sem vel ornare. Proin euismod, justo sed + mollis dictum, eros urna ultricies augu +e, eu pharetra mi ex id ante. Duis conva +llis porttitor aliquam. Nunc vitae tinci +dunt ex. Suspendisse iaculis ligula ac d +iam consectetur lacinia. Donec vel velit + dui. Etiam fringilla, dolor quis tempor + vehicula, lacus turpis bibendum velit, +et pellentesque elit odio a magna. Cras +vulputate tortor non libero vehicula eui +smod. Aliquam tincidunt nisl eget enim c +ursus, viverra sagittis magna commodo. C +ras rhoncus egestas leo nec blandit. Sus +pendisse potenti. Etiam ullamcorper leo +vel lacus vestibulum, cursus semper eros + efficitur. In hac habitasse platea dict +umst. Phasellus scelerisque vehicula fri +ngilla. diff --git a/coreutils/tests/fixtures/fold/lorem_ipsum_40_column_word.expected b/coreutils/tests/fixtures/fold/lorem_ipsum_40_column_word.expected new file mode 100644 index 000000000..a855134db --- /dev/null +++ b/coreutils/tests/fixtures/fold/lorem_ipsum_40_column_word.expected @@ -0,0 +1,21 @@ +Lorem ipsum dolor sit amet, consectetur +adipiscing elit. Nunc interdum suscipit +sem vel ornare. Proin euismod, justo +sed mollis dictum, eros urna ultricies +augue, eu pharetra mi ex id ante. Duis +convallis porttitor aliquam. Nunc vitae +tincidunt ex. Suspendisse iaculis +ligula ac diam consectetur lacinia. +Donec vel velit dui. Etiam fringilla, +dolor quis tempor vehicula, lacus +turpis bibendum velit, et pellentesque +elit odio a magna. Cras vulputate +tortor non libero vehicula euismod. +Aliquam tincidunt nisl eget enim +cursus, viverra sagittis magna commodo. +Cras rhoncus egestas leo nec blandit. +Suspendisse potenti. Etiam ullamcorper +leo vel lacus vestibulum, cursus semper +eros efficitur. In hac habitasse platea +dictumst. Phasellus scelerisque +vehicula fringilla. diff --git a/coreutils/tests/fixtures/fold/lorem_ipsum_80_column.expected b/coreutils/tests/fixtures/fold/lorem_ipsum_80_column.expected new file mode 100644 index 000000000..3964cbf1a --- /dev/null +++ b/coreutils/tests/fixtures/fold/lorem_ipsum_80_column.expected @@ -0,0 +1,10 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc interdum suscipit +sem vel ornare. Proin euismod, justo sed mollis dictum, eros urna ultricies augu +e, eu pharetra mi ex id ante. Duis convallis porttitor aliquam. Nunc vitae tinci +dunt ex. Suspendisse iaculis ligula ac diam consectetur lacinia. Donec vel velit + dui. Etiam fringilla, dolor quis tempor vehicula, lacus turpis bibendum velit, +et pellentesque elit odio a magna. Cras vulputate tortor non libero vehicula eui +smod. Aliquam tincidunt nisl eget enim cursus, viverra sagittis magna commodo. C +ras rhoncus egestas leo nec blandit. Suspendisse potenti. Etiam ullamcorper leo +vel lacus vestibulum, cursus semper eros efficitur. In hac habitasse platea dict +umst. Phasellus scelerisque vehicula fringilla. diff --git a/coreutils/tests/fixtures/hashsum/input.txt b/coreutils/tests/fixtures/hashsum/input.txt new file mode 100644 index 000000000..8c01d89ae --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/input.txt @@ -0,0 +1 @@ +hello, world \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/md5.expected b/coreutils/tests/fixtures/hashsum/md5.expected new file mode 100644 index 000000000..9a78d4ab7 --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/md5.expected @@ -0,0 +1 @@ +e4d7f1b4ed2e42d15898f4b27b019da4 \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/sha1.expected b/coreutils/tests/fixtures/hashsum/sha1.expected new file mode 100644 index 000000000..8a50a732e --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/sha1.expected @@ -0,0 +1 @@ +b7e23ec29af22b0b4e41da31e868d57226121c84 \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/sha224.expected b/coreutils/tests/fixtures/hashsum/sha224.expected new file mode 100644 index 000000000..456eca808 --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/sha224.expected @@ -0,0 +1 @@ +6e1a93e32fb44081a401f3db3ef2e6e108b7bbeeb5705afdaf01fb27 \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/sha256.expected b/coreutils/tests/fixtures/hashsum/sha256.expected new file mode 100644 index 000000000..8def59791 --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/sha256.expected @@ -0,0 +1 @@ +09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/sha384.expected b/coreutils/tests/fixtures/hashsum/sha384.expected new file mode 100644 index 000000000..489dbcef7 --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/sha384.expected @@ -0,0 +1 @@ +1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/sha3_224.expected b/coreutils/tests/fixtures/hashsum/sha3_224.expected new file mode 100644 index 000000000..595f99880 --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/sha3_224.expected @@ -0,0 +1 @@ +927b362eaf84a75785bbec3370d1c9711349e93f1104eda060784221 \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/sha3_256.expected b/coreutils/tests/fixtures/hashsum/sha3_256.expected new file mode 100644 index 000000000..46c21e529 --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/sha3_256.expected @@ -0,0 +1 @@ +bfb3959527d7a3f2f09def2f6915452d55a8f122df9e164d6f31c7fcf6093e14 \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/sha3_384.expected b/coreutils/tests/fixtures/hashsum/sha3_384.expected new file mode 100644 index 000000000..8c975b095 --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/sha3_384.expected @@ -0,0 +1 @@ +fbd0c5931195aaa9517869972b372f717bb69f7f9f72bfc0884ed0531c36a16fc2db5dd6d82131968b23ffe0e90757e5 \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/sha3_512.expected b/coreutils/tests/fixtures/hashsum/sha3_512.expected new file mode 100644 index 000000000..f766857ba --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/sha3_512.expected @@ -0,0 +1 @@ +2ed3a863a12e2f8ff140aa86232ff3603a7f24af62f0e2ca74672494ade175a9a3de42a351b5019d931a1deae0499609038d9b47268779d76198e1d410d20974 \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/sha512.expected b/coreutils/tests/fixtures/hashsum/sha512.expected new file mode 100644 index 000000000..fd8173686 --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/sha512.expected @@ -0,0 +1 @@ +8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9 \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/shake128_256.expected b/coreutils/tests/fixtures/hashsum/shake128_256.expected new file mode 100644 index 000000000..a184b53e4 --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/shake128_256.expected @@ -0,0 +1 @@ +83d41db453072caa9953f2f316480fbbcb84a5f3505460a18b3a36a814ae8e9e \ No newline at end of file diff --git a/coreutils/tests/fixtures/hashsum/shake256_512.expected b/coreutils/tests/fixtures/hashsum/shake256_512.expected new file mode 100644 index 000000000..6e7ef1417 --- /dev/null +++ b/coreutils/tests/fixtures/hashsum/shake256_512.expected @@ -0,0 +1 @@ +7c9896ea84a2a1b80b2183a3f2b4e43cd59b7d48471dc213bcedaccb699d6e6f7ad5d304928ab79329f1fc62f6db072d95b51209eb807683f5c9371872a2dd4e \ No newline at end of file diff --git a/coreutils/tests/fixtures/head/lorem_ipsum.txt b/coreutils/tests/fixtures/head/lorem_ipsum.txt new file mode 100644 index 000000000..5d974b854 --- /dev/null +++ b/coreutils/tests/fixtures/head/lorem_ipsum.txt @@ -0,0 +1,24 @@ +Lorem ipsum dolor sit amet, +consectetur adipiscing elit. +Nunc interdum suscipit sem vel ornare. +Proin euismod, +justo sed mollis dictum, +eros urna ultricies augue, +eu pharetra mi ex id ante. +Duis convallis porttitor aliquam. +Nunc vitae tincidunt ex. +Suspendisse iaculis ligula ac diam consectetur lacinia. +Donec vel velit dui. +Etiam fringilla, +dolor quis tempor vehicula, +lacus turpis bibendum velit, +et pellentesque elit odio a magna. +Cras vulputate tortor non libero vehicula euismod. +Aliquam tincidunt nisl eget enim cursus, +viverra sagittis magna commodo. +Cras rhoncus egestas leo nec blandit. +Suspendisse potenti. +Etiam ullamcorper leo vel lacus vestibulum, +cursus semper eros efficitur. +In hac habitasse platea dictumst. +Phasellus scelerisque vehicula fringilla. diff --git a/coreutils/tests/fixtures/head/lorem_ipsum_1_line.expected b/coreutils/tests/fixtures/head/lorem_ipsum_1_line.expected new file mode 100644 index 000000000..409369c4e --- /dev/null +++ b/coreutils/tests/fixtures/head/lorem_ipsum_1_line.expected @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, diff --git a/coreutils/tests/fixtures/head/lorem_ipsum_5_chars.expected b/coreutils/tests/fixtures/head/lorem_ipsum_5_chars.expected new file mode 100644 index 000000000..77aaba021 --- /dev/null +++ b/coreutils/tests/fixtures/head/lorem_ipsum_5_chars.expected @@ -0,0 +1 @@ +Lorem \ No newline at end of file diff --git a/coreutils/tests/fixtures/head/lorem_ipsum_default.expected b/coreutils/tests/fixtures/head/lorem_ipsum_default.expected new file mode 100644 index 000000000..7b721fd08 --- /dev/null +++ b/coreutils/tests/fixtures/head/lorem_ipsum_default.expected @@ -0,0 +1,10 @@ +Lorem ipsum dolor sit amet, +consectetur adipiscing elit. +Nunc interdum suscipit sem vel ornare. +Proin euismod, +justo sed mollis dictum, +eros urna ultricies augue, +eu pharetra mi ex id ante. +Duis convallis porttitor aliquam. +Nunc vitae tincidunt ex. +Suspendisse iaculis ligula ac diam consectetur lacinia. diff --git a/coreutils/tests/fixtures/head/lorem_ipsum_verbose.expected b/coreutils/tests/fixtures/head/lorem_ipsum_verbose.expected new file mode 100644 index 000000000..5f2d0ea61 --- /dev/null +++ b/coreutils/tests/fixtures/head/lorem_ipsum_verbose.expected @@ -0,0 +1,11 @@ +==> lorem_ipsum.txt <== +Lorem ipsum dolor sit amet, +consectetur adipiscing elit. +Nunc interdum suscipit sem vel ornare. +Proin euismod, +justo sed mollis dictum, +eros urna ultricies augue, +eu pharetra mi ex id ante. +Duis convallis porttitor aliquam. +Nunc vitae tincidunt ex. +Suspendisse iaculis ligula ac diam consectetur lacinia. diff --git a/coreutils/tests/fixtures/join/autoformat.expected b/coreutils/tests/fixtures/join/autoformat.expected new file mode 100644 index 000000000..d1557e25b --- /dev/null +++ b/coreutils/tests/fixtures/join/autoformat.expected @@ -0,0 +1,5 @@ +1 a a b +2 b b c +3 c d e +4 d g h +5 e i diff --git a/coreutils/tests/fixtures/join/capitalized.txt b/coreutils/tests/fixtures/join/capitalized.txt new file mode 100644 index 000000000..322f0d316 --- /dev/null +++ b/coreutils/tests/fixtures/join/capitalized.txt @@ -0,0 +1,4 @@ +A 1 +B 2 +C 4 +D 8 diff --git a/coreutils/tests/fixtures/join/case_insensitive.expected b/coreutils/tests/fixtures/join/case_insensitive.expected new file mode 100644 index 000000000..da91427a5 --- /dev/null +++ b/coreutils/tests/fixtures/join/case_insensitive.expected @@ -0,0 +1,3 @@ +A 1 2 f +B 2 3 g +C 4 4 h diff --git a/coreutils/tests/fixtures/join/default.expected b/coreutils/tests/fixtures/join/default.expected new file mode 100644 index 000000000..5b3643826 --- /dev/null +++ b/coreutils/tests/fixtures/join/default.expected @@ -0,0 +1,5 @@ +1 a +2 b +3 c +5 e +8 h diff --git a/coreutils/tests/fixtures/join/different_field.expected b/coreutils/tests/fixtures/join/different_field.expected new file mode 100644 index 000000000..fa9181ada --- /dev/null +++ b/coreutils/tests/fixtures/join/different_field.expected @@ -0,0 +1,6 @@ +2 b a f +3 c b g +4 d c h +5 e f i +6 f g j +7 g h k diff --git a/coreutils/tests/fixtures/join/different_fields.expected b/coreutils/tests/fixtures/join/different_fields.expected new file mode 100644 index 000000000..cd870783f --- /dev/null +++ b/coreutils/tests/fixtures/join/different_fields.expected @@ -0,0 +1,5 @@ +c 3 2 1 cd +d 4 3 2 de +e 5 5 3 ef +f 6 7 4 fg +g 7 11 5 gh diff --git a/coreutils/tests/fixtures/join/different_lengths.txt b/coreutils/tests/fixtures/join/different_lengths.txt new file mode 100644 index 000000000..0dfad2774 --- /dev/null +++ b/coreutils/tests/fixtures/join/different_lengths.txt @@ -0,0 +1,5 @@ +1 a b +2 b c +3 d e f +4 g h +5 i diff --git a/coreutils/tests/fixtures/join/empty.txt b/coreutils/tests/fixtures/join/empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/coreutils/tests/fixtures/join/empty_key.expected b/coreutils/tests/fixtures/join/empty_key.expected new file mode 100644 index 000000000..38a2532d2 --- /dev/null +++ b/coreutils/tests/fixtures/join/empty_key.expected @@ -0,0 +1,5 @@ +x 1 +x 2 +x 3 +x 5 +x 8 diff --git a/coreutils/tests/fixtures/join/fields_1.txt b/coreutils/tests/fixtures/join/fields_1.txt new file mode 100644 index 000000000..24d5fc285 --- /dev/null +++ b/coreutils/tests/fixtures/join/fields_1.txt @@ -0,0 +1,5 @@ +1 +2 +3 +5 +8 diff --git a/coreutils/tests/fixtures/join/fields_2.txt b/coreutils/tests/fixtures/join/fields_2.txt new file mode 100644 index 000000000..5b0d49021 --- /dev/null +++ b/coreutils/tests/fixtures/join/fields_2.txt @@ -0,0 +1,9 @@ +1 a +2 b +3 c +4 d +5 e +6 f +7 g +8 h +9 i diff --git a/coreutils/tests/fixtures/join/fields_3.txt b/coreutils/tests/fixtures/join/fields_3.txt new file mode 100644 index 000000000..4c5c0e779 --- /dev/null +++ b/coreutils/tests/fixtures/join/fields_3.txt @@ -0,0 +1,6 @@ +a 2 f +b 3 g +c 4 h +f 5 i +g 6 j +h 7 k diff --git a/coreutils/tests/fixtures/join/fields_4.txt b/coreutils/tests/fixtures/join/fields_4.txt new file mode 100644 index 000000000..680e07c8d --- /dev/null +++ b/coreutils/tests/fixtures/join/fields_4.txt @@ -0,0 +1,5 @@ +2 c 1 cd +3 d 2 de +5 e 3 ef +7 f 4 fg +11 g 5 gh diff --git a/coreutils/tests/fixtures/join/header.expected b/coreutils/tests/fixtures/join/header.expected new file mode 100644 index 000000000..160c65679 --- /dev/null +++ b/coreutils/tests/fixtures/join/header.expected @@ -0,0 +1,4 @@ +id field count +1 a abc 10 +2 b abc 25 +4 d 17 xyz diff --git a/coreutils/tests/fixtures/join/header_1.txt b/coreutils/tests/fixtures/join/header_1.txt new file mode 100644 index 000000000..ae34e6715 --- /dev/null +++ b/coreutils/tests/fixtures/join/header_1.txt @@ -0,0 +1,6 @@ +id field +1 a abc +2 b abc +3 c +4 d +5 c diff --git a/coreutils/tests/fixtures/join/header_2.txt b/coreutils/tests/fixtures/join/header_2.txt new file mode 100644 index 000000000..1e7c50e1c --- /dev/null +++ b/coreutils/tests/fixtures/join/header_2.txt @@ -0,0 +1,5 @@ +id count +1 10 +2 25 +4 17 xyz +7 18 xyz diff --git a/coreutils/tests/fixtures/join/header_autoformat.expected b/coreutils/tests/fixtures/join/header_autoformat.expected new file mode 100644 index 000000000..e32bb162a --- /dev/null +++ b/coreutils/tests/fixtures/join/header_autoformat.expected @@ -0,0 +1,4 @@ +id field count +1 a 10 +2 b 25 +4 d 17 diff --git a/coreutils/tests/fixtures/join/missing_format_fields.expected b/coreutils/tests/fixtures/join/missing_format_fields.expected new file mode 100644 index 000000000..3cf828f60 --- /dev/null +++ b/coreutils/tests/fixtures/join/missing_format_fields.expected @@ -0,0 +1,5 @@ +1 a x +2 b x +3 c f +4 d x +5 e x diff --git a/coreutils/tests/fixtures/join/semicolon_fields_1.txt b/coreutils/tests/fixtures/join/semicolon_fields_1.txt new file mode 100644 index 000000000..d21d98130 --- /dev/null +++ b/coreutils/tests/fixtures/join/semicolon_fields_1.txt @@ -0,0 +1,6 @@ +1; a +2 ;b +3; c +4 ;d +5; e +6 ;f diff --git a/coreutils/tests/fixtures/join/semicolon_fields_2.txt b/coreutils/tests/fixtures/join/semicolon_fields_2.txt new file mode 100644 index 000000000..df83a49a8 --- /dev/null +++ b/coreutils/tests/fixtures/join/semicolon_fields_2.txt @@ -0,0 +1,3 @@ +2 ;x +3; y +4 ;z diff --git a/coreutils/tests/fixtures/join/semicolon_separated.expected b/coreutils/tests/fixtures/join/semicolon_separated.expected new file mode 100644 index 000000000..21d4f24b1 --- /dev/null +++ b/coreutils/tests/fixtures/join/semicolon_separated.expected @@ -0,0 +1,3 @@ +2 ;b;x +3; c; y +4 ;d;z diff --git a/coreutils/tests/fixtures/join/suppress_joined.expected b/coreutils/tests/fixtures/join/suppress_joined.expected new file mode 100644 index 000000000..20b2556e2 --- /dev/null +++ b/coreutils/tests/fixtures/join/suppress_joined.expected @@ -0,0 +1,3 @@ +1 a +8 h +9 i diff --git a/coreutils/tests/fixtures/join/unpaired_lines.expected b/coreutils/tests/fixtures/join/unpaired_lines.expected new file mode 100644 index 000000000..1cf8624b9 --- /dev/null +++ b/coreutils/tests/fixtures/join/unpaired_lines.expected @@ -0,0 +1,9 @@ +1 a +2 a f b +3 b g c +4 c h d +5 f i e +6 g j f +7 h k g +8 h +9 i diff --git a/coreutils/tests/fixtures/join/unpaired_lines_format.expected b/coreutils/tests/fixtures/join/unpaired_lines_format.expected new file mode 100644 index 000000000..d1324aa66 --- /dev/null +++ b/coreutils/tests/fixtures/join/unpaired_lines_format.expected @@ -0,0 +1,6 @@ + f 2 a + g 3 b + h 4 c + i 5 f + j 6 g + k 7 h diff --git a/coreutils/tests/fixtures/mv/hello_world.txt b/coreutils/tests/fixtures/mv/hello_world.txt new file mode 100644 index 000000000..8ab686eaf --- /dev/null +++ b/coreutils/tests/fixtures/mv/hello_world.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/coreutils/tests/fixtures/nl/joinblanklines.txt b/coreutils/tests/fixtures/nl/joinblanklines.txt new file mode 100644 index 000000000..197859644 --- /dev/null +++ b/coreutils/tests/fixtures/nl/joinblanklines.txt @@ -0,0 +1,27 @@ +Nonempty +Nonempty +Followed by 10x empty + + + + + + + + + + +Followed by 5x empty + + + + + +Followed by 4x empty + + + + +Nonempty +Nonempty +Nonempty. diff --git a/coreutils/tests/fixtures/nl/section.txt b/coreutils/tests/fixtures/nl/section.txt new file mode 100644 index 000000000..62359e7bf --- /dev/null +++ b/coreutils/tests/fixtures/nl/section.txt @@ -0,0 +1,18 @@ +\:\:\: +HEADER1 +HEADER2 +\:\: +BODY1 +BODY2 +\: +FOOTER1 +FOOTER2 +\:\:\: +NEXTHEADER1 +NEXTHEADER2 +\:\: +NEXTBODY1 +NEXTBODY2 +\: +NEXTFOOTER1 +NEXTFOOTER2 diff --git a/coreutils/tests/fixtures/nl/simple.txt b/coreutils/tests/fixtures/nl/simple.txt new file mode 100644 index 000000000..b168ae840 --- /dev/null +++ b/coreutils/tests/fixtures/nl/simple.txt @@ -0,0 +1,15 @@ +L1 +L2 +L3 +L4 +L5 +L6 +L7 +L8 +L9 +L10 +L11 +L12 +L13 +L14 +L15 diff --git a/coreutils/tests/fixtures/od/-f b/coreutils/tests/fixtures/od/-f new file mode 100644 index 000000000..370c31180 --- /dev/null +++ b/coreutils/tests/fixtures/od/-f @@ -0,0 +1 @@ +minus lowercase f diff --git a/coreutils/tests/fixtures/od/0 b/coreutils/tests/fixtures/od/0 new file mode 100644 index 000000000..26af6a865 --- /dev/null +++ b/coreutils/tests/fixtures/od/0 @@ -0,0 +1 @@ +zero diff --git a/coreutils/tests/fixtures/od/c b/coreutils/tests/fixtures/od/c new file mode 100644 index 000000000..109c7e9b0 --- /dev/null +++ b/coreutils/tests/fixtures/od/c @@ -0,0 +1 @@ +lowercase c diff --git a/coreutils/tests/fixtures/od/x b/coreutils/tests/fixtures/od/x new file mode 100644 index 000000000..584b8c5f2 --- /dev/null +++ b/coreutils/tests/fixtures/od/x @@ -0,0 +1 @@ +lowercase x diff --git a/coreutils/tests/fixtures/paste/html_colors.expected b/coreutils/tests/fixtures/paste/html_colors.expected new file mode 100644 index 000000000..4fb65d6aa --- /dev/null +++ b/coreutils/tests/fixtures/paste/html_colors.expected @@ -0,0 +1,16 @@ +white #FFFFFF +silver #C0C0C0 +gray #808080 +black #000000 +red #FF0000 +maroon #800000 +yellow #FFFF00 +olive #808000 +lime #00FF00 +green #008000 +aqua #00FFFF +teal #008080 +blue #0000FF +navy #000080 +fuchsia #FF00FF +purple #800080 diff --git a/coreutils/tests/fixtures/paste/html_colors.txt b/coreutils/tests/fixtures/paste/html_colors.txt new file mode 100644 index 000000000..290303ff0 --- /dev/null +++ b/coreutils/tests/fixtures/paste/html_colors.txt @@ -0,0 +1,32 @@ +white +#FFFFFF +silver +#C0C0C0 +gray +#808080 +black +#000000 +red +#FF0000 +maroon +#800000 +yellow +#FFFF00 +olive +#808000 +lime +#00FF00 +green +#008000 +aqua +#00FFFF +teal +#008080 +blue +#0000FF +navy +#000080 +fuchsia +#FF00FF +purple +#800080 diff --git a/coreutils/tests/fixtures/ptx/gnu_ext_disabled_ignore_and_only_file.expected b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_ignore_and_only_file.expected new file mode 100644 index 000000000..d57017253 --- /dev/null +++ b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_ignore_and_only_file.expected @@ -0,0 +1,2 @@ +.xx "" """quotes"", for" "roff" "" +.xx "" "{brackets} for" "tex" "" diff --git a/coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_auto_ref.expected b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_auto_ref.expected new file mode 100644 index 000000000..eefba851b --- /dev/null +++ b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_auto_ref.expected @@ -0,0 +1,24 @@ +.xx "" "" """quotes"", for roff" "" "input:3" +.xx "" "and some other like" "%a, b#, c$c" "" "input:5" +.xx "" "maybe" "also~or^" "" "input:6" +.xx "" "" "and some other like %a, b#, c$c" "" "input:5" +.xx "" "oh," "and back\slash" "" "input:7" +.xx "" "and some other like %a," "b#, c$c" "" "input:5" +.xx "" "oh, and" "back\slash" "" "input:7" +.xx "" "and some other like %a, b#," "c$c" "" "input:5" +.xx "" "let's check special" "characters:" "" "input:2" +.xx "" "let's" "check special characters:" "" "input:2" +.xx "" """quotes""," "for roff" "" "input:3" +.xx "" "{brackets}" "for tex" "" "input:4" +.xx "" "" "hello world!" "" "input:1" +.xx "" "" "let's check special characters:" "" "input:2" +.xx "" "and some other" "like %a, b#, c$c" "" "input:5" +.xx "" "" "maybe also~or^" "" "input:6" +.xx "" "" "oh, and back\slash" "" "input:7" +.xx "" "and some" "other like %a, b#, c$c" "" "input:5" +.xx "" """quotes"", for" "roff" "" "input:3" +.xx "" "and" "some other like %a, b#, c$c" "" "input:5" +.xx "" "let's check" "special characters:" "" "input:2" +.xx "" "{brackets} for" "tex" "" "input:4" +.xx "" "hello" "world!" "" "input:1" +.xx "" "" "{brackets} for tex" "" "input:4" diff --git a/coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_input_ref.expected b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_input_ref.expected new file mode 100644 index 000000000..f2f3f9160 --- /dev/null +++ b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_input_ref.expected @@ -0,0 +1,17 @@ +.xx "" "some other like" "%a, b#, c$c" "" "and" +.xx "" "" "also~or^" "" "maybe" +.xx "" "" "and back\slash" "" "oh," +.xx "" "some other like %a," "b#, c$c" "" "and" +.xx "" "and" "back\slash" "" "oh," +.xx "" "some other like %a, b#," "c$c" "" "and" +.xx "" "check special" "characters:" "" "let's" +.xx "" "" "check special characters:" "" "let's" +.xx "" "" "for roff" "" """quotes""," +.xx "" "" "for tex" "" "{brackets}" +.xx "" "some other" "like %a, b#, c$c" "" "and" +.xx "" "some" "other like %a, b#, c$c" "" "and" +.xx "" "for" "roff" "" """quotes""," +.xx "" "" "some other like %a, b#, c$c" "" "and" +.xx "" "check" "special characters:" "" "let's" +.xx "" "for" "tex" "" "{brackets}" +.xx "" "" "world!" "" "hello" diff --git a/coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_no_ref.expected b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_no_ref.expected new file mode 100644 index 000000000..3886e087d --- /dev/null +++ b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_roff_no_ref.expected @@ -0,0 +1,24 @@ +.xx "" "" """quotes"", for roff" "" +.xx "" "and some other like" "%a, b#, c$c" "" +.xx "" "maybe" "also~or^" "" +.xx "" "" "and some other like %a, b#, c$c" "" +.xx "" "oh," "and back\slash" "" +.xx "" "and some other like %a," "b#, c$c" "" +.xx "" "oh, and" "back\slash" "" +.xx "" "and some other like %a, b#," "c$c" "" +.xx "" "let's check special" "characters:" "" +.xx "" "let's" "check special characters:" "" +.xx "" """quotes""," "for roff" "" +.xx "" "{brackets}" "for tex" "" +.xx "" "" "hello world!" "" +.xx "" "" "let's check special characters:" "" +.xx "" "and some other" "like %a, b#, c$c" "" +.xx "" "" "maybe also~or^" "" +.xx "" "" "oh, and back\slash" "" +.xx "" "and some" "other like %a, b#, c$c" "" +.xx "" """quotes"", for" "roff" "" +.xx "" "and" "some other like %a, b#, c$c" "" +.xx "" "let's check" "special characters:" "" +.xx "" "{brackets} for" "tex" "" +.xx "" "hello" "world!" "" +.xx "" "" "{brackets} for tex" "" diff --git a/coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_auto_ref.expected b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_auto_ref.expected new file mode 100644 index 000000000..f0b0f41c7 --- /dev/null +++ b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_auto_ref.expected @@ -0,0 +1,24 @@ +\xx {}{}{"quotes",}{ for roff}{}{input:3} +\xx {}{and some other like}{\%a,}{ b\#, c\$c}{}{input:5} +\xx {}{maybe}{also~or^}{}{}{input:6} +\xx {}{}{and}{ some other like \%a, b\#, c\$c}{}{input:5} +\xx {}{oh,}{and}{ back\backslash{}slash}{}{input:7} +\xx {}{and some other like \%a,}{b\#,}{ c\$c}{}{input:5} +\xx {}{oh, and}{back\backslash{}slash}{}{}{input:7} +\xx {}{and some other like \%a, b\#,}{c\$c}{}{}{input:5} +\xx {}{let's check special}{characters:}{}{}{input:2} +\xx {}{let's}{check}{ special characters:}{}{input:2} +\xx {}{"quotes",}{for}{ roff}{}{input:3} +\xx {}{$\{$brackets$\}$}{for}{ tex}{}{input:4} +\xx {}{}{hello}{ world!}{}{input:1} +\xx {}{}{let's}{ check special characters:}{}{input:2} +\xx {}{and some other}{like}{ \%a, b\#, c\$c}{}{input:5} +\xx {}{}{maybe}{ also~or^}{}{input:6} +\xx {}{}{oh,}{ and back\backslash{}slash}{}{input:7} +\xx {}{and some}{other}{ like \%a, b\#, c\$c}{}{input:5} +\xx {}{"quotes", for}{roff}{}{}{input:3} +\xx {}{and}{some}{ other like \%a, b\#, c\$c}{}{input:5} +\xx {}{let's check}{special}{ characters:}{}{input:2} +\xx {}{$\{$brackets$\}$ for}{tex}{}{}{input:4} +\xx {}{hello}{world!}{}{}{input:1} +\xx {}{}{$\{$brackets$\}$}{ for tex}{}{input:4} diff --git a/coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_input_ref.expected b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_input_ref.expected new file mode 100644 index 000000000..8e345339a --- /dev/null +++ b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_input_ref.expected @@ -0,0 +1,17 @@ +\xx {}{some other like}{\%a,}{ b\#, c\$c}{}{and} +\xx {}{}{also~or^}{}{}{maybe} +\xx {}{}{and}{ back\backslash{}slash}{}{oh,} +\xx {}{some other like \%a,}{b\#,}{ c\$c}{}{and} +\xx {}{and}{back\backslash{}slash}{}{}{oh,} +\xx {}{some other like \%a, b\#,}{c\$c}{}{}{and} +\xx {}{check special}{characters:}{}{}{let's} +\xx {}{}{check}{ special characters:}{}{let's} +\xx {}{}{for}{ roff}{}{"quotes",} +\xx {}{}{for}{ tex}{}{$\{$brackets$\}$} +\xx {}{some other}{like}{ \%a, b\#, c\$c}{}{and} +\xx {}{some}{other}{ like \%a, b\#, c\$c}{}{and} +\xx {}{for}{roff}{}{}{"quotes",} +\xx {}{}{some}{ other like \%a, b\#, c\$c}{}{and} +\xx {}{check}{special}{ characters:}{}{let's} +\xx {}{for}{tex}{}{}{$\{$brackets$\}$} +\xx {}{}{world!}{}{}{hello} diff --git a/coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_no_ref.expected b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_no_ref.expected new file mode 100644 index 000000000..dad32b5f1 --- /dev/null +++ b/coreutils/tests/fixtures/ptx/gnu_ext_disabled_tex_no_ref.expected @@ -0,0 +1,24 @@ +\xx {}{}{"quotes",}{ for roff}{} +\xx {}{and some other like}{\%a,}{ b\#, c\$c}{} +\xx {}{maybe}{also~or^}{}{} +\xx {}{}{and}{ some other like \%a, b\#, c\$c}{} +\xx {}{oh,}{and}{ back\backslash{}slash}{} +\xx {}{and some other like \%a,}{b\#,}{ c\$c}{} +\xx {}{oh, and}{back\backslash{}slash}{}{} +\xx {}{and some other like \%a, b\#,}{c\$c}{}{} +\xx {}{let's check special}{characters:}{}{} +\xx {}{let's}{check}{ special characters:}{} +\xx {}{"quotes",}{for}{ roff}{} +\xx {}{$\{$brackets$\}$}{for}{ tex}{} +\xx {}{}{hello}{ world!}{} +\xx {}{}{let's}{ check special characters:}{} +\xx {}{and some other}{like}{ \%a, b\#, c\$c}{} +\xx {}{}{maybe}{ also~or^}{} +\xx {}{}{oh,}{ and back\backslash{}slash}{} +\xx {}{and some}{other}{ like \%a, b\#, c\$c}{} +\xx {}{"quotes", for}{roff}{}{} +\xx {}{and}{some}{ other like \%a, b\#, c\$c}{} +\xx {}{let's check}{special}{ characters:}{} +\xx {}{$\{$brackets$\}$ for}{tex}{}{} +\xx {}{hello}{world!}{}{} +\xx {}{}{$\{$brackets$\}$}{ for tex}{} diff --git a/coreutils/tests/fixtures/ptx/ignore b/coreutils/tests/fixtures/ptx/ignore new file mode 100644 index 000000000..d671622f8 --- /dev/null +++ b/coreutils/tests/fixtures/ptx/ignore @@ -0,0 +1,2 @@ +maybe +about diff --git a/coreutils/tests/fixtures/ptx/input b/coreutils/tests/fixtures/ptx/input new file mode 100644 index 000000000..6ba595bc0 --- /dev/null +++ b/coreutils/tests/fixtures/ptx/input @@ -0,0 +1,7 @@ +hello world! +let's check special characters: +"quotes", for roff +{brackets} for tex +and some other like %a, b#, c$c +maybe also~or^ +oh, and back\slash diff --git a/coreutils/tests/fixtures/ptx/only b/coreutils/tests/fixtures/ptx/only new file mode 100644 index 000000000..917aa2431 --- /dev/null +++ b/coreutils/tests/fixtures/ptx/only @@ -0,0 +1,5 @@ +roff +tex +world +maybe +about diff --git a/coreutils/tests/fixtures/sort/check_fail.txt b/coreutils/tests/fixtures/sort/check_fail.txt new file mode 100644 index 000000000..e15a1168c --- /dev/null +++ b/coreutils/tests/fixtures/sort/check_fail.txt @@ -0,0 +1,8 @@ +1 +2 +3 +4 +6 +5 +9 +8 diff --git a/coreutils/tests/fixtures/sort/default_unsorted_ints.expected b/coreutils/tests/fixtures/sort/default_unsorted_ints.expected new file mode 100644 index 000000000..5c8fcffdf --- /dev/null +++ b/coreutils/tests/fixtures/sort/default_unsorted_ints.expected @@ -0,0 +1,100 @@ +1 +10 +100 +11 +12 +13 +14 +15 +16 +17 +18 +19 +2 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +3 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +4 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +5 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +6 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +7 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +8 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +9 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 diff --git a/coreutils/tests/fixtures/sort/default_unsorted_ints.txt b/coreutils/tests/fixtures/sort/default_unsorted_ints.txt new file mode 100644 index 000000000..d3c2bb861 --- /dev/null +++ b/coreutils/tests/fixtures/sort/default_unsorted_ints.txt @@ -0,0 +1,100 @@ +33 +16 +35 +56 +72 +37 +21 +49 +70 +48 +90 +83 +44 +79 +10 +20 +4 +26 +27 +63 +29 +47 +51 +85 +88 +46 +30 +61 +93 +81 +78 +53 +87 +18 +98 +38 +13 +39 +23 +71 +5 +100 +96 +8 +24 +14 +28 +15 +25 +43 +36 +67 +75 +66 +31 +57 +34 +80 +40 +86 +17 +55 +9 +1 +62 +12 +74 +58 +69 +76 +11 +73 +68 +59 +41 +45 +52 +97 +82 +6 +7 +77 +42 +84 +95 +94 +89 +19 +64 +2 +22 +50 +60 +32 +92 +3 +99 +65 +54 +91 diff --git a/coreutils/tests/fixtures/sort/human_block_sizes.expected b/coreutils/tests/fixtures/sort/human_block_sizes.expected new file mode 100644 index 000000000..74fad9fdf --- /dev/null +++ b/coreutils/tests/fixtures/sort/human_block_sizes.expected @@ -0,0 +1,11 @@ +844K +981K +11M +13M +14M +16M +18M +19M +20M +981T +20P diff --git a/coreutils/tests/fixtures/sort/human_block_sizes.txt b/coreutils/tests/fixtures/sort/human_block_sizes.txt new file mode 100644 index 000000000..803666dbe --- /dev/null +++ b/coreutils/tests/fixtures/sort/human_block_sizes.txt @@ -0,0 +1,11 @@ +14M +20M +20P +11M +981T +16M +18M +19M +844K +981K +13M diff --git a/coreutils/tests/fixtures/sort/ignore_case.expected b/coreutils/tests/fixtures/sort/ignore_case.expected new file mode 100644 index 000000000..b49def3da --- /dev/null +++ b/coreutils/tests/fixtures/sort/ignore_case.expected @@ -0,0 +1,7 @@ +aaa +BBB +ccc +DDD +eee +FFF +ggg diff --git a/coreutils/tests/fixtures/sort/ignore_case.txt b/coreutils/tests/fixtures/sort/ignore_case.txt new file mode 100644 index 000000000..c1c787208 --- /dev/null +++ b/coreutils/tests/fixtures/sort/ignore_case.txt @@ -0,0 +1,7 @@ +aaa +ccc +eee +ggg +BBB +DDD +FFF diff --git a/coreutils/tests/fixtures/sort/merge_ints_interleaved.expected b/coreutils/tests/fixtures/sort/merge_ints_interleaved.expected new file mode 100644 index 000000000..071939893 --- /dev/null +++ b/coreutils/tests/fixtures/sort/merge_ints_interleaved.expected @@ -0,0 +1,9 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 diff --git a/coreutils/tests/fixtures/sort/merge_ints_interleaved_1.txt b/coreutils/tests/fixtures/sort/merge_ints_interleaved_1.txt new file mode 100644 index 000000000..6e181b92b --- /dev/null +++ b/coreutils/tests/fixtures/sort/merge_ints_interleaved_1.txt @@ -0,0 +1,3 @@ +1 +4 +7 diff --git a/coreutils/tests/fixtures/sort/merge_ints_interleaved_2.txt b/coreutils/tests/fixtures/sort/merge_ints_interleaved_2.txt new file mode 100644 index 000000000..62ffd8a69 --- /dev/null +++ b/coreutils/tests/fixtures/sort/merge_ints_interleaved_2.txt @@ -0,0 +1,3 @@ +2 +5 +8 diff --git a/coreutils/tests/fixtures/sort/merge_ints_interleaved_3.txt b/coreutils/tests/fixtures/sort/merge_ints_interleaved_3.txt new file mode 100644 index 000000000..1e3ac1e93 --- /dev/null +++ b/coreutils/tests/fixtures/sort/merge_ints_interleaved_3.txt @@ -0,0 +1,3 @@ +3 +6 +9 diff --git a/coreutils/tests/fixtures/sort/merge_ints_reversed.expected b/coreutils/tests/fixtures/sort/merge_ints_reversed.expected new file mode 100644 index 000000000..abb8f7739 --- /dev/null +++ b/coreutils/tests/fixtures/sort/merge_ints_reversed.expected @@ -0,0 +1,9 @@ +9 +8 +7 +6 +5 +4 +3 +2 +1 diff --git a/coreutils/tests/fixtures/sort/merge_ints_reversed_1.txt b/coreutils/tests/fixtures/sort/merge_ints_reversed_1.txt new file mode 100644 index 000000000..8313069f4 --- /dev/null +++ b/coreutils/tests/fixtures/sort/merge_ints_reversed_1.txt @@ -0,0 +1,3 @@ +7 +4 +1 diff --git a/coreutils/tests/fixtures/sort/merge_ints_reversed_2.txt b/coreutils/tests/fixtures/sort/merge_ints_reversed_2.txt new file mode 100644 index 000000000..c0416aa97 --- /dev/null +++ b/coreutils/tests/fixtures/sort/merge_ints_reversed_2.txt @@ -0,0 +1,3 @@ +8 +5 +2 diff --git a/coreutils/tests/fixtures/sort/merge_ints_reversed_3.txt b/coreutils/tests/fixtures/sort/merge_ints_reversed_3.txt new file mode 100644 index 000000000..bd33aa425 --- /dev/null +++ b/coreutils/tests/fixtures/sort/merge_ints_reversed_3.txt @@ -0,0 +1,3 @@ +9 +6 +3 diff --git a/coreutils/tests/fixtures/sort/month_default.expected b/coreutils/tests/fixtures/sort/month_default.expected new file mode 100644 index 000000000..dc51f5d5e --- /dev/null +++ b/coreutils/tests/fixtures/sort/month_default.expected @@ -0,0 +1,10 @@ +N/A Ut enim ad minim veniam, quis +Jan Lorem ipsum dolor sit amet +mar laboris nisi ut aliquip ex ea +May sed do eiusmod tempor incididunt +JUN nostrud exercitation ullamco +Jul 1 should remain 2,1,3 +Jul 2 these three lines +Jul 3 if --stable is provided +Oct ut labore et dolore magna aliqua +Dec consectetur adipiscing elit diff --git a/coreutils/tests/fixtures/sort/month_default.txt b/coreutils/tests/fixtures/sort/month_default.txt new file mode 100644 index 000000000..6d64bf4f8 --- /dev/null +++ b/coreutils/tests/fixtures/sort/month_default.txt @@ -0,0 +1,10 @@ +Jan Lorem ipsum dolor sit amet +Dec consectetur adipiscing elit +May sed do eiusmod tempor incididunt +Oct ut labore et dolore magna aliqua +N/A Ut enim ad minim veniam, quis +JUN nostrud exercitation ullamco +mar laboris nisi ut aliquip ex ea +Jul 2 these three lines +Jul 1 should remain 2,1,3 +Jul 3 if --stable is provided diff --git a/coreutils/tests/fixtures/sort/month_stable.expected b/coreutils/tests/fixtures/sort/month_stable.expected new file mode 100644 index 000000000..868728a18 --- /dev/null +++ b/coreutils/tests/fixtures/sort/month_stable.expected @@ -0,0 +1,10 @@ +N/A Ut enim ad minim veniam, quis +Jan Lorem ipsum dolor sit amet +mar laboris nisi ut aliquip ex ea +May sed do eiusmod tempor incididunt +JUN nostrud exercitation ullamco +Jul 2 these three lines +Jul 1 should remain 2,1,3 +Jul 3 if --stable is provided +Oct ut labore et dolore magna aliqua +Dec consectetur adipiscing elit diff --git a/coreutils/tests/fixtures/sort/month_stable.txt b/coreutils/tests/fixtures/sort/month_stable.txt new file mode 100644 index 000000000..6d64bf4f8 --- /dev/null +++ b/coreutils/tests/fixtures/sort/month_stable.txt @@ -0,0 +1,10 @@ +Jan Lorem ipsum dolor sit amet +Dec consectetur adipiscing elit +May sed do eiusmod tempor incididunt +Oct ut labore et dolore magna aliqua +N/A Ut enim ad minim veniam, quis +JUN nostrud exercitation ullamco +mar laboris nisi ut aliquip ex ea +Jul 2 these three lines +Jul 1 should remain 2,1,3 +Jul 3 if --stable is provided diff --git a/coreutils/tests/fixtures/sort/multiple_files.expected b/coreutils/tests/fixtures/sort/multiple_files.expected new file mode 100644 index 000000000..071939893 --- /dev/null +++ b/coreutils/tests/fixtures/sort/multiple_files.expected @@ -0,0 +1,9 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 diff --git a/coreutils/tests/fixtures/sort/multiple_files1.txt b/coreutils/tests/fixtures/sort/multiple_files1.txt new file mode 100644 index 000000000..8cb0f1f46 --- /dev/null +++ b/coreutils/tests/fixtures/sort/multiple_files1.txt @@ -0,0 +1,4 @@ +3 +7 +2 +5 diff --git a/coreutils/tests/fixtures/sort/multiple_files2.txt b/coreutils/tests/fixtures/sort/multiple_files2.txt new file mode 100644 index 000000000..7f0581d7c --- /dev/null +++ b/coreutils/tests/fixtures/sort/multiple_files2.txt @@ -0,0 +1,5 @@ +4 +8 +1 +9 +6 diff --git a/coreutils/tests/fixtures/sort/numeric_fixed_floats.expected b/coreutils/tests/fixtures/sort/numeric_fixed_floats.expected new file mode 100644 index 000000000..7d5f4d647 --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_fixed_floats.expected @@ -0,0 +1,2 @@ +.00 +.01 diff --git a/coreutils/tests/fixtures/sort/numeric_fixed_floats.txt b/coreutils/tests/fixtures/sort/numeric_fixed_floats.txt new file mode 100644 index 000000000..490de8590 --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_fixed_floats.txt @@ -0,0 +1,2 @@ +.01 +.00 diff --git a/coreutils/tests/fixtures/sort/numeric_floats.expected b/coreutils/tests/fixtures/sort/numeric_floats.expected new file mode 100644 index 000000000..99ff9b359 --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_floats.expected @@ -0,0 +1,2 @@ +.02 +.03 diff --git a/coreutils/tests/fixtures/sort/numeric_floats.txt b/coreutils/tests/fixtures/sort/numeric_floats.txt new file mode 100644 index 000000000..25eb8cfbd --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_floats.txt @@ -0,0 +1,2 @@ +.03 +.02 diff --git a/coreutils/tests/fixtures/sort/numeric_floats_and_ints.expected b/coreutils/tests/fixtures/sort/numeric_floats_and_ints.expected new file mode 100644 index 000000000..c61659bcd --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_floats_and_ints.expected @@ -0,0 +1,2 @@ +0 +.02 diff --git a/coreutils/tests/fixtures/sort/numeric_floats_and_ints.txt b/coreutils/tests/fixtures/sort/numeric_floats_and_ints.txt new file mode 100644 index 000000000..b2cc3ea1a --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_floats_and_ints.txt @@ -0,0 +1,2 @@ +.02 +0 diff --git a/coreutils/tests/fixtures/sort/numeric_floats_with_nan.expected b/coreutils/tests/fixtures/sort/numeric_floats_with_nan.expected new file mode 100644 index 000000000..33cb481d0 --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_floats_with_nan.expected @@ -0,0 +1,3 @@ +NaN +.02 +.03 diff --git a/coreutils/tests/fixtures/sort/numeric_floats_with_nan.txt b/coreutils/tests/fixtures/sort/numeric_floats_with_nan.txt new file mode 100644 index 000000000..d3b7c2bc3 --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_floats_with_nan.txt @@ -0,0 +1,3 @@ +.03 +.02 +NaN diff --git a/coreutils/tests/fixtures/sort/numeric_unfixed_floats.expected b/coreutils/tests/fixtures/sort/numeric_unfixed_floats.expected new file mode 100644 index 000000000..ce65c1f73 --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_unfixed_floats.expected @@ -0,0 +1,2 @@ +.000 +.01 diff --git a/coreutils/tests/fixtures/sort/numeric_unfixed_floats.txt b/coreutils/tests/fixtures/sort/numeric_unfixed_floats.txt new file mode 100644 index 000000000..7d772028a --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_unfixed_floats.txt @@ -0,0 +1,2 @@ +.01 +.000 diff --git a/coreutils/tests/fixtures/sort/numeric_unsorted_ints.expected b/coreutils/tests/fixtures/sort/numeric_unsorted_ints.expected new file mode 100644 index 000000000..190423f88 --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_unsorted_ints.expected @@ -0,0 +1,100 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 diff --git a/coreutils/tests/fixtures/sort/numeric_unsorted_ints.txt b/coreutils/tests/fixtures/sort/numeric_unsorted_ints.txt new file mode 100644 index 000000000..d3c2bb861 --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_unsorted_ints.txt @@ -0,0 +1,100 @@ +33 +16 +35 +56 +72 +37 +21 +49 +70 +48 +90 +83 +44 +79 +10 +20 +4 +26 +27 +63 +29 +47 +51 +85 +88 +46 +30 +61 +93 +81 +78 +53 +87 +18 +98 +38 +13 +39 +23 +71 +5 +100 +96 +8 +24 +14 +28 +15 +25 +43 +36 +67 +75 +66 +31 +57 +34 +80 +40 +86 +17 +55 +9 +1 +62 +12 +74 +58 +69 +76 +11 +73 +68 +59 +41 +45 +52 +97 +82 +6 +7 +77 +42 +84 +95 +94 +89 +19 +64 +2 +22 +50 +60 +32 +92 +3 +99 +65 +54 +91 diff --git a/coreutils/tests/fixtures/sort/numeric_unsorted_ints_unique.expected b/coreutils/tests/fixtures/sort/numeric_unsorted_ints_unique.expected new file mode 100644 index 000000000..94ebaf900 --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_unsorted_ints_unique.expected @@ -0,0 +1,4 @@ +1 +2 +3 +4 diff --git a/coreutils/tests/fixtures/sort/numeric_unsorted_ints_unique.txt b/coreutils/tests/fixtures/sort/numeric_unsorted_ints_unique.txt new file mode 100644 index 000000000..08a9ecef0 --- /dev/null +++ b/coreutils/tests/fixtures/sort/numeric_unsorted_ints_unique.txt @@ -0,0 +1,7 @@ +4 +2 +4 +3 +3 +2 +1 diff --git a/coreutils/tests/fixtures/sort/version.expected b/coreutils/tests/fixtures/sort/version.expected new file mode 100644 index 000000000..0d6ece21b --- /dev/null +++ b/coreutils/tests/fixtures/sort/version.expected @@ -0,0 +1,4 @@ +1.2.3-alpha +1.2.3-alpha2 +1.12.4 +11.2.3 diff --git a/coreutils/tests/fixtures/sort/version.txt b/coreutils/tests/fixtures/sort/version.txt new file mode 100644 index 000000000..dbfdb2ed7 --- /dev/null +++ b/coreutils/tests/fixtures/sort/version.txt @@ -0,0 +1,4 @@ +11.2.3 +1.2.3-alpha2 +1.2.3-alpha +1.12.4 diff --git a/coreutils/tests/fixtures/sum/alice_in_wonderland.txt b/coreutils/tests/fixtures/sum/alice_in_wonderland.txt new file mode 100644 index 000000000..a95562a1c --- /dev/null +++ b/coreutils/tests/fixtures/sum/alice_in_wonderland.txt @@ -0,0 +1,5 @@ +Alice was beginning to get very tired of sitting by +her sister on the bank, and of having nothing to do: once or twice +she had peeped into the book her sister was reading, but it had no +pictures or conversations in it, "and what is the use of a book," +thought Alice "without pictures or conversation?" diff --git a/coreutils/tests/fixtures/sum/bsd_multiple_files.expected b/coreutils/tests/fixtures/sum/bsd_multiple_files.expected new file mode 100644 index 000000000..1b05ee27f --- /dev/null +++ b/coreutils/tests/fixtures/sum/bsd_multiple_files.expected @@ -0,0 +1,2 @@ +8109 1 lorem_ipsum.txt +1814 1 alice_in_wonderland.txt diff --git a/coreutils/tests/fixtures/sum/bsd_single_file.expected b/coreutils/tests/fixtures/sum/bsd_single_file.expected new file mode 100644 index 000000000..3cffc4337 --- /dev/null +++ b/coreutils/tests/fixtures/sum/bsd_single_file.expected @@ -0,0 +1 @@ +8109 1 diff --git a/coreutils/tests/fixtures/sum/bsd_stdin.expected b/coreutils/tests/fixtures/sum/bsd_stdin.expected new file mode 100644 index 000000000..3cffc4337 --- /dev/null +++ b/coreutils/tests/fixtures/sum/bsd_stdin.expected @@ -0,0 +1 @@ +8109 1 diff --git a/coreutils/tests/fixtures/sum/lorem_ipsum.txt b/coreutils/tests/fixtures/sum/lorem_ipsum.txt new file mode 100644 index 000000000..16752446c --- /dev/null +++ b/coreutils/tests/fixtures/sum/lorem_ipsum.txt @@ -0,0 +1,13 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing +elit. Nunc interdum suscipit sem vel ornare. Proin euismod, justo +sed mollis dictum, eros urna ultricies augue, eu pharetra mi ex id +ante. Duis convallis porttitor aliquam. Nunc vitae tincidunt ex. +Suspendisse iaculis ligula ac diam consectetur lacinia. Donec vel +velit dui. Etiam fringilla, dolor quis tempor vehicula, lacus +turpis bibendum velit, et pellentesque elit odio a magna. Cras +vulputate tortor non libero vehicula euismod. Aliquam tincidunt +nisl eget enim cursus, viverra sagittis magna commodo. Cras rhoncus +egestas leo nec blandit. Suspendisse potenti. Etiam ullamcorper +leo vel lacus vestibulum, cursus semper eros efficitur. In hac +habitasse platea dictumst. Phasellus scelerisque vehicula +fringilla. diff --git a/coreutils/tests/fixtures/sum/sysv_multiple_files.expected b/coreutils/tests/fixtures/sum/sysv_multiple_files.expected new file mode 100644 index 000000000..83a6d6d83 --- /dev/null +++ b/coreutils/tests/fixtures/sum/sysv_multiple_files.expected @@ -0,0 +1,2 @@ +6985 2 lorem_ipsum.txt +27441 1 alice_in_wonderland.txt diff --git a/coreutils/tests/fixtures/sum/sysv_single_file.expected b/coreutils/tests/fixtures/sum/sysv_single_file.expected new file mode 100644 index 000000000..e0f7252cb --- /dev/null +++ b/coreutils/tests/fixtures/sum/sysv_single_file.expected @@ -0,0 +1 @@ +6985 2 lorem_ipsum.txt diff --git a/coreutils/tests/fixtures/sum/sysv_stdin.expected b/coreutils/tests/fixtures/sum/sysv_stdin.expected new file mode 100644 index 000000000..f0fba8c81 --- /dev/null +++ b/coreutils/tests/fixtures/sum/sysv_stdin.expected @@ -0,0 +1 @@ +6985 2 diff --git a/coreutils/tests/fixtures/tac/delimited_primes.expected b/coreutils/tests/fixtures/tac/delimited_primes.expected new file mode 100644 index 000000000..cf58c9015 --- /dev/null +++ b/coreutils/tests/fixtures/tac/delimited_primes.expected @@ -0,0 +1 @@ +9789:83:79:73:71:67:61:59:53:47:43:41:37:31:29:23:19:17:13:11:7:5:3:2: \ No newline at end of file diff --git a/coreutils/tests/fixtures/tac/delimited_primes.txt b/coreutils/tests/fixtures/tac/delimited_primes.txt new file mode 100644 index 000000000..867a26610 --- /dev/null +++ b/coreutils/tests/fixtures/tac/delimited_primes.txt @@ -0,0 +1 @@ +2:3:5:7:11:13:17:19:23:29:31:37:41:43:47:53:59:61:67:71:73:79:83:89:97 \ No newline at end of file diff --git a/coreutils/tests/fixtures/tac/delimited_primes_before.expected b/coreutils/tests/fixtures/tac/delimited_primes_before.expected new file mode 100644 index 000000000..13cb1be06 --- /dev/null +++ b/coreutils/tests/fixtures/tac/delimited_primes_before.expected @@ -0,0 +1 @@ +97:89:83:79:73:71:67:61:59:53:47:43:41:37:31:29:23:19:17:13:11:7:5:3:2 \ No newline at end of file diff --git a/coreutils/tests/fixtures/tac/prime_per_line.expected b/coreutils/tests/fixtures/tac/prime_per_line.expected new file mode 100644 index 000000000..72413442f --- /dev/null +++ b/coreutils/tests/fixtures/tac/prime_per_line.expected @@ -0,0 +1,25 @@ +97 +89 +83 +79 +73 +71 +67 +61 +59 +53 +47 +43 +41 +37 +31 +29 +23 +19 +17 +13 +11 +7 +5 +3 +2 diff --git a/coreutils/tests/fixtures/tac/prime_per_line.txt b/coreutils/tests/fixtures/tac/prime_per_line.txt new file mode 100644 index 000000000..afc67fd93 --- /dev/null +++ b/coreutils/tests/fixtures/tac/prime_per_line.txt @@ -0,0 +1,25 @@ +2 +3 +5 +7 +11 +13 +17 +19 +23 +29 +31 +37 +41 +43 +47 +53 +59 +61 +67 +71 +73 +79 +83 +89 +97 diff --git a/coreutils/tests/fixtures/tail/follow_stdin.expected b/coreutils/tests/fixtures/tail/follow_stdin.expected new file mode 100644 index 000000000..8eacac3b5 --- /dev/null +++ b/coreutils/tests/fixtures/tail/follow_stdin.expected @@ -0,0 +1,10 @@ +dos +tres +quattro +cinco +seis +siette +ocho +nueve +diez +once diff --git a/coreutils/tests/fixtures/tail/foobar.txt b/coreutils/tests/fixtures/tail/foobar.txt new file mode 100644 index 000000000..6e1f36ddc --- /dev/null +++ b/coreutils/tests/fixtures/tail/foobar.txt @@ -0,0 +1,11 @@ +uno +dos +tres +quattro +cinco +seis +siette +ocho +nueve +diez +once diff --git a/coreutils/tests/fixtures/tail/foobar2.txt b/coreutils/tests/fixtures/tail/foobar2.txt new file mode 100644 index 000000000..25973d326 --- /dev/null +++ b/coreutils/tests/fixtures/tail/foobar2.txt @@ -0,0 +1,2 @@ +un +deux diff --git a/coreutils/tests/fixtures/tail/foobar_bytes_single.expected b/coreutils/tests/fixtures/tail/foobar_bytes_single.expected new file mode 100644 index 000000000..eca784d72 --- /dev/null +++ b/coreutils/tests/fixtures/tail/foobar_bytes_single.expected @@ -0,0 +1,2 @@ +diez +once diff --git a/coreutils/tests/fixtures/tail/foobar_bytes_stdin.expected b/coreutils/tests/fixtures/tail/foobar_bytes_stdin.expected new file mode 100644 index 000000000..c6c34e9a1 --- /dev/null +++ b/coreutils/tests/fixtures/tail/foobar_bytes_stdin.expected @@ -0,0 +1,3 @@ +ve +diez +once diff --git a/coreutils/tests/fixtures/tail/foobar_follow_multiple.expected b/coreutils/tests/fixtures/tail/foobar_follow_multiple.expected new file mode 100644 index 000000000..140a9088d --- /dev/null +++ b/coreutils/tests/fixtures/tail/foobar_follow_multiple.expected @@ -0,0 +1,15 @@ +==> foobar.txt <== +dos +tres +quattro +cinco +seis +siette +ocho +nueve +diez +once + +==> foobar2.txt <== +un +deux diff --git a/coreutils/tests/fixtures/tail/foobar_follow_multiple_appended.expected b/coreutils/tests/fixtures/tail/foobar_follow_multiple_appended.expected new file mode 100644 index 000000000..0896d3743 --- /dev/null +++ b/coreutils/tests/fixtures/tail/foobar_follow_multiple_appended.expected @@ -0,0 +1,4 @@ + +==> foobar.txt <== +doce +trece diff --git a/coreutils/tests/fixtures/tail/foobar_multiple_quiet.expected b/coreutils/tests/fixtures/tail/foobar_multiple_quiet.expected new file mode 100644 index 000000000..d4f919f09 --- /dev/null +++ b/coreutils/tests/fixtures/tail/foobar_multiple_quiet.expected @@ -0,0 +1,12 @@ +dos +tres +quattro +cinco +seis +siette +ocho +nueve +diez +once +un +deux diff --git a/coreutils/tests/fixtures/tail/foobar_single_default.expected b/coreutils/tests/fixtures/tail/foobar_single_default.expected new file mode 100644 index 000000000..8eacac3b5 --- /dev/null +++ b/coreutils/tests/fixtures/tail/foobar_single_default.expected @@ -0,0 +1,10 @@ +dos +tres +quattro +cinco +seis +siette +ocho +nueve +diez +once diff --git a/coreutils/tests/fixtures/tail/foobar_stdin_default.expected b/coreutils/tests/fixtures/tail/foobar_stdin_default.expected new file mode 100644 index 000000000..8eacac3b5 --- /dev/null +++ b/coreutils/tests/fixtures/tail/foobar_stdin_default.expected @@ -0,0 +1,10 @@ +dos +tres +quattro +cinco +seis +siette +ocho +nueve +diez +once diff --git a/coreutils/tests/fixtures/tail/foobar_with_null.txt b/coreutils/tests/fixtures/tail/foobar_with_null.txt new file mode 100644 index 0000000000000000000000000000000000000000..f6b05b48a6a06dc56a8209a220ad31380fdc4014 GIT binary patch literal 60 zcmV~$F%Ez*3UEFTe;a`bSOb19 D)EOlm literal 0 HcmV?d00001 diff --git a/coreutils/tests/fixtures/uniq/sorted-zero-terminated.txt b/coreutils/tests/fixtures/uniq/sorted-zero-terminated.txt new file mode 100644 index 0000000000000000000000000000000000000000..588f7b076462bbe7c42d8bb3d27539f6df9c9fc3 GIT binary patch literal 270 zcmY%3OauW1g-5L$6ciW~IFmpC#NCLEn+yUV*-c +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +use common::util::*; + + +#[test] +fn test_encode() { + let input = "Hello, World!"; + new_ucmd!() + .pipe_in(input) + .succeeds() + .stdout_only("JBSWY3DPFQQFO33SNRSCC===\n"); +} + +#[test] +fn test_decode() { + for decode_param in vec!["-d", "--decode"] { + let input = "JBSWY3DPFQQFO33SNRSCC===\n"; + new_ucmd!() + .arg(decode_param) + .pipe_in(input) + .succeeds() + .stdout_only("Hello, World!"); + } +} + +#[test] +fn test_garbage() { + let input = "aGVsbG8sIHdvcmxkIQ==\0"; + new_ucmd!() + .arg("-d") + .pipe_in(input) + .fails() + .stderr_only("base32: error: invalid input\n"); +} + +#[test] +fn test_ignore_garbage() { + for ignore_garbage_param in vec!["-i", "--ignore-garbage"] { + let input = "JBSWY\x013DPFQ\x02QFO33SNRSCC===\n"; + new_ucmd!() + .arg("-d") + .arg(ignore_garbage_param) + .pipe_in(input) + .succeeds() + .stdout_only("Hello, World!"); + } +} + +#[test] +fn test_wrap() { + for wrap_param in vec!["-w", "--wrap"] { + let input = "The quick brown fox jumps over the lazy dog."; + new_ucmd!() + .arg(wrap_param) + .arg("20") + .pipe_in(input) + .succeeds() + .stdout_only("KRUGKIDROVUWG2ZAMJZG\n653OEBTG66BANJ2W24DT\nEBXXMZLSEB2GQZJANRQX\nU6JAMRXWOLQ=\n"); + } +} + +#[test] +fn test_wrap_no_arg() { + for wrap_param in vec!["-w", "--wrap"] { + new_ucmd!() + .arg(wrap_param) + .fails() + .stderr_only(format!("base32: error: Argument to option '{}' missing\n", + if wrap_param == "-w" { "w" } else { "wrap" })); + } +} + +#[test] +fn test_wrap_bad_arg() { + for wrap_param in vec!["-w", "--wrap"] { + new_ucmd!() + .arg(wrap_param).arg("b") + .fails() + .stderr_only("base32: error: invalid wrap size: ‘b’: invalid digit found in string\n"); + } +} diff --git a/coreutils/tests/test_base64.rs b/coreutils/tests/test_base64.rs new file mode 100644 index 000000000..94268acda --- /dev/null +++ b/coreutils/tests/test_base64.rs @@ -0,0 +1,81 @@ +use common::util::*; + + +#[test] +fn test_encode() { + let input = "hello, world!"; + new_ucmd!() + .pipe_in(input) + .succeeds() + .stdout_only("aGVsbG8sIHdvcmxkIQ==\n"); +} + +#[test] +fn test_decode() { + for decode_param in vec!["-d", "--decode"] { + let input = "aGVsbG8sIHdvcmxkIQ=="; + new_ucmd!() + .arg(decode_param) + .pipe_in(input) + .succeeds() + .stdout_only("hello, world!"); + } +} + +#[test] +fn test_garbage() { + let input = "aGVsbG8sIHdvcmxkIQ==\0"; + new_ucmd!() + .arg("-d") + .pipe_in(input) + .fails() + .stderr_only("base64: error: invalid input\n"); +} + +#[test] +fn test_ignore_garbage() { + for ignore_garbage_param in vec!["-i", "--ignore-garbage"] { + let input = "aGVsbG8sIHdvcmxkIQ==\0"; + new_ucmd!() + .arg("-d") + .arg(ignore_garbage_param) + .pipe_in(input) + .succeeds() + .stdout_only("hello, world!"); + } +} + +#[test] +fn test_wrap() { + for wrap_param in vec!["-w", "--wrap"] { + let input = "The quick brown fox jumps over the lazy dog."; + new_ucmd!() + .arg(wrap_param) + .arg("20") + .pipe_in(input) + .succeeds() + .stdout_only("VGhlIHF1aWNrIGJyb3du\nIGZveCBqdW1wcyBvdmVy\nIHRoZSBsYXp5IGRvZy4=\n"); + } +} + +#[test] +fn test_wrap_no_arg() { + for wrap_param in vec!["-w", "--wrap"] { + new_ucmd!() + .arg(wrap_param) + .fails() + .stderr_only(format!("base64: error: Argument to option '{}' missing\n", + if wrap_param == "-w" { "w" } else { "wrap" })); + } +} + +#[test] +fn test_wrap_bad_arg() { + for wrap_param in vec!["-w", "--wrap"] { + new_ucmd!() + .arg(wrap_param) + .arg("b") + .fails() + .stderr_only("base64: error: invalid wrap size: ‘b’: invalid digit found in string\n"); + } +} diff --git a/coreutils/tests/test_basename.rs b/coreutils/tests/test_basename.rs new file mode 100644 index 000000000..0402d2b40 --- /dev/null +++ b/coreutils/tests/test_basename.rs @@ -0,0 +1,74 @@ +use common::util::*; + + +#[test] +fn test_directory() { + new_ucmd!().args(&["/root/alpha/beta/gamma/delta/epsilon/omega/"]) + .succeeds().stdout_only("omega\n"); +} + +#[test] +fn test_file() { + new_ucmd!().args(&["/etc/passwd"]).succeeds().stdout_only("passwd\n"); +} + +#[test] +fn test_remove_suffix() { + new_ucmd!().args(&["/usr/local/bin/reallylongexecutable.exe", ".exe"]) + .succeeds().stdout_only("reallylongexecutable\n"); +} + +#[test] +fn test_dont_remove_suffix() { + new_ucmd!().args(&["/foo/bar/baz", "baz"]).succeeds().stdout_only( "baz\n"); +} + +#[test] +fn test_multiple_param() { + for multiple_param in vec!["-a", "--multiple"] { + let path = "/foo/bar/baz"; + new_ucmd!().args(&[multiple_param, path, path]) + .succeeds().stdout_only("baz\nbaz\n"); + } +} + +#[test] +fn test_suffix_param() { + for suffix_param in vec!["-s", "--suffix"] { + let path = "/foo/bar/baz.exe"; + new_ucmd!() + .args(&[suffix_param, ".exe", path, path]) + .succeeds().stdout_only("baz\nbaz\n"); + } +} + +#[test] +fn test_zero_param() { + for zero_param in vec!["-z", "--zero"] { + let path = "/foo/bar/baz"; + new_ucmd!().args(&[zero_param, "-a", path, path]) + .succeeds().stdout_only("baz\0baz\0"); + } +} + + +fn expect_error(input: Vec<&str>) { + assert!(new_ucmd!().args(&input) + .fails().no_stdout().stderr.len() > 0); +} + +#[test] +fn test_invalid_option() { + let path = "/foo/bar/baz"; + expect_error(vec!["-q", path]); +} + +#[test] +fn test_no_args() { + expect_error(vec![]); +} + +#[test] +fn test_too_many_args() { + expect_error(vec!["a", "b", "c"]); +} diff --git a/coreutils/tests/test_cat.rs b/coreutils/tests/test_cat.rs new file mode 100644 index 000000000..51e7e8278 --- /dev/null +++ b/coreutils/tests/test_cat.rs @@ -0,0 +1,167 @@ +#[cfg(unix)] +extern crate unix_socket; +extern crate tempdir; + +use common::util::*; + +#[test] +fn test_output_multi_files_print_all_chars() { + new_ucmd!() + .args(&["alpha.txt", "256.txt", "-A", "-n"]) + .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-^?"); +} + +#[test] +fn test_numbered_lines_no_trailing_newline() { + new_ucmd!() + .args(&["nonewline.txt", "alpha.txt", "-n"]) + .succeeds() + .stdout_only(" 1\ttext without a trailing newlineabcde\n 2\tfghij\n \ + 3\tklmno\n 4\tpqrst\n 5\tuvwxyz\n"); +} + +#[test] +fn test_stdin_show_nonprinting() { + for same_param in vec!["-v", "--show-nonprinting"] { + new_ucmd!() + .args(&[same_param]) + .pipe_in("\t\0\n") + .succeeds() + .stdout_only("\t^@\n"); + } +} + +#[test] +fn test_stdin_show_tabs() { + for same_param in vec!["-T", "--show-tabs"] { + new_ucmd!() + .args(&[same_param]) + .pipe_in("\t\0\n") + .succeeds() + .stdout_only("^I\0\n"); + } +} + + +#[test] +fn test_stdin_show_ends() { + for same_param in vec!["-E", "--show-ends"] { + new_ucmd!() + .args(&[same_param,"-"]) + .pipe_in("\t\0\n\t") + .succeeds() + .stdout_only("\t\0$\n\t"); + } +} + +#[test] +fn test_stdin_show_all() { + for same_param in vec!["-A", "--show-all"] { + new_ucmd!() + .args(&[same_param]) + .pipe_in("\t\0\n") + .succeeds() + .stdout_only("^I^@$\n"); + } +} + +#[test] +fn test_stdin_nonprinting_and_endofline() { + new_ucmd!() + .args(&["-e"]) + .pipe_in("\t\0\n") + .succeeds() + .stdout_only("\t^@$\n"); +} + +#[test] +fn test_stdin_nonprinting_and_tabs() { + new_ucmd!() + .args(&["-t"]) + .pipe_in("\t\0\n") + .succeeds() + .stdout_only("^I^@\n"); +} + +#[test] +fn test_stdin_squeeze_blank() { + for same_param in vec!["-s", "--squeeze-blank"] { + new_ucmd!() + .arg(same_param) + .pipe_in("\n\na\n\n\n\n\nb\n\n\n") + .succeeds() + .stdout_only("\na\n\nb\n\n"); + } +} + +#[test] +fn test_stdin_number_non_blank() { + for same_param in vec!["-b", "--number-nonblank"] { + new_ucmd!() + .arg(same_param) + .arg("-") + .pipe_in("\na\nb\n\n\nc") + .succeeds() + .stdout_only("\n 1\ta\n 2\tb\n\n\n 3\tc"); + } +} + +#[test] +fn test_non_blank_overrides_number() { + for same_param in vec!["-b", "--number-nonblank"] { + new_ucmd!() + .args(&[same_param, "-"]) + .pipe_in("\na\nb\n\n\nc") + .succeeds() + .stdout_only("\n 1\ta\n 2\tb\n\n\n 3\tc"); + } +} + +#[test] +fn test_squeeze_blank_before_numbering() { + for same_param in vec!["-s", "--squeeze-blank"] { + new_ucmd!() + .args(&[same_param, "-n", "-"]) + .pipe_in("a\n\n\nb") + .succeeds() + .stdout_only(" 1\ta\n 2\t\n 3\tb"); + } +} + + + +#[test] +#[cfg(foo)] +fn test_domain_socket() { + use std::thread; + use self::unix_socket::UnixListener; + use self::tempdir::TempDir; + use std::io::prelude::*; + + let dir = TempDir::new("unix_socket").expect("failed to create dir"); + let socket_path = dir.path().join("sock"); + let listener = UnixListener::bind(&socket_path).expect("failed to create socket"); + + let thread = thread::spawn(move || { + let mut stream = listener.accept().expect("failed to accept connection").0; + stream.write_all(b"a\tb").expect("failed to write test data"); + }); + + new_ucmd!() + .args(&[socket_path]) + .succeeds() + .stdout_only("a\tb"); + + thread.join().unwrap(); +} diff --git a/coreutils/tests/test_chgrp.rs b/coreutils/tests/test_chgrp.rs new file mode 100644 index 000000000..e5a974910 --- /dev/null +++ b/coreutils/tests/test_chgrp.rs @@ -0,0 +1,148 @@ +use common::util::*; +use rust_users::*; + +#[test] +fn test_invalid_option() { + new_ucmd!() + .arg("-w") + .arg("/") + .fails(); +} + +static DIR: &'static str = "/tmp"; + +#[test] +fn test_invalid_group() { + new_ucmd!() + .arg("__nosuchgroup__") + .arg("/") + .fails() + .stderr_is("chgrp: invalid group: __nosuchgroup__"); +} + +#[test] +fn test_1() { + if get_effective_gid() != 0 { + new_ucmd!() + .arg("bin") + .arg(DIR) + .fails() + .stderr_is("chgrp: changing group of '/tmp': Operation not permitted (os error 1)"); + } +} + +#[test] +fn test_fail_silently() { + if get_effective_gid() != 0 { + for opt in &["-f", "--silent", "--quiet"] { + new_ucmd!() + .arg(opt) + .arg("bin") + .arg(DIR) + .run() + .fails_silently(); + } + } +} + +#[test] +fn test_preserve_root() { + // It's weird that on OS X, `realpath /etc/..` returns '/private' + for d in &["/", "/////tmp///../../../../", + "../../../../../../../../../../../../../../", + "./../../../../../../../../../../../../../../"] { + new_ucmd!() + .arg("--preserve-root") + .arg("-R") + .arg("bin").arg(d) + .fails() + .stderr_is("chgrp: it is dangerous to operate recursively on '/'\nchgrp: use --no-preserve-root to override this failsafe"); + } +} + +#[test] +fn test_preserve_root_symlink() { + let file = "test_chgrp_symlink2root"; + for d in &["/", "////tmp//../../../../", + "..//../../..//../..//../../../../../../../../", + ".//../../../../../../..//../../../../../../../"] { + let (at, mut ucmd) = at_and_ucmd!(); + at.symlink_file(d, file); + ucmd.arg("--preserve-root") + .arg("-HR") + .arg("bin").arg(file) + .fails() + .stderr_is("chgrp: it is dangerous to operate recursively on '/'\nchgrp: use --no-preserve-root to override this failsafe"); + } + + let (at, mut ucmd) = at_and_ucmd!(); + at.symlink_file("///usr", file); + ucmd.arg("--preserve-root") + .arg("-HR") + .arg("bin").arg(format!(".//{}/..//..//../../", file)) + .fails() + .stderr_is("chgrp: it is dangerous to operate recursively on '/'\nchgrp: use --no-preserve-root to override this failsafe"); + + let (at, mut ucmd) = at_and_ucmd!(); + at.symlink_file("/", "/tmp/__root__"); + ucmd.arg("--preserve-root") + .arg("-R") + .arg("bin").arg("/tmp/__root__/.") + .fails() + .stderr_is("chgrp: it is dangerous to operate recursively on '/'\nchgrp: use --no-preserve-root to override this failsafe"); + + use ::std::fs; + fs::remove_file("/tmp/__root__").unwrap(); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_reference() { + if get_effective_gid() != 0 { + new_ucmd!() + .arg("-v") + .arg("--reference=/etc/passwd") + .arg("/etc") + .fails() + .stderr_is("chgrp: changing group of '/etc': Operation not permitted (os error 1)\n") + .stdout_is("failed to change group of /etc from root to root\n"); + } +} + +#[test] +#[cfg(target_os = "macos")] +fn test_reference() { + new_ucmd!() + .arg("-v") + .arg("--reference=/etc/passwd") + .arg("/etc") + .succeeds(); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_big_p() { + if get_effective_gid() != 0 { + new_ucmd!() + .arg("-RP") + .arg("bin") + .arg("/proc/self/cwd") + .fails() + .stderr_is("chgrp: changing group of '/proc/self/cwd': Operation not permitted (os error 1)\n"); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_big_h() { + if get_effective_gid() != 0 { + assert!(new_ucmd!() + .arg("-RH") + .arg("bin") + .arg("/proc/self/fd") + .fails() + .stderr + .lines() + .fold(0, |acc, _| acc + 1) > 1); + } +} diff --git a/coreutils/tests/test_chmod.rs b/coreutils/tests/test_chmod.rs new file mode 100644 index 000000000..19a76e483 --- /dev/null +++ b/coreutils/tests/test_chmod.rs @@ -0,0 +1,149 @@ +use common::util::*; +use std::fs::{metadata, OpenOptions, set_permissions}; +use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; +use std::sync::Mutex; + +extern crate libc; +use self::libc::umask; + + +static TEST_FILE: &'static str = "file"; +static REFERENCE_FILE: &'static str = "reference"; +static REFERENCE_PERMS: u32 = 0o247; +lazy_static! { + static ref UMASK_MUTEX: Mutex<()> = Mutex::new(()); +} + +struct TestCase { + args: Vec<&'static str>, + before: u32, + after: u32 +} + +fn mkfile(file: &str, mode: u32) { + OpenOptions::new().mode(mode).create(true).write(true).open(file).unwrap(); + let mut perms = metadata(file).unwrap().permissions(); + perms.set_mode(mode); + set_permissions(file, perms).unwrap(); +} + +fn run_single_test(test: &TestCase, at: AtPath, mut ucmd: UCommand) { + mkfile(&at.plus_as_string(TEST_FILE), test.before); + let perms = at.metadata(TEST_FILE).permissions().mode(); + if perms != test.before { + panic!(format!("{}: expected: {:o} got: {:o}", "setting permissions on test files before actual test run failed", test.after, perms)); + } + + for arg in &test.args { + ucmd.arg(arg); + } + let r = ucmd.run(); + if !r.success { + println!("{}", r.stderr); + panic!(format!("{:?}: failed", ucmd.raw)); + } + + let perms = at.metadata(TEST_FILE).permissions().mode(); + if perms != test.after { + panic!(format!("{:?}: expected: {:o} got: {:o}", ucmd.raw, test.after, perms)); + } +} + +fn run_tests(tests: Vec) { + for test in tests { + let (at, ucmd) = at_and_ucmd!(); + run_single_test(&test, at, ucmd); + } +} + +#[test] +fn test_chmod_octal() { + let tests = vec!{ + TestCase{args: vec!{"0700", TEST_FILE}, before: 0o100000, after: 0o100700}, + TestCase{args: vec!{"0070", TEST_FILE}, before: 0o100000, after: 0o100070}, + TestCase{args: vec!{"0007", TEST_FILE}, before: 0o100000, after: 0o100007}, + TestCase{args: vec!{"-0700", TEST_FILE}, before: 0o100700, after: 0o100000}, + TestCase{args: vec!{"-0070", TEST_FILE}, before: 0o100060, after: 0o100000}, + TestCase{args: vec!{"-0007", TEST_FILE}, before: 0o100001, after: 0o100000}, + TestCase{args: vec!{"+0100", TEST_FILE}, before: 0o100600, after: 0o100700}, + TestCase{args: vec!{"+0020", TEST_FILE}, before: 0o100050, after: 0o100070}, + TestCase{args: vec!{"+0004", TEST_FILE}, before: 0o100003, after: 0o100007}, + }; + run_tests(tests); +} + +#[test] +fn test_chmod_ugoa() { + let _guard = UMASK_MUTEX.lock(); + + let last = unsafe { + umask(0) + }; + let tests = vec!{ + TestCase{args: vec!{"u=rwx", TEST_FILE}, before: 0o100000, after: 0o100700}, + TestCase{args: vec!{"g=rwx", TEST_FILE}, before: 0o100000, after: 0o100070}, + TestCase{args: vec!{"o=rwx", TEST_FILE}, before: 0o100000, after: 0o100007}, + TestCase{args: vec!{"a=rwx", TEST_FILE}, before: 0o100000, after: 0o100777}, + TestCase{args: vec!{"-r", TEST_FILE}, before: 0o100777, after: 0o100333}, + TestCase{args: vec!{"-w", TEST_FILE}, before: 0o100777, after: 0o100555}, + TestCase{args: vec!{"-x", TEST_FILE}, before: 0o100777, after: 0o100666}, + }; + run_tests(tests); + + unsafe { + umask(0o022); + } + let tests = vec!{ + TestCase{args: vec!{"u=rwx", TEST_FILE}, before: 0o100000, after: 0o100700}, + TestCase{args: vec!{"g=rwx", TEST_FILE}, before: 0o100000, after: 0o100070}, + TestCase{args: vec!{"o=rwx", TEST_FILE}, before: 0o100000, after: 0o100007}, + TestCase{args: vec!{"a=rwx", TEST_FILE}, before: 0o100000, after: 0o100777}, + TestCase{args: vec!{"+rw", TEST_FILE}, before: 0o100000, after: 0o100644}, + TestCase{args: vec!{"=rwx", TEST_FILE}, before: 0o100000, after: 0o100755}, + TestCase{args: vec!{"-w", TEST_FILE}, before: 0o100777, after: 0o100577}, + TestCase{args: vec!{"-x", TEST_FILE}, before: 0o100777, after: 0o100666}, + }; + run_tests(tests); + unsafe { + umask(last); + } +} + +#[test] +fn test_chmod_ugo_copy() { + let tests = vec!{ + TestCase{args: vec!{"u=g", TEST_FILE}, before: 0o100070, after: 0o100770}, + TestCase{args: vec!{"g=o", TEST_FILE}, before: 0o100005, after: 0o100055}, + TestCase{args: vec!{"o=u", TEST_FILE}, before: 0o100200, after: 0o100202}, + TestCase{args: vec!{"u-g", TEST_FILE}, before: 0o100710, after: 0o100610}, + TestCase{args: vec!{"u+g", TEST_FILE}, before: 0o100250, after: 0o100750}, + }; + run_tests(tests); +} + +#[test] +fn test_chmod_many_options() { + let _guard = UMASK_MUTEX.lock(); + + let original_umask = unsafe { + umask(0) + }; + let tests = vec!{ + TestCase{args: vec!{"-r,a+w", TEST_FILE}, before: 0o100444, after: 0o100222}, + }; + run_tests(tests); + unsafe { + umask(original_umask); + } +} + +#[test] +fn test_chmod_reference_file() { + let tests = vec!{ + TestCase{args: vec!{"--reference", REFERENCE_FILE, TEST_FILE}, before: 0o100070, after: 0o100247}, + TestCase{args: vec!{"a-w", "--reference", REFERENCE_FILE, TEST_FILE}, before: 0o100070, after: 0o100247}, + }; + let (at, ucmd) = at_and_ucmd!(); + mkfile(&at.plus_as_string(REFERENCE_FILE), REFERENCE_PERMS); + run_single_test(&tests[0], at, ucmd); +} diff --git a/coreutils/tests/test_chown.rs b/coreutils/tests/test_chown.rs new file mode 100644 index 000000000..24a040414 --- /dev/null +++ b/coreutils/tests/test_chown.rs @@ -0,0 +1,51 @@ +use common::util::*; + +extern crate uu_chown; +pub use self::uu_chown::*; + + +#[cfg(test)] +mod test_passgrp { + use super::uu_chown::entries::{usr2uid,grp2gid,uid2usr,gid2grp}; + + #[test] + fn test_usr2uid() { + assert_eq!(0, usr2uid("root").unwrap()); + assert!(usr2uid("88888888").is_err()); + assert!(usr2uid("auserthatdoesntexist").is_err()); + } + + #[test] + fn test_grp2gid() { + if cfg!(target_os = "linux") || cfg!(target_os = "android") || cfg!(target_os = "windows") { + assert_eq!(0, grp2gid("root").unwrap()) + } else { + assert_eq!(0, grp2gid("wheel").unwrap()); + } + assert!(grp2gid("88888888").is_err()); + assert!(grp2gid("agroupthatdoesntexist").is_err()); + } + + #[test] + fn test_uid2usr() { + assert_eq!("root", uid2usr(0).unwrap()); + assert!(uid2usr(88888888).is_err()); + } + + #[test] + fn test_gid2grp() { + if cfg!(target_os = "linux") || cfg!(target_os = "android") || cfg!(target_os = "windows") { + assert_eq!("root", gid2grp(0).unwrap()); + } else { + assert_eq!("wheel", gid2grp(0).unwrap()); + } + assert!(gid2grp(88888888).is_err()); + } +} + +#[test] +fn test_invalid_option() { + new_ucmd!() + .arg("-w").arg("-q").arg("/") + .fails(); +} diff --git a/coreutils/tests/test_cksum.rs b/coreutils/tests/test_cksum.rs new file mode 100644 index 000000000..f94b6785b --- /dev/null +++ b/coreutils/tests/test_cksum.rs @@ -0,0 +1,23 @@ +use common::util::*; + + +#[test] +fn test_single_file() { + new_ucmd!().arg("lorem_ipsum.txt") + .succeeds().stdout_is_fixture("single_file.expected"); +} + +#[test] +fn test_multiple_files() { + new_ucmd!() + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds().stdout_is_fixture("multiple_files.expected"); +} + +#[test] +fn test_stdin() { + new_ucmd!() + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds().stdout_is_fixture("stdin.expected"); +} diff --git a/coreutils/tests/test_comm.rs b/coreutils/tests/test_comm.rs new file mode 100644 index 000000000..b0f5d29f5 --- /dev/null +++ b/coreutils/tests/test_comm.rs @@ -0,0 +1,136 @@ +use common::util::*; + + +#[test] +fn ab_no_args() { + new_ucmd!().args(&["a", "b"]).succeeds().stdout_only_fixture("ab.expected"); +} + +#[test] +fn ab_dash_one() { + new_ucmd!().args(&["a", "b", "-1"]).succeeds().stdout_only_fixture("ab1.expected"); +} + +#[test] +fn ab_dash_two() { + new_ucmd!().args(&["a", "b", "-2"]).succeeds().stdout_only_fixture("ab2.expected"); +} + +#[test] +fn ab_dash_three() { + new_ucmd!().args(&["a", "b", "-3"]).succeeds().stdout_only_fixture("ab3.expected"); +} + +#[test] +fn aempty() { + new_ucmd!().args(&["a", "empty"]).succeeds().stdout_only_fixture("aempty.expected"); +} + +#[test] +fn emptyempty() { + new_ucmd!().args(&["empty", "empty"]).succeeds().stdout_only_fixture("emptyempty.expected"); +} + +#[cfg_attr(not(feature="test_unimplemented"),ignore)] +#[test] +fn output_delimiter() { + new_ucmd!().args(&["--output-delimiter=word", "a", "b"]) + .succeeds().stdout_only_fixture("ab_delimiter_word.expected"); +} + +#[cfg_attr(not(feature="test_unimplemented"),ignore)] +#[test] +fn output_delimiter_require_arg() { + new_ucmd!().args(&["--output-delimiter=", "a", "b"]) + .fails().stderr_only("error to be defined"); +} + +// even though (info) documentation suggests this is an option +// in latest GNU Coreutils comm, it actually is not. +// this test is essentially an alarm in case someone well-intendingly +// implements it. +//marked as unimplemented as error message not set yet. +#[cfg_attr(not(feature="test_unimplemented"),ignore)] +#[test] +fn zero_terminated() { + for param in vec!["-z", "--zero-terminated"] { + new_ucmd!().args(&[param, "a", "b"]).fails().stderr_only("error to be defined"); + } +} + +#[cfg_attr(not(feature="test_unimplemented"),ignore)] +#[test] +fn check_order() { + new_ucmd!().args(&["--check-order", "bad_order_1", "bad_order_2"]) + .fails() + .stdout_is_fixture("bad_order12.check_order.expected") + .stderr_is("error to be defined"); +} + +#[cfg_attr(not(feature="test_unimplemented"),ignore)] +#[test] +fn nocheck_order() { + new_ucmd!().args(&["--nocheck-order", "bad_order_1", "bad_order_2"]) + .succeeds() + .stdout_only_fixture("bad_order12.nocheck_order.expected"); +} + +// when neither --check-order nor --no-check-order is provided, +// stderr and the error code behaves like check order, but stdout +// behaves like nocheck_order. However with some quirks detailed below. +#[cfg_attr(not(feature="test_unimplemented"),ignore)] +#[test] +fn defaultcheck_order() { + new_ucmd!().args(&["a", "bad_order_1"]).fails().stderr_only("error to be defined"); +} + +// * the first: if both files are not in order, the default behavior is the only +// behavior that will provide an error message + +// * the second: if two rows are paired but are out of order, +// it won't matter if all rows in the two files are exactly the same. +// This is specified in the documentation + +#[test] +fn defaultcheck_order_identical_bad_order_files() { + new_ucmd!().args(&["bad_order_1", "bad_order_1"]) + .succeeds().stdout_only_fixture("bad_order11.defaultcheck_order.expected"); +} + +#[cfg_attr(not(feature="test_unimplemented"),ignore)] +#[test] +fn defaultcheck_order_two_different_bad_order_files() { + new_ucmd!().args(&["bad_order_1", "bad_order_2"]) + .fails() + .stdout_is_fixture("bad_order12.nocheck_order.expected") + .stderr_is("error to be defined"); +} + +// * the third: (it is not know whether this is a bug or not) +// for the first incident, and only the first incident, +// where both lines are different and one or both file lines being +// compared are out of order from the preceding line, +// it is ignored and no errors occur. + +// * the fourth: (it is not known whether this is a bug or not) +// there are additional, not-yet-understood circumstances where an out-of-order +// pair is ignored and is not counted against the 1 maximum out-of-order line. + +#[cfg_attr(not(feature="test_unimplemented"),ignore)] +#[test] +fn unintuitive_default_behavior_1() { + new_ucmd!().args(&["defaultcheck_unintuitive_1", "defaultcheck_unintuitive_2"]) + .succeeds().stdout_only_fixture("defaultcheck_unintuitive.expected"); +} + +#[ignore] //bug? should help be stdout if not called via -h|--help? +#[test] +fn no_arguments() { + new_ucmd!().fails().no_stdout().no_stderr(); +} + +#[ignore] //bug? should help be stdout if not called via -h|--help? +#[test] +fn one_argument() { + new_ucmd!().arg("a").fails().no_stdout().no_stderr(); +} diff --git a/coreutils/tests/test_cp.rs b/coreutils/tests/test_cp.rs new file mode 100644 index 000000000..c74b47f58 --- /dev/null +++ b/coreutils/tests/test_cp.rs @@ -0,0 +1,303 @@ +use common::util::*; +#[cfg(not(windows))] +use std::fs::set_permissions; + +static TEST_EXISTING_FILE: &str = "existing_file.txt"; +static TEST_HELLO_WORLD_SOURCE: &str = "hello_world.txt"; +static TEST_HELLO_WORLD_DEST: &str = "copy_of_hello_world.txt"; +static TEST_HOW_ARE_YOU_SOURCE: &str = "how_are_you.txt"; +static TEST_HOW_ARE_YOU_DEST: &str = "hello_dir/how_are_you.txt"; +static TEST_COPY_TO_FOLDER: &str = "hello_dir/"; +static TEST_COPY_TO_FOLDER_FILE: &str = "hello_dir/hello_world.txt"; +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"; + +#[test] +fn test_cp_cp() { + let (at, mut ucmd) = at_and_ucmd!(); + // Invoke our binary to make the copy. + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HELLO_WORLD_DEST) + .run(); + + // Check that the exit code represents a successful copy. + let exit_success = result.success; + assert!(exit_success); + + // Check the content of the destination file that was copied. + assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); +} + + +#[test] +fn test_cp_existing_target() { + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .run(); + + assert!(result.success); + + // Check the content of the destination file + assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); + + // No backup should have been created + assert!(!at.file_exists(&*format!("{}~", TEST_EXISTING_FILE))); +} + + +#[test] +fn test_cp_duplicate_files() { + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_COPY_TO_FOLDER) + .run(); + + assert!(result.success); + assert!(result.stderr.contains("specified more than once")); + assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); +} + + +#[test] +fn test_cp_multiple_files_target_is_file() { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .run(); + + assert!(!result.success); + assert!(result.stderr.contains("not a directory")); +} + +#[test] +fn test_cp_directory_not_recursive() { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_COPY_TO_FOLDER) + .arg(TEST_HELLO_WORLD_DEST) + .run(); + + assert!(!result.success); + assert!(result.stderr.contains("omitting directory")); +} + + +#[test] +fn test_cp_multiple_files() { + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .arg(TEST_COPY_TO_FOLDER) + .run(); + + assert!(result.success); + assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); + assert_eq!(at.read(TEST_HOW_ARE_YOU_DEST), "How are you?\n"); +} + +#[test] +fn test_cp_recurse() { + let (at, mut ucmd) = at_and_ucmd!(); + + let result = ucmd + .arg("-r") + .arg(TEST_COPY_FROM_FOLDER) + .arg(TEST_COPY_TO_FOLDER_NEW) + .run(); + + assert!(result.success); + // Check the content of the destination file that was copied. + assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n"); +} + +#[test] +fn test_cp_with_dirs_t() { + let (at, mut ucmd) = at_and_ucmd!(); + + //using -t option + let result_to_dir_t = ucmd + .arg("-t") + .arg(TEST_COPY_TO_FOLDER) + .arg(TEST_HELLO_WORLD_SOURCE) + .run(); + assert!(result_to_dir_t.success); + assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); +} + +#[test] +fn test_cp_with_dirs() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + //using -t option + let result_to_dir = scene.ucmd() + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_COPY_TO_FOLDER) + .run(); + assert!(result_to_dir.success); + assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); + + let result_from_dir = scene.ucmd() + .arg(TEST_COPY_FROM_FOLDER_FILE) + .arg(TEST_HELLO_WORLD_DEST) + .run(); + assert!(result_from_dir.success); + assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); +} + +#[test] +fn test_cp_arg_target_directory() { + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("-t") + .arg(TEST_COPY_TO_FOLDER) + .run(); + + assert!(result.success); + assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); +} + +#[test] +fn test_cp_arg_no_target_directory() { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("-v") + .arg("-T") + .arg(TEST_COPY_TO_FOLDER) + .run(); + + assert!(!result.success); + assert!(result.stderr.contains("cannot overwrite directory")); +} + +#[test] +fn test_cp_arg_interactive() { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .arg("-i") + .pipe_in("N\n") + .run(); + + assert!(result.success); + assert!(result.stderr.contains("Not overwriting")); +} + +#[test] +#[cfg(target_os="unix")] +fn test_cp_arg_link() { + use std::os::linux::fs::MetadataExt; + + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("--link") + .arg(TEST_HELLO_WORLD_DEST) + .run(); + + assert!(result.success); + assert_eq!(at.metadata(TEST_HELLO_WORLD_SOURCE).st_nlink(), 2); +} + +#[test] +fn test_cp_arg_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("--symbolic-link") + .arg(TEST_HELLO_WORLD_DEST) + .run(); + + assert!(result.success); + assert!(at.is_symlink(TEST_HELLO_WORLD_DEST)); +} + + +#[test] +fn test_cp_arg_no_clobber() { + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("--no-clobber") + .arg(TEST_HOW_ARE_YOU_SOURCE) + .run(); + + assert!(result.success); + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); + assert!(result.stderr.contains("Not overwriting")); +} + +#[test] +#[cfg(not(windows))] +fn test_cp_arg_force() { + let (at, mut ucmd) = at_and_ucmd!(); + + // create dest without write permissions + let mut permissions = at.make_file(TEST_HELLO_WORLD_DEST).metadata().unwrap().permissions(); + permissions.set_readonly(true); + set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap(); + + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("--force") + .arg(TEST_HELLO_WORLD_DEST) + .run(); + + println!("{:?}", result.stderr); + println!("{:?}", result.stdout); + + assert!(result.success); + assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); +} + +/// TODO: write a better test that differentiates --remove-destination +/// from --force. Also this test currently doesn't work on +/// Windows. This test originally checked file timestamps, which +/// proved to be unreliable per target / CI platform +#[test] +#[cfg(not(windows))] +fn test_cp_arg_remove_destination() { + let (at, mut ucmd) = at_and_ucmd!(); + + // create dest without write permissions + let mut permissions = at.make_file(TEST_HELLO_WORLD_DEST).metadata().unwrap().permissions(); + permissions.set_readonly(true); + set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap(); + + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("--remove-destination") + .arg(TEST_HELLO_WORLD_DEST) + .run(); + + assert!(result.success); + assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); +} + +#[test] +fn test_cp_arg_backup() { + let (at, mut ucmd) = at_and_ucmd!(); + + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("--backup") + .arg(TEST_HOW_ARE_YOU_SOURCE) + .run(); + + assert!(result.success); + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!(at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)), "How are you?\n"); +} + +#[test] +fn test_cp_arg_suffix() { + let (at, mut ucmd) = at_and_ucmd!(); + + let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("--suffix") + .arg(".bak") + .arg(TEST_HOW_ARE_YOU_SOURCE) + .run(); + + assert!(result.success); + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!(at.read(&*format!("{}.bak", TEST_HOW_ARE_YOU_SOURCE)), "How are you?\n"); +} diff --git a/coreutils/tests/test_cut.rs b/coreutils/tests/test_cut.rs new file mode 100644 index 000000000..055462e4a --- /dev/null +++ b/coreutils/tests/test_cut.rs @@ -0,0 +1,97 @@ +use common::util::*; + + +static INPUT: &'static str = "lists.txt"; + +struct TestedSequence<'b> { + name : &'b str, + sequence: &'b str +} + +static EXAMPLE_SEQUENCES: &'static [TestedSequence<'static>] = &[ + TestedSequence{ name: "singular", sequence:"2" }, + TestedSequence{ name: "prefix", sequence: "-2" }, + TestedSequence{ name: "suffix", sequence: "2-" }, + TestedSequence{ name: "range", sequence: "2-4" }, + TestedSequence{ name: "aggregate", sequence: "9-,6-7,-2,4" }, + TestedSequence{ name: "subsumed", sequence: "2-,3" } +]; + +static COMPLEX_SEQUENCE: &'static TestedSequence<'static> = &TestedSequence{ name: "", sequence: "9-,6-7,-2,4" }; + +#[test] +fn test_byte_sequence() { + for param in vec!["-b", "--bytes"] { + for example_seq in EXAMPLE_SEQUENCES { + new_ucmd!().args(&[param, example_seq.sequence, INPUT]) + .succeeds().stdout_only_fixture(format!("sequences/byte_{}.expected", example_seq.name)); + } + } +} + +#[test] +fn test_char_sequence() { + for param in vec!["-c", "--characters"] { + for example_seq in EXAMPLE_SEQUENCES { + //as of coreutils 8.25 a char range is effectively the same as a byte range; there is no distinct treatment of utf8 chars. + new_ucmd!().args(&[param, example_seq.sequence, INPUT]) + .succeeds().stdout_only_fixture(format!("sequences/byte_{}.expected", example_seq.name)); + } + } +} + +#[test] +fn test_field_sequence() { + for param in vec!["-f", "--fields"] { + for example_seq in EXAMPLE_SEQUENCES { + new_ucmd!().args(&[param, example_seq.sequence, INPUT]) + .succeeds().stdout_only_fixture(format!("sequences/field_{}.expected", example_seq.name)); + } + } +} + +#[test] +fn test_specify_delimiter() { + for param in vec!["-d", "--delimiter"] { + new_ucmd!().args(&[param, ":", "-f", COMPLEX_SEQUENCE.sequence, INPUT]) + .succeeds().stdout_only_fixture("delimiter_specified.expected"); + } +} + +#[test] +fn test_output_delimiter() { + // we use -d here to ensure output delimiter + // is applied to the current, and not just the default, input delimiter + new_ucmd!().args(&["-d:", "--output-delimiter=@", "-f", COMPLEX_SEQUENCE.sequence, INPUT]) + .succeeds().stdout_only_fixture("output_delimiter.expected"); +} + +#[test] +fn test_complement() { + new_ucmd!().args(&["-d_","--complement", "-f", "2"]) + .pipe_in("9_1\n8_2\n7_3") + .succeeds().stdout_only("9\n8\n7\n"); +} + +#[test] +fn test_zero_terminated() { + new_ucmd!().args(&["-d_","-z", "-f", "1"]) + .pipe_in("9_1\n8_2\n\07_3") + .succeeds().stdout_only("9\07\0"); +} + +#[test] +fn test_only_delimited() { + for param in vec!["-s", "--only-delimited"] { + new_ucmd!().args(&["-d_", param, "-f", "1"]) + .pipe_in("91\n82\n7_3") + .succeeds().stdout_only("7\n"); + } +} + +#[test] +fn test_zero_terminated_only_delimited() { + new_ucmd!().args(&["-d_","-z", "-s", "-f", "1"]) + .pipe_in("91\n\082\n7_3") + .succeeds().stdout_only("82\n7\0"); +} diff --git a/coreutils/tests/test_dircolors.rs b/coreutils/tests/test_dircolors.rs new file mode 100644 index 000000000..9deef78fd --- /dev/null +++ b/coreutils/tests/test_dircolors.rs @@ -0,0 +1,101 @@ +extern crate uu_dircolors; +use self::uu_dircolors::{StrUtils, guess_syntax, OutputFmt}; + +use common::util::*; + + +#[test] +fn test_shell_syntax() { + use std::env; + let last = env::var("SHELL"); + env::set_var("SHELL", "/path/csh"); + assert_eq!(OutputFmt::CShell, guess_syntax()); + env::set_var("SHELL", "csh"); + assert_eq!(OutputFmt::CShell, guess_syntax()); + env::set_var("SHELL", "/path/bash"); + assert_eq!(OutputFmt::Shell, guess_syntax()); + env::set_var("SHELL", "bash"); + assert_eq!(OutputFmt::Shell, guess_syntax()); + env::set_var("SHELL", "/asd/bar"); + assert_eq!(OutputFmt::Shell, guess_syntax()); + env::set_var("SHELL", "foo"); + assert_eq!(OutputFmt::Shell, guess_syntax()); + env::set_var("SHELL", ""); + assert_eq!(OutputFmt::Unknown, guess_syntax()); + env::remove_var("SHELL"); + assert_eq!(OutputFmt::Unknown, guess_syntax()); + + if let Ok(s) = last { + env::set_var("SHELL", s); + } +} + +#[test] +fn test_strutils() { + let s = " asd#zcv #hk\t\n "; + assert_eq!("asd#zcv", s.purify()); + + let s = "con256asd"; + assert!(s.fnmatch("*[2][3-6][5-9]?sd")); + + let s = "zxc \t\nqwe jlk hjl"; + let (k, v) = s.split_two(); + assert_eq!("zxc", k); + assert_eq!("qwe jlk hjl", v); +} + +#[test] +fn test1() { + test_helper("test1", "gnome"); +} + +#[test] +fn test_keywords() { + test_helper("keywords", ""); +} + +#[test] +fn test_internal_db() { + new_ucmd!() + .arg("-p") + .run() + .stdout_is_fixture("internal.expected"); +} + +#[test] +fn test_bash_default() { + new_ucmd!().env("TERM", "screen").arg("-b").run().stdout_is_fixture("bash_def.expected"); +} + +#[test] +fn test_csh_default() { + new_ucmd!().env("TERM", "screen").arg("-c").run().stdout_is_fixture("csh_def.expected"); +} + +#[test] +fn test_no_env() { + // no SHELL and TERM + new_ucmd!() + .fails(); +} + +#[test] +fn test_exclusive_option() { + new_ucmd!() + .arg("-cp") + .fails(); +} + +fn test_helper(file_name: &str, term: &str) { + new_ucmd!() + .env("TERM", term) + .arg("-c") + .arg(format!("{}.txt", file_name)) + .run().stdout_is_fixture(format!("{}.csh.expected", file_name)); + + new_ucmd!() + .env("TERM", term) + .arg("-b") + .arg(format!("{}.txt", file_name)) + .run().stdout_is_fixture(format!("{}.sh.expected", file_name)); +} diff --git a/coreutils/tests/test_dirname.rs b/coreutils/tests/test_dirname.rs new file mode 100644 index 000000000..0875bd8fb --- /dev/null +++ b/coreutils/tests/test_dirname.rs @@ -0,0 +1,29 @@ +use common::util::*; + + +#[test] +fn test_path_with_trailing_slashes() { + new_ucmd!().arg("/root/alpha/beta/gamma/delta/epsilon/omega//") + .run().stdout_is("/root/alpha/beta/gamma/delta/epsilon\n"); +} + +#[test] +fn test_path_without_trailing_slashes() { + new_ucmd!().arg("/root/alpha/beta/gamma/delta/epsilon/omega") + .run().stdout_is("/root/alpha/beta/gamma/delta/epsilon\n"); +} + +#[test] +fn test_root() { + new_ucmd!().arg("/").run().stdout_is("/\n"); +} + +#[test] +fn test_pwd() { + new_ucmd!().arg(".").run().stdout_is(".\n"); +} + +#[test] +fn test_empty() { + new_ucmd!().arg("").run().stdout_is(".\n"); +} diff --git a/coreutils/tests/test_du.rs b/coreutils/tests/test_du.rs new file mode 100644 index 000000000..7ade82dd7 --- /dev/null +++ b/coreutils/tests/test_du.rs @@ -0,0 +1,127 @@ +use common::util::*; + +const SUB_DIR: &str = "subdir/deeper"; +const SUB_DIR_LINKS: &str = "subdir/links"; +const SUB_FILE: &str = "subdir/links/subwords.txt"; +const SUB_LINK: &str = "subdir/links/sublink.txt"; + +#[test] +fn test_du_basics() { + let (_at, mut ucmd) = at_and_ucmd!(); + let result = ucmd.run(); + assert!(result.success); + assert_eq!(result.stderr, ""); +} +#[cfg(target_os = "macos")] +fn _du_basics(s: String) { + let answer = "32\t./subdir +8\t./subdir/deeper +24\t./subdir/links +40\t./ +"; + assert_eq!(s, answer); +} +#[cfg(not(target_os = "macos"))] +fn _du_basics(s: String) { + let answer = "28\t./subdir +8\t./subdir/deeper +16\t./subdir/links +36\t./ +"; + assert_eq!(s, answer); +} + +#[test] +fn test_du_basics_subdir() { + let (_at, mut ucmd) = at_and_ucmd!(); + + let result = ucmd.arg(SUB_DIR).run(); + assert!(result.success); + assert_eq!(result.stderr, ""); + _du_basics_subdir(result.stdout); +} + +#[cfg(target_os = "macos")] +fn _du_basics_subdir(s: String) { + assert_eq!(s, "4\tsubdir/deeper\n"); +} +#[cfg(not(target_os = "macos"))] +fn _du_basics_subdir(s: String) { + assert_eq!(s, "8\tsubdir/deeper\n"); +} + +#[test] +fn test_du_basics_bad_name() { + let (_at, mut ucmd) = at_and_ucmd!(); + + let result = ucmd.arg("bad_name").run(); + assert_eq!(result.stdout, ""); + assert_eq!( + result.stderr, + "du: error: bad_name: No such file or directory\n" + ); +} + +#[test] +fn test_du_soft_link() { + let ts = TestScenario::new("du"); + + let link = ts.cmd("ln").arg("-s").arg(SUB_FILE).arg(SUB_LINK).run(); + assert!(link.success); + + let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); + assert!(result.success); + assert_eq!(result.stderr, ""); + _du_soft_link(result.stdout); +} + +#[cfg(target_os = "macos")] +fn _du_soft_link(s: String) { + assert_eq!(s, "16\tsubdir/links\n"); +} +#[cfg(not(target_os = "macos"))] +fn _du_soft_link(s: String) { + assert_eq!(s, "16\tsubdir/links\n"); +} + +#[test] +fn test_du_hard_link() { + let ts = TestScenario::new("du"); + + let link = ts.cmd("ln").arg(SUB_FILE).arg(SUB_LINK).run(); + assert!(link.success); + + let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); + assert!(result.success); + assert_eq!(result.stderr, ""); + // We do not double count hard links as the inodes are identical + _du_hard_link(result.stdout); +} + +#[cfg(target_os = "macos")] +fn _du_hard_link(s: String) { + assert_eq!(s, "12\tsubdir/links\n") +} +#[cfg(not(target_os = "macos"))] +fn _du_hard_link(s: String) { + assert_eq!(s, "16\tsubdir/links\n"); +} + +#[test] +fn test_du_d_flag() { + let ts = TestScenario::new("du"); + + let result = ts.ucmd().arg("-d").arg("1").run(); + assert!(result.success); + assert_eq!(result.stderr, ""); + _du_d_flag(result.stdout); +} + +#[cfg(target_os = "macos")] +fn _du_d_flag(s: String) { + assert_eq!(s, "16\t./subdir\n20\t./\n"); +} +#[cfg(not(target_os = "macos"))] +fn _du_d_flag(s: String) { + assert_eq!(s, "28\t./subdir\n36\t./\n"); +} diff --git a/coreutils/tests/test_echo.rs b/coreutils/tests/test_echo.rs new file mode 100644 index 000000000..2a29e4377 --- /dev/null +++ b/coreutils/tests/test_echo.rs @@ -0,0 +1,114 @@ +use common::util::*; + + +#[test] +fn test_default() { + //CmdResult.stdout_only(...) trims trailing newlines + assert_eq!("hi\n", new_ucmd!().arg("hi").succeeds().no_stderr().stdout); +} + +#[test] +fn test_no_trailing_newline() { + //CmdResult.stdout_only(...) trims trailing newlines + assert_eq!("hi", new_ucmd!().arg("-n").arg("hi").succeeds().no_stderr().stdout); +} + +#[test] +fn test_escape_alert() { + new_ucmd!().args(&["-e", "\\a"]).succeeds().stdout_only("\x07\n"); +} + +#[test] +fn test_escape_backslash() { + new_ucmd!().args(&["-e", "\\\\"]).succeeds().stdout_only("\\\n"); +} + +#[test] +fn test_escape_backspace() { + new_ucmd!().args(&["-e", "\\b"]).succeeds().stdout_only("\x08\n"); +} + +#[test] +fn test_escape_carriage_return() { + new_ucmd!().args(&["-e", "\\r"]).succeeds().stdout_only("\r\n"); +} + +#[test] +fn test_escape_escape() { + new_ucmd!().args(&["-e", "\\e"]).succeeds().stdout_only("\x1B\n"); +} + +#[test] +fn test_escape_form_feed() { + new_ucmd!().args(&["-e", "\\f"]).succeeds().stdout_only("\x0C\n"); +} + +#[test] +fn test_escape_hex() { + new_ucmd!().args(&["-e", "\\x41"]).succeeds().stdout_only("A\n"); +} + +#[test] +fn test_escape_short_hex() { + new_ucmd!().args(&["-e", "foo\\xa bar"]).succeeds().stdout_only("foo\n bar\n"); +} + +#[test] +fn test_escape_no_hex() { + new_ucmd!().args(&["-e", "foo\\x bar"]).succeeds().stdout_only("foo\\x bar\n"); +} + +#[test] +fn test_escape_one_slash() { + new_ucmd!().args(&["-e", "foo\\ bar"]).succeeds().stdout_only("foo\\ bar\n"); +} + +#[test] +fn test_escape_one_slash_multi() { + new_ucmd!().args(&["-e", "foo\\", "bar"]).succeeds().stdout_only("foo\\ bar\n"); +} + +#[test] +fn test_escape_newline() { + new_ucmd!().args(&["-e", "\\na"]).succeeds().stdout_only("\na\n"); +} + +#[test] +fn test_escape_no_further_output() { + new_ucmd!().args(&["-e", "a\\cb", "c"]).succeeds().stdout_only("a\n"); +} + +#[test] +fn test_escape_octal() { + new_ucmd!().args(&["-e", "\\0100"]).succeeds().stdout_only("@\n"); +} + +#[test] +fn test_escape_short_octal() { + new_ucmd!().args(&["-e", "foo\\040bar"]).succeeds().stdout_only("foo bar\n"); +} + +#[test] +fn test_escape_no_octal() { + new_ucmd!().args(&["-e", "foo\\0 bar"]).succeeds().stdout_only("foo\\0 bar\n"); +} + +#[test] +fn test_escape_tab() { + new_ucmd!().args(&["-e", "\\t"]).succeeds().stdout_only("\t\n"); +} + +#[test] +fn test_escape_vertical_tab() { + new_ucmd!().args(&["-e", "\\v"]).succeeds().stdout_only("\x0B\n"); +} + +#[test] +fn test_disable_escapes() { + let input_str = "\\a \\\\ \\b \\r \\e \\f \\x41 \\n a\\cb \\u0100 \\t \\v"; + new_ucmd!() + .arg("-E") + .arg(input_str) + .succeeds() + .stdout_only(format!("{}\n", input_str)); +} diff --git a/coreutils/tests/test_env.rs b/coreutils/tests/test_env.rs new file mode 100644 index 000000000..acba5a48c --- /dev/null +++ b/coreutils/tests/test_env.rs @@ -0,0 +1,136 @@ +use common::util::*; + +#[test] +fn test_env_help() { + assert!(new_ucmd!().arg("--help").succeeds().no_stderr().stdout.contains("OPTIONS:")); +} + +#[test] +fn test_env_version() { + assert!(new_ucmd!().arg("--version").succeeds().no_stderr().stdout.contains(util_name!())); +} + +#[test] +fn test_echo() { + // assert!(new_ucmd!().arg("printf").arg("FOO-bar").succeeds().no_stderr().stdout.contains("FOO-bar")); + let mut cmd = new_ucmd!(); + cmd.arg("echo").arg("FOO-bar"); + println!("cmd={:?}", cmd); + + let result = cmd.run(); + println!("success={:?}", result.success); + println!("stdout={:?}", result.stdout); + println!("stderr={:?}", result.stderr); + assert!(result.success); + + let out = result.stdout.trim_end(); + + assert_eq!(out, "FOO-bar"); +} + +#[test] +fn test_file_option() { + let out = new_ucmd!() + .arg("-f").arg("vars.conf.txt") + .run().stdout; + + assert_eq!(out.lines().filter(|&line| line == "FOO=bar" || line == "BAR=bamf this").count(), 2); +} + +#[test] +fn test_combined_file_set() { + let out = new_ucmd!() + .arg("-f").arg("vars.conf.txt") + .arg("FOO=bar.alt") + .run().stdout; + + assert_eq!(out.lines().filter(|&line| line == "FOO=bar.alt").count(), 1); +} + +#[test] +fn test_combined_file_set_unset() { + let out = new_ucmd!() + .arg("-u").arg("BAR") + .arg("-f").arg("vars.conf.txt") + .arg("FOO=bar.alt") + .run().stdout; + + assert_eq!(out.lines().filter(|&line| line == "FOO=bar.alt" || line.starts_with("BAR=")).count(), 1); +} + +#[test] +fn test_single_name_value_pair() { + let out = new_ucmd!() + .arg("FOO=bar").run().stdout; + + assert!(out.lines().any(|line| line == "FOO=bar")); +} + +#[test] +fn test_multiple_name_value_pairs() { + let out = new_ucmd!() + .arg("FOO=bar") + .arg("ABC=xyz") + .run() + .stdout; + + assert_eq!(out.lines().filter(|&line| line == "FOO=bar" || line == "ABC=xyz").count(), + 2); +} + +#[test] +fn test_ignore_environment() { + let scene = TestScenario::new(util_name!()); + + let out = scene.ucmd() + .arg("-i") + .run() + .stdout; + + assert_eq!(out, ""); + + let out = scene.ucmd() + .arg("-") + .run() + .stdout; + + assert_eq!(out, ""); +} + +#[test] +fn test_null_delimiter() { + let out = new_ucmd!() + .arg("-i") + .arg("--null") + .arg("FOO=bar") + .arg("ABC=xyz") + .run() + .stdout; + + let mut vars : Vec<_> = out.split('\0').collect(); + assert_eq!(vars.len(), 3); + vars.sort(); + assert_eq!(vars[0], ""); + assert_eq!(vars[1], "ABC=xyz"); + assert_eq!(vars[2], "FOO=bar"); +} + +#[test] +fn test_unset_variable() { + // This test depends on the HOME variable being pre-defined by the + // default shell + let out = TestScenario::new(util_name!()) + .ucmd_keepenv() + .arg("-u") + .arg("HOME") + .run() + .stdout; + + assert_eq!(out.lines().any(|line| line.starts_with("HOME=")), false); +} + +#[test] +fn test_fail_null_with_program() { + let out = new_ucmd!().arg("--null").arg("cd").fails().stderr; + assert!(out.contains("cannot specify --null (-0) with command")); +} diff --git a/coreutils/tests/test_expr.rs b/coreutils/tests/test_expr.rs new file mode 100644 index 000000000..eae4274c9 --- /dev/null +++ b/coreutils/tests/test_expr.rs @@ -0,0 +1,43 @@ +use common::util::*; + + +#[test] +fn test_simple_arithmetic() { + new_ucmd!().args(&["1", "+", "1"]).run().stdout_is("2\n"); + + new_ucmd!().args(&["1", "-", "1"]).run().stdout_is("0\n"); + + new_ucmd!().args(&["3", "*", "2"]).run().stdout_is("6\n"); + + new_ucmd!().args(&["4", "/", "2"]).run().stdout_is("2\n"); +} + +#[test] +fn test_complex_arithmetic() { + let run = new_ucmd!().args(&["9223372036854775807", "+", "9223372036854775807"]).run(); + run.stdout_is(""); + run.stderr_is("expr: error: +: Numerical result out of range"); + + let run = new_ucmd!().args(&["9", "/", "0"]).run(); + run.stdout_is(""); + run.stderr_is("expr: error: division by zero"); +} + +#[test] +fn test_parenthesis() { + new_ucmd!().args(&["(", "1", "+", "1", ")", "*", "2"]).run().stdout_is("4\n"); +} + +#[test] +fn test_or() { + new_ucmd!().args(&["0", "|", "foo"]).run().stdout_is("foo\n"); + + new_ucmd!().args(&["foo", "|", "bar"]).run().stdout_is("foo\n"); +} + +#[test] +fn test_and() { + new_ucmd!().args(&["foo", "&", "1"]).run().stdout_is("foo\n"); + + new_ucmd!().args(&["", "&", "1"]).run().stdout_is("0\n"); +} diff --git a/coreutils/tests/test_factor.rs b/coreutils/tests/test_factor.rs new file mode 100644 index 000000000..3b4e14af4 --- /dev/null +++ b/coreutils/tests/test_factor.rs @@ -0,0 +1,1049 @@ +// +// This file is part of the uutils coreutils package. +// +// (c) kwantam +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +use common::util::*; + +#[path="../src/factor/sieve.rs"] +mod sieve; +use self::sieve::Sieve; + +extern crate rand; +use self::rand::{rngs::SmallRng, Rng, FromEntropy}; +use self::rand::distributions::{Distribution, Uniform}; + +const NUM_PRIMES: usize = 10000; +const LOG_PRIMES: f64 = 14.0; // ceil(log2(NUM_PRIMES)) + +const NUM_TESTS: usize = 100; + + +#[test] +fn test_random() { + let primes = Sieve::primes().take(NUM_PRIMES).collect::>(); + + let mut rng = SmallRng::from_entropy(); + let mut rand_gt = move |min: u64| { + let mut product = 1u64; + let mut factors = Vec::new(); + while product < min { + // log distribution---higher probability for lower numbers + let factor; + loop { + let next = rng.gen_range(0f64, LOG_PRIMES).exp2().floor() as usize; + if next < NUM_PRIMES { + factor = primes[next]; + break; + } + } + let factor = factor; + + match product.checked_mul(factor) { + Some(p) => { + product = p; + factors.push(factor); + } + None => break, + }; + } + + factors.sort(); + (product, factors) + }; + + // build an input and expected output string from factor + let mut instring = String::new(); + let mut outstring = String::new(); + for _ in 0..NUM_TESTS { + let (product, factors) = rand_gt(1 << 63); + instring.push_str(&(format!("{} ", product))[..]); + + outstring.push_str(&(format!("{}:", product))[..]); + for factor in factors { + outstring.push_str(&(format!(" {}", factor))[..]); + } + outstring.push_str("\n"); + } + + run(instring.as_bytes(), outstring.as_bytes()); +} + +#[test] +fn test_random_big() { + let mut rng = SmallRng::from_entropy(); + let bitrange_1 = Uniform::new(14usize, 51); + let mut rand_64 = move || { + // first, choose a random number of bits for the first factor + let f_bit_1 = bitrange_1.sample(&mut rng); + // how many more bits do we need? + let rem = 64 - f_bit_1; + + // we will have a number of additional factors equal to nfacts + 1 + // where nfacts is in [0, floor(rem/14) ) NOTE half-open interval + // Each prime factor is at least 14 bits, hence floor(rem/14) + let nfacts = Uniform::new(0usize, rem / 14).sample(&mut rng); + // we have to distribute extrabits among the (nfacts + 1) values + let extrabits = rem - (nfacts + 1) * 14; + // (remember, a Range is a half-open interval) + let extrarange = Uniform::new(0usize, extrabits + 1); + + // to generate an even split of this range, generate n-1 random elements + // in the range, add the desired total value to the end, sort this list, + // and then compute the sequential differences. + let mut f_bits = Vec::new(); + for _ in 0..nfacts { + f_bits.push(extrarange.sample(&mut rng)); + } + f_bits.push(extrabits); + f_bits.sort(); + + // compute sequential differences here. We leave off the +14 bits + // so we can just index PRIMES_BY_BITS + let mut f_bits = f_bits.iter() + .scan(0, |st, &x| { + let ret = x - *st; // + 14 would give actual number of bits + *st = x; + Some(ret) + }) + .collect::>(); + // finally, add f_bit_1 in there + f_bits.push(f_bit_1 - 14); // index of f_bit_1 in PRIMES_BY_BITS + let f_bits = f_bits; + + let mut nbits = 0; + let mut product = 1u64; + let mut factors = Vec::new(); + for bit in f_bits { + assert!(bit < 37); + nbits += 14 + bit; + let elm = Uniform::new(0, PRIMES_BY_BITS[bit].len()).sample(&mut rng); + let factor = PRIMES_BY_BITS[bit][elm]; + factors.push(factor); + product *= factor; + } + assert_eq!(nbits, 64); + + factors.sort(); + (product, factors) + }; + + let mut instring = String::new(); + let mut outstring = String::new(); + for _ in 0..NUM_TESTS { + let (product, factors) = rand_64(); + instring.push_str(&(format!("{} ", product))[..]); + + outstring.push_str(&(format!("{}:", product))[..]); + for factor in factors { + outstring.push_str(&(format!(" {}", factor))[..]); + } + outstring.push_str("\n"); + } + + run(instring.as_bytes(), outstring.as_bytes()); +} + +#[test] +fn test_big_primes() { + let mut instring = String::new(); + let mut outstring = String::new(); + for prime in PRIMES64 { + instring.push_str(&(format!("{} ", prime))[..]); + outstring.push_str(&(format!("{0}: {0}\n", prime))[..]); + } + + run(instring.as_bytes(), outstring.as_bytes()); +} + +fn run(instring: &[u8], outstring: &[u8]) { + // now run factor + new_ucmd!().pipe_in(instring).run().stdout_is(String::from_utf8(outstring.to_owned()).unwrap()); +} + +const PRIMES_BY_BITS: &'static [&'static [u64]] = &[PRIMES14, PRIMES15, PRIMES16, PRIMES17, + PRIMES18, PRIMES19, PRIMES20, PRIMES21, + PRIMES22, PRIMES23, PRIMES24, PRIMES25, + PRIMES26, PRIMES27, PRIMES28, PRIMES29, + PRIMES30, PRIMES31, PRIMES32, PRIMES33, + PRIMES34, PRIMES35, PRIMES36, PRIMES37, + PRIMES38, PRIMES39, PRIMES40, PRIMES41, + PRIMES42, PRIMES43, PRIMES44, PRIMES45, + PRIMES46, PRIMES47, PRIMES48, PRIMES49, + PRIMES50]; + +const PRIMES64: &'static [u64] = &[18446744073709551557, + 18446744073709551533, + 18446744073709551521, + 18446744073709551437, + 18446744073709551427, + 18446744073709551359, + 18446744073709551337, + 18446744073709551293, + 18446744073709551263, + 18446744073709551253, + 18446744073709551191, + 18446744073709551163, + 18446744073709551113, + 18446744073709550873, + 18446744073709550791, + 18446744073709550773, + 18446744073709550771, + 18446744073709550719, + 18446744073709550717, + 18446744073709550681, + 18446744073709550671, + 18446744073709550593, + 18446744073709550591, + 18446744073709550539, + 18446744073709550537, + 18446744073709550381, + 18446744073709550341, + 18446744073709550293, + 18446744073709550237, + 18446744073709550147, + 18446744073709550141, + 18446744073709550129, + 18446744073709550111, + 18446744073709550099, + 18446744073709550047, + 18446744073709550033, + 18446744073709550009, + 18446744073709549951, + 18446744073709549861, + 18446744073709549817, + 18446744073709549811, + 18446744073709549777, + 18446744073709549757, + 18446744073709549733, + 18446744073709549667, + 18446744073709549621, + 18446744073709549613, + 18446744073709549583, + 18446744073709549571]; + +const PRIMES14: &'static [u64] = &[16381, 16369, 16363, 16361, 16349, 16339, 16333, 16319, 16301, + 16273, 16267, 16253, 16249, 16231, 16229, 16223, 16217, 16193, + 16189, 16187, 16183, 16141, 16139, 16127, 16111, 16103, 16097, + 16091, 16087, 16073, 16069, 16067, 16063, 16061, 16057, 16033, + 16007, 16001, 15991, 15973, 15971, 15959, 15937, 15923, 15919, + 15913, 15907, 15901, 15889, 15887, 15881, 15877, 15859, 15823, + 15817, 15809, 15803, 15797, 15791, 15787, 15773, 15767, 15761, + 15749, 15739, 15737, 15733, 15731, 15727, 15683, 15679, 15671, + 15667, 15661, 15649, 15647, 15643, 15641, 15629, 15619, 15607, + 15601, 15583, 15581, 15569, 15559, 15551, 15541, 15527, 15511, + 15497, 15493, 15473, 15467, 15461, 15451, 15443, 15439, 15427, + 15413, 15401, 15391, 15383, 15377, 15373]; + +const PRIMES15: &'static [u64] = &[32749, 32719, 32717, 32713, 32707, 32693, 32687, 32653, 32647, + 32633, 32621, 32611, 32609, 32603, 32587, 32579, 32573, 32569, + 32563, 32561, 32537, 32533, 32531, 32507, 32503, 32497, 32491, + 32479, 32467, 32443, 32441, 32429, 32423, 32413, 32411, 32401, + 32381, 32377, 32371, 32369, 32363, 32359, 32353, 32341, 32327, + 32323, 32321, 32309, 32303, 32299, 32297, 32261, 32257, 32251, + 32237, 32233, 32213, 32203, 32191, 32189, 32183, 32173, 32159, + 32143, 32141, 32119, 32117, 32099, 32089, 32083, 32077, 32069, + 32063, 32059, 32057, 32051, 32029, 32027, 32009, 32003, 31991, + 31981, 31973, 31963, 31957, 31907, 31891, 31883, 31873, 31859, + 31849, 31847, 31817, 31799, 31793, 31771, 31769, 31751]; + +const PRIMES16: &'static [u64] = &[65521, 65519, 65497, 65479, 65449, 65447, 65437, 65423, 65419, + 65413, 65407, 65393, 65381, 65371, 65357, 65353, 65327, 65323, + 65309, 65293, 65287, 65269, 65267, 65257, 65239, 65213, 65203, + 65183, 65179, 65173, 65171, 65167, 65147, 65141, 65129, 65123, + 65119, 65111, 65101, 65099, 65089, 65071, 65063, 65053, 65033, + 65029, 65027, 65011, 65003, 64997, 64969, 64951, 64937, 64927, + 64921, 64919, 64901, 64891, 64879, 64877, 64871, 64853, 64849, + 64817, 64811, 64793, 64783, 64781, 64763, 64747, 64717, 64709, + 64693, 64679, 64667, 64663, 64661, 64633, 64627, 64621, 64613, + 64609, 64601, 64591, 64579, 64577, 64567, 64553]; + +const PRIMES17: &'static [u64] = &[131071, 131063, 131059, 131041, 131023, 131011, 131009, 130987, + 130981, 130973, 130969, 130957, 130927, 130873, 130859, 130843, + 130841, 130829, 130817, 130811, 130807, 130787, 130783, 130769, + 130729, 130699, 130693, 130687, 130681, 130657, 130651, 130649, + 130643, 130639, 130633, 130631, 130621, 130619, 130589, 130579, + 130553, 130547, 130531, 130523, 130517, 130513, 130489, 130483, + 130477, 130469, 130457, 130447, 130439, 130423, 130411, 130409, + 130399, 130379, 130369, 130367, 130363, 130349, 130343, 130337, + 130307, 130303, 130279, 130267, 130261, 130259, 130253, 130241, + 130223, 130211, 130201, 130199, 130183, 130171, 130147, 130127, + 130121, 130099, 130087, 130079, 130073, 130069, 130057, 130051]; + +const PRIMES18: &'static [u64] = &[262139, 262133, 262127, 262121, 262111, 262109, 262103, 262079, + 262069, 262051, 262049, 262027, 262007, 261983, 261977, 261973, + 261971, 261959, 261917, 261887, 261881, 261847, 261823, 261799, + 261791, 261787, 261773, 261761, 261757, 261739, 261721, 261713, + 261707, 261697, 261673, 261643, 261641, 261637, 261631, 261619, + 261601, 261593, 261587, 261581, 261577, 261563, 261557, 261529, + 261523, 261509, 261467, 261463, 261451, 261439, 261433, 261431, + 261427, 261407, 261389, 261379, 261353, 261347, 261337, 261329, + 261323, 261301, 261281, 261271, 261251, 261241, 261229, 261223, + 261169, 261167, 261127]; + +const PRIMES19: &'static [u64] = &[524287, 524269, 524261, 524257, 524243, 524231, 524221, 524219, + 524203, 524201, 524197, 524189, 524171, 524149, 524123, 524119, + 524113, 524099, 524087, 524081, 524071, 524063, 524057, 524053, + 524047, 523997, 523987, 523969, 523949, 523937, 523927, 523907, + 523903, 523877, 523867, 523847, 523829, 523801, 523793, 523777, + 523771, 523763, 523759, 523741, 523729, 523717, 523681, 523673, + 523669, 523667, 523657, 523639, 523637, 523631, 523603, 523597, + 523577, 523573, 523571, 523553, 523543, 523541, 523519, 523511, + 523493, 523489, 523487, 523463, 523459, 523433, 523427, 523417, + 523403, 523387, 523357, 523351, 523349, 523333, 523307, 523297]; + +const PRIMES20: &'static [u64] = &[1048573, 1048571, 1048559, 1048549, 1048517, 1048507, 1048447, + 1048433, 1048423, 1048391, 1048387, 1048367, 1048361, 1048357, + 1048343, 1048309, 1048291, 1048273, 1048261, 1048219, 1048217, + 1048213, 1048193, 1048189, 1048139, 1048129, 1048127, 1048123, + 1048063, 1048051, 1048049, 1048043, 1048027, 1048013, 1048009, + 1048007, 1047997, 1047989, 1047979, 1047971, 1047961, 1047941, + 1047929, 1047923, 1047887, 1047883, 1047881, 1047859, 1047841, + 1047833, 1047821, 1047779, 1047773, 1047763, 1047751, 1047737, + 1047721, 1047713, 1047703, 1047701, 1047691, 1047689, 1047671, + 1047667, 1047653, 1047649, 1047647, 1047589, 1047587, 1047559]; + +const PRIMES21: &'static [u64] = &[2097143, 2097133, 2097131, 2097097, 2097091, 2097083, 2097047, + 2097041, 2097031, 2097023, 2097013, 2096993, 2096987, 2096971, + 2096959, 2096957, 2096947, 2096923, 2096911, 2096909, 2096893, + 2096881, 2096873, 2096867, 2096851, 2096837, 2096807, 2096791, + 2096789, 2096777, 2096761, 2096741, 2096737, 2096713, 2096693, + 2096687, 2096681, 2096639, 2096629, 2096621, 2096599, 2096597, + 2096569, 2096539, 2096533, 2096483, 2096449, 2096431, 2096429, + 2096411, 2096407, 2096401, 2096399, 2096377, 2096357, 2096291, + 2096273, 2096261, 2096233, 2096231, 2096221, 2096209, 2096191, + 2096183, 2096147]; + +const PRIMES22: &'static [u64] = &[4194301, 4194287, 4194277, 4194271, 4194247, 4194217, 4194199, + 4194191, 4194187, 4194181, 4194173, 4194167, 4194143, 4194137, + 4194131, 4194107, 4194103, 4194023, 4194011, 4194007, 4193977, + 4193971, 4193963, 4193957, 4193939, 4193929, 4193909, 4193869, + 4193807, 4193803, 4193801, 4193789, 4193759, 4193753, 4193743, + 4193701, 4193663, 4193633, 4193573, 4193569, 4193551, 4193549, + 4193531, 4193513, 4193507, 4193459, 4193447, 4193443, 4193417, + 4193411, 4193393, 4193389, 4193381, 4193377, 4193369, 4193359, + 4193353, 4193327, 4193309, 4193303, 4193297]; + +const PRIMES23: &'static [u64] = &[8388593, 8388587, 8388581, 8388571, 8388547, 8388539, 8388473, + 8388461, 8388451, 8388449, 8388439, 8388427, 8388421, 8388409, + 8388377, 8388371, 8388319, 8388301, 8388287, 8388283, 8388277, + 8388239, 8388209, 8388187, 8388113, 8388109, 8388091, 8388071, + 8388059, 8388019, 8388013, 8387999, 8387993, 8387959, 8387957, + 8387947, 8387933, 8387921, 8387917, 8387891, 8387879, 8387867, + 8387861, 8387857, 8387839, 8387831, 8387809, 8387807, 8387741, + 8387737, 8387723, 8387707, 8387671, 8387611, 8387609, 8387591]; + +const PRIMES24: &'static [u64] = &[16777213, 16777199, 16777183, 16777153, 16777141, 16777139, + 16777127, 16777121, 16777099, 16777049, 16777027, 16776989, + 16776973, 16776971, 16776967, 16776961, 16776941, 16776937, + 16776931, 16776919, 16776901, 16776899, 16776869, 16776857, + 16776839, 16776833, 16776817, 16776763, 16776731, 16776719, + 16776713, 16776691, 16776689, 16776679, 16776659, 16776631, + 16776623, 16776619, 16776607, 16776593, 16776581, 16776547, + 16776521, 16776491, 16776481, 16776469, 16776451, 16776401, + 16776391, 16776379, 16776371, 16776367, 16776343, 16776337, + 16776317, 16776313, 16776289, 16776217, 16776211]; + +const PRIMES25: &'static [u64] = &[33554393, 33554383, 33554371, 33554347, 33554341, 33554317, + 33554291, 33554273, 33554267, 33554249, 33554239, 33554221, + 33554201, 33554167, 33554159, 33554137, 33554123, 33554093, + 33554083, 33554077, 33554051, 33554021, 33554011, 33554009, + 33553999, 33553991, 33553969, 33553967, 33553909, 33553901, + 33553879, 33553837, 33553799, 33553787, 33553771, 33553769, + 33553759, 33553747, 33553739, 33553727, 33553697, 33553693, + 33553679, 33553661, 33553657, 33553651, 33553649, 33553633, + 33553613, 33553607, 33553577, 33553549, 33553547, 33553537, + 33553519, 33553517, 33553511, 33553489, 33553463, 33553451, + 33553417]; + +const PRIMES26: &'static [u64] = &[67108859, 67108837, 67108819, 67108777, 67108763, 67108757, + 67108753, 67108747, 67108739, 67108729, 67108721, 67108709, + 67108693, 67108669, 67108667, 67108661, 67108649, 67108633, + 67108597, 67108579, 67108529, 67108511, 67108507, 67108493, + 67108471, 67108463, 67108453, 67108439, 67108387, 67108373, + 67108369, 67108351, 67108331, 67108313, 67108303, 67108289, + 67108271, 67108219, 67108207, 67108201, 67108199, 67108187, + 67108183, 67108177, 67108127, 67108109, 67108081, 67108049, + 67108039, 67108037, 67108033, 67108009, 67108007, 67108003, + 67107983, 67107977, 67107967, 67107941, 67107919, 67107913, + 67107883, 67107881, 67107871, 67107863]; + +const PRIMES27: &'static [u64] = &[134217689, 134217649, 134217617, 134217613, 134217593, + 134217541, 134217529, 134217509, 134217497, 134217493, + 134217487, 134217467, 134217439, 134217437, 134217409, + 134217403, 134217401, 134217367, 134217361, 134217353, + 134217323, 134217301, 134217277, 134217257, 134217247, + 134217221, 134217199, 134217173, 134217163, 134217157, + 134217131, 134217103, 134217089, 134217079, 134217049, + 134217047, 134217043, 134217001, 134216987, 134216947, + 134216939, 134216933, 134216911, 134216899, 134216881, + 134216869, 134216867, 134216861, 134216837, 134216827, + 134216807, 134216801, 134216791, 134216783, 134216777, + 134216759, 134216737, 134216729]; + +const PRIMES28: &'static [u64] = &[268435399, 268435367, 268435361, 268435337, 268435331, + 268435313, 268435291, 268435273, 268435243, 268435183, + 268435171, 268435157, 268435147, 268435133, 268435129, + 268435121, 268435109, 268435091, 268435067, 268435043, + 268435039, 268435033, 268435019, 268435009, 268435007, + 268434997, 268434979, 268434977, 268434961, 268434949, + 268434941, 268434937, 268434857, 268434841, 268434827, + 268434821, 268434787, 268434781, 268434779, 268434773, + 268434731, 268434721, 268434713, 268434707, 268434703, + 268434697, 268434659, 268434623, 268434619, 268434581, + 268434577, 268434563, 268434557, 268434547, 268434511, + 268434499, 268434479, 268434461]; + +const PRIMES29: &'static [u64] = &[536870909, 536870879, 536870869, 536870849, 536870839, + 536870837, 536870819, 536870813, 536870791, 536870779, + 536870767, 536870743, 536870729, 536870723, 536870717, + 536870701, 536870683, 536870657, 536870641, 536870627, + 536870611, 536870603, 536870599, 536870573, 536870569, + 536870563, 536870561, 536870513, 536870501, 536870497, + 536870473, 536870401, 536870363, 536870317, 536870303, + 536870297, 536870273, 536870267, 536870239, 536870233, + 536870219, 536870171, 536870167, 536870153, 536870123, + 536870063, 536870057, 536870041, 536870027, 536869999, + 536869951, 536869943, 536869937, 536869919, 536869901, + 536869891]; + +const PRIMES30: &'static [u64] = &[1073741789, 1073741783, 1073741741, 1073741723, 1073741719, + 1073741717, 1073741689, 1073741671, 1073741663, 1073741651, + 1073741621, 1073741567, 1073741561, 1073741527, 1073741503, + 1073741477, 1073741467, 1073741441, 1073741419, 1073741399, + 1073741387, 1073741381, 1073741371, 1073741329, 1073741311, + 1073741309, 1073741287, 1073741237, 1073741213, 1073741197, + 1073741189, 1073741173, 1073741101, 1073741077, 1073741047, + 1073740963, 1073740951, 1073740933, 1073740909, 1073740879, + 1073740853, 1073740847, 1073740819, 1073740807]; + +const PRIMES31: &'static [u64] = &[2147483647, 2147483629, 2147483587, 2147483579, 2147483563, + 2147483549, 2147483543, 2147483497, 2147483489, 2147483477, + 2147483423, 2147483399, 2147483353, 2147483323, 2147483269, + 2147483249, 2147483237, 2147483179, 2147483171, 2147483137, + 2147483123, 2147483077, 2147483069, 2147483059, 2147483053, + 2147483033, 2147483029, 2147482951, 2147482949, 2147482943, + 2147482937, 2147482921, 2147482877, 2147482873, 2147482867, + 2147482859, 2147482819, 2147482817, 2147482811, 2147482801, + 2147482763, 2147482739, 2147482697, 2147482693, 2147482681, + 2147482663, 2147482661]; + +const PRIMES32: &'static [u64] = &[4294967291, 4294967279, 4294967231, 4294967197, 4294967189, + 4294967161, 4294967143, 4294967111, 4294967087, 4294967029, + 4294966997, 4294966981, 4294966943, 4294966927, 4294966909, + 4294966877, 4294966829, 4294966813, 4294966769, 4294966667, + 4294966661, 4294966657, 4294966651, 4294966639, 4294966619, + 4294966591, 4294966583, 4294966553, 4294966477, 4294966447, + 4294966441, 4294966427, 4294966373, 4294966367, 4294966337, + 4294966297]; + +const PRIMES33: &'static [u64] = &[8589934583, 8589934567, 8589934543, 8589934513, 8589934487, + 8589934307, 8589934291, 8589934289, 8589934271, 8589934237, + 8589934211, 8589934207, 8589934201, 8589934187, 8589934151, + 8589934141, 8589934139, 8589934117, 8589934103, 8589934099, + 8589934091, 8589934069, 8589934049, 8589934027, 8589934007, + 8589933973, 8589933971, 8589933967, 8589933931, 8589933917, + 8589933907, 8589933853, 8589933827, 8589933823, 8589933787, + 8589933773, 8589933733, 8589933731, 8589933721, 8589933683, + 8589933647, 8589933641, 8589933637, 8589933631, 8589933629, + 8589933619, 8589933601, 8589933581]; + +const PRIMES34: &'static [u64] = &[17179869143, + 17179869107, + 17179869071, + 17179869053, + 17179869041, + 17179869019, + 17179868999, + 17179868977, + 17179868957, + 17179868903, + 17179868899, + 17179868887, + 17179868879, + 17179868873, + 17179868869, + 17179868861, + 17179868843, + 17179868833, + 17179868809, + 17179868807, + 17179868777, + 17179868759, + 17179868729, + 17179868711, + 17179868683, + 17179868681, + 17179868597, + 17179868549, + 17179868543, + 17179868521, + 17179868513, + 17179868479, + 17179868443, + 17179868437, + 17179868429, + 17179868383, + 17179868369, + 17179868357, + 17179868353, + 17179868351, + 17179868333, + 17179868317, + 17179868309, + 17179868297, + 17179868287, + 17179868249, + 17179868243, + 17179868183]; + +const PRIMES35: &'static [u64] = &[34359738337, + 34359738319, + 34359738307, + 34359738299, + 34359738289, + 34359738247, + 34359738227, + 34359738121, + 34359738059, + 34359738043, + 34359738011, + 34359737917, + 34359737869, + 34359737849, + 34359737837, + 34359737821, + 34359737813, + 34359737791, + 34359737777, + 34359737771, + 34359737717, + 34359737591, + 34359737567, + 34359737549, + 34359737519, + 34359737497, + 34359737479, + 34359737407, + 34359737393, + 34359737371]; + +const PRIMES36: &'static [u64] = &[68719476731, + 68719476719, + 68719476713, + 68719476671, + 68719476619, + 68719476599, + 68719476577, + 68719476563, + 68719476547, + 68719476503, + 68719476493, + 68719476479, + 68719476433, + 68719476407, + 68719476391, + 68719476389, + 68719476377, + 68719476361, + 68719476323, + 68719476307, + 68719476281, + 68719476271, + 68719476257, + 68719476247, + 68719476209, + 68719476197, + 68719476181, + 68719476169, + 68719476157, + 68719476149, + 68719476109, + 68719476053, + 68719476047, + 68719476019, + 68719475977, + 68719475947, + 68719475933, + 68719475911, + 68719475893, + 68719475879, + 68719475837, + 68719475827, + 68719475809, + 68719475791, + 68719475779, + 68719475771, + 68719475767, + 68719475731, + 68719475729]; + +const PRIMES37: &'static [u64] = &[137438953447, + 137438953441, + 137438953427, + 137438953403, + 137438953349, + 137438953331, + 137438953273, + 137438953271, + 137438953121, + 137438953097, + 137438953037, + 137438953009, + 137438952953, + 137438952901, + 137438952887, + 137438952869, + 137438952853, + 137438952731, + 137438952683, + 137438952611, + 137438952529, + 137438952503, + 137438952491]; + +const PRIMES38: &'static [u64] = &[274877906899, + 274877906857, + 274877906837, + 274877906813, + 274877906791, + 274877906759, + 274877906753, + 274877906717, + 274877906713, + 274877906687, + 274877906647, + 274877906629, + 274877906627, + 274877906573, + 274877906543, + 274877906491, + 274877906477, + 274877906473, + 274877906431, + 274877906419, + 274877906341, + 274877906333, + 274877906327, + 274877906321, + 274877906309, + 274877906267, + 274877906243, + 274877906213, + 274877906209, + 274877906203, + 274877906179, + 274877906167, + 274877906119, + 274877906063, + 274877906053, + 274877906021, + 274877905931]; + +const PRIMES39: &'static [u64] = &[549755813881, + 549755813869, + 549755813821, + 549755813797, + 549755813753, + 549755813723, + 549755813669, + 549755813657, + 549755813647, + 549755813587, + 549755813561, + 549755813513, + 549755813507, + 549755813461, + 549755813417, + 549755813401, + 549755813371, + 549755813359, + 549755813357, + 549755813351, + 549755813339, + 549755813317, + 549755813311, + 549755813281, + 549755813239, + 549755813231, + 549755813213, + 549755813207, + 549755813197, + 549755813183, + 549755813161, + 549755813149, + 549755813147, + 549755813143, + 549755813141, + 549755813059, + 549755813027, + 549755813003, + 549755812951, + 549755812937, + 549755812933, + 549755812889, + 549755812867]; + +const PRIMES40: &'static [u64] = &[1099511627689, + 1099511627609, + 1099511627581, + 1099511627573, + 1099511627563, + 1099511627491, + 1099511627483, + 1099511627477, + 1099511627387, + 1099511627339, + 1099511627321, + 1099511627309, + 1099511627297, + 1099511627293, + 1099511627261, + 1099511627213, + 1099511627191, + 1099511627177, + 1099511627173, + 1099511627143, + 1099511627089, + 1099511626987, + 1099511626949, + 1099511626937, + 1099511626793, + 1099511626781, + 1099511626771]; + +const PRIMES41: &'static [u64] = &[2199023255531, + 2199023255521, + 2199023255497, + 2199023255489, + 2199023255479, + 2199023255477, + 2199023255461, + 2199023255441, + 2199023255419, + 2199023255413, + 2199023255357, + 2199023255327, + 2199023255291, + 2199023255279, + 2199023255267, + 2199023255243, + 2199023255203, + 2199023255171, + 2199023255137, + 2199023255101, + 2199023255087, + 2199023255081, + 2199023255069, + 2199023255027, + 2199023255021, + 2199023254979, + 2199023254933, + 2199023254913, + 2199023254907, + 2199023254903, + 2199023254843, + 2199023254787, + 2199023254699, + 2199023254693, + 2199023254657, + 2199023254567]; + +const PRIMES42: &'static [u64] = &[4398046511093, + 4398046511087, + 4398046511071, + 4398046511051, + 4398046511039, + 4398046510961, + 4398046510943, + 4398046510939, + 4398046510889, + 4398046510877, + 4398046510829, + 4398046510787, + 4398046510771, + 4398046510751, + 4398046510733, + 4398046510721, + 4398046510643, + 4398046510639, + 4398046510597, + 4398046510577, + 4398046510547, + 4398046510531, + 4398046510463, + 4398046510397, + 4398046510391, + 4398046510379, + 4398046510357, + 4398046510331, + 4398046510327, + 4398046510313, + 4398046510283, + 4398046510279, + 4398046510217, + 4398046510141, + 4398046510133, + 4398046510103, + 4398046510093]; + +const PRIMES43: &'static [u64] = &[8796093022151, + 8796093022141, + 8796093022091, + 8796093022033, + 8796093021953, + 8796093021941, + 8796093021917, + 8796093021899, + 8796093021889, + 8796093021839, + 8796093021803, + 8796093021791, + 8796093021769, + 8796093021763, + 8796093021743, + 8796093021671, + 8796093021607, + 8796093021587, + 8796093021533, + 8796093021523, + 8796093021517, + 8796093021493, + 8796093021467, + 8796093021461, + 8796093021449, + 8796093021409, + 8796093021407, + 8796093021371, + 8796093021347, + 8796093021337, + 8796093021281, + 8796093021269]; + +const PRIMES44: &'static [u64] = &[17592186044399, + 17592186044299, + 17592186044297, + 17592186044287, + 17592186044273, + 17592186044267, + 17592186044129, + 17592186044089, + 17592186044057, + 17592186044039, + 17592186043987, + 17592186043921, + 17592186043889, + 17592186043877, + 17592186043841, + 17592186043829, + 17592186043819, + 17592186043813, + 17592186043807, + 17592186043741, + 17592186043693, + 17592186043667, + 17592186043631, + 17592186043591, + 17592186043577, + 17592186043547, + 17592186043483, + 17592186043451, + 17592186043433, + 17592186043409]; + +const PRIMES45: &'static [u64] = &[35184372088777, + 35184372088763, + 35184372088751, + 35184372088739, + 35184372088711, + 35184372088699, + 35184372088693, + 35184372088673, + 35184372088639, + 35184372088603, + 35184372088571, + 35184372088517, + 35184372088493, + 35184372088471, + 35184372088403, + 35184372088391, + 35184372088379, + 35184372088363, + 35184372088321, + 35184372088319, + 35184372088279, + 35184372088259, + 35184372088249, + 35184372088241, + 35184372088223, + 35184372088183, + 35184372088097, + 35184372088081, + 35184372088079, + 35184372088051, + 35184372088043, + 35184372088039, + 35184372087937, + 35184372087929, + 35184372087923, + 35184372087881, + 35184372087877, + 35184372087869]; + +const PRIMES46: &'static [u64] = &[70368744177643, + 70368744177607, + 70368744177601, + 70368744177587, + 70368744177497, + 70368744177467, + 70368744177427, + 70368744177377, + 70368744177359, + 70368744177353, + 70368744177331, + 70368744177289, + 70368744177283, + 70368744177271, + 70368744177257, + 70368744177227, + 70368744177167, + 70368744177113, + 70368744177029, + 70368744176959, + 70368744176921, + 70368744176909, + 70368744176879, + 70368744176867, + 70368744176833, + 70368744176827, + 70368744176807, + 70368744176779, + 70368744176777, + 70368744176729, + 70368744176719, + 70368744176711]; + +const PRIMES47: &'static [u64] = &[140737488355213, + 140737488355201, + 140737488355181, + 140737488355049, + 140737488355031, + 140737488354989, + 140737488354893, + 140737488354787, + 140737488354709, + 140737488354679, + 140737488354613, + 140737488354557, + 140737488354511, + 140737488354431, + 140737488354413, + 140737488354409, + 140737488354373, + 140737488354347, + 140737488354329]; + +const PRIMES48: &'static [u64] = &[281474976710597, + 281474976710591, + 281474976710567, + 281474976710563, + 281474976710509, + 281474976710491, + 281474976710467, + 281474976710423, + 281474976710413, + 281474976710399, + 281474976710339, + 281474976710327, + 281474976710287, + 281474976710197, + 281474976710143, + 281474976710131, + 281474976710129, + 281474976710107, + 281474976710089, + 281474976710087, + 281474976710029, + 281474976709987, + 281474976709891, + 281474976709859, + 281474976709831, + 281474976709757, + 281474976709741, + 281474976709711, + 281474976709649, + 281474976709637]; + +const PRIMES49: &'static [u64] = &[562949953421231, + 562949953421201, + 562949953421189, + 562949953421173, + 562949953421131, + 562949953421111, + 562949953421099, + 562949953421047, + 562949953421029, + 562949953420973, + 562949953420871, + 562949953420867, + 562949953420837, + 562949953420793, + 562949953420747, + 562949953420741, + 562949953420733, + 562949953420727, + 562949953420609, + 562949953420571, + 562949953420559, + 562949953420553, + 562949953420523, + 562949953420507, + 562949953420457, + 562949953420403, + 562949953420373, + 562949953420369, + 562949953420343, + 562949953420303, + 562949953420297]; + +const PRIMES50: &'static [u64] = &[1125899906842597, + 1125899906842589, + 1125899906842573, + 1125899906842553, + 1125899906842511, + 1125899906842507, + 1125899906842493, + 1125899906842463, + 1125899906842429, + 1125899906842391, + 1125899906842357, + 1125899906842283, + 1125899906842273, + 1125899906842247, + 1125899906842201, + 1125899906842177, + 1125899906842079, + 1125899906842033, + 1125899906842021, + 1125899906842013, + 1125899906841973, + 1125899906841971, + 1125899906841959, + 1125899906841949, + 1125899906841943, + 1125899906841917, + 1125899906841901, + 1125899906841883, + 1125899906841859, + 1125899906841811, + 1125899906841803, + 1125899906841751, + 1125899906841713, + 1125899906841673, + 1125899906841653, + 1125899906841623, + 1125899906841613]; diff --git a/coreutils/tests/test_false.rs b/coreutils/tests/test_false.rs new file mode 100644 index 000000000..037e113a5 --- /dev/null +++ b/coreutils/tests/test_false.rs @@ -0,0 +1,7 @@ +use common::util::*; + + +#[test] +fn test_exit_code() { + new_ucmd!().fails(); +} diff --git a/coreutils/tests/test_fold.rs b/coreutils/tests/test_fold.rs new file mode 100644 index 000000000..9327ccf45 --- /dev/null +++ b/coreutils/tests/test_fold.rs @@ -0,0 +1,26 @@ +use common::util::*; + + +#[test] +fn test_default_80_column_wrap() { + new_ucmd!() + .arg("lorem_ipsum.txt") + .run() + .stdout_is_fixture("lorem_ipsum_80_column.expected"); +} + +#[test] +fn test_40_column_hard_cutoff() { + new_ucmd!() + .args(&["-w", "40", "lorem_ipsum.txt"]) + .run() + .stdout_is_fixture("lorem_ipsum_40_column_hard.expected"); +} + +#[test] +fn test_40_column_word_boundary() { + new_ucmd!() + .args(&["-s", "-w", "40", "lorem_ipsum.txt"]) + .run() + .stdout_is_fixture("lorem_ipsum_40_column_word.expected"); +} diff --git a/coreutils/tests/test_hashsum.rs b/coreutils/tests/test_hashsum.rs new file mode 100644 index 000000000..85f21a358 --- /dev/null +++ b/coreutils/tests/test_hashsum.rs @@ -0,0 +1,46 @@ +macro_rules! get_hash( + ($str:expr) => ( + $str.split(' ').collect::>()[0] + ); +); + +macro_rules! test_digest { + ($($id:ident $t:ident $size:expr)*) => ($( + + mod $id { + use::common::util::*; + static DIGEST_ARG: &'static str = concat!("--", stringify!($t)); + static BITS_ARG: &'static str = concat!("--bits=", stringify!($size)); + static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected"); + + #[test] + fn test_single_file() { + let ts = TestScenario::new("hashsum"); + assert_eq!(ts.fixtures.read(EXPECTED_FILE), + get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).arg("input.txt").succeeds().no_stderr().stdout)); + } + + #[test] + fn test_stdin() { + let ts = TestScenario::new("hashsum"); + assert_eq!(ts.fixtures.read(EXPECTED_FILE), + get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).pipe_in_fixture("input.txt").succeeds().no_stderr().stdout)); + } + } + )*) +} + +test_digest! { + md5 md5 128 + sha1 sha1 160 + sha224 sha224 224 + sha256 sha256 256 + sha384 sha384 384 + sha512 sha512 512 + sha3_224 sha3 224 + sha3_256 sha3 256 + sha3_384 sha3 384 + sha3_512 sha3 512 + shake128_256 shake128 256 + shake256_512 shake256 512 +} diff --git a/coreutils/tests/test_head.rs b/coreutils/tests/test_head.rs new file mode 100644 index 000000000..d9ca4e29b --- /dev/null +++ b/coreutils/tests/test_head.rs @@ -0,0 +1,70 @@ +use common::util::*; + + +static INPUT: &'static str = "lorem_ipsum.txt"; + +#[test] +fn test_stdin_default() { + new_ucmd!() + .pipe_in_fixture(INPUT) + .run().stdout_is_fixture("lorem_ipsum_default.expected"); +} + +#[test] +fn test_stdin_1_line_obsolete() { + new_ucmd!() + .args(&["-1"]) + .pipe_in_fixture(INPUT) + .run().stdout_is_fixture("lorem_ipsum_1_line.expected"); +} + +#[test] +fn test_stdin_1_line() { + new_ucmd!() + .args(&["-n", "1"]) + .pipe_in_fixture(INPUT) + .run().stdout_is_fixture("lorem_ipsum_1_line.expected"); +} + +#[test] +fn test_stdin_5_chars() { + new_ucmd!() + .args(&["-c", "5"]) + .pipe_in_fixture(INPUT) + .run().stdout_is_fixture("lorem_ipsum_5_chars.expected"); +} + +#[test] +fn test_single_default() { + new_ucmd!() + .arg(INPUT) + .run().stdout_is_fixture("lorem_ipsum_default.expected"); +} + +#[test] +fn test_single_1_line_obsolete() { + new_ucmd!() + .args(&["-1", INPUT]) + .run().stdout_is_fixture("lorem_ipsum_1_line.expected"); +} + +#[test] +fn test_single_1_line() { + new_ucmd!() + .args(&["-n", "1", INPUT]) + .run().stdout_is_fixture("lorem_ipsum_1_line.expected"); +} + +#[test] +fn test_single_5_chars() { + new_ucmd!() + .args(&["-c", "5", INPUT]) + .run().stdout_is_fixture("lorem_ipsum_5_chars.expected"); +} + +#[test] +fn test_verbose() { + new_ucmd!() + .args(&["-v", INPUT]) + .run().stdout_is_fixture("lorem_ipsum_verbose.expected"); +} diff --git a/coreutils/tests/test_hostname.rs b/coreutils/tests/test_hostname.rs new file mode 100644 index 000000000..8df10b166 --- /dev/null +++ b/coreutils/tests/test_hostname.rs @@ -0,0 +1,12 @@ +use common::util::*; + +#[test] +fn test_hostname() { + let ls_default_res = new_ucmd!().succeeds(); + let ls_short_res = new_ucmd!().arg("-s").succeeds(); + let ls_domain_res = new_ucmd!().arg("-d").succeeds(); + + assert!(ls_default_res.stdout.len() >= ls_short_res.stdout.len()); + assert!(ls_default_res.stdout.len() >= ls_domain_res.stdout.len()); +} + diff --git a/coreutils/tests/test_install.rs b/coreutils/tests/test_install.rs new file mode 100644 index 000000000..2ce5cb407 --- /dev/null +++ b/coreutils/tests/test_install.rs @@ -0,0 +1,196 @@ +use common::util::*; +use std::os::unix::fs::PermissionsExt; + + +#[test] +fn test_install_help() { + let (_, mut ucmd) = at_and_ucmd!(); + + assert!( + ucmd.arg("--help").succeeds().no_stderr().stdout.contains("Options:")); +} + +#[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"; + + at.touch(file1); + at.touch(file2); + at.mkdir(dir); + ucmd.arg(file1).arg(file2).arg(dir).succeeds().no_stderr(); + + assert!(at.file_exists(file1)); + assert!(at.file_exists(file2)); + assert!(at.file_exists(&format!("{}/{}", dir, file1))); + assert!(at.file_exists(&format!("{}/{}", dir, file2))); +} + +#[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"; + + at.touch(file1); + at.touch(file2); + at.touch(file3); + assert!(ucmd.arg(file1).arg(file2).arg(file3) + .fails().stderr.contains("not a directory")); +} + +#[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 context_arg = "--context"; + + at.touch(file); + at.mkdir(dir); + assert!(ucmd.arg(context_arg).arg(file).arg(dir) + .fails().stderr.contains("Unimplemented")); + + assert!(!at.file_exists(&format!("{}/{}", dir, file))); +} + +#[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 directories_arg = "-d"; + + ucmd.args(&[directories_arg, component1, component2, component3]) + .succeeds().no_stderr(); + + assert!(at.dir_exists(component1)); + assert!(at.dir_exists(component2)); + assert!(at.dir_exists(component3)); +} + +#[test] +fn test_install_component_directories_failing() { + let (at, mut ucmd) = at_and_ucmd!(); + let component = "test_install_target_dir_component_d1"; + let directories_arg = "-d"; + + at.mkdir(component); + assert!(ucmd.arg(directories_arg).arg(component) + .fails().stderr.contains("File exists")); +} + +#[test] +fn test_install_mode_numeric() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_install_target_dir_dir_e"; + let file = "test_install_target_dir_file_e"; + let mode_arg = "--mode=333"; + + at.touch(file); + at.mkdir(dir); + ucmd.arg(file).arg(dir).arg(mode_arg).succeeds().no_stderr(); + + let dest_file = &format!("{}/{}", dir, file); + assert!(at.file_exists(file)); + assert!(at.file_exists(dest_file)); + let permissions = at.metadata(dest_file).permissions(); + assert_eq!(0o100333 as u32, PermissionsExt::mode(&permissions)); +} + +#[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 mode_arg = "--mode=o+wx"; + + at.touch(file); + at.mkdir(dir); + ucmd.arg(file).arg(dir).arg(mode_arg).succeeds().no_stderr(); + + let dest_file = &format!("{}/{}", dir, file); + assert!(at.file_exists(file)); + assert!(at.file_exists(dest_file)); + let permissions = at.metadata(dest_file).permissions(); + assert_eq!(0o100003 as u32, PermissionsExt::mode(&permissions)); +} + +#[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 mode_arg = "--mode=999"; + + at.touch(file); + at.mkdir(dir); + assert!(ucmd.arg(file).arg(dir).arg(mode_arg) + .fails().stderr.contains("Invalid mode string: invalid digit found in string")); + + let dest_file = &format!("{}/{}", dir, file); + assert!(at.file_exists(file)); + assert!(!at.file_exists(dest_file)); +} + +#[test] +fn test_install_mode_directories() { + let (at, mut ucmd) = at_and_ucmd!(); + let component = "test_install_target_dir_component_h"; + let directories_arg = "-d"; + let mode_arg = "--mode=333"; + + ucmd.arg(directories_arg).arg(component).arg(mode_arg).succeeds().no_stderr(); + + assert!(at.dir_exists(component)); + let permissions = at.metadata(component).permissions(); + assert_eq!(0o040333 as u32, PermissionsExt::mode(&permissions)); +} + +#[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"; + + at.touch(file1); + at.touch(file2); + ucmd.arg(file1).arg(file2).succeeds().no_stderr(); + + assert!(at.file_exists(file1)); + assert!(at.file_exists(file2)); +} + +#[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"; + + at.touch(file); + at.mkdir(dir); + ucmd.arg(file).arg(format!("{}/{}", dir, file)).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + assert!(at.file_exists(&format!("{}/{}", dir, file))); +} + +#[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"; + + at.touch(file1); + + let err = ucmd.arg(file1).arg(format!("{}/{}", dir, file2)) + .fails().stderr; + + assert!(err.contains("not a directory")) +} + diff --git a/coreutils/tests/test_join.rs b/coreutils/tests/test_join.rs new file mode 100644 index 000000000..2c1a73bad --- /dev/null +++ b/coreutils/tests/test_join.rs @@ -0,0 +1,264 @@ +use common::util::*; + + +#[test] +fn empty_files() { + new_ucmd!() + .arg("empty.txt") + .arg("empty.txt") + .succeeds().stdout_only(""); + + new_ucmd!() + .arg("empty.txt") + .arg("fields_1.txt") + .succeeds().stdout_only(""); + + new_ucmd!() + .arg("fields_1.txt") + .arg("empty.txt") + .succeeds().stdout_only(""); +} + +#[test] +fn empty_intersection() { + new_ucmd!() + .arg("fields_1.txt") + .arg("fields_2.txt") + .arg("-2") + .arg("2") + .succeeds().stdout_only(""); +} + +#[test] +fn default_arguments() { + new_ucmd!() + .arg("fields_1.txt") + .arg("fields_2.txt") + .succeeds().stdout_only_fixture("default.expected"); +} + +#[test] +fn different_fields() { + new_ucmd!() + .arg("fields_2.txt") + .arg("fields_4.txt") + .arg("-j") + .arg("2") + .succeeds().stdout_only_fixture("different_fields.expected"); + + new_ucmd!() + .arg("fields_2.txt") + .arg("fields_4.txt") + .arg("-1") + .arg("2") + .arg("-2") + .arg("2") + .succeeds().stdout_only_fixture("different_fields.expected"); +} + +#[test] +fn different_field() { + new_ucmd!() + .arg("fields_2.txt") + .arg("fields_3.txt") + .arg("-2") + .arg("2") + .succeeds().stdout_only_fixture("different_field.expected"); +} + +#[test] +fn unpaired_lines() { + new_ucmd!() + .arg("fields_2.txt") + .arg("fields_3.txt") + .arg("-a") + .arg("1") + .succeeds().stdout_only_fixture("fields_2.txt"); + + new_ucmd!() + .arg("fields_3.txt") + .arg("fields_2.txt") + .arg("-1") + .arg("2") + .arg("-a") + .arg("2") + .succeeds().stdout_only_fixture("unpaired_lines.expected"); +} + +#[test] +fn suppress_joined() { + new_ucmd!() + .arg("fields_3.txt") + .arg("fields_2.txt") + .arg("-1") + .arg("2") + .arg("-v") + .arg("2") + .succeeds().stdout_only_fixture("suppress_joined.expected"); +} + +#[test] +fn case_insensitive() { + new_ucmd!() + .arg("capitalized.txt") + .arg("fields_3.txt") + .arg("-i") + .succeeds().stdout_only_fixture("case_insensitive.expected"); +} + +#[test] +fn semicolon_separated() { + new_ucmd!() + .arg("semicolon_fields_1.txt") + .arg("semicolon_fields_2.txt") + .arg("-t") + .arg(";") + .succeeds().stdout_only_fixture("semicolon_separated.expected"); +} + +#[test] +fn new_line_separated() { + new_ucmd!() + .arg("-") + .arg("fields_2.txt") + .arg("-t") + .arg("") + .pipe_in("1 a\n1 b\n8 h\n") + .succeeds().stdout_only("1 a\n8 h\n"); +} + +#[test] +fn multitab_character() { + new_ucmd!() + .arg("semicolon_fields_1.txt") + .arg("semicolon_fields_2.txt") + .arg("-t") + .arg("э") + .fails().stderr_is("join: error: multi-character tab э"); +} + +#[test] +fn default_format() { + new_ucmd!() + .arg("fields_1.txt") + .arg("fields_2.txt") + .arg("-o") + .arg("1.1 2.2") + .succeeds().stdout_only_fixture("default.expected"); + + new_ucmd!() + .arg("fields_1.txt") + .arg("fields_2.txt") + .arg("-o") + .arg("0 2.2") + .succeeds().stdout_only_fixture("default.expected"); +} + +#[test] +fn unpaired_lines_format() { + new_ucmd!() + .arg("fields_2.txt") + .arg("fields_3.txt") + .arg("-a") + .arg("2") + .arg("-o") + .arg("1.2 1.1 2.4 2.3 2.2 0") + .succeeds().stdout_only_fixture("unpaired_lines_format.expected"); +} + +#[test] +fn autoformat() { + new_ucmd!() + .arg("fields_2.txt") + .arg("different_lengths.txt") + .arg("-o") + .arg("auto") + .succeeds().stdout_only_fixture("autoformat.expected"); + + new_ucmd!() + .arg("-") + .arg("fields_2.txt") + .arg("-o") + .arg("auto") + .pipe_in("1 x y z\n2 p") + .succeeds().stdout_only("1 x y z a\n2 p b\n"); +} + +#[test] +fn empty_format() { + new_ucmd!() + .arg("fields_1.txt") + .arg("fields_2.txt") + .arg("-o") + .arg("") + .fails().stderr_is("join: error: invalid file number in field spec: ''"); +} + +#[test] +fn empty_key() { + new_ucmd!() + .arg("fields_1.txt") + .arg("empty.txt") + .arg("-j") + .arg("2") + .arg("-a") + .arg("1") + .arg("-e") + .arg("x") + .succeeds().stdout_only_fixture("empty_key.expected"); +} + +#[test] +fn missing_format_fields() { + new_ucmd!() + .arg("fields_2.txt") + .arg("different_lengths.txt") + .arg("-o") + .arg("0 1.2 2.4") + .arg("-e") + .arg("x") + .succeeds().stdout_only_fixture("missing_format_fields.expected"); +} + +#[test] +fn wrong_line_order() { + new_ucmd!() + .arg("fields_2.txt") + .arg("fields_4.txt") + .fails().stderr_is("fields_4.txt:5: is not sorted"); +} + +#[test] +fn headers() { + new_ucmd!() + .arg("header_1.txt") + .arg("header_2.txt") + .arg("--header") + .succeeds().stdout_only_fixture("header.expected"); +} + +#[test] +fn headers_autoformat() { + new_ucmd!() + .arg("header_1.txt") + .arg("header_2.txt") + .arg("--header") + .arg("-o") + .arg("auto") + .succeeds().stdout_only_fixture("header_autoformat.expected"); +} + +#[test] +fn single_file_with_header() { + new_ucmd!() + .arg("capitalized.txt") + .arg("empty.txt") + .arg("--header") + .succeeds().stdout_is("A 1\n"); + + new_ucmd!() + .arg("empty.txt") + .arg("capitalized.txt") + .arg("--header") + .succeeds().stdout_is("A 1\n"); +} diff --git a/coreutils/tests/test_link.rs b/coreutils/tests/test_link.rs new file mode 100644 index 000000000..126c1c89a --- /dev/null +++ b/coreutils/tests/test_link.rs @@ -0,0 +1,40 @@ +use common::util::*; + + +#[test] +fn test_link_existing_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_link_existing_file"; + let link = "test_link_existing_file_link"; + + at.touch(file); + at.write(file, "foobar"); + assert!(at.file_exists(file)); + + ucmd.args(&[file, link]).succeeds().no_stderr(); + assert!(at.file_exists(file)); + assert!(at.file_exists(link)); + assert_eq!(at.read(file), at.read(link)); +} + +#[test] +fn test_link_no_circular() { + let (at, mut ucmd) = at_and_ucmd!(); + let link = "test_link_no_circular"; + + ucmd.args(&[link, link]).fails() + .stderr_is("link: error: No such file or directory (os error 2)\n"); + assert!(!at.file_exists(link)); +} + +#[test] +fn test_link_nonexistent_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_link_nonexistent_file"; + let link = "test_link_nonexistent_file_link"; + + ucmd.args(&[file, link]).fails() + .stderr_is("link: error: No such file or directory (os error 2)\n"); + assert!(!at.file_exists(file)); + assert!(!at.file_exists(link)); +} diff --git a/coreutils/tests/test_ln.rs b/coreutils/tests/test_ln.rs new file mode 100644 index 000000000..60820e09f --- /dev/null +++ b/coreutils/tests/test_ln.rs @@ -0,0 +1,382 @@ +use common::util::*; +use std::path::PathBuf; + +#[test] +fn test_symlink_existing_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_symlink_existing_file"; + let link = "test_symlink_existing_file_link"; + + at.touch(file); + + ucmd.args(&["-s", file, link]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); +} + +#[test] +fn test_symlink_dangling_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_symlink_dangling_file"; + let link = "test_symlink_dangling_file_link"; + + ucmd.args(&["-s", file, link]).succeeds().no_stderr(); + assert!(!at.file_exists(file)); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); +} + +#[test] +fn test_symlink_existing_directory() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_symlink_existing_dir"; + let link = "test_symlink_existing_dir_link"; + + at.mkdir(dir); + + ucmd.args(&["-s", dir, link]).succeeds().no_stderr(); + assert!(at.dir_exists(dir)); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), dir); +} + +#[test] +fn test_symlink_dangling_directory() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_symlink_dangling_dir"; + let link = "test_symlink_dangling_dir_link"; + + ucmd.args(&["-s", dir, link]).succeeds().no_stderr(); + assert!(!at.dir_exists(dir)); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), dir); +} + +#[test] +fn test_symlink_circular() { + let (at, mut ucmd) = at_and_ucmd!(); + let link = "test_symlink_circular"; + + ucmd.args(&["-s", link]).succeeds().no_stderr(); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), link); +} + +#[test] +fn test_symlink_dont_overwrite() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_symlink_dont_overwrite"; + let link = "test_symlink_dont_overwrite_link"; + + at.touch(file); + at.touch(link); + + ucmd.args(&["-s", file, link]).fails(); + assert!(at.file_exists(file)); + assert!(at.file_exists(link)); + assert!(!at.is_symlink(link)); +} + +#[test] +fn test_symlink_overwrite_force() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_symlink_overwrite_force_a"; + let file_b = "test_symlink_overwrite_force_b"; + let link = "test_symlink_overwrite_force_link"; + + // Create symlink + at.symlink_file(file_a, link); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file_a); + + // Force overwrite of existing symlink + ucmd.args(&["--force", "-s", file_b, link]).succeeds(); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file_b); +} + +#[test] +fn test_symlink_interactive() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let file = "test_symlink_interactive_file"; + let link = "test_symlink_interactive_file_link"; + + at.touch(file); + at.touch(link); + + scene.ucmd() + .args(&["-i", "-s", file, link]) + .pipe_in("n").succeeds().no_stderr(); + + assert!(at.file_exists(file)); + assert!(!at.is_symlink(link)); + + scene.ucmd() + .args(&["-i", "-s", file, link]) + .pipe_in("Yesh").succeeds().no_stderr(); + + assert!(at.file_exists(file)); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); +} + +#[test] +fn test_symlink_simple_backup() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_symlink_simple_backup"; + let link = "test_symlink_simple_backup_link"; + + at.touch(file); + at.symlink_file(file, link); + assert!(at.file_exists(file)); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); + + ucmd.args(&["-b", "-s", file, link]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); + + let backup = &format!("{}~", link); + assert!(at.is_symlink(backup)); + assert_eq!(at.resolve_link(backup), file); +} + +#[test] +fn test_symlink_custom_backup_suffix() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_symlink_custom_backup_suffix"; + let link = "test_symlink_custom_backup_suffix_link"; + let suffix = "super-suffix-of-the-century"; + + at.touch(file); + at.symlink_file(file, link); + assert!(at.file_exists(file)); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); + + let arg = &format!("--suffix={}", suffix); + ucmd.args(&["-b", arg, "-s", file, link]).succeeds().no_stderr(); + assert!(at.file_exists(file)); + + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); + + let backup = &format!("{}{}", link, suffix); + assert!(at.is_symlink(backup)); + assert_eq!(at.resolve_link(backup), file); +} + +#[test] +fn test_symlink_backup_numbering() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_symlink_backup_numbering"; + let link = "test_symlink_backup_numbering_link"; + + at.touch(file); + at.symlink_file(file, link); + assert!(at.file_exists(file)); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); + + ucmd.args(&["-s", "--backup=t", file, link]).succeeds().no_stderr(); + assert!(at.file_exists(file)); + + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); + + let backup = &format!("{}.~1~", link); + assert!(at.is_symlink(backup)); + assert_eq!(at.resolve_link(backup), file); +} + +#[test] +fn test_symlink_existing_backup() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_symlink_existing_backup"; + let link = "test_symlink_existing_backup_link"; + let link_backup = "test_symlink_existing_backup_link.~1~"; + let resulting_backup = "test_symlink_existing_backup_link.~2~"; + + // Create symlink and verify + at.touch(file); + at.symlink_file(file, link); + assert!(at.file_exists(file)); + assert!(at.is_symlink(link)); + assert_eq!(at.resolve_link(link), file); + + // Create backup symlink and verify + at.symlink_file(file, link_backup); + assert!(at.file_exists(file)); + assert!(at.is_symlink(link_backup)); + assert_eq!(at.resolve_link(link_backup), file); + + ucmd.args(&["-s", "--backup=nil", file, link]).succeeds().no_stderr(); + assert!(at.file_exists(file)); + + assert!(at.is_symlink(link_backup)); + assert_eq!(at.resolve_link(link_backup), file); + + assert!(at.is_symlink(resulting_backup)); + assert_eq!(at.resolve_link(resulting_backup), file); +} + +#[test] +fn test_symlink_target_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_ln_target_dir_dir"; + let file_a = "test_ln_target_dir_file_a"; + let file_b = "test_ln_target_dir_file_b"; + + at.touch(file_a); + at.touch(file_b); + at.mkdir(dir); + + ucmd.args(&["-s", "-t", dir, file_a, file_b]).succeeds().no_stderr(); + + let file_a_link = &format!("{}/{}", dir, file_a); + assert!(at.is_symlink(file_a_link)); + assert_eq!(at.resolve_link(file_a_link), file_a); + + let file_b_link = &format!("{}/{}", dir, file_b); + assert!(at.is_symlink(file_b_link)); + assert_eq!(at.resolve_link(file_b_link), file_b); +} + +#[test] +fn test_symlink_target_dir_from_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_ln_target_dir_dir"; + let from_dir = "test_ln_target_dir_from_dir"; + let filename_a = "test_ln_target_dir_file_a"; + let filename_b = "test_ln_target_dir_file_b"; + let file_a = &format!("{}/{}", from_dir, filename_a); + let file_b = &format!("{}/{}", from_dir, filename_b); + + at.mkdir(from_dir); + at.touch(file_a); + at.touch(file_b); + at.mkdir(dir); + + ucmd.args(&["-s", "-t", dir, file_a, file_b]).succeeds().no_stderr(); + + let file_a_link = &format!("{}/{}", dir, filename_a); + assert!(at.is_symlink(file_a_link)); + assert_eq!(&at.resolve_link(file_a_link), file_a); + + let file_b_link = &format!("{}/{}", dir, filename_b); + assert!(at.is_symlink(file_b_link)); + assert_eq!(&at.resolve_link(file_b_link), file_b); +} + +#[test] +fn test_symlink_overwrite_dir_fail() { + let (at, mut ucmd) = at_and_ucmd!(); + let path_a = "test_symlink_overwrite_dir_a"; + let path_b = "test_symlink_overwrite_dir_b"; + + at.touch(path_a); + at.mkdir(path_b); + + assert!(ucmd.args(&["-s", "-T", path_a, path_b]).fails().stderr.len() > 0); +} + +#[test] +fn test_symlink_errors() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_symlink_errors_dir"; + let file_a = "test_symlink_errors_file_a"; + let file_b = "test_symlink_errors_file_b"; + + at.mkdir(dir); + at.touch(file_a); + at.touch(file_b); + + // $ ln -T -t a b + // ln: cannot combine --target-directory (-t) and --no-target-directory (-T) + ucmd.args(&["-T", "-t", dir, file_a, file_b]).fails() + .stderr_is("ln: error: cannot combine --target-directory (-t) and --no-target-directory \ + (-T)\n"); +} + +#[test] +fn test_symlink_verbose() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let file_a = "test_symlink_verbose_file_a"; + let file_b = "test_symlink_verbose_file_b"; + + at.touch(file_a); + + scene.ucmd().args(&["-v", file_a, file_b]) + .succeeds().stdout_only(format!("'{}' -> '{}'\n", file_b, file_a)); + + at.touch(file_b); + + scene.ucmd().args(&["-v", "-b", file_a, file_b]) + .succeeds().stdout_only(format!("'{}' -> '{}' (backup: '{}~')\n", file_b, file_a, file_b)); +} + +#[test] +fn test_symlink_target_only() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_symlink_target_only"; + + at.mkdir(dir); + + assert!(ucmd.args(&["-s", "-t", dir]).fails().stderr.len() > 0); +} + +#[test] +fn test_symlink_implicit_target_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_symlink_implicit_target_dir"; + // On windows, slashes aren't allowed in symlink targets, so use + // PathBuf to construct `file` instead of simple "dir/file". + let filename = "test_symlink_implicit_target_file"; + let path = PathBuf::from(dir).join(filename); + let file = &path.to_string_lossy(); + + at.mkdir(dir); + at.touch(file); + + ucmd.args(&["-s", file]).succeeds().no_stderr(); + + assert!(at.file_exists(filename)); + assert!(at.is_symlink(filename)); + assert_eq!(at.resolve_link(filename), *file); +} + +#[test] +fn test_symlink_to_dir_2args() { + let (at, mut ucmd) = at_and_ucmd!(); + let filename = "test_symlink_to_dir_2args_file"; + let from_file = &format!("{}/{}", at.as_string(), filename); + let to_dir = "test_symlink_to_dir_2args_to_dir"; + let to_file = &format!("{}/{}", to_dir, filename); + + at.mkdir(to_dir); + at.touch(from_file); + + ucmd.args(&["-s", from_file, to_dir]).succeeds().no_stderr(); + + assert!(at.file_exists(to_file)); + assert!(at.is_symlink(to_file)); + assert_eq!(at.resolve_link(to_file), filename); +} + +#[test] +fn test_symlink_missing_destination() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_symlink_missing_destination"; + + at.touch(file); + + ucmd.args(&["-s", "-T", file]).fails() + .stderr_is(format!("ln: error: missing destination file operand after '{}'", file)); +} diff --git a/coreutils/tests/test_ls.rs b/coreutils/tests/test_ls.rs new file mode 100644 index 000000000..374bfbb08 --- /dev/null +++ b/coreutils/tests/test_ls.rs @@ -0,0 +1,13 @@ +use common::util::*; + + +#[test] +fn test_ls_ls() { + new_ucmd!().succeeds(); +} + +#[test] +fn test_ls_ls_i() { + new_ucmd!().arg("-i").succeeds(); + new_ucmd!().arg("-il").succeeds(); +} diff --git a/coreutils/tests/test_mkdir.rs b/coreutils/tests/test_mkdir.rs new file mode 100644 index 000000000..3e5ec4b06 --- /dev/null +++ b/coreutils/tests/test_mkdir.rs @@ -0,0 +1,59 @@ +use common::util::*; + +static TEST_DIR1: &'static str = "mkdir_test1"; +static TEST_DIR2: &'static str = "mkdir_test2"; +static TEST_DIR3: &'static str = "mkdir_test3"; +static TEST_DIR4: &'static str = "mkdir_test4/mkdir_test4_1"; +static TEST_DIR5: &'static str = "mkdir_test5/mkdir_test5_1"; +static TEST_DIR6: &'static str = "mkdir_test6"; +static TEST_FILE7: &'static str = "mkdir_test7"; + +#[test] +fn test_mkdir_mkdir() { + new_ucmd!().arg(TEST_DIR1).succeeds(); +} + +#[test] +fn test_mkdir_dup_dir() { + let scene = TestScenario::new(util_name!()); + scene.ucmd().arg(TEST_DIR2).succeeds(); + scene.ucmd().arg(TEST_DIR2).fails(); +} + +#[test] +fn test_mkdir_mode() { + new_ucmd!() + .arg("-m") + .arg("755") + .arg(TEST_DIR3) + .succeeds(); +} + +#[test] +fn test_mkdir_parent() { + let scene = TestScenario::new(util_name!()); + scene.ucmd().arg("-p").arg(TEST_DIR4).succeeds(); + scene.ucmd().arg("-p").arg(TEST_DIR4).succeeds(); +} + +#[test] +fn test_mkdir_no_parent() { + new_ucmd!().arg(TEST_DIR5).fails(); +} + +#[test] +fn test_mkdir_dup_dir_parent() { + let scene = TestScenario::new(util_name!()); + scene.ucmd().arg(TEST_DIR6).succeeds(); + scene.ucmd().arg("-p").arg(TEST_DIR6).succeeds(); +} + +#[test] +fn test_mkdir_dup_file() { + let scene = TestScenario::new(util_name!()); + scene.fixtures.touch(TEST_FILE7); + scene.ucmd().arg(TEST_FILE7).fails(); + + // mkdir should fail for a file even if -p is specified. + scene.ucmd().arg("-p").arg(TEST_FILE7).fails(); +} diff --git a/coreutils/tests/test_mktemp.rs b/coreutils/tests/test_mktemp.rs new file mode 100644 index 000000000..255af89ca --- /dev/null +++ b/coreutils/tests/test_mktemp.rs @@ -0,0 +1,110 @@ +use common::util::*; +extern crate tempdir; +use self::tempdir::TempDir; + + +static TEST_TEMPLATE1: &'static str = "tempXXXXXX"; +static TEST_TEMPLATE2: &'static str = "temp"; +static TEST_TEMPLATE3: &'static str = "tempX"; +static TEST_TEMPLATE4: &'static str = "tempXX"; +static TEST_TEMPLATE5: &'static str = "tempXXX"; +static TEST_TEMPLATE6: &'static str = "tempXXXlate"; +static TEST_TEMPLATE7: &'static str = "XXXtemplate"; +#[cfg(unix)] +static TEST_TEMPLATE8: &'static str = "tempXXXla/te"; +#[cfg(windows)] +static TEST_TEMPLATE8: &'static str = "tempXXXla\\te"; + +const TMPDIR: &'static str = "TMPDIR"; + +#[test] +fn test_mktemp_mktemp() { + let scene = TestScenario::new(util_name!()); + + let pathname = scene.fixtures.as_string(); + + scene.ucmd().env(TMPDIR, &pathname).arg(TEST_TEMPLATE1).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg(TEST_TEMPLATE2).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg(TEST_TEMPLATE3).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg(TEST_TEMPLATE4).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg(TEST_TEMPLATE5).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg(TEST_TEMPLATE6).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg(TEST_TEMPLATE7).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg(TEST_TEMPLATE8).fails(); +} + +#[test] +fn test_mktemp_make_temp_dir() { + let scene = TestScenario::new(util_name!()); + + let pathname = scene.fixtures.as_string(); + + scene.ucmd().env(TMPDIR, &pathname).arg("-d").arg(TEST_TEMPLATE1).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg("-d").arg(TEST_TEMPLATE2).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("-d").arg(TEST_TEMPLATE3).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("-d").arg(TEST_TEMPLATE4).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("-d").arg(TEST_TEMPLATE5).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg("-d").arg(TEST_TEMPLATE6).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg("-d").arg(TEST_TEMPLATE7).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg("-d").arg(TEST_TEMPLATE8).fails(); +} + +#[test] +fn test_mktemp_dry_run() { + let scene = TestScenario::new(util_name!()); + + let pathname = scene.fixtures.as_string(); + + scene.ucmd().env(TMPDIR, &pathname).arg("-u").arg(TEST_TEMPLATE1).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg("-u").arg(TEST_TEMPLATE2).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("-u").arg(TEST_TEMPLATE3).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("-u").arg(TEST_TEMPLATE4).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("-u").arg(TEST_TEMPLATE5).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg("-u").arg(TEST_TEMPLATE6).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg("-u").arg(TEST_TEMPLATE7).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg("-u").arg(TEST_TEMPLATE8).fails(); + +} + +#[test] +fn test_mktemp_quiet() { + let scene = TestScenario::new(util_name!()); + + scene.ucmd().arg("-p").arg("/definitely/not/exist/I/promise").arg("-q") + .fails().no_stdout().no_stderr(); + scene.ucmd().arg("-d").arg("-p").arg("/definitely/not/exist/I/promise").arg("-q") + .fails().no_stdout().no_stderr(); +} + +#[test] +fn test_mktemp_suffix() { + let scene = TestScenario::new(util_name!()); + + let pathname = scene.fixtures.as_string(); + + scene.ucmd().env(TMPDIR, &pathname).arg("--suffix").arg("suf").arg(TEST_TEMPLATE1).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg("--suffix").arg("suf").arg(TEST_TEMPLATE2).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("--suffix").arg("suf").arg(TEST_TEMPLATE3).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("--suffix").arg("suf").arg(TEST_TEMPLATE4).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("--suffix").arg("suf").arg(TEST_TEMPLATE5).succeeds(); + scene.ucmd().env(TMPDIR, &pathname).arg("--suffix").arg("suf").arg(TEST_TEMPLATE6).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("--suffix").arg("suf").arg(TEST_TEMPLATE7).fails(); + scene.ucmd().env(TMPDIR, &pathname).arg("--suffix").arg("suf").arg(TEST_TEMPLATE8).fails(); +} + +#[test] +fn test_mktemp_tmpdir() { + let scene = TestScenario::new(util_name!()); + + let path = TempDir::new_in(scene.fixtures.as_string(), util_name!()).unwrap(); + let pathname = path.path().as_os_str(); + + scene.ucmd().arg("-p").arg(pathname).arg(TEST_TEMPLATE1).succeeds(); + scene.ucmd().arg("-p").arg(pathname).arg(TEST_TEMPLATE2).fails(); + scene.ucmd().arg("-p").arg(pathname).arg(TEST_TEMPLATE3).fails(); + scene.ucmd().arg("-p").arg(pathname).arg(TEST_TEMPLATE4).fails(); + scene.ucmd().arg("-p").arg(pathname).arg(TEST_TEMPLATE5).succeeds(); + scene.ucmd().arg("-p").arg(pathname).arg(TEST_TEMPLATE6).succeeds(); + scene.ucmd().arg("-p").arg(pathname).arg(TEST_TEMPLATE7).succeeds(); + scene.ucmd().arg("-p").arg(pathname).arg(TEST_TEMPLATE8).fails(); +} diff --git a/coreutils/tests/test_mv.rs b/coreutils/tests/test_mv.rs new file mode 100644 index 000000000..cccaa7e56 --- /dev/null +++ b/coreutils/tests/test_mv.rs @@ -0,0 +1,438 @@ +extern crate time; +extern crate filetime; + +use self::filetime::*; +use common::util::*; + + +#[test] +fn test_mv_rename_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir1 = "test_mv_rename_dir"; + let dir2 = "test_mv_rename_dir2"; + + at.mkdir(dir1); + + ucmd.arg(dir1).arg(dir2).succeeds().no_stderr(); + + assert!(at.dir_exists(dir2)); +} + +#[test] +fn test_mv_rename_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file1 = "test_mv_rename_file"; + let file2 = "test_mv_rename_file2"; + + at.touch(file1); + + ucmd.arg(file1).arg(file2).succeeds().no_stderr(); + assert!(at.file_exists(file2)); +} + +#[test] +fn test_mv_move_file_into_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_mv_move_file_into_dir_dir"; + let file = "test_mv_move_file_into_dir_file"; + + at.mkdir(dir); + at.touch(file); + + ucmd.arg(file).arg(dir).succeeds().no_stderr(); + + assert!(at.file_exists(&format!("{}/{}", dir, file))); +} + +#[test] +fn test_mv_move_file_between_dirs() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir1 = "test_mv_move_file_between_dirs_dir1"; + let dir2 = "test_mv_move_file_between_dirs_dir2"; + let file = "test_mv_move_file_between_dirs_file"; + + at.mkdir(dir1); + at.mkdir(dir2); + at.touch(&format!("{}/{}", dir1, file)); + + assert!(at.file_exists(&format!("{}/{}", dir1, file))); + + ucmd.arg(&format!("{}/{}", dir1, file)).arg(dir2).succeeds().no_stderr(); + + assert!(!at.file_exists(&format!("{}/{}", dir1, file))); + assert!(at.file_exists(&format!("{}/{}", dir2, file))); +} + +#[test] +fn test_mv_strip_slashes() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dir = "test_mv_strip_slashes_dir"; + let file = "test_mv_strip_slashes_file"; + let mut source = file.to_owned(); + source.push_str("/"); + + at.mkdir(dir); + at.touch(file); + + scene.ucmd().arg(&source).arg(dir).fails(); + + assert!(!at.file_exists(&format!("{}/{}", dir, file))); + + scene.ucmd().arg("--strip-trailing-slashes").arg(source).arg(dir).succeeds().no_stderr(); + + assert!(at.file_exists(&format!("{}/{}", dir, file))); +} + +#[test] +fn test_mv_multiple_files() { + let (at, mut ucmd) = at_and_ucmd!(); + let target_dir = "test_mv_multiple_files_dir"; + let file_a = "test_mv_multiple_file_a"; + let file_b = "test_mv_multiple_file_b"; + + at.mkdir(target_dir); + at.touch(file_a); + at.touch(file_b); + + ucmd.arg(file_a).arg(file_b).arg(target_dir).succeeds().no_stderr(); + + assert!(at.file_exists(&format!("{}/{}", target_dir, file_a))); + assert!(at.file_exists(&format!("{}/{}", target_dir, file_b))); +} + +#[test] +fn test_mv_multiple_folders() { + let (at, mut ucmd) = at_and_ucmd!(); + let target_dir = "test_mv_multiple_dirs_dir"; + let dir_a = "test_mv_multiple_dir_a"; + let dir_b = "test_mv_multiple_dir_b"; + + at.mkdir(target_dir); + at.mkdir(dir_a); + at.mkdir(dir_b); + + ucmd.arg(dir_a).arg(dir_b).arg(target_dir).succeeds().no_stderr(); + + assert!(at.dir_exists(&format!("{}/{}", target_dir, dir_a))); + assert!(at.dir_exists(&format!("{}/{}", target_dir, dir_b))); +} + +#[test] +fn test_mv_interactive() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let file_a = "test_mv_interactive_file_a"; + let file_b = "test_mv_interactive_file_b"; + + at.touch(file_a); + at.touch(file_b); + + + scene.ucmd().arg("-i").arg(file_a).arg(file_b).pipe_in("n").succeeds().no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + + + scene.ucmd().arg("-i").arg(file_a).arg(file_b).pipe_in("Yesh").succeeds().no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); +} + +#[test] +fn test_mv_no_clobber() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_no_clobber_file_a"; + let file_b = "test_mv_no_clobber_file_b"; + + at.touch(file_a); + at.touch(file_b); + + ucmd.arg("-n").arg(file_a).arg(file_b).succeeds().no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); +} + +#[test] +fn test_mv_replace_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_replace_file_a"; + let file_b = "test_mv_replace_file_b"; + + at.touch(file_a); + at.touch(file_b); + + ucmd.arg(file_a).arg(file_b).succeeds().no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); +} + +#[test] +fn test_mv_force_replace_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_force_replace_file_a"; + let file_b = "test_mv_force_replace_file_b"; + + at.touch(file_a); + at.touch(file_b); + + ucmd.arg("--force").arg(file_a).arg(file_b).succeeds().no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); +} + +#[test] +fn test_mv_simple_backup() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_simple_backup_file_a"; + let file_b = "test_mv_simple_backup_file_b"; + + at.touch(file_a); + at.touch(file_b); + ucmd.arg("-b").arg(file_a).arg(file_b).succeeds().no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_mv_custom_backup_suffix() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_custom_backup_suffix_file_a"; + let file_b = "test_mv_custom_backup_suffix_file_b"; + let suffix = "super-suffix-of-the-century"; + + at.touch(file_a); + at.touch(file_b); + ucmd.arg("-b") + .arg(format!("--suffix={}", suffix)) + .arg(file_a) + .arg(file_b) + .succeeds().no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}{}", file_b, suffix))); +} + +#[test] +fn test_mv_custom_backup_suffix_via_env() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_custom_backup_suffix_file_a"; + let file_b = "test_mv_custom_backup_suffix_file_b"; + let suffix = "super-suffix-of-the-century"; + at.touch(file_a); + at.touch(file_b); + ucmd.arg("-b") + .env("SIMPLE_BACKUP_SUFFIX", suffix) + .arg(file_a) + .arg(file_b) + .succeeds().no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}{}", file_b, suffix))); +} + +#[test] +fn test_mv_backup_numbering() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_backup_numbering_file_a"; + let file_b = "test_mv_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + ucmd.arg("--backup=t").arg(file_a).arg(file_b).succeeds().no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}.~1~", file_b))); +} + +#[test] +fn test_mv_existing_backup() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_existing_backup_file_a"; + let file_b = "test_mv_existing_backup_file_b"; + let file_b_backup = "test_mv_existing_backup_file_b.~1~"; + let resulting_backup = "test_mv_existing_backup_file_b.~2~"; + + at.touch(file_a); + at.touch(file_b); + at.touch(file_b_backup); + ucmd.arg("--backup=nil").arg(file_a).arg(file_b).succeeds().no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(file_b_backup)); + assert!(at.file_exists(resulting_backup)); +} + +#[test] +fn test_mv_update_option() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let file_a = "test_mv_update_option_file_a"; + let file_b = "test_mv_update_option_file_b"; + + at.touch(file_a); + at.touch(file_b); + let ts = time::now().to_timespec(); + let now = FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32); + let later = FileTime::from_unix_time(ts.sec as i64 + 3600, ts.nsec as u32); + filetime::set_file_times(at.plus_as_string(file_a), now, now).unwrap(); + filetime::set_file_times(at.plus_as_string(file_b), now, later).unwrap(); + + scene.ucmd().arg("--update").arg(file_a).arg(file_b).run(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + + scene.ucmd().arg("--update").arg(file_b).arg(file_a).succeeds().no_stderr(); + + assert!(at.file_exists(file_a)); + assert!(!at.file_exists(file_b)); +} + +#[test] +fn test_mv_target_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_mv_target_dir_dir"; + let file_a = "test_mv_target_dir_file_a"; + let file_b = "test_mv_target_dir_file_b"; + + at.touch(file_a); + at.touch(file_b); + at.mkdir(dir); + ucmd.arg("-t").arg(dir).arg(file_a).arg(file_b).succeeds().no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(!at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}/{}", dir, file_a))); + assert!(at.file_exists(&format!("{}/{}", dir, file_b))); +} + +#[test] +fn test_mv_overwrite_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir_a = "test_mv_overwrite_dir_a"; + let dir_b = "test_mv_overwrite_dir_b"; + + at.mkdir(dir_a); + at.mkdir(dir_b); + ucmd.arg("-T").arg(dir_a).arg(dir_b).succeeds().no_stderr(); + + assert!(!at.dir_exists(dir_a)); + assert!(at.dir_exists(dir_b)); +} + +#[test] +fn test_mv_overwrite_nonempty_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir_a = "test_mv_overwrite_nonempty_dir_a"; + let dir_b = "test_mv_overwrite_nonempty_dir_b"; + let dummy = "test_mv_overwrite_nonempty_dir_b/file"; + + at.mkdir(dir_a); + at.mkdir(dir_b); + at.touch(dummy); + // Not same error as GNU; the error message is a rust builtin + // TODO: test (and implement) correct error message (or at least decide whether to do so) + // Current: "mv: error: couldn't rename path (Directory not empty; from=a; to=b)" + // GNU: "mv: cannot move ‘a’ to ‘b’: Directory not empty" + + // Verbose output for the move should not be shown on failure + assert!(ucmd.arg("-vT").arg(dir_a).arg(dir_b).fails().no_stdout().stderr.len() > 0); + + assert!(at.dir_exists(dir_a)); + assert!(at.dir_exists(dir_b)); +} + +#[test] +fn test_mv_backup_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir_a = "test_mv_backup_dir_dir_a"; + let dir_b = "test_mv_backup_dir_dir_b"; + + at.mkdir(dir_a); + at.mkdir(dir_b); + ucmd.arg("-vbT").arg(dir_a).arg(dir_b).succeeds() + .stdout_only(format!("‘{}’ -> ‘{}’ (backup: ‘{}~’)\n", + dir_a, + dir_b, + dir_b)); + + assert!(!at.dir_exists(dir_a)); + assert!(at.dir_exists(dir_b)); + assert!(at.dir_exists(&format!("{}~", dir_b))); +} + +#[test] +fn test_mv_errors() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dir = "test_mv_errors_dir"; + let file_a = "test_mv_errors_file_a"; + let file_b = "test_mv_errors_file_b"; + at.mkdir(dir); + at.touch(file_a); + at.touch(file_b); + + // $ mv -T -t a b + // mv: cannot combine --target-directory (-t) and --no-target-directory (-T) + scene.ucmd().arg("-T").arg("-t").arg(dir).arg(file_a).arg(file_b).fails() + .stderr_is("mv: error: cannot combine --target-directory (-t) and --no-target-directory (-T)\n"); + + // $ at.touch file && at.mkdir dir + // $ mv -T file dir + // err == mv: cannot overwrite directory ‘dir’ with non-directory + scene.ucmd().arg("-T").arg(file_a).arg(dir).fails() + .stderr_is(format!("mv: error: cannot overwrite directory ‘{}’ with non-directory\n", + dir)); + + // $ at.mkdir dir && at.touch file + // $ mv dir file + // err == mv: cannot overwrite non-directory ‘file’ with directory ‘dir’ + assert!(scene.ucmd().arg(dir).arg(file_a).fails().stderr.len() > 0); +} + +#[test] +fn test_mv_verbose() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dir = "test_mv_verbose_dir"; + let file_a = "test_mv_verbose_file_a"; + let file_b = "test_mv_verbose_file_b"; + at.mkdir(dir); + at.touch(file_a); + at.touch(file_b); + + scene.ucmd().arg("-v").arg(file_a).arg(file_b).succeeds() + .stdout_only(format!("‘{}’ -> ‘{}’\n", file_a, file_b)); + + at.touch(file_a); + scene.ucmd().arg("-vb").arg(file_a).arg(file_b).succeeds() + .stdout_only(format!("‘{}’ -> ‘{}’ (backup: ‘{}~’)\n", + file_a, + file_b, + file_b)); +} + +// Todo: + +// $ at.touch a b +// $ chmod -w b +// $ ll +// total 0 +// -rw-rw-r-- 1 user user 0 okt 25 11:21 a +// -r--r--r-- 1 user user 0 okt 25 11:21 b +// $ +// $ mv -v a b +// mv: try to overwrite ‘b’, overriding mode 0444 (r--r--r--)? y +// ‘a’ -> ‘b’ diff --git a/coreutils/tests/test_nl.rs b/coreutils/tests/test_nl.rs new file mode 100644 index 000000000..782087ff3 --- /dev/null +++ b/coreutils/tests/test_nl.rs @@ -0,0 +1,53 @@ +use common::util::*; + + +#[test] +fn test_stdin_nonewline() { + new_ucmd!().pipe_in("No Newline").run().stdout_is(" 1\tNo Newline\n"); +} +#[test] +fn test_stdin_newline() { + new_ucmd!() + .args(&["-s", "-", "-w", "1"]) + .pipe_in("Line One\nLine Two\n") + .run() + .stdout_is("1-Line One\n2-Line Two\n"); +} + +#[test] +fn test_padding_without_overflow() { + new_ucmd!() + .args(&["-i", "1000", "-s", "x", "-n", "rz", "simple.txt"]).run() + .stdout_is( + "000001xL1\n001001xL2\n002001xL3\n003001xL4\n004001xL5\n005001xL6\n006001xL7\n0070\ + 01xL8\n008001xL9\n009001xL10\n010001xL11\n011001xL12\n012001xL13\n013001xL14\n014\ + 001xL15\n"); +} + +#[test] +fn test_padding_with_overflow() { + new_ucmd!() + .args(&["-i", "1000", "-s", "x", "-n", "rz", "-w", "4", "simple.txt"]).run() + .stdout_is( + "0001xL1\n1001xL2\n2001xL3\n3001xL4\n4001xL5\n5001xL6\n6001xL7\n7001xL8\n8001xL9\n\ + 9001xL10\n10001xL11\n11001xL12\n12001xL13\n13001xL14\n14001xL15\n"); +} + +#[test] +fn test_sections_and_styles() { + for &(fixture, output) in &[("section.txt", + "\nHEADER1\nHEADER2\n\n1 |BODY1\n2 \ + |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")] + { + new_ucmd!() + .args(&["-s", "|", "-n", "ln", "-w", "3", "-b", "a", "-l", "5", fixture]) + .run() + .stdout_is(output); + } +} diff --git a/coreutils/tests/test_numfmt.rs b/coreutils/tests/test_numfmt.rs new file mode 100644 index 000000000..c8b734970 --- /dev/null +++ b/coreutils/tests/test_numfmt.rs @@ -0,0 +1,147 @@ +use common::util::*; + +#[test] +fn test_from_si() { + new_ucmd!() + .args(&["--from=si"]) + .pipe_in("1000\n1.1M\n0.1G") + .run() + .stdout_is("1000\n1100000\n100000000\n"); +} + +#[test] +fn test_from_iec() { + new_ucmd!() + .args(&["--from=iec"]) + .pipe_in("1024\n1.1M\n0.1G") + .run() + .stdout_is("1024\n1153434\n107374182\n"); +} + +#[test] +fn test_from_iec_i() { + new_ucmd!() + .args(&["--from=iec-i"]) + .pipe_in("1024\n1.1Mi\n0.1Gi") + .run() + .stdout_is("1024\n1153434\n107374182\n"); +} + +#[test] +fn test_from_auto() { + new_ucmd!() + .args(&["--from=auto"]) + .pipe_in("1K\n1Ki") + .run() + .stdout_is("1000\n1024\n"); +} + +#[test] +fn test_to_si() { + new_ucmd!() + .args(&["--to=si"]) + .pipe_in("1000\n1100000\n100000000") + .run() + .stdout_is("1.0K\n1.1M\n100.0M\n"); +} + +#[test] +fn test_to_iec() { + new_ucmd!() + .args(&["--to=iec"]) + .pipe_in("1024\n1153434\n107374182") + .run() + .stdout_is("1.0K\n1.1M\n102.4M\n"); +} + +#[test] +fn test_to_iec_i() { + new_ucmd!() + .args(&["--to=iec-i"]) + .pipe_in("1024\n1153434\n107374182") + .run() + .stdout_is("1.0Ki\n1.1Mi\n102.4Mi\n"); +} + +#[test] +fn test_input_from_free_arguments() { + new_ucmd!() + .args(&["--from=si", "1K", "1.1M", "0.1G"]) + .run() + .stdout_is("1000\n1100000\n100000000\n"); +} + +#[test] +fn test_padding() { + new_ucmd!() + .args(&["--from=si", "--padding=8"]) + .pipe_in("1K\n1.1M\n0.1G") + .run() + .stdout_is(" 1000\n 1100000\n100000000\n"); +} + +#[test] +fn test_negative_padding() { + new_ucmd!() + .args(&["--from=si", "--padding=-8"]) + .pipe_in("1K\n1.1M\n0.1G") + .run() + .stdout_is("1000 \n1100000 \n100000000\n"); +} + +#[test] +fn test_header() { + new_ucmd!() + .args(&["--from=si", "--header=2"]) + .pipe_in("header\nheader2\n1K\n1.1M\n0.1G") + .run() + .stdout_is("header\nheader2\n1000\n1100000\n100000000\n"); +} + +#[test] +fn test_header_default() { + new_ucmd!() + .args(&["--from=si", "--header"]) + .pipe_in("header\n1K\n1.1M\n0.1G") + .run() + .stdout_is("header\n1000\n1100000\n100000000\n"); +} + +#[test] +fn test_negative() { + new_ucmd!() + .args(&["--from=si"]) + .pipe_in("-1000\n-1.1M\n-0.1G") + .run() + .stdout_is("-1000\n-1100000\n-100000000\n"); + new_ucmd!() + .args(&["--to=iec-i"]) + .pipe_in("-1024\n-1153434\n-107374182") + .run() + .stdout_is("-1.0Ki\n-1.1Mi\n-102.4Mi\n"); +} + +#[test] +fn test_no_op() { + new_ucmd!() + .pipe_in("1024\n1234567") + .run() + .stdout_is("1024\n1234567\n"); +} + +#[test] +fn test_normalize() { + new_ucmd!() + .args(&["--from=si", "--to=si"]) + .pipe_in("10000000K\n0.001K") + .run() + .stdout_is("10.0G\n1\n"); +} + +#[test] +fn test_si_to_iec() { + new_ucmd!() + .args(&["--from=si", "--to=iec", "15334263563K"]) + .run() + .stdout_is("13.9T\n"); +} diff --git a/coreutils/tests/test_od.rs b/coreutils/tests/test_od.rs new file mode 100644 index 000000000..b0de215e2 --- /dev/null +++ b/coreutils/tests/test_od.rs @@ -0,0 +1,662 @@ +extern crate unindent; + +use common::util::*; +use std::path::Path; +use std::env; +use std::io::Write; +use std::fs::File; +use std::fs::remove_file; +use self::unindent::*; + + +// octal dump of 'abcdefghijklmnopqrstuvwxyz\n' +static ALPHA_OUT: &'static str = " + 0000000 061141 062143 063145 064147 065151 066153 067155 070157 + 0000020 071161 072163 073165 074167 075171 000012 + 0000033 + "; + +// XXX We could do a better job of ensuring that we have a fresh temp dir to ourselves, +// not a general one full of other proc's leftovers. + +// Test that od can read one file and dump with default format +#[test] +fn test_file() { + use std::env; + let temp = env::temp_dir(); + let tmpdir = Path::new(&temp); + let file = tmpdir.join("test"); + + { + let mut f = File::create(&file).unwrap(); + if f.write_all(b"abcdefghijklmnopqrstuvwxyz\n").is_err() { + panic!("Test setup failed - could not write file"); + } + } + + let result = new_ucmd!().arg("--endian=little").arg(file.as_os_str()).run(); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(ALPHA_OUT)); + + let _ = remove_file(file); +} + +// Test that od can read 2 files and concatenate the contents +#[test] +fn test_2files() { + let temp = env::temp_dir(); + let tmpdir = Path::new(&temp); + let file1 = tmpdir.join("test1"); + let file2 = tmpdir.join("test2"); + + for &(n,a) in &[(1,"a"), (2,"b")] { + println!("number: {} letter:{}", n, a); + } + + for &(path,data)in &[(&file1, "abcdefghijklmnop"),(&file2, "qrstuvwxyz\n")] { + let mut f = File::create(&path).unwrap(); + if f.write_all(data.as_bytes()).is_err() { + panic!("Test setup failed - could not write file"); + } + } + + let result = new_ucmd!().arg("--endian=little").arg(file1.as_os_str()).arg(file2.as_os_str()).run(); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(ALPHA_OUT)); + + let _ = remove_file(file1); + let _ = remove_file(file2); +} + +// Test that od gives non-0 exit val for filename that doesn't exist. +#[test] +fn test_no_file() { + let temp = env::temp_dir(); + let tmpdir = Path::new(&temp); + let file = tmpdir.join("}surely'none'would'thus'a'file'name"); + + let result = new_ucmd!().arg(file.as_os_str()).run(); + + assert!(!result.success); +} + +// Test that od reads from stdin instead of a file +#[test] +fn test_from_stdin() { + let input = "abcdefghijklmnopqrstuvwxyz\n"; + let result = new_ucmd!().arg("--endian=little").run_piped_stdin(input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(ALPHA_OUT)); +} + +// Test that od reads from stdin and also from files +#[test] +fn test_from_mixed() { + let temp = env::temp_dir(); + let tmpdir = Path::new(&temp); + let file1 = tmpdir.join("test-1"); + let file3 = tmpdir.join("test-3"); + + let (data1, data2, data3) = ("abcdefg","hijklmnop","qrstuvwxyz\n"); + for &(path,data)in &[(&file1, data1),(&file3, data3)] { + let mut f = File::create(&path).unwrap(); + if f.write_all(data.as_bytes()).is_err() { + panic!("Test setup failed - could not write file"); + } + } + + let result = new_ucmd!().arg("--endian=little").arg(file1.as_os_str()).arg("-").arg(file3.as_os_str()).run_piped_stdin(data2.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(ALPHA_OUT)); +} + +#[test] +fn test_multiple_formats() { + let input = "abcdefghijklmnopqrstuvwxyz\n"; + let result = new_ucmd!().arg("-c").arg("-b").run_piped_stdin(input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(" + 0000000 a b c d e f g h i j k l m n o p + 141 142 143 144 145 146 147 150 151 152 153 154 155 156 157 160 + 0000020 q r s t u v w x y z \\n + 161 162 163 164 165 166 167 170 171 172 012 + 0000033 + ")); +} + +#[test] +fn test_dec() { + let input = [ + 0u8, 0u8, + 1u8, 0u8, + 2u8, 0u8, + 3u8, 0u8, + 0xffu8,0x7fu8, + 0x00u8,0x80u8, + 0x01u8,0x80u8,]; + let expected_output = unindent(" + 0000000 0 1 2 3 32767 -32768 -32767 + 0000016 + "); + let result = new_ucmd!().arg("--endian=little").arg("-s").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_hex16(){ + let input: [u8; 9] = [ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xff]; + let expected_output = unindent(" + 0000000 2301 6745 ab89 efcd 00ff + 0000011 + "); + let result = new_ucmd!().arg("--endian=little").arg("-x").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_hex32(){ + let input: [u8; 9] = [ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xff]; + let expected_output = unindent(" + 0000000 67452301 efcdab89 000000ff + 0000011 + "); + let result = new_ucmd!().arg("--endian=little").arg("-X").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_f16(){ + let input: [u8; 14] = [ + 0x00, 0x3c, // 0x3C00 1.0 + 0x00, 0x00, // 0x0000 0.0 + 0x00, 0x80, // 0x8000 -0.0 + 0x00, 0x7c, // 0x7C00 Inf + 0x00, 0xfc, // 0xFC00 -Inf + 0x00, 0xfe, // 0xFE00 NaN + 0x00, 0x84];// 0x8400 -6.104e-5 + let expected_output = unindent(" + 0000000 1.000 0 -0 inf + 0000010 -inf NaN -6.104e-5 + 0000016 + "); + let result = new_ucmd!().arg("--endian=little").arg("-tf2").arg("-w8").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_f32(){ + let input: [u8; 28] = [ + 0x52, 0x06, 0x9e, 0xbf, // 0xbf9e0652 -1.2345679 + 0x4e, 0x61, 0x3c, 0x4b, // 0x4b3c614e 12345678 + 0x0f, 0x9b, 0x94, 0xfe, // 0xfe949b0f -9.876543E37 + 0x00, 0x00, 0x00, 0x80, // 0x80000000 -0.0 + 0xff, 0xff, 0xff, 0x7f, // 0x7fffffff NaN + 0xc2, 0x16, 0x01, 0x00, // 0x000116c2 1e-40 + 0x00, 0x00, 0x7f, 0x80];// 0x807f0000 -1.1663108E-38 + let expected_output = unindent(" + 0000000 -1.2345679 12345678 -9.8765427e37 -0 + 0000020 NaN 1e-40 -1.1663108e-38 + 0000034 + "); + let result = new_ucmd!().arg("--endian=little").arg("-f").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_f64(){ + let input: [u8; 40] = [ + 0x27, 0x6b, 0x0a, 0x2f, 0x2a, 0xee, 0x45, 0x43, // 0x4345EE2A2F0A6B27 12345678912345678 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0000000000000000 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x80, // 0x8010000000000000 -2.2250738585072014e-308 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0000000000000001 5e-324 (subnormal) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0];// 0xc000000000000000 -2 + let expected_output = unindent(" + 0000000 12345678912345678 0 + 0000020 -2.2250738585072014e-308 5e-324 + 0000040 -2.0000000000000000 + 0000050 + "); + let result = new_ucmd!().arg("--endian=little").arg("-F").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_multibyte() { + let result = new_ucmd!().arg("-c").arg("-w12").run_piped_stdin("Universität Tübingen \u{1B000}".as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(" + 0000000 U n i v e r s i t ä ** t + 0000014 T ü ** b i n g e n \u{1B000} + 0000030 ** ** ** + 0000033 + ")); +} + +#[test] +fn test_width(){ + let input: [u8; 8] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let expected_output = unindent(" + 0000000 000000 000000 + 0000004 000000 000000 + 0000010 + "); + + let result = new_ucmd!().arg("-w4").arg("-v").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_invalid_width(){ + let input: [u8; 4] = [0x00, 0x00, 0x00, 0x00]; + let expected_output = unindent(" + 0000000 000000 + 0000002 000000 + 0000004 + "); + + let result = new_ucmd!().arg("-w5").arg("-v").run_piped_stdin(&input[..]); + + assert_eq!(result.stderr, "od: warning: invalid width 5; using 2 instead\n"); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_zero_width(){ + let input: [u8; 4] = [0x00, 0x00, 0x00, 0x00]; + let expected_output = unindent(" + 0000000 000000 + 0000002 000000 + 0000004 + "); + + let result = new_ucmd!().arg("-w0").arg("-v").run_piped_stdin(&input[..]); + + assert_eq!(result.stderr, "od: warning: invalid width 0; using 2 instead\n"); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_width_without_value(){ + let input: [u8; 40] = [0 ; 40]; + let expected_output = unindent(" + 0000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 + 0000040 000000 000000 000000 000000 + 0000050 + "); + + let result = new_ucmd!().arg("-w").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_suppress_duplicates(){ + let input: [u8; 41] = [ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 1, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0]; + let expected_output = unindent(" + 0000000 00000000000 + 0000 0000 + * + 0000020 00000000001 + 0001 0000 + 0000024 00000000000 + 0000 0000 + * + 0000050 00000000000 + 0000 + 0000051 + "); + + let result = new_ucmd!().arg("-w4").arg("-O").arg("-x").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_big_endian() { + let input: [u8; 8] = [ + 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];// 0xc000000000000000 -2 + + let expected_output = unindent(" + 0000000 -2.0000000000000000 + -2.0000000 0 + c0000000 00000000 + c000 0000 0000 0000 + 0000010 + "); + + let result = new_ucmd!().arg("--endian=big").arg("-F").arg("-f").arg("-X").arg("-x").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +#[allow(non_snake_case)] +fn test_alignment_Xxa() { + let input: [u8; 8] = [ + 0x0A, 0x0D, 0x65, 0x66, 0x67, 0x00, 0x9e, 0x9f]; + + let expected_output = unindent(" + 0000000 66650d0a 9f9e0067 + 0d0a 6665 0067 9f9e + nl cr e f g nul rs us + 0000010 + "); + + // in this case the width of the -a (8-bit) determines the alignment for the other fields + let result = new_ucmd!().arg("--endian=little").arg("-X").arg("-x").arg("-a").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +#[allow(non_snake_case)] +fn test_alignment_Fx() { + let input: [u8; 8] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0];// 0xc000000000000000 -2 + + let expected_output = unindent(" + 0000000 -2.0000000000000000 + 0000 0000 0000 c000 + 0000010 + "); + + // in this case the width of the -F (64-bit) determines the alignment for the other field + let result = new_ucmd!().arg("--endian=little").arg("-F").arg("-x").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_maxuint(){ + let input = [0xFFu8 ; 8]; + let expected_output = unindent(" + 0000000 1777777777777777777777 + 37777777777 37777777777 + 177777 177777 177777 177777 + 377 377 377 377 377 377 377 377 + 18446744073709551615 + 4294967295 4294967295 + 65535 65535 65535 65535 + 255 255 255 255 255 255 255 255 + 0000010 + "); + + let result = new_ucmd!().arg("--format=o8").arg("-Oobtu8").arg("-Dd").arg("--format=u1").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_hex_offset(){ + let input = [0u8 ; 0x1F]; + let expected_output = unindent(" + 000000 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 000010 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 00001F + "); + + let result = new_ucmd!().arg("-Ax").arg("-X").arg("-X").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_dec_offset(){ + let input = [0u8 ; 19]; + let expected_output = unindent(" + 0000000 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + 0000016 00000000 + 00000000 + 0000019 + "); + + let result = new_ucmd!().arg("-Ad").arg("-X").arg("-X").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_no_offset(){ + let input = [0u8 ; 31]; + const LINE: &'static str = " 00000000 00000000 00000000 00000000\n"; + let expected_output = [LINE, LINE, LINE, LINE].join(""); + + let result = new_ucmd!().arg("-An").arg("-X").arg("-X").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, expected_output); +} + +#[test] +fn test_invalid_offset(){ + let result = new_ucmd!().arg("-Ab").run(); + + assert!(!result.success); +} + +#[test] +fn test_skip_bytes(){ + let input = "abcdefghijklmnopq"; + let result = new_ucmd!().arg("-c").arg("--skip-bytes=5").run_piped_stdin(input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(" + 0000005 f g h i j k l m n o p q + 0000021 + ")); +} + +#[test] +fn test_skip_bytes_error(){ + let input = "12345"; + let result = new_ucmd!().arg("--skip-bytes=10").run_piped_stdin(input.as_bytes()); + + assert!(!result.success); +} + +#[test] +fn test_read_bytes(){ + let input = "abcdefghijklmnopqrstuvwxyz\n12345678"; + let result = new_ucmd!().arg("--endian=little").arg("--read-bytes=27").run_piped_stdin(input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(ALPHA_OUT)); +} + +#[test] +fn test_ascii_dump(){ + let input: [u8; 22] = [ + 0x00, 0x01, 0x0a, 0x0d, 0x10, 0x1f, 0x20, 0x61, 0x62, 0x63, 0x7d, + 0x7e, 0x7f, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0xff]; + let result = new_ucmd!().arg("-tx1zacz").run_piped_stdin(&input[..]); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(r" + 0000000 00 01 0a 0d 10 1f 20 61 62 63 7d 7e 7f 80 90 a0 >...... abc}~....< + nul soh nl cr dle us sp a b c } ~ del nul dle sp + \0 001 \n \r 020 037 a b c } ~ 177 ** ** ** >...... abc}~....< + 0000020 b0 c0 d0 e0 f0 ff >......< + 0 @ P ` p del + ** 300 320 340 360 377 >......< + 0000026 + ")); +} + +#[test] +fn test_filename_parsing(){ + // files "a" and "x" both exists, but are no filenames in the commandline below + // "-f" must be treated as a filename, it contains the text: minus lowercase f + // so "-f" should not be interpreted as a formatting option. + let result = new_ucmd!().arg("--format").arg("a").arg("-A").arg("x").arg("--").arg("-f").run(); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(" + 000000 m i n u s sp l o w e r c a s e sp + 000010 f nl + 000012 + ")); +} + +#[test] +fn test_stdin_offset(){ + let input = "abcdefghijklmnopq"; + let result = new_ucmd!().arg("-c").arg("+5").run_piped_stdin(input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(" + 0000005 f g h i j k l m n o p q + 0000021 + ")); +} + +#[test] +fn test_file_offset(){ + let result = new_ucmd!().arg("-c").arg("--").arg("-f").arg("10").run(); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(r" + 0000010 w e r c a s e f \n + 0000022 + ")); +} + +#[test] +fn test_traditional(){ + // note gnu od does not align both lines + let input = "abcdefghijklmnopq"; + let result = new_ucmd!().arg("--traditional").arg("-a").arg("-c").arg("-").arg("10").arg("0").run_piped_stdin(input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(r" + 0000010 (0000000) i j k l m n o p q + i j k l m n o p q + 0000021 (0000011) + ")); +} + +#[test] +fn test_traditional_with_skip_bytes_override(){ + // --skip-bytes is ignored in this case + let input = "abcdefghijklmnop"; + let result = new_ucmd!().arg("--traditional").arg("--skip-bytes=10").arg("-c").arg("0").run_piped_stdin(input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(r" + 0000000 a b c d e f g h i j k l m n o p + 0000020 + ")); +} + +#[test] +fn test_traditional_with_skip_bytes_non_override(){ + // no offset specified in the traditional way, so --skip-bytes is used + let input = "abcdefghijklmnop"; + let result = new_ucmd!().arg("--traditional").arg("--skip-bytes=10").arg("-c").run_piped_stdin(input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(r" + 0000012 k l m n o p + 0000020 + ")); +} + +#[test] +fn test_traditional_error(){ + // file "0" exists - don't fail on that, but --traditional only accepts a single input + let result = new_ucmd!().arg("--traditional").arg("0").arg("0").arg("0").arg("0").run(); + + assert!(!result.success); +} + +#[test] +fn test_traditional_only_label(){ + let input = "abcdefghijklmnopqrstuvwxyz"; + let result = new_ucmd!().arg("-An").arg("--traditional").arg("-a").arg("-c").arg("-").arg("10").arg("0x10").run_piped_stdin(input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, unindent(r" + (0000020) i j k l m n o p q r s t u v w x + i j k l m n o p q r s t u v w x + (0000040) y z + y z + (0000042) + ")); +} diff --git a/coreutils/tests/test_paste.rs b/coreutils/tests/test_paste.rs new file mode 100644 index 000000000..1b73c8349 --- /dev/null +++ b/coreutils/tests/test_paste.rs @@ -0,0 +1,10 @@ +use common::util::*; + + +#[test] +fn test_combine_pairs_of_lines() { + new_ucmd!() + .args(&["-s", "-d", "\t\n", "html_colors.txt"]) + .run() + .stdout_is_fixture("html_colors.expected"); +} diff --git a/coreutils/tests/test_pathchk.rs b/coreutils/tests/test_pathchk.rs new file mode 100644 index 000000000..b478ccea1 --- /dev/null +++ b/coreutils/tests/test_pathchk.rs @@ -0,0 +1,13 @@ +use common::util::*; + + +#[test] +fn test_default_mode() { + // test the default mode + + // accept some reasonable default + new_ucmd!().args(&["abc/def"]).succeeds().no_stdout(); + + // fail on long inputs + new_ucmd!().args(&[repeat_str("test", 20000)]).fails().no_stdout(); +} diff --git a/coreutils/tests/test_pinky.rs b/coreutils/tests/test_pinky.rs new file mode 100644 index 000000000..377abe74d --- /dev/null +++ b/coreutils/tests/test_pinky.rs @@ -0,0 +1,52 @@ +extern crate uucore; + +use common::util::*; + +use self::uucore::entries::{Locate, Passwd}; + +extern crate uu_pinky; +pub use self::uu_pinky::*; + +#[test] +fn test_capitalize() { + assert_eq!("Zbnmasd", "zbnmasd".capitalize()); + assert_eq!("Abnmasd", "Abnmasd".capitalize()); + assert_eq!("1masd", "1masd".capitalize()); + assert_eq!("", "".capitalize()); +} + +#[test] +fn test_long_format() { + let ulogin = "root"; + let pw: Passwd = Passwd::locate(ulogin).unwrap(); + let real_name = pw.user_info().replace("&", &pw.name().capitalize()); + new_ucmd!() + .arg("-l").arg(ulogin) + .run() + .stdout_is(format!("Login name: {:<28}In real life: {}\nDirectory: {:<29}Shell: {}\n\n", + ulogin, real_name, pw.user_dir(), pw.user_shell())); + + new_ucmd!() + .arg("-lb") + .arg(ulogin) + .run() + .stdout_is(format!("Login name: {:<28}In real life: {1}\n\n", + ulogin, real_name)); +} + +#[cfg(target_os = "linux")] +#[test] +fn test_short_format() { + let scene = TestScenario::new(util_name!()); + + let args = ["-i"]; + scene.ucmd().args(&args).run().stdout_is(expected_result(&args)); + + let args = ["-q"]; + scene.ucmd().args(&args).run().stdout_is(expected_result(&args)); +} + +#[cfg(target_os = "linux")] +fn expected_result(args: &[&str]) -> String { + TestScenario::new(util_name!()).cmd_keepenv(util_name!()).env("LANGUAGE", "C").args(args).run().stdout +} diff --git a/coreutils/tests/test_printf.rs b/coreutils/tests/test_printf.rs new file mode 100644 index 000000000..fc5280416 --- /dev/null +++ b/coreutils/tests/test_printf.rs @@ -0,0 +1,279 @@ +use common::util::*; + + +#[test] +fn basic_literal() { + new_ucmd!().args(&["hello world"]).succeeds().stdout_only("hello world"); +} + +#[test] +fn escaped_tab() { + new_ucmd!().args(&["hello\\t world"]).succeeds().stdout_only("hello\t world"); +} + +#[test] +fn escaped_newline() { + new_ucmd!().args(&["hello\\n world"]).succeeds().stdout_only("hello\n world"); +} + +#[test] +fn escaped_slash() { + new_ucmd!().args(&["hello\\\\ world"]).succeeds().stdout_only("hello\\ world"); +} + +#[test] +fn escaped_hex() { + new_ucmd!().args(&["\\x41"]).succeeds().stdout_only("A"); +} + +#[test] +fn escaped_octal() { + new_ucmd!().args(&["\\101"]).succeeds().stdout_only("A"); +} + +#[test] +fn escaped_unicode_fourdigit() { + new_ucmd!().args(&["\\u0125"]).succeeds().stdout_only("ĥ"); +} + +#[test] +fn escaped_unicode_eightdigit() { + new_ucmd!().args(&["\\U00000125"]).succeeds().stdout_only("ĥ"); +} + +#[test] +fn escaped_percent_sign() { + new_ucmd!().args(&["hello%% world"]).succeeds().stdout_only("hello% world"); +} + +#[test] +fn escaped_unrecognized() { + new_ucmd!().args(&["c\\d"]).succeeds().stdout_only("c\\d"); +} + +#[test] +fn sub_string() { + new_ucmd!().args(&["hello %s", "world"]).succeeds().stdout_only("hello world"); +} + +#[test] +fn sub_multifield() { + new_ucmd!().args(&["%s %s", "hello", "world"]).succeeds().stdout_only("hello world"); +} + +#[test] +fn sub_repeat_formatstr() { + new_ucmd!().args(&["%s.", "hello", "world"]).succeeds().stdout_only("hello.world."); +} + +#[test] +fn sub_string_ignore_escapes() { + new_ucmd!().args(&["hello %s", "\\tworld"]).succeeds().stdout_only("hello \\tworld"); +} + +#[test] +fn sub_bstring_handle_escapes() { + new_ucmd!().args(&["hello %b", "\\tworld"]).succeeds().stdout_only("hello \tworld"); +} + +#[test] +fn sub_bstring_ignore_subs() { + new_ucmd!().args(&["hello %b", "world %% %i"]).succeeds().stdout_only("hello world %% %i"); +} + +#[test] +fn sub_char() { + new_ucmd!().args(&["the letter %c", "A"]).succeeds().stdout_only("the letter A"); +} + +#[test] +fn sub_num_int() { + new_ucmd!().args(&["twenty is %i", "20"]).succeeds().stdout_only("twenty is 20"); +} + +#[test] +fn sub_num_int_minwidth() { + new_ucmd!().args(&["twenty is %1i", "20"]).succeeds().stdout_only("twenty is 20"); +} + +#[test] +fn sub_num_int_neg() { + new_ucmd!().args(&["neg. twenty is %i", "-20"]).succeeds().stdout_only("neg. twenty is -20"); +} + +#[test] +fn sub_num_int_oct_in() { + new_ucmd!().args(&["twenty is %i", "024"]).succeeds().stdout_only("twenty is 20"); +} + +#[test] +fn sub_num_int_oct_in_neg() { + new_ucmd!().args(&["neg. twenty is %i", "-024"]).succeeds().stdout_only("neg. twenty is -20"); +} + +#[test] +fn sub_num_int_hex_in() { + new_ucmd!().args(&["twenty is %i", "0x14"]).succeeds().stdout_only("twenty is 20"); +} + +#[test] +fn sub_num_int_hex_in_neg() { + new_ucmd!().args(&["neg. twenty is %i", "-0x14"]).succeeds().stdout_only("neg. twenty is -20"); +} + +#[test] +fn sub_num_int_charconst_in() { + new_ucmd!().args(&["ninetyseven is %i", "'a"]).succeeds().stdout_only("ninetyseven is 97"); +} + +#[test] +fn sub_num_uint() { + new_ucmd!().args(&["twenty is %u", "20"]).succeeds().stdout_only("twenty is 20"); +} + +#[test] +fn sub_num_octal() { + new_ucmd!().args(&["twenty in octal is %o", "20"]).succeeds().stdout_only("twenty in octal is 24"); +} + +#[test] +fn sub_num_hex_lower() { + new_ucmd!().args(&["thirty in hex is %x", "30"]).succeeds().stdout_only("thirty in hex is 1e"); +} + +#[test] +fn sub_num_hex_upper() { + new_ucmd!().args(&["thirty in hex is %X", "30"]).succeeds().stdout_only("thirty in hex is 1E"); +} + +#[test] +fn sub_num_float() { + new_ucmd!().args(&["twenty is %f", "20"]).succeeds().stdout_only("twenty is 20.000000"); +} + +#[test] +fn sub_num_float_round() { + new_ucmd!().args(&["two is %f", "1.9999995"]).succeeds().stdout_only("two is 2.000000"); +} + +#[test] +fn sub_num_sci_lower() { + new_ucmd!().args(&["twenty is %e", "20"]).succeeds().stdout_only("twenty is 2.000000e+01"); +} + +#[test] +fn sub_num_sci_upper() { + new_ucmd!().args(&["twenty is %E", "20"]).succeeds().stdout_only("twenty is 2.000000E+01"); +} + +#[test] +fn sub_num_sci_trunc() { + new_ucmd!().args(&["pi is ~ %e", "3.1415926535"]).succeeds().stdout_only("pi is ~ 3.141593e+00"); +} + +#[test] +fn sub_num_dec_trunc() { + new_ucmd!().args(&["pi is ~ %g", "3.1415926535"]).succeeds().stdout_only("pi is ~ 3.141593"); +} + +#[cfg_attr(not(feature="test_unimplemented"),ignore)] +#[test] +fn sub_num_hex_float_lower() { + new_ucmd!().args(&["%a", ".875"]).succeeds().stdout_only("0xep-4"); +} + +#[cfg_attr(not(feature="test_unimplemented"),ignore)] +#[test] +fn sub_num_hex_float_upper() { + new_ucmd!().args(&["%A", ".875"]).succeeds().stdout_only("0XEP-4"); +} + +#[test] +fn sub_minwidth() { + new_ucmd!().args(&["hello %7s", "world"]).succeeds().stdout_only("hello world"); +} + +#[test] +fn sub_minwidth_negative() { + new_ucmd!().args(&["hello %-7s", "world"]).succeeds().stdout_only("hello world "); +} + +#[test] +fn sub_str_max_chars_input() { + new_ucmd!().args(&["hello %7.2s", "world"]).succeeds().stdout_only("hello wo"); +} + +#[test] +fn sub_int_decimal() { + new_ucmd!().args(&["%0.i", "11"]).succeeds().stdout_only("11"); +} + +#[test] +fn sub_int_leading_zeroes() { + new_ucmd!().args(&["%.4i", "11"]).succeeds().stdout_only("0011"); +} + +#[test] +fn sub_int_leading_zeroes_prio() { + new_ucmd!().args(&["%5.4i", "11"]).succeeds().stdout_only(" 0011"); +} + +#[test] +fn sub_float_dec_places() { + new_ucmd!().args(&["pi is ~ %.11f", "3.1415926535"]).succeeds().stdout_only("pi is ~ 3.14159265350"); +} + +#[test] +fn sub_float_hex_in() { + new_ucmd!().args(&["%f", "0xF1.1F"]).succeeds().stdout_only("241.121094"); +} + +#[test] +fn sub_float_no_octal_in() { + new_ucmd!().args(&["%f", "077"]).succeeds().stdout_only("77.000000"); +} + +#[test] +fn sub_any_asterisk_firstparam() { + new_ucmd!().args(&["%*i", "3", "11", "4", "12"]).succeeds().stdout_only(" 11 12"); +} + +#[test] +fn sub_any_asterisk_second_param() { + new_ucmd!().args(&["%.*i", "3", "11", "4", "12"]).succeeds().stdout_only("0110012"); +} + +#[test] +fn sub_any_asterisk_both_params() { + new_ucmd!().args(&["%*.*i", "4", "3", "11", "5", "4", "12"]).succeeds().stdout_only(" 011 0012"); +} + +#[test] +fn sub_any_asterisk_octal_arg() { + new_ucmd!().args(&["%.*i", "011", "12345678"]).succeeds().stdout_only("012345678"); +} + +#[test] +fn sub_any_asterisk_hex_arg() { + new_ucmd!().args(&["%.*i", "0xA", "123456789"]).succeeds().stdout_only("0123456789"); +} + +#[test] +fn sub_any_specifiers_no_params() { + new_ucmd!().args(&["%ztlhLji", "3"]).succeeds().stdout_only("3"); +} + +#[test] +fn sub_any_specifiers_after_first_param() { + new_ucmd!().args(&["%0ztlhLji", "3"]).succeeds().stdout_only("3"); +} + +#[test] +fn sub_any_specifiers_after_period() { + new_ucmd!().args(&["%0.ztlhLji", "3"]).succeeds().stdout_only("3"); +} + +#[test] +fn sub_any_specifiers_after_second_param() { + new_ucmd!().args(&["%0.0ztlhLji", "3"]).succeeds().stdout_only("3"); +} diff --git a/coreutils/tests/test_ptx.rs b/coreutils/tests/test_ptx.rs new file mode 100644 index 000000000..231c03f13 --- /dev/null +++ b/coreutils/tests/test_ptx.rs @@ -0,0 +1,44 @@ +use common::util::*; + + +#[test] +fn gnu_ext_disabled_roff_no_ref() { + new_ucmd!().args(&["-G", "-R", "input"]) + .succeeds().stdout_only_fixture("gnu_ext_disabled_roff_no_ref.expected"); +} + +#[test] +fn gnu_ext_disabled_roff_input_ref() { + new_ucmd!().args(&["-G", "-r", "-R", "input"]) + .succeeds().stdout_only_fixture("gnu_ext_disabled_roff_input_ref.expected"); +} + +#[test] +fn gnu_ext_disabled_roff_auto_ref() { + new_ucmd!().args(&["-G", "-A", "-R", "input"]) + .succeeds().stdout_only_fixture("gnu_ext_disabled_roff_auto_ref.expected"); +} + +#[test] +fn gnu_ext_disabled_tex_no_ref() { + new_ucmd!().args(&["-G", "-T", "-R", "input"]) + .succeeds().stdout_only_fixture("gnu_ext_disabled_tex_no_ref.expected"); +} + +#[test] +fn gnu_ext_disabled_tex_input_ref() { + new_ucmd!().args(&["-G", "-T", "-r", "-R", "input"]) + .succeeds().stdout_only_fixture("gnu_ext_disabled_tex_input_ref.expected"); +} + +#[test] +fn gnu_ext_disabled_tex_auto_ref() { + new_ucmd!().args(&["-G", "-T", "-A", "-R", "input"]) + .succeeds().stdout_only_fixture("gnu_ext_disabled_tex_auto_ref.expected"); +} + +#[test] +fn gnu_ext_disabled_ignore_and_only_file() { + new_ucmd!().args(&["-G", "-o", "only", "-i", "ignore", "input"]) + .succeeds().stdout_only_fixture("gnu_ext_disabled_ignore_and_only_file.expected"); +} diff --git a/coreutils/tests/test_pwd.rs b/coreutils/tests/test_pwd.rs new file mode 100644 index 000000000..c17ac27d4 --- /dev/null +++ b/coreutils/tests/test_pwd.rs @@ -0,0 +1,8 @@ +use common::util::*; + + +#[test] +fn test_default() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.run().stdout_is(at.root_dir_resolved() + "\n"); +} diff --git a/coreutils/tests/test_readlink.rs b/coreutils/tests/test_readlink.rs new file mode 100644 index 000000000..e8088dcd6 --- /dev/null +++ b/coreutils/tests/test_readlink.rs @@ -0,0 +1,56 @@ +use common::util::*; + + +static GIBBERISH: &'static str = "supercalifragilisticexpialidocious"; + +#[test] +fn test_canonicalize() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.arg("-f") + .arg(".") + .run() + .stdout_is(at.root_dir_resolved() + "\n"); +} + +#[test] +fn test_canonicalize_existing() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.arg("-e") + .arg(".") + .run() + .stdout_is(at.root_dir_resolved() + "\n"); +} + +#[test] +fn test_canonicalize_missing() { + let (at, mut ucmd) = at_and_ucmd!(); + let expected = path_concat!(at.root_dir_resolved(), GIBBERISH); + ucmd.arg("-m") + .arg(GIBBERISH) + .run() + .stdout_is(expected + "\n"); +} + +#[test] +fn test_long_redirection_to_current_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + // Create a 256-character path to current directory + let dir = path_concat!(".", ..128); + ucmd.arg("-n") + .arg("-m") + .arg(dir) + .run() + .stdout_is(at.root_dir_resolved()); +} + +#[test] +fn test_long_redirection_to_root() { + // Create a 255-character path to root + let dir = path_concat!("..", ..85); + new_ucmd!() + .arg("-n") + .arg("-m") + .arg(dir) + .run() + .stdout_is(get_root_path()); +} diff --git a/coreutils/tests/test_realpath.rs b/coreutils/tests/test_realpath.rs new file mode 100644 index 000000000..efe263a54 --- /dev/null +++ b/coreutils/tests/test_realpath.rs @@ -0,0 +1,23 @@ +use common::util::*; + + +#[test] +fn test_current_directory() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.arg(".").run().stdout_is(at.root_dir_resolved() + "\n"); +} + +#[test] +fn test_long_redirection_to_current_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + // Create a 256-character path to current directory + let dir = path_concat!(".", ..128); + ucmd.arg(dir).run().stdout_is(at.root_dir_resolved() + "\n"); +} + +#[test] +fn test_long_redirection_to_root() { + // Create a 255-character path to root + let dir = path_concat!("..", ..85); + new_ucmd!().arg(dir).run().stdout_is(get_root_path().to_owned() + "\n"); +} diff --git a/coreutils/tests/test_rm.rs b/coreutils/tests/test_rm.rs new file mode 100644 index 000000000..2ee85edf8 --- /dev/null +++ b/coreutils/tests/test_rm.rs @@ -0,0 +1,175 @@ +use common::util::*; + + +#[test] +fn test_rm_one_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_rm_one_file"; + + at.touch(file); + + ucmd.arg(file).succeeds().no_stderr(); + + assert!(!at.file_exists(file)); +} + +#[test] +fn test_rm_multiple_files() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_rm_multiple_file_a"; + let file_b = "test_rm_multiple_file_b"; + + at.touch(file_a); + at.touch(file_b); + + ucmd.arg(file_a).arg(file_b).succeeds().no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(!at.file_exists(file_b)); +} + +#[test] +fn test_rm_interactive() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_rm_interactive_file_a"; + let file_b = "test_rm_interactive_file_b"; + + at.touch(file_a); + at.touch(file_b); + + scene.ucmd() + .arg("-i") + .arg(file_a) + .arg(file_b) + .pipe_in("n") + .succeeds(); + + assert!(at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + + scene.ucmd() + .arg("-i") + .arg(file_a) + .arg(file_b) + .pipe_in("Yesh") + .succeeds(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); +} + +#[test] +fn test_rm_force() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_rm_force_a"; + let file_b = "test_rm_force_b"; + + ucmd.arg("-f") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(!at.file_exists(file_b)); +} + +#[test] +fn test_rm_empty_directory() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rm_empty_directory"; + + at.mkdir(dir); + + ucmd.arg("-d").arg(dir).succeeds().no_stderr(); + + assert!(!at.dir_exists(dir)); +} + +#[test] +fn test_rm_recursive() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rm_recursive_directory"; + let file_a = "test_rm_recursive_directory/test_rm_recursive_file_a"; + let file_b = "test_rm_recursive_directory/test_rm_recursive_file_b"; + + at.mkdir(dir); + at.touch(file_a); + at.touch(file_b); + + ucmd.arg("-r").arg(dir).succeeds().no_stderr(); + + assert!(!at.dir_exists(dir)); + assert!(!at.file_exists(file_a)); + assert!(!at.file_exists(file_b)); +} + +#[test] +fn test_rm_errors() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rm_errors_directory"; + let file_a = "test_rm_errors_directory/test_rm_errors_file_a"; + let file_b = "test_rm_errors_directory/test_rm_errors_file_b"; + + at.mkdir(dir); + at.touch(file_a); + at.touch(file_b); + + // $ rm test_rm_errors_directory + // 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'?)\n"); +} + +#[test] +fn test_rm_verbose() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_rm_verbose_file_a"; + let file_b = "test_rm_verbose_file_b"; + + at.touch(file_a); + at.touch(file_b); + + ucmd.arg("-v").arg(file_a).arg(file_b).succeeds() + .stdout_only(format!("removed '{}'\nremoved '{}'\n", file_a, file_b)); +} + +#[test] +fn test_rm_dir_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rm_dir_symlink_dir"; + let link = "test_rm_dir_symlink_link"; + + at.mkdir(dir); + at.symlink_dir(dir, link); + + ucmd.arg(link).succeeds(); +} + +#[test] +fn test_rm_invalid_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + let link = "test_rm_invalid_symlink"; + + at.symlink_file(link, link); + + ucmd.arg(link).succeeds(); +} + +#[test] +fn test_rm_force_no_operand() { + let mut ucmd = new_ucmd!(); + + ucmd.arg("-f").succeeds().no_stderr(); +} + +#[test] +fn test_rm_no_operand() { + let mut ucmd = new_ucmd!(); + + ucmd.fails() + .stderr_is("rm: error: missing an argument\nrm: error: for help, try 'rm --help'\n"); +} diff --git a/coreutils/tests/test_rmdir.rs b/coreutils/tests/test_rmdir.rs new file mode 100644 index 000000000..3116ca665 --- /dev/null +++ b/coreutils/tests/test_rmdir.rs @@ -0,0 +1,103 @@ +use common::util::*; + + +#[test] +fn test_rmdir_empty_directory_no_parents() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rmdir_empty_no_parents"; + + at.mkdir(dir); + assert!(at.dir_exists(dir)); + + ucmd.arg(dir).succeeds().no_stderr(); + + assert!(!at.dir_exists(dir)); +} + +#[test] +fn test_rmdir_empty_directory_with_parents() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rmdir_empty/with/parents"; + + at.mkdir_all(dir); + assert!(at.dir_exists(dir)); + + ucmd.arg("-p").arg(dir).succeeds().no_stderr(); + + assert!(!at.dir_exists(dir)); +} + +#[test] +fn test_rmdir_nonempty_directory_no_parents() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rmdir_nonempty_no_parents"; + let file = "test_rmdir_nonempty_no_parents/foo"; + + at.mkdir(dir); + assert!(at.dir_exists(dir)); + + at.touch(file); + assert!(at.file_exists(file)); + + ucmd.arg(dir).fails() + .stderr_is("rmdir: error: failed to remove 'test_rmdir_nonempty_no_parents': Directory not \ + empty\n"); + + assert!(at.dir_exists(dir)); +} + +#[test] +fn test_rmdir_nonempty_directory_with_parents() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rmdir_nonempty/with/parents"; + let file = "test_rmdir_nonempty/with/parents/foo"; + + at.mkdir_all(dir); + assert!(at.dir_exists(dir)); + + at.touch(file); + assert!(at.file_exists(file)); + + 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"); + + assert!(at.dir_exists(dir)); +} + +#[test] +fn test_rmdir_ignore_nonempty_directory_no_parents() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rmdir_ignore_nonempty_no_parents"; + let file = "test_rmdir_ignore_nonempty_no_parents/foo"; + + at.mkdir(dir); + assert!(at.dir_exists(dir)); + + at.touch(file); + assert!(at.file_exists(file)); + + ucmd.arg("--ignore-fail-on-non-empty").arg(dir).succeeds().no_stderr(); + + assert!(at.dir_exists(dir)); +} + +#[test] +fn test_rmdir_ignore_nonempty_directory_with_parents() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_rmdir_ignore_nonempty/with/parents"; + let file = "test_rmdir_ignore_nonempty/with/parents/foo"; + + at.mkdir_all(dir); + assert!(at.dir_exists(dir)); + + at.touch(file); + assert!(at.file_exists(file)); + + ucmd.arg("--ignore-fail-on-non-empty").arg("-p").arg(dir).succeeds().no_stderr(); + + assert!(at.dir_exists(dir)); +} diff --git a/coreutils/tests/test_seq.rs b/coreutils/tests/test_seq.rs new file mode 100644 index 000000000..fc9f59140 --- /dev/null +++ b/coreutils/tests/test_seq.rs @@ -0,0 +1,26 @@ +use common::util::*; + + +#[test] +fn test_count_up() { + new_ucmd!() + .args(&["10"]).run().stdout_is("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"); +} + +#[test] +fn test_count_down() { + new_ucmd!() + .args(&["--", "5", "-1", "1"]).run().stdout_is("5\n4\n3\n2\n1\n"); +} + +#[test] +fn test_separator_and_terminator() { + new_ucmd!() + .args(&["-s", ",", "-t", "!", "2", "6"]).run().stdout_is("2,3,4,5,6!"); +} + +#[test] +fn test_equalize_widths() { + new_ucmd!() + .args(&["-w", "5", "10"]).run().stdout_is("05\n06\n07\n08\n09\n10\n"); +} diff --git a/coreutils/tests/test_sort.rs b/coreutils/tests/test_sort.rs new file mode 100644 index 000000000..d68e384d1 --- /dev/null +++ b/coreutils/tests/test_sort.rs @@ -0,0 +1,130 @@ +use common::util::*; + + + +#[test] +fn test_numeric_floats_and_ints() { + test_helper("numeric_floats_and_ints", "-n"); +} + +#[test] +fn test_numeric_floats() { + test_helper("numeric_floats", "-n"); +} + +#[test] +fn test_numeric_floats_with_nan() { + test_helper("numeric_floats_with_nan", "-n"); +} + +#[test] +fn test_numeric_unfixed_floats() { + test_helper("numeric_unfixed_floats", "-n"); +} + +#[test] +fn test_numeric_fixed_floats() { + test_helper("numeric_fixed_floats", "-n"); +} + +#[test] +fn test_numeric_unsorted_ints() { + test_helper("numeric_unsorted_ints", "-n"); +} + +#[test] +fn test_human_block_sizes() { + test_helper("human_block_sizes", "-h"); +} + +#[test] +fn test_month_default() { + test_helper("month_default", "-M"); +} + +#[test] +fn test_month_stable() { + test_helper("month_stable", "-Ms"); +} + +#[test] +fn test_default_unsorted_ints() { + test_helper("default_unsorted_ints", ""); +} + +#[test] +fn test_numeric_unique_ints() { + test_helper("numeric_unsorted_ints_unique", "-nu"); +} + +#[test] +fn test_version() { + test_helper("version", "-V"); +} + +#[test] +fn test_ignore_case() { + test_helper("ignore_case", "-f"); +} + +#[test] +fn test_multiple_files() { + new_ucmd!() + .arg("-n") + .arg("multiple_files1.txt") + .arg("multiple_files2.txt") + .succeeds().stdout_only_fixture("multiple_files.expected"); +} + +#[test] +fn test_merge_interleaved() { + new_ucmd!() + .arg("-m") + .arg("merge_ints_interleaved_1.txt") + .arg("merge_ints_interleaved_2.txt") + .arg("merge_ints_interleaved_3.txt") + .succeeds().stdout_only_fixture("merge_ints_interleaved.expected"); +} + +#[test] +fn test_merge_unique() { + new_ucmd!() + .arg("-m") + .arg("--unique") + .arg("merge_ints_interleaved_1.txt") + .arg("merge_ints_interleaved_2.txt") + .arg("merge_ints_interleaved_3.txt") + .arg("merge_ints_interleaved_3.txt") + .arg("merge_ints_interleaved_2.txt") + .arg("merge_ints_interleaved_1.txt") + .succeeds().stdout_only_fixture("merge_ints_interleaved.expected"); +} + +#[test] +fn test_merge_reversed() { + new_ucmd!() + .arg("-m") + .arg("--reverse") + .arg("merge_ints_reversed_1.txt") + .arg("merge_ints_reversed_2.txt") + .arg("merge_ints_reversed_3.txt") + .succeeds().stdout_only_fixture("merge_ints_reversed.expected"); +} + +#[test] +fn test_check() { + new_ucmd!() + .arg("-c") + .arg("check_fail.txt") + .fails().stdout_is("sort: disorder in line 4\n"); + + new_ucmd!() + .arg("-c") + .arg("multiple_files.expected") + .succeeds().stdout_is(""); +} + +fn test_helper(file_name: &str, args: &str) { + new_ucmd!().arg(args).arg(format!("{}{}", file_name, ".txt")) + .succeeds().stdout_is_fixture(format!("{}{}", file_name, ".expected")); +} diff --git a/coreutils/tests/test_split.rs b/coreutils/tests/test_split.rs new file mode 100644 index 000000000..90c312a2f --- /dev/null +++ b/coreutils/tests/test_split.rs @@ -0,0 +1,145 @@ +extern crate rand; +extern crate regex; + +use std::fs::{File, read_dir}; +use std::io::Write; +use std::path::Path; +use self::rand::{Rng, thread_rng}; +use self::regex::Regex; +use common::util::*; + + +fn random_chars(n: usize) -> String { + thread_rng().sample_iter(&rand::distributions::Alphanumeric).take(n).collect::() +} + +struct Glob { + directory: AtPath, + regex: Regex, +} + +impl Glob { + fn new(at: &AtPath, directory: &str, regex: &str) -> Glob { + Glob { + directory: AtPath::new(Path::new(&at.plus_as_string(directory))), + regex: Regex::new(regex).unwrap(), + } + } + + fn count(&self) -> usize { + self.collect().len() + } + + fn collect(&self) -> Vec { + read_dir(Path::new(&self.directory.subdir)) + .unwrap() + .filter_map(|entry| { + let path = entry.unwrap().path(); + let name = self.directory.minus_as_string(path.as_path().to_str().unwrap_or("")); + if self.regex.is_match(&name) { + Some(name) + } else { + None + } + }) + .collect() + } + + fn collate(&self) -> Vec { + let mut files = self.collect(); + files.sort(); + let mut data: Vec = vec![]; + for name in &files { + data.extend(self.directory.read(name).into_bytes()); + } + data + } +} + +struct RandomFile { + inner: File, +} + +impl RandomFile { + fn new(at: &AtPath, name: &str) -> RandomFile { + RandomFile { inner: File::create(&at.plus(name)).unwrap() } + } + + fn add_bytes(&mut self, bytes: usize) { + let chunk_size: usize = if bytes >= 1024 { + 1024 + } else { + bytes + }; + let mut n = bytes; + while n > chunk_size { + let _ = write!(self.inner, "{}", random_chars(chunk_size)); + n -= chunk_size; + } + let _ = write!(self.inner, "{}", random_chars(n)); + } + + fn add_lines(&mut self, lines: usize) { + let line_size: usize = 32; + let mut n = lines; + while n > 0 { + let _ = writeln!(self.inner, "{}", random_chars(line_size)); + n -= 1; + } + } +} + +#[test] +fn test_split_default() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_default"; + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + RandomFile::new(&at, name).add_lines(2000); + ucmd.args(&[name]).succeeds(); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read(name).into_bytes()); +} + +#[test] +fn test_split_num_prefixed_chunks_by_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_num_prefixed_chunks_by_bytes"; + let glob = Glob::new(&at, ".", r"a\d\d$"); + RandomFile::new(&at, name).add_bytes(10000); + ucmd.args(&["-d", "-b", "1000", name, "a"]).succeeds(); + assert_eq!(glob.count(), 10); + assert_eq!(glob.collate(), at.read(name).into_bytes()); +} + +#[test] +fn test_split_str_prefixed_chunks_by_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_str_prefixed_chunks_by_bytes"; + let glob = Glob::new(&at, ".", r"b[[:alpha:]][[:alpha:]]$"); + RandomFile::new(&at, name).add_bytes(10000); + ucmd.args(&["-b", "1000", name, "b"]).succeeds(); + assert_eq!(glob.count(), 10); + assert_eq!(glob.collate(), at.read(name).into_bytes()); +} + +#[test] +fn test_split_num_prefixed_chunks_by_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_num_prefixed_chunks_by_lines"; + let glob = Glob::new(&at, ".", r"c\d\d$"); + RandomFile::new(&at, name).add_lines(10000); + ucmd.args(&["-d", "-l", "1000", name, "c"]).succeeds(); + assert_eq!(glob.count(), 10); + assert_eq!(glob.collate(), at.read(name).into_bytes()); +} + +#[test] +fn test_split_str_prefixed_chunks_by_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_str_prefixed_chunks_by_lines"; + let glob = Glob::new(&at, ".", r"d[[:alpha:]][[:alpha:]]$"); + RandomFile::new(&at, name).add_lines(10000); + ucmd.args(&["-l", "1000", name, "d"]).succeeds(); + assert_eq!(glob.count(), 10); + assert_eq!(glob.collate(), at.read(name).into_bytes()); +} diff --git a/coreutils/tests/test_stat.rs b/coreutils/tests/test_stat.rs new file mode 100644 index 000000000..cac756548 --- /dev/null +++ b/coreutils/tests/test_stat.rs @@ -0,0 +1,240 @@ +use common::util::*; + +extern crate uu_stat; +pub use self::uu_stat::*; + + +#[cfg(test)] +mod test_fsext { + use super::*; + + #[test] + fn test_access() { + assert_eq!("drwxr-xr-x", pretty_access(S_IFDIR | 0o755)); + assert_eq!("-rw-r--r--", pretty_access(S_IFREG | 0o644)); + assert_eq!("srw-r-----", pretty_access(S_IFSOCK | 0o640)); + assert_eq!("lrw-r-xr-x", pretty_access(S_IFLNK | 0o655)); + assert_eq!("?rw-r-xr-x", pretty_access(0o655)); + + assert_eq!("brwSr-xr-x", + pretty_access(S_IFBLK | S_ISUID as mode_t | 0o655)); + assert_eq!("brwsr-xr-x", + pretty_access(S_IFBLK | S_ISUID as mode_t | 0o755)); + + assert_eq!("prw---sr--", + pretty_access(S_IFIFO | S_ISGID as mode_t | 0o614)); + assert_eq!("prw---Sr--", + pretty_access(S_IFIFO | S_ISGID as mode_t | 0o604)); + + assert_eq!("c---r-xr-t", + pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o055)); + assert_eq!("c---r-xr-T", + pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o054)); + } + + #[test] + fn test_file_type() { + assert_eq!("block special file", pretty_filetype(S_IFBLK, 0)); + assert_eq!("character special file", pretty_filetype(S_IFCHR, 0)); + assert_eq!("regular file", pretty_filetype(S_IFREG, 1)); + assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0)); + assert_eq!("weird file", pretty_filetype(0, 0)); + } + + #[test] + fn test_fs_type() { + assert_eq!("ext2/ext3", pretty_fstype(0xEF53)); + assert_eq!("tmpfs", pretty_fstype(0x01021994)); + assert_eq!("nfs", pretty_fstype(0x6969)); + assert_eq!("btrfs", pretty_fstype(0x9123683e)); + assert_eq!("xfs", pretty_fstype(0x58465342)); + assert_eq!("zfs", pretty_fstype(0x2FC12FC1)); + assert_eq!("ntfs", pretty_fstype(0x5346544e)); + assert_eq!("fat", pretty_fstype(0x4006)); + assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234)); + } +} + +#[test] +fn test_scanutil() { + assert_eq!(Some((-5, 2)), "-5zxc".scan_num::()); + assert_eq!(Some((51, 2)), "51zxc".scan_num::()); + assert_eq!(Some((192, 4)), "+192zxc".scan_num::()); + assert_eq!(None, "z192zxc".scan_num::()); + + assert_eq!(Some(('a', 3)), "141zxc".scan_char(8)); + assert_eq!(Some(('\n', 2)), "12qzxc".scan_char(8)); + assert_eq!(Some(('\r', 1)), "dqzxc".scan_char(16)); + assert_eq!(None, "z2qzxc".scan_char(8)); +} + +#[test] +fn test_groupnum() { + assert_eq!("12,379,821,234", group_num("12379821234")); + assert_eq!("21,234", group_num("21234")); + assert_eq!("821,234", group_num("821234")); + assert_eq!("1,821,234", group_num("1821234")); + assert_eq!("1,234", group_num("1234")); + assert_eq!("234", group_num("234")); + assert_eq!("24", group_num("24")); + assert_eq!("4", group_num("4")); + assert_eq!("", group_num("")); +} + +#[cfg(test)] +mod test_generate_tokens { + use super::*; + + #[test] + fn normal_format() { + let s = "%'010.2ac%-#5.w\n"; + let expected = vec![Token::Directive { + flag: F_GROUP | F_ZERO, + width: 10, + precision: 2, + format: 'a', + }, + Token::Char('c'), + Token::Directive { + flag: F_LEFT | F_ALTER, + width: 5, + precision: 0, + format: 'w', + }, + Token::Char('\n')]; + assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap()); + } + + #[test] + fn printf_format() { + let s = "%-# 15a\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.-23w\\x12\\167\\132\\112\\n"; + let expected = vec![Token::Directive { + flag: F_LEFT | F_ALTER | F_SPACE, + width: 15, + precision: -1, + format: 'a', + }, + Token::Char('\r'), + Token::Char('"'), + Token::Char('\\'), + Token::Char('\x07'), + Token::Char('\x08'), + Token::Char('\x1B'), + Token::Char('\x0C'), + Token::Char('\x0B'), + Token::Directive { + flag: F_SIGN | F_ZERO, + width: 20, + precision: -1, + format: 'w', + }, + Token::Char('\x12'), + Token::Char('w'), + Token::Char('Z'), + Token::Char('J'), + Token::Char('\n')]; + assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap()); + } +} + +#[test] +fn test_invalid_option() { + new_ucmd!() + .arg("-w").arg("-q").arg("/").fails(); +} + +#[cfg(target_os = "linux")] +const NORMAL_FMTSTR: &'static str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s %u %U %w %W %x %X %y %Y %z %Z"; +#[cfg(target_os = "linux")] +const DEV_FMTSTR: &'static str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s (%t/%T) %u %U %w %W %x %X %y %Y %z %Z"; +#[cfg(target_os = "linux")] +const FS_FMTSTR: &'static str = "%a %b %c %d %f %i %l %n %s %S %t %T"; + +#[test] +#[cfg(target_os = "linux")] +fn test_terse_fs_format() { + let args = ["-f", "-t", "/proc"]; + new_ucmd!().args(&args) + .run() + .stdout_is(expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_fs_format() { + let args = ["-f", "-c", FS_FMTSTR, "/dev/shm"]; + new_ucmd!().args(&args) + .run() + .stdout_is(expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_terse_normal_format() { + let args = ["-t", "/"]; + new_ucmd!().args(&args) + .run() + .stdout_is(expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_normal_format() { + let args = ["-c", NORMAL_FMTSTR, "/boot"]; + new_ucmd!().args(&args) + .run() + .stdout_is(expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_follow_symlink() { + let args = ["-L", "-c", DEV_FMTSTR, "/dev/cdrom"]; + new_ucmd!().args(&args) + .run() + .stdout_is(expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_symlink() { + let args = ["-c", DEV_FMTSTR, "/dev/cdrom"]; + new_ucmd!().args(&args) + .run() + .stdout_is(expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_char() { + let args = ["-c", DEV_FMTSTR, "/dev/pts/ptmx"]; + new_ucmd!().args(&args) + .run() + .stdout_is(expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_multi_files() { + let args = ["-c", NORMAL_FMTSTR, "/dev", "/usr/lib", "/etc/fstab", "/var"]; + new_ucmd!().args(&args) + .run() + .stdout_is(expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_printf() { + let args = ["--printf=123%-# 15q\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.23m\\x12\\167\\132\\112\\n", "/"]; + new_ucmd!().args(&args) + .run() + .stdout_is(expected_result(&args)); +} + +#[cfg(target_os = "linux")] +fn expected_result(args: &[&str]) -> String { + use std::process::Command; + + let output = Command::new(util_name!()).env("LANGUAGE", "C").args(args).output().unwrap(); + String::from_utf8_lossy(&output.stdout).into_owned() +} diff --git a/coreutils/tests/test_stdbuf.rs b/coreutils/tests/test_stdbuf.rs new file mode 100644 index 000000000..27d8de26b --- /dev/null +++ b/coreutils/tests/test_stdbuf.rs @@ -0,0 +1,12 @@ +use common::util::*; + + +#[test] +fn test_stdbuf_unbuffered_stdout() { + if cfg!(target_os="linux") { + // This is a basic smoke test + new_ucmd!().args(&["-o0", "head"]) + .pipe_in("The quick brown fox jumps over the lazy dog.").run() + .stdout_is("The quick brown fox jumps over the lazy dog."); + } +} diff --git a/coreutils/tests/test_sum.rs b/coreutils/tests/test_sum.rs new file mode 100644 index 000000000..99a5ff566 --- /dev/null +++ b/coreutils/tests/test_sum.rs @@ -0,0 +1,48 @@ +use common::util::*; + + +#[test] +fn test_bsd_single_file() { + new_ucmd!() + .arg("lorem_ipsum.txt") + .succeeds().stdout_only_fixture("bsd_single_file.expected"); +} + +#[test] +fn test_bsd_multiple_files() { + new_ucmd!() + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds().stdout_only_fixture("bsd_multiple_files.expected"); +} + +#[test] +fn test_bsd_stdin() { + new_ucmd!() + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds().stdout_only_fixture("bsd_stdin.expected"); +} + +#[test] +fn test_sysv_single_file() { + new_ucmd!() + .arg("-s").arg("lorem_ipsum.txt") + .succeeds().stdout_only_fixture("sysv_single_file.expected"); +} + +#[test] +fn test_sysv_multiple_files() { + new_ucmd!() + .arg("-s") + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds().stdout_only_fixture("sysv_multiple_files.expected"); +} + +#[test] +fn test_sysv_stdin() { + new_ucmd!() + .arg("-s") + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds().stdout_only_fixture("sysv_stdin.expected"); +} diff --git a/coreutils/tests/test_tac.rs b/coreutils/tests/test_tac.rs new file mode 100644 index 000000000..aa78b946e --- /dev/null +++ b/coreutils/tests/test_tac.rs @@ -0,0 +1,46 @@ +use common::util::*; + + +#[test] +fn test_stdin_default() { + new_ucmd!() + .pipe_in("100\n200\n300\n400\n500") + .run() + .stdout_is("500400\n300\n200\n100\n"); +} + +#[test] +fn test_stdin_non_newline_separator() { + new_ucmd!() + .args(&["-s", ":"]) + .pipe_in("100:200:300:400:500") + .run() + .stdout_is("500400:300:200:100:"); +} + +#[test] +fn test_stdin_non_newline_separator_before() { + new_ucmd!() + .args(&["-b", "-s", ":"]) + .pipe_in("100:200:300:400:500") + .run() + .stdout_is("500:400:300:200:100"); +} + +#[test] +fn test_single_default() { + new_ucmd!().arg("prime_per_line.txt") + .run().stdout_is_fixture("prime_per_line.expected"); +} + +#[test] +fn test_single_non_newline_separator() { + new_ucmd!().args(&["-s", ":", "delimited_primes.txt"]) + .run().stdout_is_fixture("delimited_primes.expected"); +} + +#[test] +fn test_single_non_newline_separator_before() { + new_ucmd!().args(&["-b", "-s", ":", "delimited_primes.txt"]) + .run().stdout_is_fixture("delimited_primes_before.expected"); +} diff --git a/coreutils/tests/test_tail.rs b/coreutils/tests/test_tail.rs new file mode 100644 index 000000000..9d9e44883 --- /dev/null +++ b/coreutils/tests/test_tail.rs @@ -0,0 +1,263 @@ +extern crate uu_tail; + +use common::util::*; +use std::char::from_digit; +use self::uu_tail::parse_size; +use std::io::Write; +use std::process::{Command, Stdio}; +use std::thread::sleep; +use std::time::Duration; + + +static FOOBAR_TXT: &'static str = "foobar.txt"; +static FOOBAR_2_TXT: &'static str = "foobar2.txt"; +static FOOBAR_WITH_NULL_TXT: &'static str = "foobar_with_null.txt"; + +#[test] +fn test_stdin_default() { + new_ucmd!().pipe_in_fixture(FOOBAR_TXT).run().stdout_is_fixture("foobar_stdin_default.expected"); +} + +#[test] +fn test_single_default() { + new_ucmd!().arg(FOOBAR_TXT).run().stdout_is_fixture("foobar_single_default.expected"); +} + +#[test] +fn test_n_greater_than_number_of_lines() { + new_ucmd!().arg("-n").arg("99999999").arg(FOOBAR_TXT).run() + .stdout_is_fixture(FOOBAR_TXT); +} + +#[test] +fn test_null_default() { + new_ucmd!().arg("-z").arg(FOOBAR_WITH_NULL_TXT).run().stdout_is_fixture("foobar_with_null_default.expected"); +} + +#[test] +fn test_follow() { + let (at, mut ucmd) = at_and_ucmd!(); + + let mut child = ucmd.arg("-f").arg(FOOBAR_TXT).run_no_wait(); + + let expected = at.read("foobar_single_default.expected"); + assert_eq!(read_size(&mut child, expected.len()), expected); + + // We write in a temporary copy of foobar.txt + let expected = "line1\nline2\n"; + at.append(FOOBAR_TXT, expected); + + assert_eq!(read_size(&mut child, expected.len()), expected); + + child.kill().unwrap(); +} + +#[test] +fn test_follow_multiple() { + let (at, mut ucmd) = at_and_ucmd!(); + let mut child = ucmd.arg("-f").arg(FOOBAR_TXT).arg(FOOBAR_2_TXT).run_no_wait(); + + let expected = at.read("foobar_follow_multiple.expected"); + assert_eq!(read_size(&mut child, expected.len()), expected); + + let first_append = "trois\n"; + at.append(FOOBAR_2_TXT, first_append); + assert_eq!(read_size(&mut child, first_append.len()), first_append); + + let second_append = "doce\ntrece\n"; + let expected = at.read("foobar_follow_multiple_appended.expected"); + at.append(FOOBAR_TXT, second_append); + assert_eq!(read_size(&mut child, expected.len()), expected); + + child.kill().unwrap(); +} + +#[test] +fn test_follow_stdin() { + new_ucmd!().arg("-f").pipe_in_fixture(FOOBAR_TXT).run().stdout_is_fixture("follow_stdin.expected"); +} + +#[test] +fn test_follow_with_pid() { + let (at, mut ucmd) = at_and_ucmd!(); + + #[cfg(unix)] + let dummy_cmd = "sh"; + #[cfg(windows)] + let dummy_cmd = "cmd"; + + let mut dummy = Command::new(dummy_cmd).stdout(Stdio::null()).spawn().unwrap(); + let pid = dummy.id(); + + let mut child = ucmd.arg("-f").arg(format!("--pid={}", pid)).arg(FOOBAR_TXT).arg(FOOBAR_2_TXT).run_no_wait(); + + let expected = at.read("foobar_follow_multiple.expected"); + assert_eq!(read_size(&mut child, expected.len()), expected); + + let first_append = "trois\n"; + at.append(FOOBAR_2_TXT, first_append); + assert_eq!(read_size(&mut child, first_append.len()), first_append); + + let second_append = "doce\ntrece\n"; + let expected = at.read("foobar_follow_multiple_appended.expected"); + at.append(FOOBAR_TXT, second_append); + assert_eq!(read_size(&mut child, expected.len()), expected); + + // kill the dummy process and give tail time to notice this + dummy.kill().unwrap(); + let _ = dummy.wait(); + sleep(Duration::from_secs(1)); + + let third_append = "should\nbe\nignored\n"; + at.append(FOOBAR_TXT, third_append); + assert_eq!(read_size(&mut child, 1), "\u{0}"); + + // On Unix, trying to kill a process that's already dead is fine; on Windows it's an error. + #[cfg(unix)] + child.kill().unwrap(); + #[cfg(windows)] + assert_eq!(child.kill().is_err(), true); +} + +#[test] +fn test_single_big_args() { + const FILE: &'static str = "single_big_args.txt"; + const EXPECTED_FILE: &'static str = "single_big_args_expected.txt"; + const LINES: usize = 1_000_000; + const N_ARG: usize = 100_000; + + let (at, mut ucmd) = at_and_ucmd!(); + + let mut big_input = at.make_file(FILE); + for i in 0..LINES { + write!(&mut big_input, "Line {}\n", i).expect("Could not write to FILE"); + } + big_input.flush().expect("Could not flush FILE"); + + let mut big_expected = at.make_file(EXPECTED_FILE); + for i in (LINES - N_ARG)..LINES { + write!(&mut big_expected, "Line {}\n", i).expect("Could not write to EXPECTED_FILE"); + } + big_expected.flush().expect("Could not flush EXPECTED_FILE"); + + ucmd.arg(FILE).arg("-n").arg(format!("{}", N_ARG)).run().stdout_is(at.read(EXPECTED_FILE)); +} + +#[test] +fn test_bytes_single() { + new_ucmd!().arg("-c").arg("10").arg(FOOBAR_TXT).run() + .stdout_is_fixture("foobar_bytes_single.expected"); +} + +#[test] +fn test_bytes_stdin() { + new_ucmd!().arg("-c").arg("13").pipe_in_fixture(FOOBAR_TXT).run() + .stdout_is_fixture("foobar_bytes_stdin.expected"); +} + +#[test] +fn test_bytes_big() { + const FILE: &'static str = "test_bytes_big.txt"; + const EXPECTED_FILE: &'static str = "test_bytes_big_expected.txt"; + const BYTES: usize = 1_000_000; + const N_ARG: usize = 100_000; + + let (at, mut ucmd) = at_and_ucmd!(); + + let mut big_input = at.make_file(FILE); + for i in 0..BYTES { + let digit = from_digit((i % 10) as u32, 10).unwrap(); + write!(&mut big_input, "{}", digit).expect("Could not write to FILE"); + } + big_input.flush().expect("Could not flush FILE"); + + let mut big_expected = at.make_file(EXPECTED_FILE); + for i in (BYTES - N_ARG)..BYTES { + let digit = from_digit((i % 10) as u32, 10).unwrap(); + write!(&mut big_expected, "{}", digit).expect("Could not write to EXPECTED_FILE"); + } + big_expected.flush().expect("Could not flush EXPECTED_FILE"); + + let result = ucmd.arg(FILE).arg("-c").arg(format!("{}", N_ARG)).run().stdout; + let expected = at.read(EXPECTED_FILE); + + assert_eq!(result.len(), expected.len()); + for (actual_char, expected_char) in result.chars().zip(expected.chars()) { + assert_eq!(actual_char, expected_char); + } +} + +#[test] +fn test_parse_size() { + // No suffix. + assert_eq!(Ok(1234), parse_size("1234")); + + // kB is 1000 + assert_eq!(Ok(9 * 1000), parse_size("9kB")); + + // K is 1024 + assert_eq!(Ok(2 * 1024), parse_size("2K")); + + let suffixes = [ + ('M', 2u32), + ('G', 3u32), + ('T', 4u32), + ('P', 5u32), + ('E', 6u32), + ]; + + for &(c, exp) in &suffixes { + let s = format!("2{}B", c); + assert_eq!(Ok(2 * (1000 as u64).pow(exp)), parse_size(&s)); + + let s = format!("2{}", c); + assert_eq!(Ok(2 * (1024 as u64).pow(exp)), parse_size(&s)); + } + + // Sizes that are too big. + assert!(parse_size("1Z").is_err()); + assert!(parse_size("1Y").is_err()); + + // Bad number + assert!(parse_size("328hdsf3290").is_err()); +} + +#[test] +fn test_lines_with_size_suffix() { + const FILE: &'static str = "test_lines_with_size_suffix.txt"; + const EXPECTED_FILE: &'static str = "test_lines_with_size_suffix_expected.txt"; + const LINES: usize = 3_000; + const N_ARG: usize = 2 * 1024; + + let (at, mut ucmd) = at_and_ucmd!(); + + let mut big_input = at.make_file(FILE); + for i in 0..LINES { + writeln!(&mut big_input, "Line {}", i).expect("Could not write to FILE"); + } + big_input.flush().expect("Could not flush FILE"); + + let mut big_expected = at.make_file(EXPECTED_FILE); + for i in (LINES - N_ARG)..LINES { + writeln!(&mut big_expected, "Line {}", i).expect("Could not write to EXPECTED_FILE"); + } + big_expected.flush().expect("Could not flush EXPECTED_FILE"); + + ucmd.arg(FILE).arg("-n").arg("2K").run().stdout_is_fixture(EXPECTED_FILE); +} + +#[test] +fn test_multiple_input_files() { + new_ucmd!().arg(FOOBAR_TXT).arg(FOOBAR_2_TXT).run().stdout_is_fixture("foobar_follow_multiple.expected"); +} + +#[test] +fn test_multiple_input_files_with_suppressed_headers() { + new_ucmd!().arg(FOOBAR_TXT).arg(FOOBAR_2_TXT).arg("-q").run().stdout_is_fixture("foobar_multiple_quiet.expected"); +} + +#[test] +fn test_multiple_input_quiet_flag_overrides_verbose_flag_for_suppressing_headers() { + // TODO: actually the later one should win, i.e. -qv should lead to headers being printed, -vq to them being suppressed + new_ucmd!().arg(FOOBAR_TXT).arg(FOOBAR_2_TXT).arg("-q").arg("-v").run().stdout_is_fixture("foobar_multiple_quiet.expected"); +} diff --git a/coreutils/tests/test_test.rs b/coreutils/tests/test_test.rs new file mode 100644 index 000000000..048a11489 --- /dev/null +++ b/coreutils/tests/test_test.rs @@ -0,0 +1,32 @@ +// +// This file is part of the uutils coreutils package. +// +// (c) mahkoh (ju.orth [at] gmail [dot] com) +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// + +use common::util::*; + + +#[test] +fn test_op_prec_and_or_1() { + new_ucmd!() + .args(&[" ", "-o", "", "-a", ""]) + .succeeds(); +} + +#[test] +fn test_op_prec_and_or_2() { + new_ucmd!() + .args(&["", "-a", "", "-o", " ", "-a", " "]) + .succeeds(); +} + +#[test] +fn test_or_as_filename() { + new_ucmd!() + .args(&["x", "-a", "-z", "-o"]) + .fails(); +} diff --git a/coreutils/tests/test_touch.rs b/coreutils/tests/test_touch.rs new file mode 100644 index 000000000..f219e0183 --- /dev/null +++ b/coreutils/tests/test_touch.rs @@ -0,0 +1,286 @@ +extern crate uu_touch; +use self::uu_touch::filetime::{self, FileTime}; + +extern crate time; + +use common::util::*; + +fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) { + let m = at.metadata(path); + (FileTime::from_last_access_time(&m), + FileTime::from_last_modification_time(&m)) +} + +fn get_symlink_times(at: &AtPath, path: &str) -> (FileTime, FileTime) { + let m = at.symlink_metadata(path); + (FileTime::from_last_access_time(&m), + FileTime::from_last_modification_time(&m)) +} + +fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) { + filetime::set_file_times(&at.plus_as_string(path), atime, mtime).unwrap() +} + +// Adjusts for local timezone +fn str_to_filetime(format: &str, s: &str) -> FileTime { + let mut tm = time::strptime(s, format).unwrap(); + tm.tm_utcoff = time::now().tm_utcoff; + let ts = tm.to_timespec(); + FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) +} + +#[test] +fn test_touch_default() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_default_file"; + + ucmd.arg(file).succeeds().no_stderr(); + + + assert!(at.file_exists(file)); +} + +#[test] +fn test_touch_no_create_file_absent() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_no_create_file_absent"; + + ucmd.arg("-c").arg(file).succeeds().no_stderr(); + + assert!(!at.file_exists(file)); +} + +#[test] +fn test_touch_no_create_file_exists() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_no_create_file_exists"; + + at.touch(file); + assert!(at.file_exists(file)); + + ucmd.arg("-c").arg(file).succeeds().no_stderr(); + + assert!(at.file_exists(file)); +} + +#[test] +fn test_touch_set_mdhm_time() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_mdhm_time"; + + ucmd.args(&["-t", "01011234", file]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", &format!("{}01010000", 1900+time::now().tm_year)); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), + 45240); + assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), + 45240); +} + +#[test] +fn test_touch_set_mdhms_time() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_mdhms_time"; + + ucmd.args(&["-t", "01011234.56", file]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M.%S", &format!("{}01010000.00", 1900+time::now().tm_year)); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), + 45296); + assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), + 45296); +} + +#[test] +fn test_touch_set_ymdhm_time() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_ymdhm_time"; + + ucmd.args(&["-t", "1501011234", file]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + let start_of_year = str_to_filetime("%y%m%d%H%M", "1501010000"); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), + 45240); + assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), + 45240); +} + +#[test] +fn test_touch_set_ymdhms_time() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_ymdhms_time"; + + ucmd.args(&["-t", "1501011234.56", file]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + let start_of_year = str_to_filetime("%y%m%d%H%M.%S", "1501010000.00"); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), + 45296); + assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), + 45296); +} + +#[test] +fn test_touch_set_cymdhm_time() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_cymdhm_time"; + + ucmd.args(&["-t", "201501011234", file]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), + 45240); + assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), + 45240); +} + +#[test] +fn test_touch_set_cymdhms_time() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_cymdhms_time"; + + ucmd.args(&["-t", "201501011234.56", file]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M.%S", "201501010000.00"); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), + 45296); + assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), + 45296); +} + +#[test] +fn test_touch_set_only_atime() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_only_atime"; + + ucmd.args(&["-t", "201501011234", "-a", file]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let (atime, mtime) = get_file_times(&at, file); + assert!(atime != mtime); + assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), + 45240); +} + +#[test] +fn test_touch_set_only_mtime() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_only_mtime"; + + ucmd.args(&["-t", "201501011234", "-m", file]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let (atime, mtime) = get_file_times(&at, file); + assert!(atime != mtime); + assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), + 45240); +} + +#[test] +fn test_touch_set_both() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_both"; + + ucmd.args(&["-t", "201501011234", "-a", "-m", file]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), + 45240); + assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), + 45240); +} + +#[test] +fn test_touch_no_dereference() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_touch_no_dereference_a"; + let file_b = "test_touch_no_dereference_b"; + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let end_of_year = str_to_filetime("%Y%m%d%H%M", "201512312359"); + + at.touch(file_a); + set_file_times(&at, file_a, start_of_year, start_of_year); + at.symlink_file(file_a, file_b); + assert!(at.file_exists(file_a)); + assert!(at.is_symlink(file_b)); + + ucmd.args(&["-t", "201512312359", "-h", file_b]).succeeds().no_stderr(); + + let (atime, mtime) = get_symlink_times(&at, file_b); + assert_eq!(atime, mtime); + assert_eq!(atime, end_of_year); + assert_eq!(mtime, end_of_year); + + let (atime, mtime) = get_file_times(&at, file_a); + assert_eq!(atime, mtime); + assert_eq!(atime, start_of_year); + assert_eq!(mtime, start_of_year); +} + +#[test] +fn test_touch_reference() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_touch_reference_a"; + let file_b = "test_touch_reference_b"; + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + + at.touch(file_a); + set_file_times(&at, file_a, start_of_year, start_of_year); + assert!(at.file_exists(file_a)); + + ucmd.args(&["-r", file_a, file_b]).succeeds().no_stderr(); + + assert!(at.file_exists(file_b)); + + let (atime, mtime) = get_file_times(&at, file_b); + assert_eq!(atime, mtime); + assert_eq!(atime, start_of_year); + assert_eq!(mtime, start_of_year); +} + +#[test] +fn test_touch_set_date() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_date"; + + ucmd.args(&["-d", "Thu Jan 01 12:34:00 2015", file]).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501011234"); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime, start_of_year); + assert_eq!(mtime, start_of_year); +} diff --git a/coreutils/tests/test_tr.rs b/coreutils/tests/test_tr.rs new file mode 100644 index 000000000..e1dbf2dc6 --- /dev/null +++ b/coreutils/tests/test_tr.rs @@ -0,0 +1,83 @@ +use common::util::*; + + +#[test] +fn test_toupper() { + new_ucmd!() + .args(&["a-z", "A-Z"]).pipe_in("!abcd!").run().stdout_is("!ABCD!"); +} + +#[test] +fn test_small_set2() { + new_ucmd!() + .args(&["0-9", "X"]).pipe_in("@0123456789").run().stdout_is("@XXXXXXXXXX"); +} + +#[test] +fn test_unicode() { + new_ucmd!() + .args(&[", ┬─┬", "╯︵┻━┻"]) + .pipe_in("(,°□°), ┬─┬").run() + .stdout_is("(╯°□°)╯︵┻━┻"); +} + +#[test] +fn test_delete() { + new_ucmd!() + .args(&["-d", "a-z"]).pipe_in("aBcD").run().stdout_is("BD"); +} + +#[test] +fn test_delete_complement() { + new_ucmd!() + .args(&["-d", "-c", "a-z"]).pipe_in("aBcD").run().stdout_is("ac"); +} + +#[test] +fn test_squeeze() { + new_ucmd!() + .args(&["-s", "a-z"]).pipe_in("aaBBcDcc").run().stdout_is("aBBcDc"); +} + + +#[test] +fn test_squeeze_complement() { + new_ucmd!() + .args(&["-sc", "a-z"]).pipe_in("aaBBcDcc").run().stdout_is("aaBcDcc"); +} + +#[test] +fn test_delete_and_squeeze() { + new_ucmd!() + .args(&["-ds", "a-z", "A-Z"]).pipe_in("abBcB").run().stdout_is("B"); +} + +#[test] +fn test_delete_and_squeeze_complement() { + new_ucmd!() + .args(&["-dsc", "a-z", "A-Z"]).pipe_in("abBcB").run().stdout_is("abc"); +} + +#[test] +fn test_set1_longer_than_set2() { + new_ucmd!() + .args(&["abc", "xy"]).pipe_in("abcde").run().stdout_is("xyyde"); +} + +#[test] +fn test_set1_shorter_than_set2() { + new_ucmd!() + .args(&["ab", "xyz"]).pipe_in("abcde").run().stdout_is("xycde"); +} + +#[test] +fn test_truncate() { + new_ucmd!() + .args(&["-t", "abc", "xy"]).pipe_in("abcde").run().stdout_is("xycde"); +} + +#[test] +fn test_truncate_with_set1_shorter_than_set2() { + new_ucmd!() + .args(&["-t", "ab", "xyz"]).pipe_in("abcde").run().stdout_is("xycde"); +} diff --git a/coreutils/tests/test_true.rs b/coreutils/tests/test_true.rs new file mode 100644 index 000000000..e8dbb0fdc --- /dev/null +++ b/coreutils/tests/test_true.rs @@ -0,0 +1,7 @@ +use common::util::*; + + +#[test] +fn test_exit_code() { + new_ucmd!().succeeds(); +} diff --git a/coreutils/tests/test_truncate.rs b/coreutils/tests/test_truncate.rs new file mode 100644 index 000000000..005dc0ec5 --- /dev/null +++ b/coreutils/tests/test_truncate.rs @@ -0,0 +1,26 @@ +use common::util::*; +use std::io::{Seek, SeekFrom, Write}; + + +static TFILE1: &'static str = "truncate_test_1"; +static TFILE2: &'static str = "truncate_test_2"; + +#[test] +fn test_increase_file_size() { + let (at, mut ucmd) = at_and_ucmd!(); + let mut file = at.make_file(TFILE1); + ucmd.args(&["-s", "+5K", TFILE1]).succeeds(); + + file.seek(SeekFrom::End(0)).unwrap(); + assert!(file.seek(SeekFrom::Current(0)).unwrap() == 5 * 1024); +} + +#[test] +fn test_decrease_file_size() { + let (at, mut ucmd) = at_and_ucmd!(); + let mut file = at.make_file(TFILE2); + file.write_all(b"1234567890").unwrap(); + ucmd.args(&["--size=-4", TFILE2]).succeeds(); + file.seek(SeekFrom::End(0)).unwrap(); + assert!(file.seek(SeekFrom::Current(0)).unwrap() == 6); +} diff --git a/coreutils/tests/test_tsort.rs b/coreutils/tests/test_tsort.rs new file mode 100644 index 000000000..9503ad969 --- /dev/null +++ b/coreutils/tests/test_tsort.rs @@ -0,0 +1,17 @@ +use common::util::*; + + +#[test] +fn test_sort_call_graph() { + new_ucmd!() + .arg("call_graph.txt") + .run() + .stdout_is_fixture("call_graph.expected"); +} + +#[test] +fn test_sort_self_loop() { + new_ucmd!() + .pipe_in("first first\nfirst second second second") + .succeeds().stdout_only("first\nsecond\n"); +} diff --git a/coreutils/tests/test_unexpand.rs b/coreutils/tests/test_unexpand.rs new file mode 100644 index 000000000..49220dcb7 --- /dev/null +++ b/coreutils/tests/test_unexpand.rs @@ -0,0 +1,112 @@ +use common::util::*; + + +#[test] +fn unexpand_init_0() { + new_ucmd!() + .args(&["-t4"]).pipe_in(" 1\n 2\n 3\n 4\n") + .run().stdout_is(" 1\n 2\n 3\n\t4\n"); +} + +#[test] +fn unexpand_init_1() { + new_ucmd!() + .args(&["-t4"]).pipe_in(" 5\n 6\n 7\n 8\n") + .run().stdout_is("\t 5\n\t 6\n\t 7\n\t\t8\n"); +} + +#[test] +fn unexpand_init_list_0() { + new_ucmd!() + .args(&["-t2,4"]).pipe_in(" 1\n 2\n 3\n 4\n") + .run().stdout_is(" 1\n\t2\n\t 3\n\t\t4\n"); +} + +#[test] +fn unexpand_init_list_1() { + // Once the list is exhausted, spaces are not converted anymore + new_ucmd!() + .args(&["-t2,4"]).pipe_in(" 5\n 6\n 7\n 8\n") + .run().stdout_is("\t\t 5\n\t\t 6\n\t\t 7\n\t\t 8\n"); +} + +#[test] +fn unexpand_aflag_0() { + new_ucmd!() + .args(&["--"]).pipe_in("e E\nf F\ng G\nh H\n") + .run().stdout_is("e E\nf F\ng G\nh H\n"); +} + +#[test] +fn unexpand_aflag_1() { + new_ucmd!() + .args(&["-a"]).pipe_in("e E\nf F\ng G\nh H\n") + .run().stdout_is("e E\nf F\ng\tG\nh\t H\n"); +} + +#[test] +fn unexpand_aflag_2() { + new_ucmd!() + .args(&["-t8"]).pipe_in("e E\nf F\ng G\nh H\n") + .run().stdout_is("e E\nf F\ng\tG\nh\t H\n"); +} + +#[test] +fn unexpand_first_only_0() { + new_ucmd!() + .args(&["-t3"]).pipe_in(" A B") + .run().stdout_is("\t\t A\t B"); +} + +#[test] +fn unexpand_first_only_1() { + new_ucmd!() + .args(&["-t3", "--first-only"]).pipe_in(" A B") + .run().stdout_is("\t\t A B"); +} + +#[test] +fn unexpand_trailing_space_0() { + // evil + // Individual spaces before fields starting with non blanks should not be + // converted, unless they are at the beginning of the line. + new_ucmd!() + .args(&["-t4"]).pipe_in("123 \t1\n123 1\n123 \n123 ") + .run().stdout_is("123\t\t1\n123 1\n123 \n123 "); +} + +#[test] +fn unexpand_trailing_space_1() { + // super evil + new_ucmd!() + .args(&["-t1"]).pipe_in(" abc d e f g ") + .run().stdout_is("\tabc d e\t\tf\t\tg "); +} + +#[test] +fn unexpand_spaces_follow_tabs_0() { + // The two first spaces can be included into the first tab. + new_ucmd!() + .pipe_in(" \t\t A") + .run().stdout_is("\t\t A"); +} + +#[test] +fn unexpand_spaces_follow_tabs_1() { + // evil + // Explanation of what is going on here: + // 'a' -> 'a' // first tabstop (1) + // ' \t' -> '\t' // second tabstop (4) + // ' ' -> '\t' // third tabstop (5) + // ' B \t' -> ' B \t' // after the list is exhausted, nothing must change + new_ucmd!() + .args(&["-t1,4,5"]).pipe_in("a \t B \t") + .run().stdout_is("a\t\t B \t"); +} + +#[test] +fn unexpand_spaces_after_fields() { + new_ucmd!() + .args(&["-a"]).pipe_in(" \t A B C D A\t\n") + .run().stdout_is("\t\tA B C D\t\t A\t\n"); +} diff --git a/coreutils/tests/test_uniq.rs b/coreutils/tests/test_uniq.rs new file mode 100644 index 000000000..ffc4d5e37 --- /dev/null +++ b/coreutils/tests/test_uniq.rs @@ -0,0 +1,105 @@ +use common::util::*; + + +static INPUT: &'static str = "sorted.txt"; +static SKIP_CHARS: &'static str = "skip-chars.txt"; +static SKIP_FIELDS: &'static str = "skip-fields.txt"; +static SORTED_ZERO_TERMINATED: &'static str = "sorted-zero-terminated.txt"; + +#[test] +fn test_stdin_default() { + new_ucmd!() + .pipe_in_fixture(INPUT) + .run().stdout_is_fixture("sorted-simple.expected"); +} + +#[test] +fn test_single_default() { + new_ucmd!() + .arg(INPUT) + .run().stdout_is_fixture("sorted-simple.expected"); +} + +#[test] +fn test_stdin_counts() { + new_ucmd!() + .args(&["-c"]).pipe_in_fixture(INPUT) + .run().stdout_is_fixture("sorted-counts.expected"); +} + +#[test] +fn test_stdin_skip_1_char() { + new_ucmd!() + .args(&["-s1"]).pipe_in_fixture(SKIP_CHARS) + .run().stdout_is_fixture("skip-1-char.expected"); +} + +#[test] +fn test_stdin_skip_5_chars() { + new_ucmd!() + .args(&["-s5"]).pipe_in_fixture(SKIP_CHARS) + .run().stdout_is_fixture("skip-5-chars.expected"); +} + +#[test] +fn test_stdin_skip_and_check_2_chars() { + new_ucmd!() + .args(&["-s3", "-w2"]).pipe_in_fixture(SKIP_CHARS) + .run().stdout_is_fixture("skip-3-check-2-chars.expected"); +} + +#[test] +fn test_stdin_skip_1_field() { + new_ucmd!() + .args(&["-f2"]).pipe_in_fixture(SKIP_FIELDS) + .run().stdout_is_fixture("skip-2-fields.expected"); +} + +#[test] +fn test_stdin_all_repeated() { + new_ucmd!() + .args(&["--all-repeated"]).pipe_in_fixture(INPUT) + .run().stdout_is_fixture("sorted-all-repeated.expected"); +} + +#[test] +fn test_stdin_all_repeated_separate() { + new_ucmd!() + .args(&["--all-repeated=separate"]).pipe_in_fixture(INPUT) + .run().stdout_is_fixture("sorted-all-repeated-separate.expected"); +} + +#[test] +fn test_stdin_all_repeated_prepend() { + new_ucmd!() + .args(&["--all-repeated=prepend"]).pipe_in_fixture(INPUT) + .run().stdout_is_fixture("sorted-all-repeated-prepend.expected"); +} + +#[test] +fn test_stdin_unique_only() { + new_ucmd!() + .args(&["-u"]).pipe_in_fixture(INPUT) + .run().stdout_is_fixture("sorted-unique-only.expected"); +} + +#[test] +fn test_stdin_repeated_only() { + new_ucmd!() + .args(&["-d"]).pipe_in_fixture(INPUT) + .run().stdout_is_fixture("sorted-repeated-only.expected"); +} + +#[test] +fn test_stdin_ignore_case() { + new_ucmd!() + .args(&["-i"]).pipe_in_fixture(INPUT) + .run().stdout_is_fixture("sorted-ignore-case.expected"); +} + +#[test] +fn test_stdin_zero_terminated() { + new_ucmd!() + .args(&["-z"]).pipe_in_fixture(SORTED_ZERO_TERMINATED) + .run().stdout_is_fixture("sorted-zero-terminated.expected"); +} diff --git a/coreutils/tests/test_unlink.rs b/coreutils/tests/test_unlink.rs new file mode 100644 index 000000000..1146a4085 --- /dev/null +++ b/coreutils/tests/test_unlink.rs @@ -0,0 +1,49 @@ +use common::util::*; + + +#[test] +fn test_unlink_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_unlink_file"; + + at.touch(file); + + ucmd.arg(file).succeeds().no_stderr(); + + assert!(!at.file_exists(file)); +} + +#[test] +fn test_unlink_multiple_files() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_unlink_multiple_file_a"; + let file_b = "test_unlink_multiple_file_b"; + + at.touch(file_a); + at.touch(file_b); + + 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"); +} + +#[test] +fn test_unlink_directory() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "test_unlink_empty_directory"; + + at.mkdir(dir); + + ucmd.arg(dir).fails() + .stderr_is("unlink: error: cannot unlink 'test_unlink_empty_directory': Not a regular file \ + or symlink\n"); +} + +#[test] +fn test_unlink_nonexistent() { + let file = "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"); +} diff --git a/coreutils/tests/test_wc.rs b/coreutils/tests/test_wc.rs new file mode 100644 index 000000000..6d3027c2b --- /dev/null +++ b/coreutils/tests/test_wc.rs @@ -0,0 +1,50 @@ +use common::util::*; + + +#[test] +fn test_stdin_default() { + new_ucmd!().pipe_in_fixture("lorem_ipsum.txt") + .run().stdout_is(" 13 109 772\n"); +} + +#[test] +fn test_stdin_only_bytes() { + new_ucmd!().args(&["-c"]).pipe_in_fixture("lorem_ipsum.txt") + .run().stdout_is(" 772\n"); +} + +#[test] +fn test_stdin_all_counts() { + new_ucmd!().args(&["-c", "-m", "-l", "-L", "-w"]) + .pipe_in_fixture("alice_in_wonderland.txt") + .run() + .stdout_is(" 5 57 302 302 66\n"); +} + +#[test] +fn test_single_default() { + new_ucmd!() + .arg("moby_dick.txt").run().stdout_is(" 18 204 1115 moby_dick.txt\n"); +} + +#[test] +fn test_single_only_lines() { + new_ucmd!() + .args(&["-l", "moby_dick.txt"]).run().stdout_is(" 18 moby_dick.txt\n"); +} + +#[test] +fn test_single_all_counts() { + new_ucmd!() + .args(&["-c", "-l", "-L", "-m", "-w", "alice_in_wonderland.txt"]).run() + .stdout_is(" 5 57 302 302 66 alice_in_wonderland.txt\n"); +} + +#[test] +fn test_multiple_default() { + new_ucmd!() + .args(&["lorem_ipsum.txt", "moby_dick.txt", "alice_in_wonderland.txt"]).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"); +} diff --git a/coreutils/tests/test_who.rs b/coreutils/tests/test_who.rs new file mode 100644 index 000000000..039f2e86c --- /dev/null +++ b/coreutils/tests/test_who.rs @@ -0,0 +1,72 @@ +use common::util::*; + + + +#[cfg(target_os = "linux")] +#[test] +fn test_count() { + for opt in vec!["-q", "--count"] { + new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_boot() { + for opt in vec!["-b", "--boot"] { + new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_heading() { + for opt in vec!["-H"] { + new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_short() { + for opt in vec!["-s", "--short"] { + new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_login() { + for opt in vec!["-l", "--login"] { + new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_m() { + for opt in vec!["-m"] { + new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_dead() { + for opt in vec!["-d", "--dead"] { + new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_all() { + for opt in vec!["-a", "--all"] { + new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +fn expected_result(arg: &str) -> String { + TestScenario::new(util_name!()).cmd_keepenv(util_name!()).env("LANGUAGE", "C").args(&[arg]).run().stdout +} diff --git a/coreutils/tests/tests.rs b/coreutils/tests/tests.rs new file mode 100644 index 000000000..f74d8e326 --- /dev/null +++ b/coreutils/tests/tests.rs @@ -0,0 +1,98 @@ +#[macro_use] +mod common; + +#[cfg(unix)] +#[macro_use] +extern crate lazy_static; + +#[cfg(unix)] +extern crate rust_users; + +// For conditional compilation +macro_rules! unix_only { + ($($fea:expr, $m:ident);+) => { + $( + #[cfg(unix)] + #[cfg(feature = $fea)] + mod $m; + )+ + }; +} +unix_only! { + "chmod", test_chmod; + "chown", test_chown; + "chgrp", test_chgrp; + "install", test_install; + "pathchk", test_pathchk; + "pinky", test_pinky; + "stdbuf", test_stdbuf; + "unlink", test_unlink; + "who", test_who; + // Be aware of the trailing semicolon after the last item + "stat", test_stat +} + +macro_rules! generic { + ($($fea:expr, $m:ident);+) => { + $( + #[cfg(feature = $fea)] + mod $m; + )+ + }; +} +generic! { + "base32", test_base32; + "base64", test_base64; + "basename", test_basename; + "cat", test_cat; + "cksum", test_cksum; + "comm", test_comm; + "cp", test_cp; + "cut", test_cut; + "dircolors", test_dircolors; + "dirname", test_dirname; + "du", test_du; + "echo", test_echo; + "env", test_env; + "expr", test_expr; + "factor", test_factor; + "false", test_false; + "fold", test_fold; + "hashsum", test_hashsum; + "head", test_head; + "join", test_join; + "link", test_link; + "ln", test_ln; + "ls", test_ls; + "mkdir", test_mkdir; + "mktemp", test_mktemp; + "mv", test_mv; + "numfmt", test_numfmt; + "nl", test_nl; + "od", test_od; + "paste", test_paste; + "printf", test_printf; + "ptx", test_ptx; + "pwd", test_pwd; + "readlink", test_readlink; + "realpath", test_realpath; + "rm", test_rm; + "rmdir", test_rmdir; + "seq", test_seq; + "sort", test_sort; + "split", test_split; + "sum", test_sum; + "tac", test_tac; + "tail", test_tail; + "test", test_test; + "touch", test_touch; + "tr", test_tr; + "true", test_true; + "truncate", test_truncate; + "tsort", test_tsort; + "unexpand", test_unexpand; + "uniq", test_uniq; + "wc", test_wc; + // Be aware of the trailing semicolon after the last item + "hostname", test_hostname +} diff --git a/coreutils/util/rewrite_rules.rs b/coreutils/util/rewrite_rules.rs new file mode 100644 index 000000000..6aba8166c --- /dev/null +++ b/coreutils/util/rewrite_rules.rs @@ -0,0 +1,18 @@ +//! Rules to update the codebase using Rerast + +/// Converts try!() to ? +fn try_to_question_mark>(r: Result) -> Result { + replace!(try!(r) => r?); + unreachable!() +} + +fn trim_left_to_start(s: &str) { + replace!(s.trim_left() => s.trim_start()); + replace!(s.trim_right() => s.trim_end()); +} + +fn trim_left_matches_to_start bool>(s: &str, inner: P) { + replace!(s.trim_left_matches(inner) => s.trim_start_matches(inner)); + replace!(s.trim_right_matches(inner) => s.trim_end_matches(inner)); +} + diff --git a/coreutils/uumain.rs b/coreutils/uumain.rs new file mode 100644 index 000000000..d3c87408c --- /dev/null +++ b/coreutils/uumain.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/main.rs")); From e99049e3c24b939808d89514d84389da3e3530c0 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Mon, 21 Oct 2019 13:40:43 +0000 Subject: [PATCH 02/10] update coreutils from coreutils_sunrise master --- coreutils/Cargo.toml | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/coreutils/Cargo.toml b/coreutils/Cargo.toml index 551278865..6ccd209ac 100644 --- a/coreutils/Cargo.toml +++ b/coreutils/Cargo.toml @@ -238,9 +238,6 @@ sunrise_generic = [ test_unimplemented = [] nightly = [] -default = ["unix"] - -[workspace] [dependencies] uucore = "0.0.1" @@ -359,35 +356,3 @@ path = "src/uutils/uutils.rs" [[test]] name = "tests" - -[patch.crates-io.libc] -git = "https://github.com/sunriseos/libc.git" -branch = "sunrise-0.2.62" - -[patch.crates-io.uucore] -git = "https://github.com/sunriseos/uucore.git" -branch = "master" - -[patch.crates-io.atty] -git = "https://github.com/sunriseos/atty.git" -branch = "master" - -[patch.crates-io.clap] -git = "https://github.com/sunriseos/clap.git" -branch = "master" - -[patch.crates-io.filetime] -git = "https://github.com/sunriseos/filetime.git" -branch = "master" - -[patch.crates-io.platform-info] -git = "https://github.com/sunriseos/platform-info.git" -branch = "master" - -[patch.crates-io.time] -git = "https://github.com/sunriseos/time.git" -branch = "v0.1" - -[patch.crates-io.termsize] -git = "https://github.com/sunriseos/termsize.git" -branch = "master" From 62b6c4cdc05a9ba3a8117cebf0a7adee7e8f0530 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Mon, 21 Oct 2019 15:02:40 +0000 Subject: [PATCH 03/10] Integrate coreutils to main repo --- Cargo.lock | 2214 +++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 35 +- Makefile.toml | 30 +- update_subtree | 23 +- 4 files changed, 2184 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d2042a2e..696f72f56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,23 @@ dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "advapi32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aho-corasick" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "aho-corasick" version = "0.7.6" @@ -25,6 +42,14 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "arch" +version = "0.0.1" +dependencies = [ + "platform-info 0.0.1 (git+https://github.com/sunriseos/platform-info.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "arrayvec" version = "0.4.11" @@ -41,9 +66,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "atty" version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/sunriseos/atty.git#2ac1c868a2fc532fde6cbe228c161d27ba0fd231" dependencies = [ - "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -52,6 +77,61 @@ name = "autocfg" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "backtrace" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", +] + +[[package]] +name = "base32" +version = "0.0.1" +dependencies = [ + "clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "base64" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "basename" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "bit-set" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit-vec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bit_field" version = "0.9.0" @@ -67,11 +147,30 @@ name = "bitfield" version = "0.13.2" 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 = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "block-buffer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "block-buffer" version = "0.7.3" @@ -104,6 +203,11 @@ name = "build_const" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "byte-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byte-tools" version = "0.3.1" @@ -119,18 +223,98 @@ name = "cargo-5730" version = "0.1.0" source = "git+https://github.com/Thog/cargo-5730.git?branch=feature/windows-support#be3c927c3d38df740db41f41fb872f130b79ea6f" +[[package]] +name = "cargo_metadata" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cat" +version = "0.0.1" +dependencies = [ + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "cc" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cfg-if" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "chgrp" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "chmod" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "walker 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "chown" +version = "0.0.1" +dependencies = [ + "clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "chrono" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", +] + +[[package]] +name = "chroot" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "cksum" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "clap" version = "2.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/sunriseos/clap.git#6dd48d847dd9149a34a9c8f55001ea3492e24950" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.13 (git+https://github.com/sunriseos/atty.git)", "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -138,16 +322,160 @@ dependencies = [ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "clippy" +version = "0.0.212" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy_lints 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clippy_lints" +version = "0.0.212" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cargo_metadata 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", + "if_chain 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "color_quant" version = "1.0.1" source = "git+https://github.com/SunriseOS/color_quant#3f7a1f3a166ace12001ebd932cd88d2d0858218a" +[[package]] +name = "comm" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "core-futures-tls" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cp" +version = "0.0.1" +dependencies = [ + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "filetime 0.2.7 (git+https://github.com/sunriseos/filetime.git)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "ioctl-sys 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cpp_macros 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_build" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_common 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_common" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_common 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_syn" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_synmap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cpp_synom" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crc" version = "1.8.1" @@ -156,6 +484,13 @@ dependencies = [ "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cut" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "darling" version = "0.9.0" @@ -188,6 +523,28 @@ dependencies = [ "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "data-encoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "date" +version = "0.0.1" +dependencies = [ + "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "digest" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "digest" version = "0.8.1" @@ -196,6 +553,22 @@ dependencies = [ "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dircolors" +version = "0.0.1" +dependencies = [ + "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "dirname" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "disk-initializer" version = "0.1.0" @@ -215,23 +588,88 @@ dependencies = [ name = "docs" version = "0.1.0" +[[package]] +name = "du" +version = "0.0.1" +dependencies = [ + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "echo" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "env" +version = "0.0.1" +dependencies = [ + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "env_logger" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.13 (git+https://github.com/sunriseos/atty.git)", "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "error-chain" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "expand" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "expr" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "onig 4.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "factor" +version = "0.0.1" +dependencies = [ + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "failure" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -252,19 +690,58 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "fnv" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "false" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] [[package]] -name = "font-rs" -version = "0.1.3" -source = "git+https://github.com/SunriseOS/font-rs#ae9d2f89e0b538c3233fe3ae8a082ddfe0ee978d" +name = "filetime" +version = "0.2.7" +source = "git+https://github.com/sunriseos/filetime.git#9e9e419fccc29ee6e2f06b4bbe03926b184a1f9b" dependencies = [ - "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fmt" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fold" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "font-rs" +version = "0.1.3" +source = "git+https://github.com/SunriseOS/font-rs#ae9d2f89e0b538c3233fe3ae8a082ddfe0ee978d" +dependencies = [ + "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "futures-channel-preview" version = "0.3.0-alpha.16" @@ -339,6 +816,15 @@ dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "generic-array" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "generic-array" version = "0.12.3" @@ -355,6 +841,14 @@ dependencies = [ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gif" version = "0.10.0" @@ -364,11 +858,53 @@ dependencies = [ "lzw 0.10.0 (git+https://github.com/SunriseOS/lzw)", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "groups" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "half" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "hashbrown" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "hashsum" +version = "0.0.1" +dependencies = [ + "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.12 (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)", + "sha3 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "head" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "heck" version = "0.3.1" @@ -377,11 +913,34 @@ dependencies = [ "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "hex" version = "0.3.2" source = "git+https://github.com/KokaKiwi/rust-hex#264b89f9ca171efe3bf9ca105617edfacaa96f6c" +[[package]] +name = "hostid" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "hostname" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "humantime" version = "1.3.0" @@ -390,11 +949,94 @@ dependencies = [ "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "id" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "if_chain" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "install" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "ioctl-sys" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itertools" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itertools" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "join" +version = "0.0.1" +dependencies = [ + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kill" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "lazy_static" version = "1.3.0" @@ -405,8 +1047,8 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.60" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.64" +source = "git+https://github.com/sunriseos/libc.git?branch=sunrise#53c8be041956681d19827c3173b1551a38984033" [[package]] name = "libfat" @@ -420,6 +1062,24 @@ dependencies = [ "storage_device 1.0.0 (git+https://github.com/sunriseos/storage_device.git)", ] +[[package]] +name = "libstdbuf" +version = "0.0.1" +dependencies = [ + "cpp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpp_build 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "link" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "linked_list_allocator" version = "0.6.4" @@ -428,6 +1088,14 @@ dependencies = [ "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ln" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "log" version = "0.4.8" @@ -436,6 +1104,14 @@ dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "logname" +version = "0.0.1" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "lru" version = "0.1.17" @@ -444,6 +1120,20 @@ dependencies = [ "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ls" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "termsize 0.1.6 (git+https://github.com/sunriseos/termsize.git)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "lzw" version = "0.10.0" @@ -473,170 +1163,836 @@ dependencies = [ ] [[package]] -name = "memchr" -version = "2.2.1" +name = "matches" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "multiboot2" -version = "0.7.1" -source = "git+https://github.com/sunriseos/multiboot2-elf64.git#01f37450ae3d7f996c34337853e96124bfa07822" -dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "nodrop" -version = "0.1.13" +name = "md5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "nom" -version = "4.2.3" +name = "memchr" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", ] [[package]] -name = "num-traits" -version = "0.2.8" +name = "memchr" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mkdir" +version = "0.0.1" dependencies = [ - "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] [[package]] -name = "opaque-debug" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "mkfifo" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] [[package]] -name = "pest" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "mknod" +version = "0.0.1" dependencies = [ - "ucd-trie 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] [[package]] -name = "pest_derive" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "mktemp" +version = "0.0.1" dependencies = [ - "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] [[package]] -name = "pest_generator" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "more" +version = "0.0.1" dependencies = [ - "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pest_meta 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", + "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)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] [[package]] -name = "pest_meta" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "multiboot2" +version = "0.7.1" +source = "git+https://github.com/sunriseos/multiboot2-elf64.git#01f37450ae3d7f996c34337853e96124bfa07822" dependencies = [ - "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "pin-utils" -version = "0.1.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "mv" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] [[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "nice" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] [[package]] -name = "proc-macro-hack" -version = "0.4.2" +name = "nix" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-hack-impl 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "proc-macro-hack-impl" -version = "0.4.2" +name = "nix" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "proc-macro2" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "nl" +version = "0.0.1" dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] [[package]] -name = "quick-error" -version = "1.2.2" +name = "nodrop" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "nohup" +version = "0.0.1" dependencies = [ - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] [[package]] -name = "regex" -version = "1.3.1" +name = "nom" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "regex-syntax" -version = "0.6.12" +name = "nproc" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "num-integer" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num_cpus" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", +] + +[[package]] +name = "number_prefix" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "numfmt" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "od" +version = "0.0.1" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "half 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "onig" +version = "4.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "onig_sys" +version = "69.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "opaque-debug" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "paste" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "pathchk" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pest" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-trie 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_generator" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_meta 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_meta" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pin-utils" +version = "0.1.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pinky" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "pkg-config" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "platform-info" +version = "0.0.1" +source = "git+https://github.com/sunriseos/platform-info.git#6f38ff0cf0e4c872567b8bdf20317a4836607461" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "printenv" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "printf" +version = "0.0.1" +dependencies = [ + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "proc-macro-hack" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack-impl 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-hack-impl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ptx" +version = "0.0.1" +dependencies = [ + "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "pulldown-cmark" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pwd" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quine-mc_cluskey" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "readlink" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "realpath" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "relpath" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rm" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rmdir" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rust-users" +version = "0.6.0" +source = "git+https://github.com/uutils/rust-users#e64253f2b995e7f1a458c68a7eca66e0171d183a" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "same-file" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "seq" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "serde" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha-1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "sha2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha3" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "rustc-demangle" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "shred" +version = "0.0.1" +dependencies = [ + "filetime 0.2.7 (git+https://github.com/sunriseos/filetime.git)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] [[package]] -name = "sha-1" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "shuf" +version = "0.0.1" dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] [[package]] -name = "sha1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "sleep" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] [[package]] name = "smallvec" version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "sort" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "spin" version = "0.4.10" @@ -647,6 +2003,23 @@ name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "split" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "stat" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "static_assertions" version = "0.3.3" @@ -659,6 +2032,16 @@ dependencies = [ "sunrise-libuser 0.1.0", ] +[[package]] +name = "stdbuf" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libstdbuf 0.0.1", + "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "storage_device" version = "1.0.0" @@ -683,7 +2066,7 @@ name = "structopt" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", "structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -698,6 +2081,14 @@ dependencies = [ "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sum" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "sunrise-ahci" version = "0.1.0" @@ -951,6 +2342,27 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syn" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sync" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "synstructure" version = "0.10.2" @@ -962,6 +2374,64 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tac" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "tail" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tee" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tempfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "term_grid" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "termcolor" version = "1.0.5" @@ -970,6 +2440,38 @@ dependencies = [ "wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "termion" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termsize" +version = "0.1.6" +source = "git+https://github.com/sunriseos/termsize.git#07ebe43eb0f57e60e89e00383f0557bb1d1e0693" +dependencies = [ + "atty 0.2.13 (git+https://github.com/sunriseos/atty.git)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "test" +version = "0.0.1" +dependencies = [ + "atty 0.2.13 (git+https://github.com/sunriseos/atty.git)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -986,6 +2488,26 @@ dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "time" +version = "0.1.42" +source = "git+https://github.com/sunriseos/time.git?branch=v0.1#79790b6c4e937c4591706e68143926f05696923d" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "timeout" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "tinybmp" version = "0.1.0" @@ -994,6 +2516,66 @@ dependencies = [ "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "toml" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "touch" +version = "0.0.1" +dependencies = [ + "filetime 0.2.7 (git+https://github.com/sunriseos/filetime.git)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "tr" +version = "0.0.1" +dependencies = [ + "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "true" +version = "0.0.1" +dependencies = [ + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "truncate" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "tsort" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "tty" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "typenum" version = "1.10.0" @@ -1004,6 +2586,40 @@ name = "ucd-trie" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "uname" +version = "0.0.1" +dependencies = [ + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "platform-info 0.0.1 (git+https://github.com/sunriseos/platform-info.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "unexpand" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-segmentation" version = "1.3.0" @@ -1014,16 +2630,212 @@ name = "unicode-width" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unindent" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "uniq" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "unix_socket" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", +] + +[[package]] +name = "unlink" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "uptime" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "users" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "uucore" +version = "0.0.1" +source = "git+https://github.com/sunriseos/uucore.git#a60175ba8d841347c44c9117eaab432e1c7c0144" +dependencies = [ + "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "platform-info 0.0.1 (git+https://github.com/sunriseos/platform-info.git)", + "termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "wild 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "uuid" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "uutils" +version = "0.0.1" +dependencies = [ + "arch 0.0.1", + "base32 0.0.1", + "base64 0.0.1", + "basename 0.0.1", + "cat 0.0.1", + "chgrp 0.0.1", + "chmod 0.0.1", + "chown 0.0.1", + "chroot 0.0.1", + "cksum 0.0.1", + "comm 0.0.1", + "cp 0.0.1", + "cut 0.0.1", + "date 0.0.1", + "dircolors 0.0.1", + "dirname 0.0.1", + "du 0.0.1", + "echo 0.0.1", + "env 0.0.1", + "expand 0.0.1", + "expr 0.0.1", + "factor 0.0.1", + "false 0.0.1", + "filetime 0.2.7 (git+https://github.com/sunriseos/filetime.git)", + "fmt 0.0.1", + "fold 0.0.1", + "groups 0.0.1", + "hashsum 0.0.1", + "head 0.0.1", + "hostid 0.0.1", + "hostname 0.0.1", + "id 0.0.1", + "install 0.0.1", + "join 0.0.1", + "kill 0.0.1", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", + "link 0.0.1", + "ln 0.0.1", + "logname 0.0.1", + "ls 0.0.1", + "mkdir 0.0.1", + "mkfifo 0.0.1", + "mknod 0.0.1", + "mktemp 0.0.1", + "more 0.0.1", + "mv 0.0.1", + "nice 0.0.1", + "nl 0.0.1", + "nohup 0.0.1", + "nproc 0.0.1", + "numfmt 0.0.1", + "od 0.0.1", + "paste 0.0.1", + "pathchk 0.0.1", + "pinky 0.0.1", + "printenv 0.0.1", + "printf 0.0.1", + "ptx 0.0.1", + "pwd 0.0.1", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "readlink 0.0.1", + "realpath 0.0.1", + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "relpath 0.0.1", + "rm 0.0.1", + "rmdir 0.0.1", + "rust-users 0.6.0 (git+https://github.com/uutils/rust-users)", + "seq 0.0.1", + "shred 0.0.1", + "shuf 0.0.1", + "sleep 0.0.1", + "sort 0.0.1", + "split 0.0.1", + "stat 0.0.1", + "stdbuf 0.0.1", + "sum 0.0.1", + "sync 0.0.1", + "tac 0.0.1", + "tail 0.0.1", + "tee 0.0.1", + "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "test 0.0.1", + "time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)", + "timeout 0.0.1", + "touch 0.0.1", + "tr 0.0.1", + "true 0.0.1", + "truncate 0.0.1", + "tsort 0.0.1", + "tty 0.0.1", + "uname 0.0.1", + "unexpand 0.0.1", + "unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "uniq 0.0.1", + "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unlink 0.0.1", + "uptime 0.0.1", + "users 0.0.1", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "wc 0.0.1", + "who 0.0.1", + "whoami 0.0.1", + "yes 0.0.1", +] + [[package]] name = "vec_map" version = "0.8.1" @@ -1034,6 +2846,65 @@ name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "walkdir" +version = "2.2.9" +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)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "walker" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wc" +version = "0.0.1" +dependencies = [ + "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "who" +version = "0.0.1" +dependencies = [ + "clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + +[[package]] +name = "whoami" +version = "0.0.1" +dependencies = [ + "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wild" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.3.7" @@ -1043,6 +2914,11 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1070,6 +2946,14 @@ dependencies = [ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", +] + [[package]] name = "xmas-elf" version = "0.7.0" @@ -1078,6 +2962,14 @@ dependencies = [ "zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "yes" +version = "0.0.1" +dependencies = [ + "clap 2.33.0 (git+https://github.com/sunriseos/clap.git)", + "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", +] + [[package]] name = "zero" version = "0.1.2" @@ -1085,38 +2977,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum acpi 0.1.0 (git+https://github.com/sunriseos/acpi.git)" = "" +"checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" +"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" "checksum atomic 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c210c1f4db048cda477b652d170572d84c9640695835f17663595d3bd543fc28" -"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" +"checksum atty 0.2.13 (git+https://github.com/sunriseos/atty.git)" = "" "checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" +"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" +"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" +"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" "checksum bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0" "checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" "checksum bitfield 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" +"checksum block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" "checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" "checksum bstr 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94cdf78eb7e94c566c1f5dbe2abf8fc70a548fc902942a48c4b3a98b48ca9ade" "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" +"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum cargo-5730 0.1.0 (git+https://github.com/Thog/cargo-5730.git?branch=feature/windows-support)" = "" +"checksum cargo_metadata 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1efca0b863ca03ed4c109fb1c55e0bc4bbeb221d3e103d86251046b06a526bd0" +"checksum cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68" +"checksum clap 2.33.0 (git+https://github.com/sunriseos/clap.git)" = "" +"checksum clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)" = "7e253af13a0cc39c7f22cf16f1be49d593dedc5895fe2fbb15f14d66ead00533" +"checksum clippy_lints 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)" = "bd2326065405649672adbd5cb30dad2fad3a470935653d51c70591d47d3a8512" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum color_quant 1.0.1 (git+https://github.com/SunriseOS/color_quant)" = "" "checksum core-futures-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0407dd6f9b5ed2842f9d287934231e3d270a240d2bb9bf8a56f02e37381c793a" +"checksum cpp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d1cd8699ffa1b18fd388183f7762e0545eddbd5c6ec95e9e3b42a4a71a507ff" +"checksum cpp_build 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c47531e7e09532ad4827098729794f5e1a5b1c2ccbb5e295498d2e7ab451c445" +"checksum cpp_common 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "79e39149a7943affa02f5b6e347ca2840a129cc78d5883ee229f0f1c4027d628" +"checksum cpp_macros 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6bba562eb4d65561efb6cef4e5f0de5936edfee7c6af7a4dfc323f6f2c997e40" +"checksum cpp_syn 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8cd649bf5b3804d92fe12a60c7698f5a538a6033ed8a668bf5241d4d4f1644e" +"checksum cpp_synmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "897e4f9cdbe2874edd3ffe53718ee5d8b89e2a970057b2c93d3214104f2e90b6" +"checksum cpp_synom 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fc8da5694233b646150c785118f77835ad0a49680c7f312a10ef30957c67b6d" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" "checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6" "checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c" "checksum darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d8dac1c6f1d29a41c4712b4400f878cb4fcc4c7628f298dd75038e024998d1" +"checksum data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" +"checksum digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum env_logger 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39ecdb7dd54465526f0a56d666e3b2dd5f3a218665a030b6e4ad9e70fa95d8fa" +"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum filetime 0.2.7 (git+https://github.com/sunriseos/filetime.git)" = "" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum font-rs 0.1.3 (git+https://github.com/SunriseOS/font-rs)" = "" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum futures-channel-preview 0.3.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4cd523712fc272e9b714669165a2832debee5a5b7e409bfccdc7c0d5cd0cf07a" "checksum futures-core-preview 0.3.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)" = "719770f328642b657b849856bb5a607db9538dd5bb3000122e5ead55d0a58c36" "checksum futures-executor-preview 0.3.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)" = "315dc58c908535d059576a329b86cd185933433382cfcd394fb2fa353330de03" @@ -1127,14 +3048,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum generational-arena 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "921c3803adaeb9f9639de5149d9f0f9f4b79f00c423915b701db2e02ed80b9ce" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" +"checksum generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fceb69994e330afed50c93524be68c42fa898c2d9fd4ee8da03bd7363acd26f2" +"checksum getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" "checksum gif 0.10.0 (git+https://github.com/SunriseOS/image-gif)" = "" +"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +"checksum half 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ff54597ea139063f4225f1ec47011b03c9de4a486957ff3fc506881dac951d0" "checksum hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1de41fb8dba9714efd92241565cdff73f78508c95697dd56787d3cba27e2353" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" "checksum hex 0.3.2 (git+https://github.com/KokaKiwi/rust-hex)" = "" "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" "checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum if_chain 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4bac95d9aa0624e7b78187d6fb8ab012b41d9f6f54b1bcb61e61c4845f8357ec" +"checksum ioctl-sys 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" +"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" -"checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" +"checksum libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)" = "" "checksum libfat 0.1.0 (git+https://github.com/sunriseos/libfat.git)" = "" "checksum linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "47314ec1d29aa869ee7cb5a5be57be9b1055c56567d59c3fb6689926743e0bea" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" @@ -1143,28 +3076,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" "checksum mashup 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f2d82b34c7fb11bb41719465c060589e291d505ca4735ea30016a91f6fc79c3b" "checksum mashup-impl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "aa607bfb674b4efb310512527d64266b065de3f894fc52f84efcbf7eaa5965fb" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" +"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum multiboot2 0.7.1 (git+https://github.com/sunriseos/multiboot2-elf64.git)" = "" +"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.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dbf9993e59c894e3c08aa1c2712914e9e6bf1fcbfc6bef283e2183df345a4fee" +"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +"checksum onig 4.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8518fcb2b1b8c2f45f0ad499df4fda6087fc3475ca69a185c173b8315d2fb383" +"checksum onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0" "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "933085deae3f32071f135d799d75667b63c8dc1f4537159756e3d4ceab41868c" "checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" "checksum pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63120576c4efd69615b5537d3d052257328a4ca82876771d6944424ccfd9f646" "checksum pest_meta 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f249ea6de7c7b7aba92b4ff4376a994c6dbd98fd2166c89d5c4947397ecb574d" "checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea" "checksum plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +"checksum platform-info 0.0.1 (git+https://github.com/sunriseos/platform-info.git)" = "" "checksum proc-macro-hack 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "463bf29e7f11344e58c9e01f171470ab15c925c6822ad75028cc1c0e1d1eb63b" "checksum proc-macro-hack-impl 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "38c47dcb1594802de8c02f3b899e2018c78291168a22c281be21ea0fb4796842" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" +"checksum pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fdf85cda6cadfae5428a54661d431330b312bc767ddbc57adbedc24da66e32" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +"checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +"checksum rust-users 0.6.0 (git+https://github.com/uutils/rust-users)" = "" "checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" +"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.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" +"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" +"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" "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" +"checksum sha3 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26405905b6a56a94c60109cfda62610507ac14a65be531f5767dec5c5a8dd6a0" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" "checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" @@ -1175,23 +3156,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7" "checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107" "checksum syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)" = "bc945221ccf4a7e8c31222b9d1fc77aefdd6638eb901a6ce457a3dc29d4c31e8" +"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" +"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +"checksum tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0" +"checksum term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "230d3e804faaed5a39b08319efb797783df2fd9671b39b7596490cb486d702cf" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" +"checksum termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8fb22f7cde82c8220e5aeacb3258ed7ce996142c77cba193f203515e26c330" +"checksum termsize 0.1.6 (git+https://github.com/sunriseos/termsize.git)" = "" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)" = "" "checksum tinybmp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "503e3fde7e36b1aa2345af8a3af0086c9b01d9db07b24f3fb0aab07316b9fa10" +"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum ucd-trie 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" "checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" +"checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" +"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +"checksum uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)" = "" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" +"checksum walker 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44971d5e5ae4f7904dffb6260ebd3910e7bcae104a94730e04a24cb6af40646b" +"checksum wild 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "97d34fecce28871e5c0e059deae21ef7f7d13b98a5964b24c58b3735c8052fc8" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9" +"checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" "checksum xmas-elf 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e74de9a366f6ab8c405fa6b371d9ac24943921fa14b3d64afcb202065c405f11" "checksum zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" diff --git a/Cargo.toml b/Cargo.toml index 44f536bd1..07647c576 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,37 @@ [workspace] -members = ["kernel", "bootstrap", "shell", "time", "libuser", "wall-clock", "sm", "vi", "ahci", "fs", "libutils", "libkern", "swipc-gen", "swipc-parser", "docs", "libtimezone", "disk-initializer", "loader", "keyboard", "std_hello_world"] +members = ["kernel", "bootstrap", "shell", "time", "libuser", "wall-clock", "sm", "vi", "ahci", "fs", "libutils", "libkern", "swipc-gen", "swipc-parser", "docs", "libtimezone", "disk-initializer", "loader", "keyboard", "std_hello_world", "coreutils"] -[profile.release] -debug = true +[patch.crates-io.libc] +git = "https://github.com/sunriseos/libc.git" +branch = "sunrise" + +[patch.crates-io.uucore] +git = "https://github.com/sunriseos/uucore.git" +branch = "master" + +[patch.crates-io.atty] +git = "https://github.com/sunriseos/atty.git" +branch = "master" + +[patch.crates-io.clap] +git = "https://github.com/sunriseos/clap.git" +branch = "master" + +[patch.crates-io.filetime] +git = "https://github.com/sunriseos/filetime.git" +branch = "master" + +[patch.crates-io.platform-info] +git = "https://github.com/sunriseos/platform-info.git" +branch = "master" + +[patch.crates-io.time] +git = "https://github.com/sunriseos/time.git" +branch = "v0.1" + +[patch.crates-io.termsize] +git = "https://github.com/sunriseos/termsize.git" +branch = "master" [profile.test] debug = true diff --git a/Makefile.toml b/Makefile.toml index 74d5cd6da..0b5c9e5a0 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -149,9 +149,15 @@ dependencies = ["install-xargo"] command = "xargo" args = ["build", "--target=i386-unknown-sunrise-user", "--package=std_hello_world", "@@split(COMPILER_FLAGS, )"] +[tasks.uutils] +description = "Compiles uutils (coreutils)" +dependencies = ["install-xargo"] +command = "xargo" +args = ["build", "--target=i386-unknown-sunrise-user", "--package=uutils", "-Z", "package-features", "--features=sunrise", "--no-default-features", "@@split(COMPILER_FLAGS, )"] + [tasks.userspace] description = "Compiles userspace apps" -dependencies = ["shell", "wall-clock", "sm", "vi", "ahci", "time", "fs", "loader", "keyboard", "std_hello_world"] +dependencies = ["shell", "wall-clock", "sm", "vi", "ahci", "time", "fs", "loader", "keyboard", "std_hello_world", "uutils"] [tasks.iso] description = "Creates a bootable ISO containing the kernel and grub." @@ -188,6 +194,9 @@ mkdir -p external/filesystem/disk_template/bin/std_hello_world/flags cp target/i386-unknown-sunrise-user/$PROFILE_NAME/std_hello_world external/filesystem/disk_template/bin/std_hello_world/main touch external/filesystem/disk_template/bin/std_hello_world/flags/boot.flag +mkdir -p external/filesystem/disk_template/bin/uutils +cp target/i386-unknown-sunrise-user/$PROFILE_NAME/uutils external/filesystem/disk_template/bin/uutils/main + cargo run --manifest-path disk-initializer/Cargo.toml -- DISK.img 52428800 external/filesystem/disk_template/ ''' ] @@ -274,12 +283,19 @@ dependencies = ["install-xargo", "refresh-crates"] install_crate = { rustup_component_name = "clippy" } command = "xargo" args = ["clippy", "--target=i386-unknown-sunrise-user", - "--all", "--exclude", "swipc-gen", "--exclude", "swipc-parser", - "--exclude", "disk-initializer", - "--exclude", "docs", - "--exclude", "sunrise-kernel", - "--exclude", "sunrise-bootstrap", - "--exclude", "std_hello_world", + "-p", "sunrise-shell", + "-p", "sunrise-time", + "-p", "sunrise-libuser", + "-p", "sunrise-wall-clock", + "-p", "sunrise-sm", + "-p", "sunrise-vi", + "-p", "sunrise-ahci", + "-p", "sunrise-fs", + "-p", "sunrise-libutils", + "-p", "sunrise-libkern", + "-p", "sunrise-libtimezone", + "-p", "sunrise-loader", + "-p", "sunrise-keyboard", "--", "@@split(CLIPPY_RULES, )", "${@}", diff --git a/update_subtree b/update_subtree index 1a1cea994..9f36fef2a 100755 --- a/update_subtree +++ b/update_subtree @@ -1,7 +1,24 @@ #!/bin/sh -remote=rust_sunrise -branch=sunrise-squashed -prefix=rust/ + +usage() +{ + cat << EOF +Usage: ./update_subtree +Where: + remote: The name of the remote to use. + branch: The target branch on the remote to use. + prefix: the path to the directory to update +EOF + exit 1 +} + +if [ $# -lt 3 ]; then + usage +fi + +remote=$1 +branch=$2 +prefix=$3 git fetch "$remote" && git rm -r --ignore-unmatch "$prefix" && From 15349330d510c810ff8d970d44618fe30d27a1e5 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Mon, 21 Oct 2019 15:37:36 +0000 Subject: [PATCH 04/10] Update disk size to 150MB For debug builds of uutils --- Makefile.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.toml b/Makefile.toml index 0b5c9e5a0..327291519 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -197,7 +197,7 @@ touch external/filesystem/disk_template/bin/std_hello_world/flags/boot.flag mkdir -p external/filesystem/disk_template/bin/uutils cp target/i386-unknown-sunrise-user/$PROFILE_NAME/uutils external/filesystem/disk_template/bin/uutils/main -cargo run --manifest-path disk-initializer/Cargo.toml -- DISK.img 52428800 external/filesystem/disk_template/ +cargo run --manifest-path disk-initializer/Cargo.toml -- DISK.img 157286400 external/filesystem/disk_template/ ''' ] From 6006aa4f7f1d184a25bc39fce4b59264742fd4fc Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Mon, 21 Oct 2019 16:05:10 +0000 Subject: [PATCH 05/10] actions: disable fail fast --- .github/workflows/rust.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 110aa27a9..85c62e7bf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -6,6 +6,7 @@ jobs: name: Rust project runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest, macOS-latest, windows-latest] steps: From 7880a1cb578fbd894b7c726b6351cfb010c4c6be Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Mon, 21 Oct 2019 18:25:24 +0000 Subject: [PATCH 06/10] update coreutils from coreutils_sunrise master --- coreutils/src/base32/Cargo.toml | 4 ---- coreutils/src/chown/Cargo.toml | 4 ---- coreutils/src/who/Cargo.toml | 4 ---- 3 files changed, 12 deletions(-) diff --git a/coreutils/src/base32/Cargo.toml b/coreutils/src/base32/Cargo.toml index 75b25454b..904719654 100644 --- a/coreutils/src/base32/Cargo.toml +++ b/coreutils/src/base32/Cargo.toml @@ -12,10 +12,6 @@ path = "base32.rs" version = "0.0.1" features = ["encoding"] -[dependencies.clippy] -version = "0.0.212" -optional = true - [[bin]] name = "base32" path = "../../uumain.rs" diff --git a/coreutils/src/chown/Cargo.toml b/coreutils/src/chown/Cargo.toml index 1e6ec9e36..0d1a67126 100644 --- a/coreutils/src/chown/Cargo.toml +++ b/coreutils/src/chown/Cargo.toml @@ -16,10 +16,6 @@ walkdir = "2.2" version = "0.0.1" features = ["entries", "fs"] -[dependencies.clippy] -version = "0.0.212" -optional = true - [[bin]] name = "chown" path = "../../uumain.rs" diff --git a/coreutils/src/who/Cargo.toml b/coreutils/src/who/Cargo.toml index 036ba672b..821797a5b 100644 --- a/coreutils/src/who/Cargo.toml +++ b/coreutils/src/who/Cargo.toml @@ -12,10 +12,6 @@ path = "who.rs" version = "0.0.1" features = ["utmpx"] -[dependencies.clippy] -version = "0.0.212" -optional = true - [[bin]] name = "who" path = "../../uumain.rs" From 05cea645b73552e32431e9c28b097e36ce17bcbf Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Mon, 21 Oct 2019 18:25:36 +0000 Subject: [PATCH 07/10] Update Cargo.lock for coreutils changes --- Cargo.lock | 271 +---------------------------------------------------- 1 file changed, 1 insertion(+), 270 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 696f72f56..34f2d6c1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,31 +77,10 @@ name = "autocfg" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "backtrace" -version = "0.3.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", - "rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", -] - [[package]] name = "base32" version = "0.0.1" dependencies = [ - "clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] @@ -152,11 +131,6 @@ name = "bitflags" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "bitflags" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "bitflags" version = "1.1.0" @@ -223,18 +197,6 @@ name = "cargo-5730" version = "0.1.0" source = "git+https://github.com/Thog/cargo-5730.git?branch=feature/windows-support#be3c927c3d38df740db41f41fb872f130b79ea6f" -[[package]] -name = "cargo_metadata" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "cat" version = "0.0.1" @@ -275,7 +237,6 @@ dependencies = [ name = "chown" version = "0.0.1" dependencies = [ - "clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -322,42 +283,6 @@ dependencies = [ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "clippy" -version = "0.0.212" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", - "clippy_lints 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "clippy_lints" -version = "0.0.212" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cargo_metadata 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", - "if_chain 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "cloudabi" version = "0.0.3" @@ -630,14 +555,6 @@ dependencies = [ "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "error-chain" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "expand" version = "0.0.1" @@ -669,7 +586,6 @@ name = "failure" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -961,21 +877,6 @@ name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "if_chain" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "install" version = "0.0.1" @@ -991,14 +892,6 @@ name = "ioctl-sys" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "itertools" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "itertools" version = "0.8.0" @@ -1007,11 +900,6 @@ dependencies = [ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "itoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "join" version = "0.0.1" @@ -1162,11 +1050,6 @@ dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "md5" version = "0.3.8" @@ -1426,11 +1309,6 @@ dependencies = [ "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "pest" version = "2.1.1" @@ -1538,14 +1416,6 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "proc-macro2" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ptx" version = "0.0.1" @@ -1559,15 +1429,6 @@ dependencies = [ "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] -[[package]] -name = "pulldown-cmark" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "pwd" version = "0.0.1" @@ -1581,11 +1442,6 @@ name = "quick-error" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "quine-mc_cluskey" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "quote" version = "0.3.15" @@ -1599,14 +1455,6 @@ dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "quote" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "rand" version = "0.3.23" @@ -1842,19 +1690,6 @@ name = "rustc-demangle" version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ryu" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "same-file" version = "1.0.5" @@ -1869,7 +1704,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1885,31 +1719,6 @@ dependencies = [ "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] -[[package]] -name = "serde" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde_derive" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_json" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "sha-1" version = "0.8.1" @@ -2342,16 +2151,6 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "syn" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "sync" version = "0.0.1" @@ -2516,14 +2315,6 @@ dependencies = [ "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "toml" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "touch" version = "0.0.1" @@ -2604,22 +2395,6 @@ dependencies = [ "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "unicode-segmentation" version = "1.3.0" @@ -2640,11 +2415,6 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "unindent" version = "0.1.5" @@ -2685,16 +2455,6 @@ dependencies = [ "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "users" version = "0.0.1" @@ -2706,7 +2466,7 @@ dependencies = [ [[package]] name = "uucore" version = "0.0.1" -source = "git+https://github.com/sunriseos/uucore.git#a60175ba8d841347c44c9117eaab432e1c7c0144" +source = "git+https://github.com/sunriseos/uucore.git#4278610b7f83b95110e0a7ea52b17d7fa44b6a16" dependencies = [ "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2878,7 +2638,6 @@ dependencies = [ name = "who" version = "0.0.1" dependencies = [ - "clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)", ] @@ -2985,15 +2744,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum atomic 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c210c1f4db048cda477b652d170572d84c9640695835f17663595d3bd543fc28" "checksum atty 0.2.13 (git+https://github.com/sunriseos/atty.git)" = "" "checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" -"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" -"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" "checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" "checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" "checksum bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0" "checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" "checksum bitfield 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" -"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" @@ -3004,13 +2760,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum cargo-5730 0.1.0 (git+https://github.com/Thog/cargo-5730.git?branch=feature/windows-support)" = "" -"checksum cargo_metadata 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1efca0b863ca03ed4c109fb1c55e0bc4bbeb221d3e103d86251046b06a526bd0" "checksum cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68" "checksum clap 2.33.0 (git+https://github.com/sunriseos/clap.git)" = "" -"checksum clippy 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)" = "7e253af13a0cc39c7f22cf16f1be49d593dedc5895fe2fbb15f14d66ead00533" -"checksum clippy_lints 0.0.212 (registry+https://github.com/rust-lang/crates.io-index)" = "bd2326065405649672adbd5cb30dad2fad3a470935653d51c70591d47d3a8512" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum color_quant 1.0.1 (git+https://github.com/SunriseOS/color_quant)" = "" "checksum core-futures-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0407dd6f9b5ed2842f9d287934231e3d270a240d2bb9bf8a56f02e37381c793a" @@ -3030,7 +2783,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum env_logger 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39ecdb7dd54465526f0a56d666e3b2dd5f3a218665a030b6e4ad9e70fa95d8fa" -"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" @@ -3059,12 +2811,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hex 0.3.2 (git+https://github.com/KokaKiwi/rust-hex)" = "" "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" "checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -"checksum if_chain 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4bac95d9aa0624e7b78187d6fb8ab012b41d9f6f54b1bcb61e61c4845f8357ec" "checksum ioctl-sys 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" -"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" -"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)" = "" @@ -3076,7 +2824,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" "checksum mashup 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f2d82b34c7fb11bb41719465c060589e291d505ca4735ea30016a91f6fc79c3b" "checksum mashup-impl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "aa607bfb674b4efb310512527d64266b065de3f894fc52f84efcbf7eaa5965fb" -"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" "checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" @@ -3093,7 +2840,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum onig 4.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8518fcb2b1b8c2f45f0ad499df4fda6087fc3475ca69a185c173b8315d2fb383" "checksum onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0" "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" -"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "933085deae3f32071f135d799d75667b63c8dc1f4537159756e3d4ceab41868c" "checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" "checksum pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63120576c4efd69615b5537d3d052257328a4ca82876771d6944424ccfd9f646" @@ -3105,13 +2851,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum proc-macro-hack 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "463bf29e7f11344e58c9e01f171470ab15c925c6822ad75028cc1c0e1d1eb63b" "checksum proc-macro-hack-impl 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "38c47dcb1594802de8c02f3b899e2018c78291168a22c281be21ea0fb4796842" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" -"checksum pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fdf85cda6cadfae5428a54661d431330b312bc767ddbc57adbedc24da66e32" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" -"checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" @@ -3134,14 +2876,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" "checksum rust-users 0.6.0 (git+https://github.com/uutils/rust-users)" = "" "checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" "checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" "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.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" -"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" -"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" "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" @@ -3156,7 +2893,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7" "checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107" "checksum syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)" = "bc945221ccf4a7e8c31222b9d1fc77aefdd6638eb901a6ce457a3dc29d4c31e8" -"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" "checksum tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0" @@ -3168,19 +2904,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum time 0.1.42 (git+https://github.com/sunriseos/time.git?branch=v0.1)" = "" "checksum tinybmp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "503e3fde7e36b1aa2345af8a3af0086c9b01d9db07b24f3fb0aab07316b9fa10" -"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum ucd-trie 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" "checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" "checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" -"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum uucore 0.0.1 (git+https://github.com/sunriseos/uucore.git)" = "" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" From 159ccdd0e635fd94284e773b82c5e93e07a19bc4 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Mon, 21 Oct 2019 19:13:32 +0000 Subject: [PATCH 08/10] Update Cargo.lock Hopefully fixing building issues on CI. --- Cargo.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34f2d6c1b..6c5681233 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2466,11 +2466,10 @@ dependencies = [ [[package]] name = "uucore" version = "0.0.1" -source = "git+https://github.com/sunriseos/uucore.git#4278610b7f83b95110e0a7ea52b17d7fa44b6a16" +source = "git+https://github.com/sunriseos/uucore.git#0bf822ef6c4d29a27fe4e1d9571c02953898d430" dependencies = [ "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.64 (git+https://github.com/sunriseos/libc.git?branch=sunrise)", From f0cdcefc3dfea1b59f069ff565229caf2ec9c49d Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Tue, 22 Oct 2019 12:38:17 +0000 Subject: [PATCH 09/10] Fix test for CI --- Makefile.toml | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/Makefile.toml b/Makefile.toml index 327291519..83f70c07c 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -242,12 +242,52 @@ args = ["doc-upload", "--clobber-index"] # See #226 description = "Run the doctests - doesn't set the target" command = "cargo" -args = ["test", "--doc"] +args = ["test", "--doc", + "-p", "docs", + "-p", "sunrise-bootstrap", + "-p", "sunrise-kernel", + "-p", "sunrise-shell", + "-p", "sunrise-time", + "-p", "sunrise-libuser", + "-p", "sunrise-wall-clock", + "-p", "sunrise-sm", + "-p", "sunrise-vi", + "-p", "sunrise-ahci", + "-p", "sunrise-fs", + "-p", "sunrise-libutils", + "-p", "sunrise-libkern", + "-p", "sunrise-libtimezone", + "-p", "sunrise-loader", + "-p", "sunrise-keyboard", + "-p", "swipc-gen", + "-p", "swipc-parser", + "-p", "disk-initializer", +] [tasks.testinner] description = "Run the tests in 32bit mode" command = "cargo" -args = ["test", "--target=i686-unknown-linux-gnu"] +args = ["test", "--target=i686-unknown-linux-gnu", + "-p", "docs", + "-p", "sunrise-bootstrap", + "-p", "sunrise-kernel", + "-p", "sunrise-shell", + "-p", "sunrise-time", + "-p", "sunrise-libuser", + "-p", "sunrise-wall-clock", + "-p", "sunrise-sm", + "-p", "sunrise-vi", + "-p", "sunrise-ahci", + "-p", "sunrise-fs", + "-p", "sunrise-libutils", + "-p", "sunrise-libkern", + "-p", "sunrise-libtimezone", + "-p", "sunrise-loader", + "-p", "sunrise-keyboard", + "-p", "swipc-gen", + "-p", "swipc-parser", + "-p", "disk-initializer", +] [tasks.test] description = "Run all the tests." From 41d095f5ae00955c833c518b46e5747106995f42 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Tue, 22 Oct 2019 13:20:00 +0000 Subject: [PATCH 10/10] Fix doc-test --- Makefile.toml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Makefile.toml b/Makefile.toml index 83f70c07c..000a5dd6c 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -228,7 +228,27 @@ args = ["doc", "--no-deps"] description = "Generate the project's documentation, including private items" env = { "RUSTDOCFLAGS" = "-Z unstable-options --enable-index-page" } command = "cargo" -args = ["doc", "--no-deps", "--document-private-items"] +args = ["doc", "--no-deps", "--document-private-items", + "-p", "docs", + "-p", "sunrise-bootstrap", + "-p", "sunrise-kernel", + "-p", "sunrise-shell", + "-p", "sunrise-time", + "-p", "sunrise-libuser", + "-p", "sunrise-wall-clock", + "-p", "sunrise-sm", + "-p", "sunrise-vi", + "-p", "sunrise-ahci", + "-p", "sunrise-fs", + "-p", "sunrise-libutils", + "-p", "sunrise-libkern", + "-p", "sunrise-libtimezone", + "-p", "sunrise-loader", + "-p", "sunrise-keyboard", + "-p", "swipc-gen", + "-p", "swipc-parser", + "-p", "disk-initializer", +] [tasks.deploy-doc] install_crate = { crate_name = "cargo-travis", binary = "cargo", test_arg = ["doc-upload", "--help"] }