Add last Utility (#65)

This commit is contained in:
Andy
2024-09-19 23:43:18 +08:00
committed by GitHub
parent 201b2fe829
commit 49fc7ff3ca
12 changed files with 1037 additions and 80 deletions

295
Cargo.lock generated
View File

@@ -4,24 +4,25 @@ version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.7"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
@@ -33,27 +34,27 @@ checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
@@ -67,9 +68,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bytecount"
@@ -77,6 +78,12 @@ version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -122,9 +129,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.0"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]]
name = "clap_mangen"
@@ -138,15 +145,15 @@ dependencies = [
[[package]]
name = "colorchoice"
version = "1.0.0"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crossbeam-deque"
@@ -169,9 +176,18 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
]
[[package]]
name = "diff"
@@ -180,16 +196,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "either"
version = "1.9.0"
name = "dns-lookup"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc"
dependencies = [
"cfg-if",
"libc",
"socket2",
"windows-sys 0.48.0",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "errno"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.52.0",
@@ -197,9 +225,9 @@ dependencies = [
[[package]]
name = "fastrand"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "fnv"
@@ -209,9 +237,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.12"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
@@ -232,9 +260,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.4"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hex"
@@ -253,6 +281,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.11"
@@ -261,9 +295,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "lazy_static"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
@@ -279,15 +313,15 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "linux-raw-sys"
version = "0.4.12"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "memchr"
version = "2.7.1"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nix"
@@ -295,7 +329,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.6.0",
"cfg-if",
"cfg_aliases",
"libc",
@@ -310,6 +344,21 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "number_prefix"
version = "0.4.0"
@@ -381,10 +430,19 @@ dependencies = [
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "pretty_assertions"
@@ -422,9 +480,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
@@ -435,11 +493,11 @@ version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.6.0",
"hex",
"lazy_static",
"procfs-core",
"rustix 0.38.34",
"rustix 0.38.37",
]
[[package]]
@@ -448,15 +506,15 @@ version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.6.0",
"hex",
]
[[package]]
name = "quote"
version = "1.0.35"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
@@ -493,9 +551,9 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.8.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
@@ -503,9 +561,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@@ -525,9 +583,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
@@ -536,9 +594,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.2"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rlimit"
@@ -551,9 +609,9 @@ dependencies = [
[[package]]
name = "roff"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]]
name = "rustix"
@@ -571,14 +629,14 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.34"
version = "0.38.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys 0.4.12",
"linux-raw-sys 0.4.14",
"windows-sys 0.52.0",
]
@@ -605,7 +663,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.77",
]
[[package]]
@@ -632,6 +690,16 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
[[package]]
name = "socket2"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "strsim"
version = "0.11.1"
@@ -651,9 +719,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.58"
version = "2.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
dependencies = [
"proc-macro2",
"quote",
@@ -706,7 +774,7 @@ dependencies = [
"cfg-if",
"fastrand",
"once_cell",
"rustix 0.38.34",
"rustix 0.38.37",
"windows-sys 0.59.0",
]
@@ -726,7 +794,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
dependencies = [
"rustix 0.38.34",
"rustix 0.38.37",
"windows-sys 0.48.0",
]
@@ -743,10 +811,43 @@ dependencies = [
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
name = "time"
version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-linebreak"
@@ -762,9 +863,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "utf8parse"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "util-linux"
@@ -773,6 +874,7 @@ dependencies = [
"clap",
"clap_complete",
"clap_mangen",
"dns-lookup",
"libc",
"phf",
"phf_codegen",
@@ -784,6 +886,7 @@ dependencies = [
"tempfile",
"textwrap",
"uu_ctrlaltdel",
"uu_last",
"uu_lscpu",
"uu_lsmem",
"uu_mountpoint",
@@ -800,6 +903,15 @@ dependencies = [
"uucore",
]
[[package]]
name = "uu_last"
version = "0.0.1"
dependencies = [
"clap",
"dns-lookup",
"uucore",
]
[[package]]
name = "uu_lscpu"
version = "0.0.1"
@@ -844,21 +956,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b54aad02cf7e96f5fafabb6b836efa73eef934783b17530095a29ffd4fdc154"
dependencies = [
"clap",
"dns-lookup",
"glob",
"libc",
"nix",
"number_prefix",
"once_cell",
"os_display",
"time",
"uucore_procs",
"wild",
]
[[package]]
name = "uucore_procs"
version = "0.0.24"
version = "0.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eb9aeeb06d1f15c5b3b51acddddf3436e3e1480902b2a200618ca5dbb24e392"
checksum = "c3d588f57acb2ba416e072a6fa652f2e11cf727267c697d2e2d65175f3b10c41"
dependencies = [
"proc-macro2",
"quote",
@@ -867,9 +981,9 @@ dependencies = [
[[package]]
name = "uuhelp_parser"
version = "0.0.24"
version = "0.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d841f8408028085ca65896cdd60b9925d4e407cb69989a64889f2bebbb51147b"
checksum = "96f26868814bf1ca9deec910a08007c93eb1d8e407ce36451999d4c1c1ea6767"
[[package]]
name = "version_check"
@@ -944,7 +1058,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.77",
]
[[package]]
@@ -955,7 +1069,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
"syn 2.0.77",
]
[[package]]
@@ -1122,8 +1236,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f"
dependencies = [
"libc",
"linux-raw-sys 0.4.12",
"rustix 0.38.34",
"linux-raw-sys 0.4.14",
"rustix 0.38.37",
]
[[package]]
@@ -1131,3 +1245,24 @@ name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
]

View File

@@ -31,6 +31,7 @@ feat_common_core = [
"lsmem",
"ctrlaltdel",
"rev",
"last"
]
[workspace.dependencies]
@@ -50,6 +51,7 @@ rand = { version = "0.8", features = ["small_rng"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.122"
tabled = "0.16.0"
dns-lookup = "2.0.4"
[dependencies]
clap = { workspace = true }
@@ -58,6 +60,7 @@ clap_mangen = { workspace = true }
uucore = { workspace = true }
phf = { workspace = true }
textwrap = { workspace = true }
dns-lookup = { workspace = true }
#
lscpu = { optional = true, version = "0.0.1", package = "uu_lscpu", path = "src/uu/lscpu" }
@@ -65,6 +68,7 @@ lsmem = { optional = true, version = "0.0.1", package = "uu_lsmem", path = "src/
mountpoint = { optional = true, version = "0.0.1", package = "uu_mountpoint", path = "src/uu/mountpoint" }
ctrlaltdel = { optional = true, version = "0.0.1", package = "uu_ctrlaltdel", path = "src/uu/ctrlaltdel" }
rev = { optional = true, version = "0.0.1", package = "uu_rev", path = "src/uu/rev" }
last = { optional = true, version = "0.0.1", package = "uu_last", path = "src/uu/last" }
[dev-dependencies]
pretty_assertions = "1"

12
src/uu/last/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "uu_last"
version = "0.0.1"
edition = "2021"
[lib]
path = "src/last.rs"
[dependencies]
uucore = { workspace = true, features = ["utmpx"] }
clap = { workspace = true}
dns-lookup = { workspace = true }

8
src/uu/last/last.md Normal file
View File

@@ -0,0 +1,8 @@
# last
```
Usage:
[options] [<username>...] [<tty>...]
```
Show a listing of last logged in users.

94
src/uu/last/src/last.rs Normal file
View File

@@ -0,0 +1,94 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use clap::{crate_version, Arg, ArgAction, Command};
use uucore::{format_usage, help_about, help_usage};
mod platform;
mod options {
pub const SYSTEM: &str = "system";
pub const HOSTLAST: &str = "hostlast";
pub const NO_HOST: &str = "nohostname";
pub const LIMIT: &str = "limit";
pub const DNS: &str = "dns";
pub const TIME_FORMAT: &str = "time-format";
pub const USER_TTY: &str = "username";
pub const FILE: &str = "file";
}
const ABOUT: &str = help_about!("last.md");
const USAGE: &str = help_usage!("last.md");
#[uucore::main]
use platform::uumain;
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.arg(
Arg::new(options::FILE)
.short('f')
.long("file")
.action(ArgAction::Set)
.default_value("/var/log/wtmp")
.help("use a specific file instead of /var/log/wtmp")
.required(false),
)
.arg(
Arg::new(options::SYSTEM)
.short('x')
.long(options::SYSTEM)
.action(ArgAction::SetTrue)
.required(false)
.help("display system shutdown entries and run level changes"),
)
.arg(
Arg::new(options::DNS)
.short('d')
.long(options::DNS)
.action(ArgAction::SetTrue)
.required(false)
.help("translate the IP number back into a hostname"),
)
.arg(
Arg::new(options::HOSTLAST)
.short('a')
.long(options::HOSTLAST)
.action(ArgAction::SetTrue)
.required(false)
.help("display hostnames in the last column"),
)
.arg(
Arg::new(options::NO_HOST)
.short('R')
.long(options::NO_HOST)
.action(ArgAction::SetTrue)
.required(false)
.help("don't display the hostname field"),
)
.arg(
Arg::new(options::LIMIT)
.short('n')
.long(options::LIMIT)
.action(ArgAction::Set)
.required(false)
.help("how many lines to show")
.value_parser(clap::value_parser!(i32))
.allow_negative_numbers(true),
)
.arg(
Arg::new(options::TIME_FORMAT)
.long(options::TIME_FORMAT)
.action(ArgAction::Set)
.required(false)
.help("show timestamps in the specified <format>: notime|short|full|iso")
.default_value("short"),
)
.arg(Arg::new(options::USER_TTY).action(ArgAction::Append))
}

1
src/uu/last/src/main.rs Normal file
View File

@@ -0,0 +1 @@
uucore::bin!(uu_last);

View File

@@ -0,0 +1,19 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use self::unix::*;
#[cfg(target_os = "openbsd")]
mod openbsd;
#[cfg(target_os = "openbsd")]
pub use self::openbsd::*;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use self::windows::*;

View File

@@ -0,0 +1,17 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// Specific implementation for OpenBSD: tool unsupported (utmpx not supported)
use crate::uu_app;
use uucore::error::UResult;
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let _matches = uu_app().try_get_matches_from(args)?;
println!("unsupported command on OpenBSD");
Ok(())
}

View File

@@ -0,0 +1,535 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use crate::options;
use crate::uu_app;
use uucore::error::UIoError;
use uucore::error::UResult;
use uucore::error::USimpleError;
use uucore::utmpx::time::OffsetDateTime;
use uucore::utmpx::{time, Utmpx};
use std::fmt::Write;
use std::fs;
use std::io;
use std::net::Ipv4Addr;
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
fn get_long_usage() -> String {
format!(
"If FILE is not specified, use {}. /var/log/wtmp as FILE is common.",
WTMP_PATH,
)
}
const WTMP_PATH: &str = "/var/log/wtmp";
static TIME_FORMAT_STR: [&str; 4] = ["notime", "short", "full", "iso"];
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app()
.after_help(get_long_usage())
.try_get_matches_from(args)?;
let system = matches.get_flag(options::SYSTEM);
let dns = matches.get_flag(options::DNS);
let hostlast = matches.get_flag(options::HOSTLAST);
let nohost = matches.get_flag(options::NO_HOST);
let limit: i32 = if let Some(num) = matches.get_one::<i32>(options::LIMIT) {
*num
} else {
0 // Original implementation has 0 mean no limit (print all values)
};
let time_format = if let Some(format) = matches.get_one::<String>(options::TIME_FORMAT) {
let format_str = format.as_str().trim();
if TIME_FORMAT_STR.contains(&format_str) {
Ok(format.to_string())
} else {
Err(USimpleError::new(
0,
format!("unknown time format: {format}"),
))
}
} else {
Ok("short".to_string())
}?;
let file: String = if let Some(files) = matches.get_one::<String>(options::FILE) {
files.to_string()
} else {
WTMP_PATH.to_string()
};
let user: Option<Vec<String>> =
if let Some(users) = matches.get_many::<String>(options::USER_TTY) {
users
.map(|v| {
if is_numeric(v) {
Some(format!("tty{v}"))
} else {
Some(v.to_owned())
}
})
.collect()
} else {
None
};
let mut last = Last {
last_reboot_ut: None,
last_shutdown_ut: None,
last_dead_ut: vec![],
system,
dns,
host_last: hostlast,
no_host: nohost,
limit,
file: file.to_string(),
users: user,
time_format,
};
last.exec()
}
const RUN_LEVEL_STR: &str = "runlevel";
const REBOOT_STR: &str = "reboot";
const SHUTDOWN_STR: &str = "shutdown";
struct Last {
last_reboot_ut: Option<Utmpx>,
last_shutdown_ut: Option<Utmpx>,
last_dead_ut: Vec<Utmpx>,
system: bool,
dns: bool,
host_last: bool,
no_host: bool,
file: String,
time_format: String,
users: Option<Vec<String>>,
limit: i32,
}
fn is_numeric(s: &str) -> bool {
s.chars().all(|c| c.is_numeric())
}
#[inline]
fn calculate_time_delta(
curr_datetime: &OffsetDateTime,
last_datetime: &OffsetDateTime,
) -> time::Duration {
let curr_duration = time::Duration::new(
curr_datetime.unix_timestamp(),
curr_datetime.nanosecond().try_into().unwrap_or_default(), // nanosecond value is always a value between 0 and 1.000.000.000, shouldn't panic
);
let last_duration = time::Duration::new(
last_datetime.unix_timestamp(),
last_datetime.nanosecond().try_into().unwrap_or_default(), // nanosecond value is always a value between 0 and 1.000.000.000, shouldn't panic
);
last_duration - curr_duration
}
#[inline]
fn duration_string(duration: time::Duration) -> String {
let mut seconds = duration.whole_seconds();
let days = seconds / 86400;
seconds -= days * 86400;
let hours = seconds / 3600;
seconds -= hours * 3600;
let minutes = seconds / 60;
if days > 0 {
format!("({}+{:0>2}:{:0>2})", days, hours, minutes)
} else {
format!("({:0>2}:{:0>2})", hours, minutes)
}
}
fn find_dns_name(ut: &Utmpx) -> String {
let default = Ipv4Addr::new(0, 0, 0, 0);
let ip = std::net::IpAddr::V4(Ipv4Addr::from_str(&ut.host()).unwrap_or(default));
if ip.to_string().trim() == "0.0.0.0" {
ip.to_string()
} else {
dns_lookup::lookup_addr(&ip).unwrap_or_default()
}
}
impl Last {
const TIME_FULL_FMT: &'static str = "[weekday repr:short] [month repr:short] [day padding:space] [hour]:[minute]:[second] [year]";
const END_TIME_SHORT_FMT: &'static str = "[hour]:[minute]";
const START_TIME_SHORT_FMT: &'static str =
"[weekday repr:short] [month repr:short] [day padding:space] [hour]:[minute]";
const TIME_ISO_FMT: &'static str =
"[year]-[month]-[day]T[hour]:[minute]:[second]+[offset_hour]:[offset_minute]";
#[allow(clippy::cognitive_complexity)]
fn exec(&mut self) -> UResult<()> {
let mut ut_stack: Vec<Utmpx> = vec![];
// For 'last' output, older output needs to be printed last (FILO), as
// UtmpxIter does not implement Rev trait. A better implementation
// might include implementing UtmpxIter as doubly linked
Utmpx::iter_all_records_from(&self.file).for_each(|ut| ut_stack.push(ut));
let mut counter = 0;
let mut first_ut_time = None;
while let Some(ut) = ut_stack.pop() {
if ut_stack.is_empty() {
// By the end of loop we will have the earliest time
// (This avoids getting into issues with the compiler)
let first_login_time = ut.login_time();
first_ut_time = Some(self.utmp_file_time(
first_login_time.unix_timestamp(),
first_login_time.nanosecond().into(),
));
}
if counter >= self.limit && self.limit > 0 {
break;
}
if ut.is_user_process() {
let mut dead_proc: Option<Utmpx> = None;
if let Some(pos) = self
.last_dead_ut
.iter()
.position(|dead_ut| ut.tty_device() == dead_ut.tty_device())
{
dead_proc = Some(self.last_dead_ut.swap_remove(pos));
}
if self.print_user(&ut, dead_proc.as_ref()) {
counter += 1;
}
} else if ut.user() == RUN_LEVEL_STR {
if self.print_runlevel(&ut) {
counter += 1;
}
} else if ut.user() == SHUTDOWN_STR {
if self.print_shutdown(&ut) {
counter += 1;
}
self.last_shutdown_ut = Some(ut);
} else if ut.user() == REBOOT_STR {
if self.print_reboot(&ut) {
counter += 1;
}
self.last_reboot_ut = Some(ut);
} else if ut.user() == "" {
// Dead process end date
self.last_dead_ut.push(ut);
}
}
let path = std::path::absolute(&self.file)?;
let path_str = path
.file_name()
.ok_or_else(|| {
if path.is_dir() {
UIoError::new(io::ErrorKind::InvalidData, "Is a directory")
} else {
UIoError::new(io::ErrorKind::Unsupported, "Undefined")
}
})?
.to_str()
.ok_or(UIoError::new(
io::ErrorKind::InvalidData,
"invalid character data (not UTF-8)",
))?;
if let Some(file_time) = first_ut_time {
println!("\n{} begins {}", path_str, file_time);
} else {
let secs = fs::metadata(&self.file)?.ctime();
let nsecs = fs::metadata(&self.file)?.ctime_nsec() as u64;
let file_time = self.utmp_file_time(secs, nsecs);
println!("\n{} begins {}", path_str, file_time);
}
Ok(())
}
#[inline]
fn utmp_file_time(&self, secs: i64, nsecs: u64) -> String {
let description = match self.time_format.as_str() {
"short" | "full" => Self::TIME_FULL_FMT,
"iso" => Self::TIME_ISO_FMT,
_ => return "".to_string(),
};
let time_format: Vec<time::format_description::FormatItem> =
time::format_description::parse(description).unwrap_or_default();
let time = time::OffsetDateTime::from_unix_timestamp(secs)
.unwrap_or(time::OffsetDateTime::UNIX_EPOCH)
+ Duration::from_nanos(nsecs);
let offset = time::UtcOffset::current_local_offset().unwrap_or(time::UtcOffset::UTC);
let offset_secs: u64 = offset.whole_seconds() as u64;
// Adding back the time to the offset so that offset_time is correct.
let offset_time = time.replace_offset(offset) + Duration::from_secs(offset_secs);
offset_time.format(&time_format).unwrap_or_default()
}
#[inline]
fn time_string(&self, ut: &Utmpx) -> String {
let description = match self.time_format.as_str() {
"short" => Self::START_TIME_SHORT_FMT,
"full" => Self::TIME_FULL_FMT,
"iso" => Self::TIME_ISO_FMT,
_ => return "".to_string(),
};
// "%b %e %H:%M"
let time_format: Vec<time::format_description::FormatItem> =
time::format_description::parse(description).unwrap_or_default();
ut.login_time().format(&time_format).unwrap_or_default()
}
#[inline]
fn end_time_string(&self, user_process_str: Option<&str>, end_ut: &OffsetDateTime) -> String {
match user_process_str {
Some(val) => val.to_string(),
_ => {
let description = match self.time_format.as_str() {
"short" => format!("- {}", Self::END_TIME_SHORT_FMT),
"full" => format!("- {}", Self::TIME_FULL_FMT),
"iso" => format!("- {}", Self::TIME_ISO_FMT),
_ => return "".to_string(),
};
// "%H:%M"
let time_format: Vec<time::format_description::FormatItem> =
time::format_description::parse(&description).unwrap_or_default();
end_ut.format(&time_format).unwrap_or_default()
}
}
}
#[inline]
fn end_state_string(&self, ut: &Utmpx, dead_ut: Option<&Utmpx>) -> (String, String) {
// This function takes a considerable amount of CPU cycles to complete;
// root cause seems to be the ut.login_time function, which reads a
// file to determine local offset for UTC. Perhaps this function
// should be updated to save that UTC offset for subsequent calls
let mut proc_status: Option<&str> = None;
let curr_datetime = ut.login_time();
if let Some(dead) = dead_ut {
let dead_datetime = dead.login_time();
let time_delta = duration_string(calculate_time_delta(&curr_datetime, &dead_datetime));
return (
self.end_time_string(proc_status, &dead_datetime),
time_delta.to_string(),
);
}
let reboot_datetime: Option<OffsetDateTime>;
let shutdown_datetime: Option<OffsetDateTime>;
if let Some(reboot) = &self.last_reboot_ut {
reboot_datetime = Some(reboot.login_time());
} else {
reboot_datetime = None;
}
if let Some(shutdown) = &self.last_shutdown_ut {
shutdown_datetime = Some(shutdown.login_time());
} else {
shutdown_datetime = None;
}
if shutdown_datetime.is_none() {
if ut.is_user_process() {
// If a reboot has occurred since the user logged in, but not shutdown is recorded
// then a crash must have occurred.
if reboot_datetime.is_some() && reboot_datetime.unwrap() > ut.login_time() {
("- crash".to_string(), "".to_string())
} else {
(" still logged in".to_string(), "".to_string())
}
} else {
(" still running".to_string(), "".to_string())
}
} else {
let shutdown = shutdown_datetime
.unwrap_or_else(|| time::OffsetDateTime::from_unix_timestamp(0).unwrap());
let time_delta = duration_string(calculate_time_delta(&curr_datetime, &shutdown));
if ut.is_user_process() {
proc_status = Some("- down");
}
(
self.end_time_string(proc_status, &shutdown),
time_delta.to_string(),
)
}
}
#[inline]
fn print_runlevel(&self, ut: &Utmpx) -> bool {
if let Some(users) = &self.users {
if !users
.iter()
.any(|val| val.as_str().trim() == ut.user().trim())
{
return false;
}
}
if self.system {
let curr = (ut.pid() % 256) as u8 as char;
let runlvline = format!("(to lvl {curr})");
let (end_date, delta) = self.end_state_string(ut, None);
let host = if self.dns {
find_dns_name(ut)
} else {
ut.host()
};
self.print_line(
RUN_LEVEL_STR,
&runlvline,
&self.time_string(ut),
&host,
&end_date,
&delta,
);
true
} else {
false
}
}
#[inline]
fn print_shutdown(&self, ut: &Utmpx) -> bool {
if let Some(users) = &self.users {
if !users.iter().any(|val| {
val.as_str().trim() == "system down" || val.as_str().trim() == ut.user().trim()
}) {
return false;
}
}
let host = if self.dns {
find_dns_name(ut)
} else {
ut.host()
};
if self.system {
let (end_date, delta) = self.end_state_string(ut, None);
self.print_line(
SHUTDOWN_STR,
"system down",
&self.time_string(ut),
&host,
&end_date,
&delta,
);
true
} else {
false
}
}
#[inline]
fn print_reboot(&self, ut: &Utmpx) -> bool {
if let Some(users) = &self.users {
if !users.iter().any(|val| {
val.as_str().trim() == ut.user().trim() || val.as_str().trim() == "system boot"
}) {
return false;
}
}
let (end_date, delta) = self.end_state_string(ut, None);
let host = if self.dns {
find_dns_name(ut)
} else {
ut.host()
};
self.print_line(
REBOOT_STR,
"system boot",
&self.time_string(ut),
&host,
&end_date,
&delta,
);
true
}
#[inline]
fn print_user(&self, ut: &Utmpx, dead_ut: Option<&Utmpx>) -> bool {
if let Some(users) = &self.users {
if !users.iter().any(|val| {
val.as_str().trim() == ut.tty_device().as_str().trim()
|| val.as_str().trim() == ut.user().trim()
}) {
return false;
}
}
let mut p = PathBuf::from("/dev");
p.push(ut.tty_device().as_str());
let host = if self.dns {
find_dns_name(ut)
} else {
ut.host()
};
let (end_date, delta) = self.end_state_string(ut, dead_ut);
self.print_line(
ut.user().as_ref(),
ut.tty_device().as_ref(),
self.time_string(ut).as_str(),
&host,
&end_date,
&delta,
);
true
}
#[inline]
#[allow(clippy::too_many_arguments)]
fn print_line(
&self,
user: &str,
line: &str,
time: &str,
host: &str,
end_time: &str,
delta: &str,
) {
let mut buf = String::with_capacity(64);
let host_to_print = host.get(0..16).unwrap_or(host);
write!(buf, "{user:<8}").unwrap_or_default();
write!(buf, " {line:<12}").unwrap_or_default();
if !self.host_last && !self.no_host {
write!(buf, " {host_to_print:<16}").unwrap_or_default();
}
let time_size = 3 + 2 + 2 + 1 + 2;
if self.host_last && !self.no_host && self.time_format != "notime" {
write!(buf, " {time:<time_size$}").unwrap_or_default();
write!(buf, " {end_time:<8}").unwrap_or_default();
write!(buf, " {host_to_print}").unwrap_or_default();
} else if self.time_format != "notime" {
write!(buf, " {time:<time_size$}").unwrap_or_default();
write!(buf, " {end_time:<8}").unwrap_or_default();
}
write!(buf, " {delta:^6}").unwrap_or_default();
println!("{}", buf.trim_end());
}
}

View File

@@ -0,0 +1,17 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// Specific implementation for Windows: tool unsupported (utmpx not supported)
use crate::uu_app;
use uucore::error::UResult;
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let _matches = uu_app().try_get_matches_from(args)?;
println!("unsupported command on Windows");
Ok(())
}

111
tests/by-util/test_last.rs Normal file
View File

@@ -0,0 +1,111 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (words) symdir somefakedir
use crate::common::util::TestScenario;
use regex::Regex;
use std::fs;
use std::io::Write;
#[test]
#[cfg(unix)]
fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
}
#[test]
#[cfg(unix)]
fn test_last() {
let regex = Regex::new("still running|still logged in").unwrap();
TestScenario::new(util_name!())
.ucmd()
.succeeds()
.stdout_matches(&regex);
}
#[test]
#[cfg(unix)]
fn test_limit_arg() {
let line_check = |input: &str| input.lines().count() == 3;
new_ucmd!()
.arg("--limit=1")
.succeeds()
.stdout_str_check(line_check);
}
#[test]
// The -x flag generally adds two rows "shutdown" and "runlevel"
// "shutdown" cannot be checked for since not every machine will have shutdown
// "runlevel" only makes sense for Linux systems, so only Linux is included for
// this test.
#[cfg(target_os = "linux")]
#[ignore = "fails on Arch Linux"]
fn test_system_arg() {
new_ucmd!().arg("-x").succeeds().stdout_contains("runlevel");
}
#[test]
#[cfg(unix)]
fn test_timestamp_format_no_time() {
let regex = Regex::new(" [0-9][0-9]:[0-9][0-9] ").unwrap();
new_ucmd!()
.arg("--time-format=notime")
.succeeds()
.stdout_does_not_match(&regex);
}
#[test]
#[cfg(unix)]
fn test_timestamp_format_short() {
let regex = Regex::new(" [0-9][0-9]:[0-9][0-9] ").unwrap();
new_ucmd!()
.arg("--time-format=short")
.succeeds()
.stdout_matches(&regex);
}
#[test]
#[cfg(unix)]
fn test_timestamp_format_full() {
let regex = Regex::new(" [0-9][0-9]:[0-9][0-9]:[0-9][0-9] ").unwrap();
new_ucmd!()
.arg("--time-format=full")
.succeeds()
.stdout_matches(&regex);
}
// 2024-07-11T19:30:44+08:00
#[test]
#[cfg(unix)]
fn test_timestamp_format_iso() {
let regex =
Regex::new(" [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]")
.unwrap();
new_ucmd!()
.arg("--time-format=iso")
.succeeds()
.stdout_matches(&regex);
}
#[test]
#[cfg(unix)]
fn test_short_invalid_utmp_file() {
let filepath = "/tmp/testfile";
let testfile = fs::File::create(filepath);
// Random bytes
let data: Vec<u8> = vec![
4, 5, 6, 16, 8, 13, 2, 12, 5, 3, 11, 5, 1, 13, 1, 1, 0, 9, 5, 5, 2, 8, 4,
];
let _ = testfile.unwrap().write_all(&data);
let regex = Regex::new(r"\n\S*\sbegins\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s*[0-9][0-9]?\s*[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\s*[0-9]*")
.unwrap();
new_ucmd!()
.arg(format!("--file={filepath}"))
.succeeds()
.stdout_matches(&regex);
}

View File

@@ -24,3 +24,7 @@ mod test_ctrlaltdel;
#[cfg(feature = "rev")]
#[path = "by-util/test_rev.rs"]
mod test_rev;
#[cfg(feature = "last")]
#[path = "by-util/test_last.rs"]
mod test_last;