diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2dffd3a..90f4197 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - if: ${{ contains(matrix.os, 'ubuntu') }} run: | @@ -28,7 +28,7 @@ jobs: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - if: ${{ contains(matrix.os, 'ubuntu') }} run: | @@ -43,7 +43,7 @@ jobs: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - if: ${{ contains(matrix.os, 'ubuntu') }} run: | @@ -52,8 +52,8 @@ jobs: - name: build and test all programs separately shell: bash run: | - ## TODO: add hugetop and skill - programs="free pgrep pidof pidwait pkill pmap ps pwdx slabtop snice sysctl tload top vmstat w watch" + ## TODO: add hugetop + programs="free pgrep pidof pidwait pkill pmap ps pwdx skill slabtop snice sysctl tload top vmstat w watch" for program in $programs; do echo "Building and testing $program" cargo test -p "uu_$program" || exit 1 @@ -70,7 +70,7 @@ jobs: - { os: macos-latest , features: macos } - { os: windows-latest , features: windows } steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Initialize workflow variables id: vars shell: bash diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 89744ba..8237c54 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -25,7 +25,7 @@ jobs: name: cargo fmt --all -- --check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: rustup component add rustfmt @@ -45,7 +45,7 @@ jobs: - { os: macos-latest } - { os: windows-latest } steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@master with: toolchain: stable diff --git a/Cargo.lock b/Cargo.lock index bc45fbb..5cfce04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,39 +162,28 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" dependencies = [ "chrono", - "chrono-tz-build", - "phf 0.11.3", -] - -[[package]] -name = "chrono-tz-build" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402" -dependencies = [ - "parse-zoneinfo", - "phf_codegen 0.11.3", + "phf", ] [[package]] name = "clap" -version = "4.5.40" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" dependencies = [ "anstream", "anstyle", @@ -205,9 +194,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad5b1b4de04fead402672b48897030eec1f3bfe1550776322f59f6d6e6a5677" +checksum = "4d9501bd3f5f09f7bbee01da9a511073ed30a80cd7a509f1214bb74eadea71ad" dependencies = [ "clap", ] @@ -220,9 +209,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clap_mangen" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc33c849748320656a90832f54a5eeecaa598e92557fb5dedebc3355746d31e4" +checksum = "27b4c3c54b30f0d9adcb47f25f61fcce35c4dd8916638c6b82fbd5f4fb4179e2" dependencies = [ "clap", "roff", @@ -329,19 +318,29 @@ dependencies = [ [[package]] name = "ctor" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4735f265ba6a1188052ca32d461028a7d1125868be18e287e756019da7607b5" +checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" dependencies = [ "ctor-proc-macro", - "dtor", + "dtor 0.0.6", +] + +[[package]] +name = "ctor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" +dependencies = [ + "ctor-proc-macro", + "dtor 0.1.0", ] [[package]] name = "ctor-proc-macro" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" [[package]] name = "darling" @@ -414,6 +413,15 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -424,6 +432,18 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.0", + "windows-sys 0.60.2", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -431,7 +451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -473,7 +493,16 @@ version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" dependencies = [ - "dtor-proc-macro", + "dtor-proc-macro 0.0.5", +] + +[[package]] +name = "dtor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" +dependencies = [ + "dtor-proc-macro 0.0.6", ] [[package]] @@ -482,6 +511,12 @@ version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "either" version = "1.15.0" @@ -507,7 +542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -558,7 +593,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -701,7 +736,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -743,9 +778,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libredox" @@ -894,6 +929,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_display" version = "0.1.4" @@ -926,68 +967,30 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "parse-zoneinfo" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" -dependencies = [ - "regex", -] - [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared 0.11.3", -] - [[package]] name = "phf" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" dependencies = [ - "phf_shared 0.12.1", + "phf_shared", "serde", ] -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - [[package]] name = "phf_codegen" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efbdcb6f01d193b17f0b9c3360fa7e0e620991b193ff08702f78b3ce365d7e61" dependencies = [ - "phf_generator 0.12.1", - "phf_shared 0.12.1", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", + "phf_generator", + "phf_shared", ] [[package]] @@ -997,16 +1000,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b" dependencies = [ "fastrand", - "phf_shared 0.12.1", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", + "phf_shared", ] [[package]] @@ -1080,12 +1074,12 @@ dependencies = [ "clap", "clap_complete", "clap_mangen", - "ctor", + "ctor 0.5.0", "libc", - "phf 0.12.1", - "phf_codegen 0.12.1", + "phf", + "phf_codegen", "pretty_assertions", - "rand 0.9.1", + "rand", "regex", "rlimit", "sysinfo", @@ -1099,6 +1093,7 @@ dependencies = [ "uu_pmap", "uu_ps", "uu_pwdx", + "uu_skill", "uu_slabtop", "uu_snice", "uu_sysctl", @@ -1129,21 +1124,12 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rand" -version = "0.8.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -1153,15 +1139,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - [[package]] name = "rand_core" version = "0.9.3" @@ -1212,6 +1192,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 2.0.14", +] + [[package]] name = "regex" version = "1.11.1" @@ -1272,7 +1263,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1285,7 +1276,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1452,9 +1443,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.35.2" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" +checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753" dependencies = [ "libc", "memchr", @@ -1474,7 +1465,7 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix 1.0.5", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1490,12 +1481,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix 1.0.5", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1521,11 +1512,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.14", ] [[package]] @@ -1541,9 +1532,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", @@ -1749,6 +1740,7 @@ name = "uu_pmap" version = "0.0.1" dependencies = [ "clap", + "dirs", "uucore", ] @@ -1774,6 +1766,16 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_skill" +version = "0.0.1" +dependencies = [ + "clap", + "nix", + "uu_snice", + "uucore", +] + [[package]] name = "uu_slabtop" version = "0.0.1" @@ -1791,7 +1793,7 @@ dependencies = [ "nix", "prettytable-rs", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.14", "uu_pgrep", "uucore", ] @@ -1842,6 +1844,7 @@ dependencies = [ "chrono", "clap", "terminal_size", + "uu_slabtop", "uucore", ] @@ -1880,7 +1883,7 @@ dependencies = [ "nix", "number_prefix", "os_display", - "thiserror 2.0.12", + "thiserror 2.0.14", "time", "unic-langid", "utmp-classic", @@ -1912,12 +1915,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12453ee52c9cffa6bf2c74f9f35ed0c824b846cb0b4bdee829d8150332e7204c" dependencies = [ - "ctor", + "ctor 0.4.3", "glob", "libc", "nix", "pretty_assertions", - "rand 0.9.1", + "rand", "regex", "rlimit", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 293b924..b714444 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ feat_common_core = [ "pmap", "ps", "pwdx", + "skill", "slabtop", "snice", "sysctl", @@ -57,7 +58,8 @@ clap = { version = "4.5.4", features = ["wrap_help", "cargo", "env"] } clap_complete = "4.5.2" clap_mangen = "0.2.20" crossterm = "0.29.0" -ctor = "0.4.1" +ctor = "0.5.0" +dirs = "6.0.0" libc = "0.2.154" nix = { version = "0.30", default-features = false, features = ["process"] } phf = "0.12.1" @@ -66,7 +68,7 @@ prettytable-rs = "0.10.0" rand = { version = "0.9.0", features = ["small_rng"] } ratatui = "0.29.0" regex = "1.10.4" -sysinfo = "0.35.0" +sysinfo = "0.37.0" tempfile = "3.10.1" terminal_size = "0.4.2" textwrap = { version = "0.16.1", features = ["terminal_size"] } @@ -97,6 +99,7 @@ pkill = { optional = true, version = "0.0.1", package = "uu_pkill", path = "src/ pmap = { optional = true, version = "0.0.1", package = "uu_pmap", path = "src/uu/pmap" } ps = { optional = true, version = "0.0.1", package = "uu_ps", path = "src/uu/ps" } pwdx = { optional = true, version = "0.0.1", package = "uu_pwdx", path = "src/uu/pwdx" } +skill = { optional = true, version = "0.0.1", package = "uu_skill", path = "src/uu/skill" } slabtop = { optional = true, version = "0.0.1", package = "uu_slabtop", path = "src/uu/slabtop" } snice = { optional = true, version = "0.0.1", package = "uu_snice", path = "src/uu/snice" } sysctl = { optional = true, version = "0.0.1", package = "uu_sysctl", path = "src/uu/sysctl" } diff --git a/README.md b/README.md index 95edecb..307b0c4 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Ongoing: * `pmap`: Displays the memory map of a process. * `ps`: Displays information about active processes. * `pwdx`: Shows the current working directory of a process. +* `skill`: Sends a signal to processes based on criteria like user, terminal, etc. * `slabtop`: Displays detailed kernel slab cache information in real time. * `snice`: Changes the scheduling priority of a running process. * `sysctl`: Read or write kernel parameters at run-time. @@ -31,7 +32,6 @@ Ongoing: TODO: * `hugetop`: Report hugepage usage of processes and the system as a whole. -* `skill`: Sends a signal to processes based on criteria like user, terminal, etc. Elsewhere: diff --git a/src/uu/pkill/src/pkill.rs b/src/uu/pkill/src/pkill.rs index d4c96a7..8659433 100644 --- a/src/uu/pkill/src/pkill.rs +++ b/src/uu/pkill/src/pkill.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. // Pid utils -use clap::{arg, crate_version, Command}; +use clap::{arg, crate_version, value_parser, Command}; #[cfg(unix)] use nix::{ sys::signal::{self, Signal}, @@ -48,7 +48,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else { let sig = (settings.signal as i32) .try_into() - .map_err(|e| std::io::Error::from_raw_os_error(e as i32))?; + .map_err(|e| Error::from_raw_os_error(e as i32))?; Some(sig) }; @@ -56,11 +56,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let pids = process_matcher::find_matching_pids(&settings)?; // Send signal - // TODO: Implement -q #[cfg(unix)] - let echo = matches.get_flag("echo"); - #[cfg(unix)] - kill(&pids, sig, echo); + { + let echo = matches.get_flag("echo"); + let queue = matches.get_one::("queue").cloned(); + + kill(&pids, sig, queue, echo); + } if matches.get_flag("count") { println!("{}", pids.len()); @@ -71,25 +73,50 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { #[cfg(unix)] fn handle_obsolete(args: &mut [String]) { - // Sanity check - if args.len() > 2 { - // Old signal can only be in the first argument position - let slice = args[1].as_str(); - if let Some(signal) = slice.strip_prefix('-') { + for arg in &mut args[1..] { + if let Some(signal) = arg.strip_prefix('-') { // Check if it is a valid signal let opt_signal = signal_by_name_or_value(signal); if opt_signal.is_some() { // Replace with long option that clap can parse - args[1] = format!("--signal={signal}"); + *arg = format!("--signal={signal}"); } } } } +// Not contains in libc +#[cfg(target_os = "linux")] +extern "C" { + fn sigqueue( + pid: uucore::libc::pid_t, + sig: uucore::libc::c_int, + val: uucore::libc::sigval, + ) -> uucore::libc::c_int; +} + #[cfg(unix)] -fn kill(pids: &Vec, sig: Option, echo: bool) { +#[allow(unused_variables)] +fn kill(pids: &Vec, sig: Option, queue: Option, echo: bool) { for pid in pids { - if let Err(e) = signal::kill(Pid::from_raw(pid.pid as i32), sig) { + #[cfg(target_os = "linux")] + let result = if let Some(queue) = queue { + let v = unsafe { + sigqueue( + pid.pid as i32, + sig.map_or(0, |s| s as uucore::libc::c_int), + uucore::libc::sigval { + sival_ptr: queue as usize as *mut uucore::libc::c_void, + }, + ) + }; + nix::errno::Errno::result(v).map(drop) + } else { + signal::kill(Pid::from_raw(pid.pid as i32), sig) + }; + #[cfg(not(target_os = "linux"))] + let result = signal::kill(Pid::from_raw(pid.pid as i32), sig); + if let Err(e) = result { show!(Error::from_raw_os_error(e as i32) .map_err_context(|| format!("killing pid {} failed", pid.pid))); } else if echo { @@ -111,7 +138,8 @@ pub fn uu_app() -> Command { .args_override_self(true) .args([ // arg!(- "signal to send (either number or name)"), - // arg!(-q --queue "integer value to be sent with the signal"), + arg!(-q --queue "integer value to be sent with the signal") + .value_parser(value_parser!(u32)), arg!(-e --echo "display what is killed"), ]) .args(process_matcher::clap_args( diff --git a/src/uu/pmap/Cargo.toml b/src/uu/pmap/Cargo.toml index dd17918..60a42ab 100644 --- a/src/uu/pmap/Cargo.toml +++ b/src/uu/pmap/Cargo.toml @@ -13,6 +13,7 @@ version.workspace = true [dependencies] uucore = { workspace = true } clap = { workspace = true } +dirs = { workspace = true } [lib] path = "src/pmap.rs" diff --git a/src/uu/pmap/src/maps_format_parser.rs b/src/uu/pmap/src/maps_format_parser.rs index 6516363..7572970 100644 --- a/src/uu/pmap/src/maps_format_parser.rs +++ b/src/uu/pmap/src/maps_format_parser.rs @@ -10,15 +10,44 @@ use std::io::{Error, ErrorKind}; // Represents a parsed single line from /proc//maps. #[derive(Debug, Clone, Default, PartialEq)] pub struct MapLine { - pub address: String, + pub address: Address, pub size_in_kb: u64, pub perms: Perms, pub offset: String, - pub device: String, + pub device: Device, pub inode: u64, pub mapping: String, } +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Address { + pub start: String, + pub low: u64, + pub high: u64, +} + +impl fmt::Display for Address { + // By default, pads with white spaces. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{: >16}", self.start) + } +} + +impl Address { + // Format for default, extended option, and device option. + // Pads the start address with zero. + pub fn zero_pad(&self) -> String { + format!("{:0>16}", self.start) + } + + // Checks whether an entry's address range overlaps the limits specified by the range option. + // Note: Even if a reversed range (high < low) is given, an entry still hits + // only if the specified range lies entirely within the entry's address range. + pub fn is_within_range(&self, pmap_config: &PmapConfig) -> bool { + pmap_config.range_low < self.high && self.low <= pmap_config.range_high + } +} + // Represents a set of permissions from the "perms" column of /proc//maps. #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct Perms { @@ -69,6 +98,28 @@ impl Perms { } } +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Device { + pub major: String, + pub minor: String, + pub width: usize, +} + +impl fmt::Display for Device { + // By default, does not pad. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.major, self.minor) + } +} + +impl Device { + // Format for device option. + // Pads the device info from /proc//maps with zeros and turns AB:CD into 0AB:000CD. + pub fn device(&self) -> String { + format!("{:0>3}:{:0>5}", self.major, self.minor) + } +} + // Parses a single line from /proc//maps. See // https://www.kernel.org/doc/html/latest/filesystems/proc.html for details about the expected // format. @@ -90,7 +141,7 @@ pub fn parse_map_line(line: &str) -> Result { let (offset, rest) = rest .split_once(' ') .ok_or_else(|| Error::from(ErrorKind::InvalidData))?; - let offset = format!("{offset:0>16}"); + let offset = format!("{offset:0>8}"); let (device, rest) = rest .split_once(' ') @@ -116,9 +167,8 @@ pub fn parse_map_line(line: &str) -> Result { }) } -// Returns the start address and the size of the provided memory range. The start address is always -// 16-digits and padded with 0, if necessary. The size is in KB. -fn parse_address(memory_range: &str) -> Result<(String, u64), Error> { +// Returns Address instance and the size of the provided memory range. The size is in KB. +fn parse_address(memory_range: &str) -> Result<(Address, u64), Error> { let (start, end) = memory_range .split_once('-') .ok_or_else(|| Error::from(ErrorKind::InvalidData))?; @@ -127,15 +177,26 @@ fn parse_address(memory_range: &str) -> Result<(String, u64), Error> { let high = u64::from_str_radix(end, 16).map_err(|_| Error::from(ErrorKind::InvalidData))?; let size_in_kb = (high - low) / 1024; - Ok((format!("{start:0>16}"), size_in_kb)) + Ok(( + Address { + start: start.to_string(), + low, + high, + }, + size_in_kb, + )) } -// Pads the device info from /proc//maps with zeros and turns AB:CD into 0AB:000CD. -fn parse_device(device: &str) -> Result { +// Returns Device instance. +fn parse_device(device: &str) -> Result { let (major, minor) = device .split_once(':') .ok_or_else(|| Error::from(ErrorKind::InvalidData))?; - Ok(format!("{major:0>3}:{minor:0>5}")) + Ok(Device { + major: major.to_string(), + minor: minor.to_string(), + width: device.len(), + }) } impl MapLine { @@ -174,19 +235,31 @@ mod test { fn create_map_line( address: &str, + low: u64, + high: u64, size_in_kb: u64, perms: Perms, offset: &str, - device: &str, + major: &str, + minor: &str, + width: usize, inode: u64, mapping: &str, ) -> MapLine { MapLine { - address: address.to_string(), + address: Address { + start: address.to_string(), + low, + high, + }, size_in_kb, perms, offset: offset.to_string(), - device: device.to_string(), + device: Device { + major: major.to_string(), + minor: minor.to_string(), + width, + }, inode, mapping: mapping.to_string(), } @@ -210,31 +283,31 @@ mod test { fn test_parse_map_line() { let data = [ ( - create_map_line("000062442eb9e000", 16, Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "/usr/bin/konsole"), + create_map_line("62442eb9e000", 0x62442eb9e000, 0x62442eba2000, 16, Perms::from("r--p"), "00000000", "08", "08", 5, 10813151, "/usr/bin/konsole"), "62442eb9e000-62442eba2000 r--p 00000000 08:08 10813151 /usr/bin/konsole" ), ( - create_map_line("000071af50000000", 132, Perms::from("rw-p"), "0000000000000000", "000:00000", 0, ""), + create_map_line("71af50000000", 0x71af50000000, 0x71af50021000, 132, Perms::from("rw-p"), "00000000", "00", "00", 5, 0, ""), "71af50000000-71af50021000 rw-p 00000000 00:00 0 " ), ( - create_map_line("00007ffc3f8df000", 132, Perms::from("rw-p"), "0000000000000000", "000:00000", 0, "[stack]"), + create_map_line("7ffc3f8df000", 0x7ffc3f8df000, 0x7ffc3f900000, 132, Perms::from("rw-p"), "00000000", "00", "00", 5, 0, "[stack]"), "7ffc3f8df000-7ffc3f900000 rw-p 00000000 00:00 0 [stack]" ), ( - create_map_line("000071af8c9e6000", 16, Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem"), + create_map_line("71af8c9e6000", 0x71af8c9e6000, 0x71af8c9ea000, 16, Perms::from("rw-s"), "105830000", "00", "10", 5, 1075, "anon_inode:i915.gem"), "71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem" ), ( - create_map_line("000071af6cf0c000", 3560, Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)"), + create_map_line("71af6cf0c000", 0x71af6cf0c000, 0x71af6d286000, 3560, Perms::from("rw-s"), "00000000", "00", "01", 5, 256481, "/memfd:wayland-shm (deleted)"), "71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481 /memfd:wayland-shm (deleted)" ), ( - create_map_line("ffffffffff600000", 4, Perms::from("--xp"), "0000000000000000", "000:00000", 0, "[vsyscall]"), + create_map_line("ffffffffff600000", 0xffffffffff600000, 0xffffffffff601000, 4, Perms::from("--xp"), "00000000", "00", "00", 5, 0, "[vsyscall]"), "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]" ), ( - create_map_line("00005e8187da8000", 24, Perms::from("r--p"), "0000000000000000", "008:00008", 9524160, "/usr/bin/hello world"), + create_map_line("5e8187da8000", 0x5e8187da8000, 0x5e8187dae000, 24, Perms::from("r--p"), "00000000", "08", "08", 5, 9524160, "/usr/bin/hello world"), "5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160 /usr/bin/hello world" ), ]; @@ -251,12 +324,16 @@ mod test { #[test] fn test_parse_address() { - let (start, size) = parse_address("ffffffffff600000-ffffffffff601000").unwrap(); - assert_eq!(start, "ffffffffff600000"); + let (address, size) = parse_address("ffffffffff600000-ffffffffff601000").unwrap(); + assert_eq!(address.start, "ffffffffff600000"); + assert_eq!(address.low, 0xffffffffff600000); + assert_eq!(address.high, 0xffffffffff601000); assert_eq!(size, 4); - let (start, size) = parse_address("7ffc4f0c2000-7ffc4f0e3000").unwrap(); - assert_eq!(start, "00007ffc4f0c2000"); + let (address, size) = parse_address("7ffc4f0c2000-7ffc4f0e3000").unwrap(); + assert_eq!(address.start, "7ffc4f0c2000"); + assert_eq!(address.low, 0x7ffc4f0c2000); + assert_eq!(address.high, 0x7ffc4f0e3000); assert_eq!(size, 132); } @@ -271,10 +348,101 @@ mod test { assert!(parse_address("ffffffffff600000-zfffffffff601000").is_err()); } + fn limit_address_range_and_assert(address: &Address, low: u64, high: u64, expected: bool) { + let mut pmap_config = PmapConfig::default(); + (pmap_config.range_low, pmap_config.range_high) = (low, high); + assert_eq!( + address.is_within_range(&pmap_config), + expected, + "`--range 0x{low:x},0x{high:x}` expected to be {expected} for address 0x{:x},0x{:x}", + address.low, + address.high, + ); + } + + #[test] + fn test_limit_address_range() { + let low: u64 = 0x71af50000000; + let high: u64 = 0x71af50021000; + let address = Address { + start: "0x71af50000000".to_string(), + low, + high, + }; + + limit_address_range_and_assert(&address, 0x0, u64::MAX, true); + limit_address_range_and_assert(&address, 0x70000000, 0xffffffffffff, true); + + limit_address_range_and_assert(&address, 0x0, 0x0, false); + limit_address_range_and_assert(&address, 0x0, 0x70000000, false); + limit_address_range_and_assert(&address, 0x0, low - 1, false); + limit_address_range_and_assert(&address, low - 1, low - 1, false); + + limit_address_range_and_assert(&address, 0x0, low, true); + limit_address_range_and_assert(&address, low - 1, low, true); + limit_address_range_and_assert(&address, low, low, true); + + limit_address_range_and_assert(&address, low - 1, high - 1, true); + limit_address_range_and_assert(&address, low - 1, high, true); + limit_address_range_and_assert(&address, low - 1, high + 1, true); + limit_address_range_and_assert(&address, low, high - 1, true); + limit_address_range_and_assert(&address, low, high, true); + limit_address_range_and_assert(&address, low, high + 1, true); + limit_address_range_and_assert(&address, low + 1, high - 1, true); + limit_address_range_and_assert(&address, low + 1, high, true); + limit_address_range_and_assert(&address, low + 1, high + 1, true); + + limit_address_range_and_assert(&address, high - 1, high - 1, true); + limit_address_range_and_assert(&address, high - 1, high, true); + limit_address_range_and_assert(&address, high - 1, u64::MAX, true); + + limit_address_range_and_assert(&address, high, high, false); + limit_address_range_and_assert(&address, high, high + 1, false); + limit_address_range_and_assert(&address, high, u64::MAX, false); + limit_address_range_and_assert(&address, 0xffffffffffff, u64::MAX, false); + limit_address_range_and_assert(&address, u64::MAX, u64::MAX, false); + + // Reversed range + + limit_address_range_and_assert(&address, u64::MAX, 0, false); + limit_address_range_and_assert(&address, 0xffffffffffff, 0x70000000, false); + + limit_address_range_and_assert(&address, 0x70000000, 0x0, false); + limit_address_range_and_assert(&address, low - 1, 0x0, false); + limit_address_range_and_assert(&address, low - 1, low - 1, false); + + limit_address_range_and_assert(&address, low, 0x0, false); + limit_address_range_and_assert(&address, low, low - 1, false); + + limit_address_range_and_assert(&address, high - 1, low - 1, false); + limit_address_range_and_assert(&address, high, low - 1, false); + limit_address_range_and_assert(&address, high + 1, low - 1, false); + limit_address_range_and_assert(&address, high - 1, low, true); + limit_address_range_and_assert(&address, high, low, false); + limit_address_range_and_assert(&address, high + 1, low, false); + limit_address_range_and_assert(&address, high - 1, low + 1, true); + limit_address_range_and_assert(&address, high, low + 1, false); + limit_address_range_and_assert(&address, high + 1, low + 1, false); + + limit_address_range_and_assert(&address, high, high - 1, false); + limit_address_range_and_assert(&address, u64::MAX, high - 1, false); + + limit_address_range_and_assert(&address, high + 1, high, false); + limit_address_range_and_assert(&address, u64::MAX, high, false); + limit_address_range_and_assert(&address, u64::MAX, 0xffffffffffff, false); + } + #[test] fn test_parse_device() { - assert_eq!("012:00034", parse_device("12:34").unwrap()); - assert_eq!("000:00000", parse_device("00:00").unwrap()); + assert_eq!("12:34", parse_device("12:34").unwrap().to_string()); + assert_eq!("00:00", parse_device("00:00").unwrap().to_string()); + assert_eq!("fe:01", parse_device("fe:01").unwrap().to_string()); + assert_eq!("103:100", parse_device("103:100").unwrap().to_string()); + + assert_eq!("012:00034", parse_device("12:34").unwrap().device()); + assert_eq!("000:00000", parse_device("00:00").unwrap().device()); + assert_eq!("0fe:00001", parse_device("fe:01").unwrap().device()); + assert_eq!("103:00100", parse_device("103:100").unwrap().device()); } #[test] diff --git a/src/uu/pmap/src/pmap.rs b/src/uu/pmap/src/pmap.rs index 34073b4..05a046e 100644 --- a/src/uu/pmap/src/pmap.rs +++ b/src/uu/pmap/src/pmap.rs @@ -5,12 +5,12 @@ use clap::{crate_version, Arg, ArgAction, Command}; use maps_format_parser::{parse_map_line, MapLine}; -use pmap_config::{pmap_field_name, PmapConfig}; +use pmap_config::{create_rc, pmap_field_name, PmapConfig}; use smaps_format_parser::{parse_smaps, SmapTable}; use std::env; use std::fs; -use std::io::Error; -use uucore::error::{set_exit_code, UResult}; +use std::io::{Error, ErrorKind}; +use uucore::error::{set_exit_code, UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage}; mod maps_format_parser; @@ -39,18 +39,104 @@ mod options { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; + if matches.get_flag(options::CREATE_RC) { + let path = pmap_config::get_rc_default_path(); + if std::fs::exists(&path)? { + eprintln!("pmap: the file already exists - delete or rename it first"); + eprintln!( + "pmap: couldn't create {}", + pmap_config::get_rc_default_path_str() + ); + set_exit_code(1); + } else { + create_rc(&path)?; + eprintln!( + "pmap: {} file successfully created, feel free to edit the content", + pmap_config::get_rc_default_path_str() + ); + } + return Ok(()); + } else if let Some(path_str) = matches.get_one::(options::CREATE_RC_TO) { + let path = std::path::PathBuf::from(path_str); + if std::fs::exists(&path)? { + eprintln!("pmap: the file already exists - delete or rename it first"); + eprintln!("pmap: couldn't create the rc file"); + set_exit_code(1); + } else { + create_rc(&path)?; + eprintln!("pmap: rc file successfully created, feel free to edit the content"); + } + return Ok(()); + } + let mut pmap_config = PmapConfig::default(); if matches.get_flag(options::MORE_EXTENDED) { pmap_config.set_more_extended(); } else if matches.get_flag(options::MOST_EXTENDED) { pmap_config.set_most_extended(); + } else if matches.get_flag(options::READ_RC) { + let path = pmap_config::get_rc_default_path(); + if !std::fs::exists(&path)? { + eprintln!( + "pmap: couldn't read {}", + pmap_config::get_rc_default_path_str() + ); + set_exit_code(1); + return Ok(()); + } + pmap_config.read_rc(&path)?; + } else if let Some(path) = matches.get_one::(options::READ_RC_FROM) { + let path = std::fs::canonicalize(path)?; + if !std::fs::exists(&path)? { + eprintln!("pmap: couldn't read the rc file"); + set_exit_code(1); + return Ok(()); + } + pmap_config.read_rc(&path)?; } // Options independent with field selection: pmap_config.quiet = matches.get_flag(options::QUIET); - if matches.get_flag(options::SHOW_PATH) { - pmap_config.show_path = true; + pmap_config.show_path = matches.get_flag(options::SHOW_PATH); + + if let Some(range) = matches.get_one::(options::RANGE) { + match range.matches(',').count() { + 0 => { + let address = u64::from_str_radix(range, 16).map_err(|_| { + USimpleError::new(1, format!("failed to parse argument: '{range}'")) + })?; + pmap_config.range_low = address; + pmap_config.range_high = address; + } + 1 => { + let (low, high) = range + .split_once(',') + .ok_or_else(|| Error::from(ErrorKind::InvalidData))?; + pmap_config.range_low = if low.is_empty() { + 0 + } else { + u64::from_str_radix(low, 16).map_err(|_| { + USimpleError::new(1, format!("failed to parse argument: '{range}'")) + })? + }; + pmap_config.range_high = if high.is_empty() { + u64::MAX + } else { + u64::from_str_radix(high, 16).map_err(|_| { + USimpleError::new(1, format!("failed to parse argument: '{range}'")) + })? + }; + } + _ => { + eprintln!("pmap: failed to parse argument: '{range}'"); + set_exit_code(1); + return Ok(()); + } + } + } else { + pmap_config.range_low = 0; + pmap_config.range_high = u64::MAX; } let pids = matches @@ -133,14 +219,16 @@ fn output_default_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Erro let mut total = 0; process_maps(pid, None, |map_line| { - println!( - "{} {:>6}K {} {}", - map_line.address, - map_line.size_in_kb, - map_line.perms.mode(), - map_line.parse_mapping(pmap_config) - ); - total += map_line.size_in_kb; + if map_line.address.is_within_range(pmap_config) { + println!( + "{} {:>6}K {} {}", + map_line.address.zero_pad(), + map_line.size_in_kb, + map_line.perms.mode(), + map_line.parse_mapping(pmap_config) + ); + total += map_line.size_in_kb; + } })?; if !pmap_config.quiet { @@ -157,25 +245,31 @@ fn output_extended_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Err println!("Address Kbytes RSS Dirty Mode Mapping"); } + let mut total_size_in_kb = 0; + let mut total_rss_in_kb = 0; + let mut total_dirty_in_kb = 0; + for smap_entry in smap_table.entries { - println!( - "{} {:>7} {:>7} {:>7} {} {}", - smap_entry.map_line.address, - smap_entry.map_line.size_in_kb, - smap_entry.rss_in_kb, - smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb, - smap_entry.map_line.perms.mode(), - smap_entry.map_line.parse_mapping(pmap_config) - ); + if smap_entry.map_line.address.is_within_range(pmap_config) { + println!( + "{} {:>7} {:>7} {:>7} {} {}", + smap_entry.map_line.address.zero_pad(), + smap_entry.map_line.size_in_kb, + smap_entry.rss_in_kb, + smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb, + smap_entry.map_line.perms.mode(), + smap_entry.map_line.parse_mapping(pmap_config) + ); + total_size_in_kb += smap_entry.map_line.size_in_kb; + total_rss_in_kb += smap_entry.rss_in_kb; + total_dirty_in_kb += smap_entry.shared_dirty_in_kb + smap_entry.private_dirty_in_kb; + } } if !pmap_config.quiet { println!("---------------- ------- ------- ------- "); println!( - "total kB {:>7} {:>7} {:>7}", - smap_table.info.total_size_in_kb, - smap_table.info.total_rss_in_kb, - smap_table.info.total_shared_dirty_in_kb + smap_table.info.total_private_dirty_in_kb, + "total kB {total_size_in_kb:>7} {total_rss_in_kb:>7} {total_dirty_in_kb:>7}" ); } @@ -311,23 +405,25 @@ fn output_device_format(pid: &str, pmap_config: &PmapConfig) -> Result<(), Error None }, |map_line| { - println!( - "{} {:>7} {} {} {} {}", - map_line.address, - map_line.size_in_kb, - map_line.perms.mode(), - map_line.offset, - map_line.device, - map_line.parse_mapping(pmap_config) - ); - total_mapped += map_line.size_in_kb; + if map_line.address.is_within_range(pmap_config) { + println!( + "{} {:>7} {} {:0>16} {} {}", + map_line.address.zero_pad(), + map_line.size_in_kb, + map_line.perms.mode(), + map_line.offset, + map_line.device.device(), + map_line.parse_mapping(pmap_config) + ); + total_mapped += map_line.size_in_kb; - if map_line.perms.writable && !map_line.perms.shared { - total_writeable_private += map_line.size_in_kb; - } + if map_line.perms.writable && !map_line.perms.shared { + total_writeable_private += map_line.size_in_kb; + } - if map_line.perms.shared { - total_shared += map_line.size_in_kb; + if map_line.perms.shared { + total_shared += map_line.size_in_kb; + } } }, )?; @@ -405,36 +501,81 @@ pub fn uu_app() -> Command { .short('c') .long("read-rc") .help("read the default rc") - .action(ArgAction::SetTrue), - ) + .action(ArgAction::SetTrue) + .conflicts_with_all([ + "read-rc-from", + "device", + "create-rc", + "create-rc-to", + "extended", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::READ_RC_FROM) .short('C') .long("read-rc-from") .num_args(1) - .help("read the rc from file"), - ) + .help("read the rc from file") + .conflicts_with_all([ + "read-rc", + "device", + "create-rc", + "create-rc-to", + "extended", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::CREATE_RC) .short('n') .long("create-rc") .help("create new default rc") - .action(ArgAction::SetTrue), - ) + .action(ArgAction::SetTrue) + .conflicts_with_all([ + "read-rc", + "read-rc-from", + "device", + "create-rc-to", + "extended", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::CREATE_RC_TO) .short('N') .long("create-rc-to") .num_args(1) - .help("create new rc to file"), - ) + .help("create new rc to file") + .conflicts_with_all([ + "read-rc", + "read-rc-from", + "device", + "create-rc", + "extended", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::DEVICE) .short('d') .long("device") .help("show the device format") - .action(ArgAction::SetTrue), - ) + .action(ArgAction::SetTrue) + .conflicts_with_all([ + "read-rc", + "read-rc-from", + "create-rc", + "create-rc-to", + "extended", + "more-extended", + "most-extended", + ]), + ) // pmap: options -c, -C, -d, -n, -N, -x, -X are mutually exclusive .arg( Arg::new(options::QUIET) .short('q') @@ -453,7 +594,9 @@ pub fn uu_app() -> Command { Arg::new(options::RANGE) .short('A') .long("range") - .num_args(1..=2) - .help("limit results to the given range"), + .num_args(1) + .help("limit results to the given range [,]"), + // This option applies only to the default, extended, or device formats, + // yet it will not raise an error in any other case. ) } diff --git a/src/uu/pmap/src/pmap_config.rs b/src/uu/pmap/src/pmap_config.rs index 5f544c8..d399eb6 100644 --- a/src/uu/pmap/src/pmap_config.rs +++ b/src/uu/pmap/src/pmap_config.rs @@ -3,6 +3,10 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use dirs::home_dir; +use std::io::Error; +use std::path::PathBuf; + pub mod pmap_field_name { pub const ADDRESS: &str = "Address"; pub const PERM: &str = "Perm"; @@ -77,6 +81,8 @@ pub struct PmapConfig { // Misc pub quiet: bool, pub custom_format_enabled: bool, + pub range_low: u64, + pub range_high: u64, } impl PmapConfig { @@ -200,6 +206,10 @@ impl PmapConfig { } } + pub fn enable_field(&mut self, field_name: &str) { + self.set_field(field_name, true); + } + pub fn disable_field(&mut self, field_name: &str) { self.set_field(field_name, false); } @@ -244,4 +254,91 @@ impl PmapConfig { self.anon_huge_pages = true; self.vmflags = true; } + + pub fn read_rc(&mut self, path: &PathBuf) -> Result<(), Error> { + self.custom_format_enabled = true; + + let contents = std::fs::read_to_string(path)?; + + let mut in_field_display = false; + let mut in_mapping = false; + + for line in contents.lines() { + let line = line.trim_ascii(); + if line.starts_with("#") || line.is_empty() { + continue; + } + + // The leftmost category on the line is recoginized. + if line.starts_with("[Fields Display]") { + in_field_display = true; + in_mapping = false; + continue; + } else if line.starts_with("[Mapping]") { + in_field_display = false; + in_mapping = true; + continue; + } + + if in_field_display { + self.enable_field(line); + } else if in_mapping && line == "ShowPath" { + self.show_path = true; + } + } + + Ok(()) + } +} + +pub fn create_rc(path: &PathBuf) -> Result<(), Error> { + let contents = "# pmap's Config File\n".to_string() + + "\n" + + "# All the entries are case sensitive.\n" + + "# Unsupported entries are ignored!\n" + + "\n" + + "[Fields Display]\n" + + "\n" + + "# To enable a field uncomment its entry\n" + + "\n" + + "#Perm\n" + + "#Offset\n" + + "#Device\n" + + "#Inode\n" + + "#Size\n" + + "#Rss\n" + + "#Pss\n" + + "#Shared_Clean\n" + + "#Shared_Dirty\n" + + "#Private_Clean\n" + + "#Private_Dirty\n" + + "#Referenced\n" + + "#Anonymous\n" + + "#AnonHugePages\n" + + "#Swap\n" + + "#KernelPageSize\n" + + "#MMUPageSize\n" + + "#Locked\n" + + "#VmFlags\n" + + "#Mapping\n" + + "\n" + + "[Mapping]\n" + + "\n" + + "# to show paths in the mapping column uncomment the following line\n" + + "#ShowPath\n" + + "\n"; + + std::fs::write(path, contents)?; + + Ok(()) +} + +pub fn get_rc_default_path() -> PathBuf { + let mut path = home_dir().expect("home directory should not be None"); + path.push(".pmaprc"); + path +} + +pub fn get_rc_default_path_str() -> &'static str { + "~/.pmaprc" } diff --git a/src/uu/pmap/src/smaps_format_parser.rs b/src/uu/pmap/src/smaps_format_parser.rs index 779a2b8..f478916 100644 --- a/src/uu/pmap/src/smaps_format_parser.rs +++ b/src/uu/pmap/src/smaps_format_parser.rs @@ -40,10 +40,10 @@ pub struct SmapEntry { impl SmapEntry { pub fn get_field(&self, field_name: &str) -> String { match field_name { - pmap_field_name::ADDRESS => self.map_line.address.clone(), + pmap_field_name::ADDRESS => self.map_line.address.to_string(), pmap_field_name::PERM => self.map_line.perms.to_string(), pmap_field_name::OFFSET => self.map_line.offset.clone(), - pmap_field_name::DEVICE => self.map_line.device.clone(), + pmap_field_name::DEVICE => self.map_line.device.to_string(), pmap_field_name::INODE => self.map_line.inode.to_string(), pmap_field_name::SIZE => self.map_line.size_in_kb.to_string(), pmap_field_name::KERNEL_PAGE_SIZE => self.kernel_page_size_in_kb.to_string(), @@ -107,6 +107,9 @@ pub struct SmapTableInfo { pub total_thp_eligible: u64, pub total_protection_key: u64, // Width + pub offset_width: usize, + pub device_width: usize, + pub inode_width: usize, pub size_in_kb_width: usize, pub kernel_page_size_in_kb_width: usize, pub mmu_page_size_in_kb_width: usize, @@ -165,6 +168,9 @@ impl Default for SmapTableInfo { total_thp_eligible: 0, total_protection_key: 0, + device_width: pmap_field_name::DEVICE.len(), + offset_width: pmap_field_name::OFFSET.len(), + inode_width: pmap_field_name::INODE.len(), size_in_kb_width: pmap_field_name::SIZE.len(), kernel_page_size_in_kb_width: pmap_field_name::KERNEL_PAGE_SIZE.len(), mmu_page_size_in_kb_width: pmap_field_name::MMU_PAGE_SIZE.len(), @@ -200,9 +206,9 @@ impl SmapTableInfo { match field_name { pmap_field_name::ADDRESS => 16, // See maps_format_parser.rs pmap_field_name::PERM => 4, // See maps_format_parser.rs - pmap_field_name::OFFSET => 16, // See maps_format_parser.rs - pmap_field_name::DEVICE => 9, // See maps_format_parser.rs - pmap_field_name::INODE => 10, // See maps_format_parser.rs + pmap_field_name::OFFSET => self.offset_width, + pmap_field_name::DEVICE => self.device_width, + pmap_field_name::INODE => self.inode_width, pmap_field_name::SIZE => self.size_in_kb_width, pmap_field_name::KERNEL_PAGE_SIZE => self.kernel_page_size_in_kb_width, pmap_field_name::MMU_PAGE_SIZE => self.mmu_page_size_in_kb_width, @@ -289,6 +295,12 @@ pub fn parse_smaps(contents: &str) -> Result { smap_entry = SmapEntry::default(); } smap_table.info.total_size_in_kb += map_line.size_in_kb; + smap_table.info.offset_width = smap_table.info.offset_width.max(map_line.offset.len()); + smap_table.info.device_width = smap_table.info.device_width.max(map_line.device.width); + smap_table.info.inode_width = smap_table + .info + .inode_width + .max(map_line.inode.to_string().len()); smap_entry.map_line = map_line; } else { let (key, val) = line @@ -544,9 +556,13 @@ mod test { #[allow(clippy::too_many_arguments)] fn create_smap_entry( address: &str, + low: u64, + high: u64, perms: Perms, offset: &str, - device: &str, + major: &str, + minor: &str, + width: usize, inode: u64, mapping: &str, size_in_kb: u64, @@ -577,11 +593,19 @@ mod test { ) -> SmapEntry { SmapEntry { map_line: MapLine { - address: address.to_string(), + address: crate::maps_format_parser::Address { + start: address.to_string(), + low, + high, + }, size_in_kb, perms, offset: offset.to_string(), - device: device.to_string(), + device: crate::maps_format_parser::Device { + major: major.to_string(), + minor: minor.to_string(), + width, + }, inode, mapping: mapping.to_string(), }, @@ -617,7 +641,7 @@ mod test { let data = [ ( vec![create_smap_entry( - "0000560880413000", Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "/usr/bin/konsole", + "560880413000", 0x560880413000, 0x560880440000, Perms::from("r--p"), "00000000", "08", "08", 5, 10813151, "/usr/bin/konsole", 180, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 0, "rd mr mw me dw sd")], concat!( @@ -651,7 +675,7 @@ mod test { ), ( vec![create_smap_entry( - "000071af50000000", Perms::from("rw-p"), "0000000000000000", "000:00000", 0, "", + "71af50000000", 0x71af50000000, 0x71af50021000, Perms::from("rw-p"), "00000000", "00", "00", 5, 0, "", 132, 4, 4, 128, 9, 9, 128, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, "rd mr mw me sd")], concat!( @@ -684,7 +708,7 @@ mod test { ), ( vec![create_smap_entry( - "00007ffc3f8df000", Perms::from("rw-p"), "0000000000000000", "000:00000", 0, "[stack]", + "7ffc3f8df000", 0x7ffc3f8df000, 0x7ffc3f900000, Perms::from("rw-p"), "00000000", "00", "00", 5, 0, "[stack]", 132, 4, 4, 108, 108, 108, 0, 0, 0, 108, 108, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, "rd wr mr mw me gd ac")], concat!( @@ -717,7 +741,7 @@ mod test { ), ( vec![create_smap_entry( - "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem", + "71af8c9e6000", 0x71af8c9e6000, 0x71af8c9ea000, Perms::from("rw-s"), "105830000", "00", "10", 5, 1075, "anon_inode:i915.gem", 16, 4, 4, 16, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd wr mr mw me ac sd")], concat!( @@ -749,7 +773,7 @@ mod test { ), ( vec![create_smap_entry( - "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)", + "71af6cf0c000", 0x71af6cf0c000, 0x71af6d286000, Perms::from("rw-s"), "00000000", "00", "01", 5, 256481, "/memfd:wayland-shm (deleted)", 3560, 4, 4, 532, 108, 0, 524, 0, 8, 0, 532, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd mr mw me sd")], concat!( @@ -781,7 +805,7 @@ mod test { ), ( vec![create_smap_entry( - "ffffffffff600000", Perms::from("--xp"), "0000000000000000", "000:00000", 0, "[vsyscall]", + "ffffffffff600000", 0xffffffffff600000, 0xffffffffff601000, Perms::from("--xp"), "00000000", "00", "00", 5, 0, "[vsyscall]", 4, 4, 4, 4, 4, 4, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd wr mr mw me ac sd")], concat!( @@ -813,7 +837,7 @@ mod test { ), ( vec![create_smap_entry( - "00005e8187da8000", Perms::from("r--p"), "0000000000000000", "008:00008", 9524160, "/usr/bin/hello world", + "5e8187da8000", 0x5e8187da8000, 0x5e8187dae000, Perms::from("r--p"), "00000000", "08", "08", 5, 9524160, "/usr/bin/hello world", 24, 4, 4, 24, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd ex mr mw me sd")], concat!( @@ -846,11 +870,11 @@ mod test { ( vec![ create_smap_entry( - "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem", + "71af8c9e6000", 0x71af8c9e6000, 0x71af8c9ea000, Perms::from("rw-s"), "105830000", "00", "10", 5, 1075, "anon_inode:i915.gem", 16, 4, 4, 16, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd wr mr mw me ac sd"), create_smap_entry( - "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)", + "71af6cf0c000", 0x71af6cf0c000, 0x71af6d286000, Perms::from("rw-s"), "00000000", "00", "01", 5, 256481, "/memfd:wayland-shm (deleted)", 3560, 4, 4, 532, 108, 0, 524, 0, 8, 0, 532, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd mr mw me sd"), ], @@ -909,15 +933,15 @@ mod test { ( vec![ create_smap_entry( - "000071af8c9e6000", Perms::from("rw-s"), "0000000105830000", "000:00010", 1075, "anon_inode:i915.gem", + "71af8c9e6000", 0x71af8c9e6000, 0x71af8c9ea000, Perms::from("rw-s"), "105830000", "00", "10", 5, 1075, "anon_inode:i915.gem", 16, 4, 4, 16, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, "rd wr mr mw me ac sd"), create_smap_entry( - "0000560880413000", Perms::from("r--p"), "0000000000000000", "008:00008", 10813151, "/usr/bin/konsole", + "560880413000", 0x560880413000, 0x560880440000, Perms::from("r--p"), "00000000", "08", "08", 5, 10813151, "/usr/bin/konsole", 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ""), create_smap_entry( - "000071af6cf0c000", Perms::from("rw-s"), "0000000000000000", "000:00001", 256481, "/memfd:wayland-shm (deleted)", + "71af6cf0c000", 0x71af6cf0c000, 0x71af6d286000, Perms::from("rw-s"), "00000000", "00", "01", 5, 256481, "/memfd:wayland-shm (deleted)", 3560, 4, 4, 532, 108, 0, 524, 0, 8, 0, 532, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "rd mr mw me sd"), ], diff --git a/src/uu/skill/Cargo.toml b/src/uu/skill/Cargo.toml new file mode 100644 index 0000000..dedc7bf --- /dev/null +++ b/src/uu/skill/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "uu_skill" +description = "skill - (uutils) send a signal or report process status" +repository = "https://github.com/uutils/procps/tree/main/src/uu/skill" +authors.workspace = true +categories.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] +uucore = { workspace = true, features = ["signals"] } +clap = { workspace = true } +nix = { workspace = true } + +uu_snice = { path = "../snice" } + + +[lib] +path = "src/skill.rs" + +[[bin]] +name = "skill" +path = "src/main.rs" diff --git a/src/uu/skill/skill.md b/src/uu/skill/skill.md new file mode 100644 index 0000000..8fdb454 --- /dev/null +++ b/src/uu/skill/skill.md @@ -0,0 +1,7 @@ +# skill + +``` +skill [signal] [options] +``` + +Report processes matching an expression and send a signal to them. \ No newline at end of file diff --git a/src/uu/skill/src/main.rs b/src/uu/skill/src/main.rs new file mode 100644 index 0000000..2e827bc --- /dev/null +++ b/src/uu/skill/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_skill); diff --git a/src/uu/skill/src/skill.rs b/src/uu/skill/src/skill.rs new file mode 100644 index 0000000..e1c47c6 --- /dev/null +++ b/src/uu/skill/src/skill.rs @@ -0,0 +1,106 @@ +// This file is part of the uutils procps package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use clap::{arg, crate_version, value_parser, Arg, Command}; +#[cfg(unix)] +use nix::{sys::signal, sys::signal::Signal, unistd::Pid}; +use uu_snice::{ + collect_pids, construct_verbose_result, print_signals, process_matcher, ActionResult, +}; +use uucore::error::USimpleError; +#[cfg(unix)] +use uucore::signals::signal_by_name_or_value; +use uucore::{error::UResult, format_usage, help_about, help_usage}; + +const ABOUT: &str = help_about!("skill.md"); +const USAGE: &str = help_usage!("skill.md"); + +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches = uu_app().try_get_matches_from(args)?; + let settings = process_matcher::Settings::try_new(&matches)?; + + // Case0: Print SIGNALS + if let Some(display) = &settings.display { + print_signals(display); + return Ok(()); + } + + // Case1: Send signal + if let Some(targets) = settings.expressions { + let pids = collect_pids(&targets); + + #[cfg(unix)] + let signal_str = matches.get_one::("signal").cloned(); + + #[cfg(unix)] + let signal = if let Some(sig) = signal_str { + (signal_by_name_or_value(sig.strip_prefix('-').unwrap()).unwrap() as i32).try_into()? + } else { + Signal::SIGTERM + }; + + #[cfg(unix)] + let results = perform_action(&pids, &signal); + #[cfg(not(unix))] + let results: Vec> = Vec::new(); + + if results.iter().all(|it| it.is_none()) || results.is_empty() { + return Err(USimpleError::new(1, "no process selection criteria")); + } + + if settings.verbose { + let output = construct_verbose_result(&pids, &results).trim().to_owned(); + println!("{output}"); + } + } + + Ok(()) +} + +#[cfg(unix)] +fn perform_action(pids: &[u32], signal: &Signal) -> Vec> { + pids.iter() + .map(|pid| { + { + Some(match signal::kill(Pid::from_raw(*pid as i32), *signal) { + Ok(_) => ActionResult::Success, + + Err(_) => ActionResult::PermissionDenied, + }) + } + }) + .collect() +} + +#[allow(clippy::cognitive_complexity)] +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_required_else_help(true) + .arg(Arg::new("signal")) + .args([ + // arg!(-f --fast "fast mode (not implemented)"), + // arg!(-i --interactive "interactive"), + arg!(-l --list "list all signal names"), + arg!(-L --table "list all signal names in a nice table"), + // arg!(-n --"no-action" "do not actually kill processes; just print what would happen"), + arg!(-v --verbose "explain what is being done"), + // arg!(-w --warnings "enable warnings (not implemented)"), + // Expressions + arg!(-c --command ... "expression is a command name"), + arg!(-p --pid ... "expression is a process id number") + .value_parser(value_parser!(u32)), + arg!(-t --tty ... "expression is a terminal"), + arg!(-u --user ... "expression is a username"), + // arg!(--ns "match the processes that belong to the same namespace as "), + // arg!(--nslist "list which namespaces will be considered for the --ns option.") + // .value_delimiter(',') + // .value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]), + ]) +} diff --git a/src/uu/slabtop/src/parse.rs b/src/uu/slabtop/src/parse.rs index 0730379..eb01646 100644 --- a/src/uu/slabtop/src/parse.rs +++ b/src/uu/slabtop/src/parse.rs @@ -3,25 +3,35 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use std::{ - cmp::Ordering, - fs, - io::{Error, ErrorKind}, -}; +use std::io::Error; +use std::{cmp::Ordering, fs, io::ErrorKind}; +use uucore::error::{UError, UResult, USimpleError}; #[derive(Debug, Default)] -pub(crate) struct SlabInfo { - pub(crate) meta: Vec, - pub(crate) data: Vec<(String, Vec)>, +pub struct SlabInfo { + pub meta: Vec, + pub data: Vec<(String, Vec)>, } impl SlabInfo { // parse slabinfo from /proc/slabinfo // need root permission - pub fn new() -> Result { - let content = fs::read_to_string("/proc/slabinfo")?; + pub fn new() -> UResult { + let error_wrapper = |e: Error| { + USimpleError::new( + 1, + format!( + "Unable to create slabinfo structure: {}", + Box::::from(e) // We need Display impl of UError + ), + ) + }; - Self::parse(&content).ok_or(ErrorKind::Unsupported.into()) + let content = fs::read_to_string("/proc/slabinfo").map_err(error_wrapper)?; + + Self::parse(&content) + .ok_or(ErrorKind::Unsupported.into()) + .map_err(error_wrapper) } pub fn parse(content: &str) -> Option { diff --git a/src/uu/slabtop/src/slabtop.rs b/src/uu/slabtop/src/slabtop.rs index 3fc0165..1dc5c91 100644 --- a/src/uu/slabtop/src/slabtop.rs +++ b/src/uu/slabtop/src/slabtop.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::parse::SlabInfo; +pub use crate::parse::SlabInfo; use clap::{arg, crate_version, ArgAction, Command}; use uucore::{error::UResult, format_usage, help_about, help_section, help_usage}; @@ -25,6 +25,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let slabinfo = SlabInfo::new()?.sort(*sort_flag, false); + println!("{slabinfo:?}"); + if matches.get_flag("once") { output_header(&slabinfo); println!(); diff --git a/src/uu/snice/src/action.rs b/src/uu/snice/src/action.rs index 3fac9ad..dff7bd3 100644 --- a/src/uu/snice/src/action.rs +++ b/src/uu/snice/src/action.rs @@ -24,7 +24,7 @@ pub(crate) fn users() -> &'static Users { } #[derive(Debug)] -pub(crate) enum SelectedTarget { +pub enum SelectedTarget { Command(String), Pid(u32), Tty(Teletype), @@ -93,7 +93,7 @@ impl SelectedTarget { #[allow(unused)] #[derive(Debug, Clone)] -pub(crate) enum ActionResult { +pub enum ActionResult { PermissionDenied, Success, } @@ -111,7 +111,7 @@ impl Display for ActionResult { /// /// But we don't know if the process of pid are exist, if [None], the process doesn't exist #[cfg(target_os = "linux")] -fn set_priority(pid: u32, prio: &Priority) -> Option { +fn set_priority(pid: u32, prio: &Priority, take_action: bool) -> Option { use libc::{getpriority, setpriority, PRIO_PROCESS}; use nix::errno::Errno; @@ -136,6 +136,10 @@ fn set_priority(pid: u32, prio: &Priority) -> Option { prio }; + if !take_action { + return Some(ActionResult::Success); + } + let prio = match prio { Priority::Increase(prio) => current_priority + *prio as i32, Priority::Decrease(prio) => current_priority - *prio as i32, @@ -159,11 +163,15 @@ fn set_priority(pid: u32, prio: &Priority) -> Option { // TODO: Implemented this on other platform #[cfg(not(target_os = "linux"))] -fn set_priority(_pid: u32, _prio: &Priority) -> Option { +fn set_priority(_pid: u32, _prio: &Priority, _take_action: bool) -> Option { None } -pub(crate) fn perform_action(pids: &[u32], prio: &Priority) -> Vec> { - let f = |pid: &u32| set_priority(*pid, prio); +pub(crate) fn perform_action( + pids: &[u32], + prio: &Priority, + take_action: bool, +) -> Vec> { + let f = |pid: &u32| set_priority(*pid, prio, take_action); pids.iter().map(f).collect() } diff --git a/src/uu/snice/src/priority.rs b/src/uu/snice/src/priority.rs index d87dd9a..889ebdb 100644 --- a/src/uu/snice/src/priority.rs +++ b/src/uu/snice/src/priority.rs @@ -13,7 +13,7 @@ pub enum Error { } #[derive(Debug, PartialEq, Eq)] -pub(crate) enum Priority { +pub enum Priority { // The default priority is +4. (snice +4 ...) Increase(u32), Decrease(u32), diff --git a/src/uu/snice/src/process_matcher.rs b/src/uu/snice/src/process_matcher.rs new file mode 100644 index 0000000..939d326 --- /dev/null +++ b/src/uu/snice/src/process_matcher.rs @@ -0,0 +1,101 @@ +// This file is part of the uutils procps package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use crate::action::SelectedTarget; +use crate::SignalDisplay; +use clap::{arg, value_parser, Arg, ArgMatches}; +use uu_pgrep::process::Teletype; +use uucore::error::UResult; + +#[derive(Debug)] +pub struct Settings { + pub display: Option, + pub expressions: Option>, + pub verbose: bool, +} + +impl Settings { + pub fn try_new(matches: &ArgMatches) -> UResult { + let display = if matches.get_flag("table") { + Some(SignalDisplay::Table) + } else if matches.get_flag("list") { + Some(SignalDisplay::List) + } else { + None + }; + + Ok(Self { + display, + expressions: Self::targets(matches), + verbose: matches.get_flag("verbose"), + }) + } + + fn targets(matches: &ArgMatches) -> Option> { + let cmd = matches + .get_many::("command") + .unwrap_or_default() + .map(Into::into) + .map(SelectedTarget::Command) + .collect::>(); + + let pid = matches + .get_many::("pid") + .unwrap_or_default() + .map(Clone::clone) + .map(SelectedTarget::Pid) + .collect::>(); + + let tty = matches + .get_many::("tty") + .unwrap_or_default() + .flat_map(|it| Teletype::try_from(it.as_str())) + .map(SelectedTarget::Tty) + .collect::>(); + + let user = matches + .get_many::("user") + .unwrap_or_default() + .map(Into::into) + .map(SelectedTarget::User) + .collect::>(); + + let collected = cmd + .into_iter() + .chain(pid) + .chain(tty) + .chain(user) + .collect::>(); + + if collected.is_empty() { + None + } else { + Some(collected) + } + } +} + +#[allow(clippy::cognitive_complexity)] +pub fn clap_args() -> Vec { + vec![ + // arg!(-f --fast "fast mode (not implemented)"), + // arg!(-i --interactive "interactive"), + arg!(-l --list "list all signal names"), + arg!(-L --table "list all signal names in a nice table"), + arg!(-n --"no-action" "do not actually kill processes; just print what would happen"), + arg!(-v --verbose "explain what is being done"), + // arg!(-w --warnings "enable warnings (not implemented)"), + // Expressions + arg!(-c --command ... "expression is a command name"), + arg!(-p --pid ... "expression is a process id number") + .value_parser(value_parser!(u32)), + arg!(-t --tty ... "expression is a terminal"), + arg!(-u --user ... "expression is a username"), + // arg!(--ns "match the processes that belong to the same namespace as "), + // arg!(--nslist "list which namespaces will be considered for the --ns option.") + // .value_delimiter(',') + // .value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]), + ] +} diff --git a/src/uu/snice/src/snice.rs b/src/uu/snice/src/snice.rs index cebf5df..5bb0c06 100644 --- a/src/uu/snice/src/snice.rs +++ b/src/uu/snice/src/snice.rs @@ -5,12 +5,14 @@ use std::{collections::HashSet, path::PathBuf, str::FromStr}; -use action::{perform_action, process_snapshot, users, ActionResult, SelectedTarget}; -use clap::{arg, crate_version, value_parser, Arg, ArgMatches, Command}; +use crate::priority::Priority; +pub use action::ActionResult; +use action::{perform_action, process_snapshot, users, SelectedTarget}; +use clap::{crate_version, Arg, Command}; use prettytable::{format::consts::FORMAT_CLEAN, row, Table}; -use priority::Priority; +use process_matcher::*; use sysinfo::Pid; -use uu_pgrep::process::{ProcessInformation, Teletype}; +use uu_pgrep::process::ProcessInformation; #[cfg(target_family = "unix")] use uucore::signals::ALL_SIGNALS; use uucore::{ @@ -23,9 +25,10 @@ const USAGE: &str = help_usage!("snice.md"); mod action; mod priority; +pub mod process_matcher; #[derive(Debug)] -enum SignalDisplay { +pub enum SignalDisplay { List, Table, } @@ -66,85 +69,13 @@ impl SignalDisplay { } } -#[derive(Debug)] -struct Settings { - display: Option, - expressions: Option>, - priority: Priority, - verbose: bool, -} +#[allow(unused)] // unused argument under non-unix targets +pub fn print_signals(display: &SignalDisplay) { + #[cfg(target_family = "unix")] + { + let result = display.display(&ALL_SIGNALS); -impl Settings { - fn try_new(matches: &ArgMatches) -> UResult { - let priority = matches - .try_get_one::("priority") - .unwrap_or(Some(&String::new())) - .cloned(); - - let expression = match priority { - Some(expr) => { - Priority::try_from(expr).map_err(|err| USimpleError::new(1, err.to_string()))? - } - None => Priority::default(), - }; - - let display = if matches.get_flag("table") { - Some(SignalDisplay::Table) - } else if matches.get_flag("list") { - Some(SignalDisplay::List) - } else { - None - }; - - Ok(Self { - display, - expressions: Self::targets(matches), - priority: expression, - verbose: matches.get_flag("verbose"), - }) - } - - fn targets(matches: &ArgMatches) -> Option> { - let cmd = matches - .get_many::("command") - .unwrap_or_default() - .map(Into::into) - .map(SelectedTarget::Command) - .collect::>(); - - let pid = matches - .get_many::("pid") - .unwrap_or_default() - .map(Clone::clone) - .map(SelectedTarget::Pid) - .collect::>(); - - let tty = matches - .get_many::("tty") - .unwrap_or_default() - .flat_map(|it| Teletype::try_from(it.as_str())) - .map(SelectedTarget::Tty) - .collect::>(); - - let user = matches - .get_many::("user") - .unwrap_or_default() - .map(Into::into) - .map(SelectedTarget::User) - .collect::>(); - - let collected = cmd - .into_iter() - .chain(pid) - .chain(tty) - .chain(user) - .collect::>(); - - if collected.is_empty() { - None - } else { - Some(collected) - } + println!("{result}"); } } @@ -155,27 +86,25 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let settings = Settings::try_new(&matches)?; // Case0: Print SIGNALS - #[cfg(target_family = "unix")] - { - if let Some(display) = settings.display { - let result = display.display(&ALL_SIGNALS); - - println!("{result}"); - return Ok(()); - } - } - - #[cfg(not(target_family = "unix"))] - { - if let Some(_display) = settings.display { - return Ok(()); - } + if let Some(display) = &settings.display { + print_signals(display); + return Ok(()); } // Case1: Perform priority + let take_action = !matches.get_flag("no-action"); if let Some(targets) = settings.expressions { + let priority_str = matches.get_one::("priority").cloned(); + + let priority = match priority_str { + Some(expr) => { + Priority::try_from(expr).map_err(|err| USimpleError::new(1, err.to_string()))? + } + None => Priority::default(), + }; + let pids = collect_pids(&targets); - let results = perform_action(&pids, &settings.priority); + let results = perform_action(&pids, &priority, take_action); if results.iter().all(|it| it.is_none()) || results.is_empty() { return Err(USimpleError::new(1, "no process selection criteria")); @@ -184,6 +113,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if settings.verbose { let output = construct_verbose_result(&pids, &results).trim().to_owned(); println!("{output}"); + } else if !take_action { + pids.iter().for_each(|pid| println!("{pid}")); } } @@ -191,7 +122,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } #[allow(unused)] -fn construct_verbose_result(pids: &[u32], action_results: &[Option]) -> String { +pub fn construct_verbose_result(pids: &[u32], action_results: &[Option]) -> String { let mut table = action_results .iter() .enumerate() @@ -229,7 +160,7 @@ fn construct_verbose_result(pids: &[u32], action_results: &[Option } /// Map and sort `SelectedTarget` to pids. -fn collect_pids(targets: &[SelectedTarget]) -> Vec { +pub fn collect_pids(targets: &[SelectedTarget]) -> Vec { let collected = targets .iter() .flat_map(SelectedTarget::to_pids) @@ -240,7 +171,6 @@ fn collect_pids(targets: &[SelectedTarget]) -> Vec { collected } -#[allow(clippy::cognitive_complexity)] pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) @@ -249,22 +179,7 @@ pub fn uu_app() -> Command { .infer_long_args(true) .arg_required_else_help(true) .arg(Arg::new("priority")) - .args([ - // Options - // arg!(-f --fast "fast mode (not implemented)"), - // arg!(-i --interactive "interactive"), - arg!(-l --list "list all signal names"), - arg!(-L --table "list all signal names in a nice table"), - // arg!(-n --"no-action" "do not actually kill processes; just print what would happen"), - arg!(-v --verbose "explain what is being done"), - // arg!(-w --warnings "enable warnings (not implemented)"), - // Expressions - arg!(-c --command ... "expression is a command name"), - arg!(-p --pid ... "expression is a process id number") - .value_parser(value_parser!(u32)), - arg!(-t --tty ... "expression is a terminal"), - arg!(-u --user ... "expression is a username"), - ]) + .args(clap_args()) } #[cfg(test)] diff --git a/src/uu/top/src/picker.rs b/src/uu/top/src/picker.rs index d2c7575..448f801 100644 --- a/src/uu/top/src/picker.rs +++ b/src/uu/top/src/picker.rs @@ -26,6 +26,7 @@ pub(crate) fn pickers(fields: &[String]) -> Vec String>> { "PID" => helper(pid), "USER" => helper(user), "PR" => helper(pr), + "NI" => helper(ni), "RES" => helper(res), "SHR" => helper(shr), "S" => helper(s), @@ -76,11 +77,39 @@ fn user(pid: u32) -> String { .to_string() } -#[cfg(not(target_os = "windows"))] +#[cfg(target_os = "linux")] fn pr(pid: u32) -> String { + use uucore::libc::*; + let policy = unsafe { sched_getscheduler(pid as i32) }; + if policy == -1 { + return String::new(); + } + + // normal processes + if policy == SCHED_OTHER || policy == SCHED_BATCH || policy == SCHED_IDLE { + return (get_nice(pid) + 20).to_string(); + } + + // real-time processes + let mut param = sched_param { sched_priority: 0 }; + unsafe { sched_getparam(pid as c_int, &mut param) }; + if param.sched_priority == -1 { + return String::new(); + } + param.sched_priority.to_string() +} + +#[cfg(not(target_os = "linux"))] +fn pr(pid: u32) -> String { + todo(pid) +} + +#[cfg(not(target_os = "windows"))] +fn get_nice(pid: u32) -> i32 { use libc::{getpriority, PRIO_PROCESS}; use nix::errno::Errno; + // this is nice value, not priority value let result = unsafe { getpriority(PRIO_PROCESS, pid) }; let result = if Errno::last() == Errno::UnknownErrno { @@ -90,12 +119,17 @@ fn pr(pid: u32) -> String { 0 }; - format!("{result}") + result as i32 +} + +#[cfg(not(target_os = "windows"))] +fn ni(pid: u32) -> String { + format!("{}", get_nice(pid)) } // TODO: Implement this function for Windows #[cfg(target_os = "windows")] -fn pr(_pid: u32) -> String { +fn ni(_pid: u32) -> String { "0".into() } diff --git a/src/uu/vmstat/Cargo.toml b/src/uu/vmstat/Cargo.toml index 6538ed4..f912261 100644 --- a/src/uu/vmstat/Cargo.toml +++ b/src/uu/vmstat/Cargo.toml @@ -17,6 +17,8 @@ clap = { workspace = true } terminal_size = { workspace = true } uucore = { workspace = true, features = ["custom-tz-fmt"] } +uu_slabtop = {path = "../slabtop"} + [lib] path = "src/vmstat.rs" diff --git a/src/uu/vmstat/src/parser.rs b/src/uu/vmstat/src/parser.rs index 6cff6ea..a6441de 100644 --- a/src/uu/vmstat/src/parser.rs +++ b/src/uu/vmstat/src/parser.rs @@ -5,6 +5,8 @@ #[cfg(target_os = "linux")] use std::collections::HashMap; +#[cfg(target_os = "linux")] +use std::fmt::{Debug, Display, Formatter}; #[cfg(target_os = "linux")] pub fn parse_proc_file(path: &str) -> HashMap { @@ -31,6 +33,7 @@ pub struct ProcData { pub stat: HashMap, pub meminfo: HashMap, pub vmstat: HashMap, + pub diskstat: Vec, } #[cfg(target_os = "linux")] impl Default for ProcData { @@ -45,11 +48,17 @@ impl ProcData { let stat = parse_proc_file("/proc/stat"); let meminfo = parse_proc_file("/proc/meminfo"); let vmstat = parse_proc_file("/proc/vmstat"); + let diskstat = std::fs::read_to_string("/proc/diskstats") + .unwrap() + .lines() + .map(|line| line.to_string()) + .collect(); Self { uptime, stat, meminfo, vmstat, + diskstat, } } @@ -61,6 +70,30 @@ impl ProcData { let idle_time = parts.next().unwrap().parse::().unwrap(); (uptime, idle_time) } + + pub fn get_one(table: &HashMap, name: &str) -> T + where + T: Default + std::str::FromStr, + { + table + .get(name) + .and_then(|v| v.parse().ok()) + .unwrap_or_default() + } +} + +#[cfg(target_os = "linux")] +pub struct CpuLoadRaw { + pub user: u64, + pub nice: u64, + pub system: u64, + pub idle: u64, + pub io_wait: u64, + pub hardware_interrupt: u64, + pub software_interrupt: u64, + pub steal_time: u64, + pub guest: u64, + pub guest_nice: u64, } #[cfg(target_os = "linux")] @@ -78,7 +111,7 @@ pub struct CpuLoad { } #[cfg(target_os = "linux")] -impl CpuLoad { +impl CpuLoadRaw { pub fn current() -> Self { let file = std::fs::File::open(std::path::Path::new("/proc/stat")).unwrap(); // do not use `parse_proc_file` here because only one line is used let content = std::io::read_to_string(file).unwrap(); @@ -93,37 +126,64 @@ impl CpuLoad { fn from_str(s: &str) -> Self { let load = s.split(' ').filter(|s| !s.is_empty()).collect::>(); - let user = load[0].parse::().unwrap(); - let nice = load[1].parse::().unwrap(); - let system = load[2].parse::().unwrap(); - let idle = load[3].parse::().unwrap_or_default(); // since 2.5.41 - let io_wait = load[4].parse::().unwrap_or_default(); // since 2.5.41 - let hardware_interrupt = load[5].parse::().unwrap_or_default(); // since 2.6.0 - let software_interrupt = load[6].parse::().unwrap_or_default(); // since 2.6.0 - let steal_time = load[7].parse::().unwrap_or_default(); // since 2.6.11 - let guest = load[8].parse::().unwrap_or_default(); // since 2.6.24 - let guest_nice = load[9].parse::().unwrap_or_default(); // since 2.6.33 - let total = user - + nice - + system - + idle - + io_wait - + hardware_interrupt - + software_interrupt - + steal_time - + guest - + guest_nice; + let user = load[0].parse::().unwrap(); + let nice = load[1].parse::().unwrap(); + let system = load[2].parse::().unwrap(); + let idle = load[3].parse::().unwrap_or_default(); // since 2.5.41 + let io_wait = load[4].parse::().unwrap_or_default(); // since 2.5.41 + let hardware_interrupt = load[5].parse::().unwrap_or_default(); // since 2.6.0 + let software_interrupt = load[6].parse::().unwrap_or_default(); // since 2.6.0 + let steal_time = load[7].parse::().unwrap_or_default(); // since 2.6.11 + let guest = load[8].parse::().unwrap_or_default(); // since 2.6.24 + let guest_nice = load[9].parse::().unwrap_or_default(); // since 2.6.33 + Self { - user: user / total * 100.0, - system: system / total * 100.0, - nice: nice / total * 100.0, - idle: idle / total * 100.0, - io_wait: io_wait / total * 100.0, - hardware_interrupt: hardware_interrupt / total * 100.0, - software_interrupt: software_interrupt / total * 100.0, - steal_time: steal_time / total * 100.0, - guest: guest / total * 100.0, - guest_nice: guest_nice / total * 100.0, + user, + system, + nice, + idle, + io_wait, + hardware_interrupt, + software_interrupt, + steal_time, + guest, + guest_nice, + } + } +} + +#[cfg(target_os = "linux")] +impl CpuLoad { + pub fn current() -> Self { + Self::from_raw(CpuLoadRaw::current()) + } + + pub fn from_proc_map(proc_map: &HashMap) -> Self { + Self::from_raw(CpuLoadRaw::from_proc_map(proc_map)) + } + + pub fn from_raw(raw_data: CpuLoadRaw) -> Self { + let total = (raw_data.user + + raw_data.nice + + raw_data.system + + raw_data.idle + + raw_data.io_wait + + raw_data.hardware_interrupt + + raw_data.software_interrupt + + raw_data.steal_time + + raw_data.guest + + raw_data.guest_nice) as f64; + Self { + user: raw_data.user as f64 / total * 100.0, + system: raw_data.system as f64 / total * 100.0, + nice: raw_data.nice as f64 / total * 100.0, + idle: raw_data.idle as f64 / total * 100.0, + io_wait: raw_data.io_wait as f64 / total * 100.0, + hardware_interrupt: raw_data.hardware_interrupt as f64 / total * 100.0, + software_interrupt: raw_data.software_interrupt as f64 / total * 100.0, + steal_time: raw_data.steal_time as f64 / total * 100.0, + guest: raw_data.guest as f64 / total * 100.0, + guest_nice: raw_data.guest_nice as f64 / total * 100.0, } } } @@ -177,3 +237,104 @@ impl Meminfo { } } } + +#[cfg(target_os = "linux")] +#[derive(Debug)] +pub struct DiskStatParseError; + +#[cfg(target_os = "linux")] +impl Display for DiskStatParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt("Failed to parse diskstat line", f) + } +} + +#[cfg(target_os = "linux")] +impl std::error::Error for DiskStatParseError {} + +#[cfg(target_os = "linux")] +pub struct DiskStat { + // Name from https://www.kernel.org/doc/html/latest/admin-guide/iostats.html + pub major: u64, + pub minor: u64, + pub device: String, + pub reads_completed: u64, + pub reads_merged: u64, + pub sectors_read: u64, + pub milliseconds_spent_reading: u64, + pub writes_completed: u64, + pub writes_merged: u64, + pub sectors_written: u64, + pub milliseconds_spent_writing: u64, + pub ios_currently_in_progress: u64, + pub milliseconds_spent_doing_ios: u64, + pub weighted_milliseconds_spent_doing_ios: u64, + pub discards_completed: u64, + pub discards_merged: u64, + pub sectors_discarded: u64, + pub milliseconds_spent_discarding: u64, + pub flush_requests_completed: u64, + pub milliseconds_spent_flushing: u64, +} + +#[cfg(target_os = "linux")] +impl std::str::FromStr for DiskStat { + type Err = DiskStatParseError; + + fn from_str(line: &str) -> Result { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 14 { + Err(DiskStatParseError)?; + } + + let parse_value = |s: &str| s.parse::().map_err(|_| DiskStatParseError); + let parse_optional_value = |s: Option<&&str>| match s { + None => Ok(0), + Some(value) => value.parse::().map_err(|_| DiskStatParseError), + }; + + Ok(Self { + major: parse_value(parts[0])?, + minor: parse_value(parts[1])?, + device: parts[2].to_string(), + reads_completed: parse_value(parts[3])?, + reads_merged: parse_value(parts[4])?, + sectors_read: parse_value(parts[5])?, + milliseconds_spent_reading: parse_value(parts[6])?, + writes_completed: parse_value(parts[7])?, + writes_merged: parse_value(parts[8])?, + sectors_written: parse_value(parts[9])?, + milliseconds_spent_writing: parse_value(parts[10])?, + ios_currently_in_progress: parse_value(parts[11])?, + milliseconds_spent_doing_ios: parse_value(parts[12])?, + weighted_milliseconds_spent_doing_ios: parse_optional_value(parts.get(13))?, + discards_completed: parse_optional_value(parts.get(14))?, + discards_merged: parse_optional_value(parts.get(15))?, + sectors_discarded: parse_optional_value(parts.get(16))?, + milliseconds_spent_discarding: parse_optional_value(parts.get(17))?, + flush_requests_completed: parse_optional_value(parts.get(18))?, + milliseconds_spent_flushing: parse_optional_value(parts.get(19))?, + }) + } +} + +#[cfg(target_os = "linux")] +impl DiskStat { + pub fn is_disk(&self) -> bool { + std::path::Path::new(&format!("/sys/block/{}", self.device)).exists() + } + + pub fn current() -> Result, DiskStatParseError> { + let diskstats = + std::fs::read_to_string("/proc/diskstats").map_err(|_| DiskStatParseError)?; + let lines = diskstats.lines(); + Self::from_proc_vec(&lines.map(|line| line.to_string()).collect::>()) + } + + pub fn from_proc_vec(proc_vec: &[String]) -> Result, DiskStatParseError> { + proc_vec + .iter() + .map(|line| line.parse::()) + .collect() + } +} diff --git a/src/uu/vmstat/src/picker.rs b/src/uu/vmstat/src/picker.rs index 5e5362b..8f69cd4 100644 --- a/src/uu/vmstat/src/picker.rs +++ b/src/uu/vmstat/src/picker.rs @@ -4,9 +4,11 @@ // file that was distributed with this source code. #[cfg(target_os = "linux")] -use crate::{CpuLoad, Meminfo, ProcData}; +use crate::{CpuLoad, CpuLoadRaw, DiskStat, Meminfo, ProcData}; #[cfg(target_os = "linux")] use clap::ArgMatches; +#[cfg(target_os = "linux")] +use uucore::error::{UResult, USimpleError}; #[cfg(target_os = "linux")] pub type Picker = ( @@ -76,6 +78,188 @@ pub fn get_pickers(matches: &ArgMatches) -> Vec { pickers } +#[cfg(target_os = "linux")] +pub fn get_stats() -> Vec<(String, u64)> { + let proc_data = ProcData::new(); + let memory_info = Meminfo::from_proc_map(&proc_data.meminfo); + let cpu_load = CpuLoadRaw::from_proc_map(&proc_data.stat); + + vec![ + ( + "K total memory".to_string(), + memory_info.mem_total.0 / bytesize::KB, + ), + ( + "K used memory".to_string(), + (memory_info.mem_total - memory_info.mem_available).0 / bytesize::KB, + ), + ( + "K active memory".to_string(), + memory_info.active.0 / bytesize::KB, + ), + ( + "K inactive memory".to_string(), + memory_info.inactive.0 / bytesize::KB, + ), + ( + "K free memory".to_string(), + memory_info.mem_free.0 / bytesize::KB, + ), + ( + "K buffer memory".to_string(), + memory_info.buffers.0 / bytesize::KB, + ), + ( + "K swap cache".to_string(), + memory_info.cached.0 / bytesize::KB, + ), + ( + "K total swap".to_string(), + memory_info.swap_total.0 / bytesize::KB, + ), + ( + "K used swap".to_string(), + (memory_info.swap_total - memory_info.swap_free).0 / bytesize::KB, + ), + ( + "K free swap".to_string(), + memory_info.swap_free.0 / bytesize::KB, + ), + ( + "non-nice user cpu ticks".to_string(), + cpu_load.user - cpu_load.nice, + ), + ("nice user cpu ticks".to_string(), cpu_load.nice), + ("system cpu ticks".to_string(), cpu_load.system), + ("idle cpu ticks".to_string(), cpu_load.idle), + ("IO-wait cpu ticks".to_string(), cpu_load.io_wait), + ("IRQ cpu ticks".to_string(), cpu_load.hardware_interrupt), + ("softirq cpu ticks".to_string(), cpu_load.software_interrupt), + ("stolen cpu ticks".to_string(), cpu_load.steal_time), + ("non-nice guest cpu ticks".to_string(), cpu_load.guest), + ("nice guest cpu ticks".to_string(), cpu_load.guest_nice), + ( + "K paged in".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgpgin"), + ), + ( + "K paged out".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgpgout"), + ), + ( + "pages swapped in".to_string(), + ProcData::get_one(&proc_data.vmstat, "pswpin"), + ), + ( + "pages swapped out".to_string(), + ProcData::get_one(&proc_data.vmstat, "pswpout"), + ), + ( + "pages alloc in dma".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgalloc_dma"), + ), + ( + "pages alloc in dma32".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgalloc_dma32"), + ), + ( + "pages alloc in high".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgalloc_high"), + ), + ( + "pages alloc in movable".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgalloc_movable"), + ), + ( + "pages alloc in normal".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgalloc_normal"), + ), + ( + "pages free".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgfree"), + ), + ( + "interrupts".to_string(), + proc_data + .stat + .get("intr") + .unwrap() + .split_whitespace() + .next() + .unwrap() + .parse::() + .unwrap(), + ), + ( + "CPU context switches".to_string(), + ProcData::get_one(&proc_data.stat, "ctxt"), + ), + ( + "boot time".to_string(), + ProcData::get_one(&proc_data.stat, "btime"), + ), + ( + "forks".to_string(), + ProcData::get_one(&proc_data.stat, "processes"), + ), + ] +} + +#[cfg(target_os = "linux")] +pub fn get_disk_sum() -> UResult> { + let disk_data = DiskStat::current() + .map_err(|_| USimpleError::new(1, "Unable to retrieve disk statistics"))?; + + let mut disks = 0; + let mut partitions = 0; + let mut total_reads = 0; + let mut merged_reads = 0; + let mut read_sectors = 0; + let mut milli_reading = 0; + let mut writes = 0; + let mut merged_writes = 0; + let mut written_sectors = 0; + let mut milli_writing = 0; + let mut inprogress_io = 0; + let mut milli_spent_io = 0; + let mut milli_weighted_io = 0; + + for disk in disk_data.iter() { + if disk.is_disk() { + disks += 1; + total_reads += disk.reads_completed; + merged_reads += disk.reads_merged; + read_sectors += disk.sectors_read; + milli_reading += disk.milliseconds_spent_reading; + writes += disk.writes_completed; + merged_writes += disk.writes_merged; + written_sectors += disk.sectors_written; + milli_writing += disk.milliseconds_spent_writing; + inprogress_io += disk.ios_currently_in_progress; + milli_spent_io += disk.milliseconds_spent_doing_ios / 1000; + milli_weighted_io += disk.weighted_milliseconds_spent_doing_ios / 1000; + } else { + partitions += 1; + } + } + + Ok(vec![ + ("disks".to_string(), disks), + ("partitions".to_string(), partitions), + ("total reads".to_string(), total_reads), + ("merged reads".to_string(), merged_reads), + ("read sectors".to_string(), read_sectors), + ("milli reading".to_string(), milli_reading), + ("writes".to_string(), writes), + ("merged writes".to_string(), merged_writes), + ("written sectors".to_string(), written_sectors), + ("milli writing".to_string(), milli_writing), + ("in progress IO".to_string(), inprogress_io), + ("milli spent IO".to_string(), milli_spent_io), + ("milli weighted IO".to_string(), milli_weighted_io), + ]) +} + #[cfg(target_os = "linux")] fn with_unit(x: u64, arg: &ArgMatches) -> u64 { if let Some(unit) = arg.get_one::("unit") { diff --git a/src/uu/vmstat/src/vmstat.rs b/src/uu/vmstat/src/vmstat.rs index 93996af..5be6006 100644 --- a/src/uu/vmstat/src/vmstat.rs +++ b/src/uu/vmstat/src/vmstat.rs @@ -7,7 +7,7 @@ mod parser; mod picker; #[cfg(target_os = "linux")] -use crate::picker::{get_pickers, Picker}; +use crate::picker::{get_disk_sum, get_pickers, get_stats, Picker}; use clap::value_parser; #[allow(unused_imports)] use clap::{arg, crate_version, ArgMatches, Command}; @@ -26,6 +26,32 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; #[cfg(target_os = "linux")] { + let wide = matches.get_flag("wide"); + let one_header = matches.get_flag("one-header"); + let no_first = matches.get_flag("no-first"); + let term_height = terminal_size::terminal_size() + .map(|size| size.1 .0) + .unwrap_or(0); + + if matches.get_flag("forks") { + return print_forks(); + } + if matches.get_flag("slabs") { + return print_slabs(one_header, term_height); + } + if matches.get_flag("stats") { + return print_stats(); + } + if matches.get_flag("disk") { + return print_disk(wide, one_header, term_height); + } + if matches.get_flag("disk-sum") { + return print_disk_sum(); + } + if let Some(device) = matches.get_one::("partition") { + return print_partition(device); + } + // validate unit if let Some(unit) = matches.get_one::("unit") { if !["k", "K", "m", "M"].contains(&unit.as_str()) { @@ -36,9 +62,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - let one_header = matches.get_flag("one-header"); - let no_first = matches.get_flag("no-first"); - let delay = matches.get_one::("delay"); let count = matches.get_one::("count"); let mut count = count.copied().map(|c| if c == 0 { 1 } else { c }); @@ -57,14 +80,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { line_count += 1; } - let term_height = terminal_size::terminal_size() - .map(|size| size.1 .0) - .unwrap_or(0); - while count.is_none() || line_count < count.unwrap() { std::thread::sleep(std::time::Duration::from_secs(delay)); let proc_data_now = ProcData::new(); - if !one_header && term_height > 0 && ((line_count + 3) % term_height as u64 == 0) { + if needs_header(one_header, term_height, line_count) { print_header(&pickers); } print_data(&pickers, &proc_data_now, Some(&proc_data), &matches); @@ -76,6 +95,165 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(()) } +#[cfg(target_os = "linux")] +fn print_forks() -> UResult<()> { + let data = get_stats(); + + let fork_data = data.last().unwrap(); + println!("{:>13} {}", fork_data.1, fork_data.0); + + Ok(()) +} + +#[cfg(target_os = "linux")] +fn print_stats() -> UResult<()> { + let data = get_stats(); + + data.iter() + .for_each(|(name, value)| println!("{value:>13} {name}")); + + Ok(()) +} + +#[cfg(target_os = "linux")] +fn print_slabs(one_header: bool, term_height: u16) -> UResult<()> { + let mut slab_data = uu_slabtop::SlabInfo::new()?.data; + + slab_data.sort_by_key(|k| k.0.to_lowercase()); + + print_slab_header(); + + for (line_count, slab_item) in slab_data.into_iter().enumerate() { + if needs_header(one_header, term_height, line_count as u64) { + print_slab_header(); + } + + println!( + "{:<24} {:>6} {:>6} {:>6} {:>6}", + slab_item.0, slab_item.1[0], slab_item.1[1], slab_item.1[2], slab_item.1[3] + ); + } + + Ok(()) +} + +#[cfg(target_os = "linux")] +fn needs_header(one_header: bool, term_height: u16, line_count: u64) -> bool { + !one_header && term_height > 0 && ((line_count + 3) % term_height as u64 == 0) +} + +#[cfg(target_os = "linux")] +fn print_slab_header() { + println!( + "{:<24} {:>6} {:>6} {:>6} {:>6}", + "Cache", "Num", "Total", "Size", "Pages" + ); +} + +#[cfg(target_os = "linux")] +fn print_disk_header(wide: bool) { + if wide { + println!("disk- -------------------reads------------------- -------------------writes------------------ ------IO-------"); + println!( + "{:>15} {:>9} {:>11} {:>11} {:>9} {:>9} {:>11} {:>11} {:>7} {:>7}", + "total", "merged", "sectors", "ms", "total", "merged", "sectors", "ms", "cur", "sec" + ); + } else { + println!("disk- ------------reads------------ ------------writes----------- -----IO------"); + println!( + "{:>12} {:>6} {:>7} {:>7} {:>6} {:>6} {:>7} {:>7} {:>6} {:>6}", + "total", "merged", "sectors", "ms", "total", "merged", "sectors", "ms", "cur", "sec" + ); + } +} + +#[cfg(target_os = "linux")] +fn print_disk(wide: bool, one_header: bool, term_height: u16) -> UResult<()> { + let disk_data = DiskStat::current() + .map_err(|_| USimpleError::new(1, "Unable to retrieve disk statistics"))?; + + let mut line_count = 0; + + print_disk_header(wide); + + for disk in disk_data { + if !disk.is_disk() { + continue; + } + + if needs_header(one_header, term_height, line_count) { + print_disk_header(wide); + } + line_count += 1; + + if wide { + println!( + "{:<5} {:>9} {:>9} {:>11} {:>11} {:>9} {:>9} {:>11} {:>11} {:>7} {:>7}", + disk.device, + disk.reads_completed, + disk.reads_merged, + disk.sectors_read, + disk.milliseconds_spent_reading, + disk.writes_completed, + disk.writes_merged, + disk.sectors_written, + disk.milliseconds_spent_writing, + disk.ios_currently_in_progress / 1000, + disk.milliseconds_spent_doing_ios / 1000 + ); + } else { + println!( + "{:<5} {:>6} {:>6} {:>7} {:>7} {:>6} {:>6} {:>7} {:>7} {:>6} {:>6}", + disk.device, + disk.reads_completed, + disk.reads_merged, + disk.sectors_read, + disk.milliseconds_spent_reading, + disk.writes_completed, + disk.writes_merged, + disk.sectors_written, + disk.milliseconds_spent_writing, + disk.ios_currently_in_progress / 1000, + disk.milliseconds_spent_doing_ios / 1000 + ); + } + } + + Ok(()) +} + +#[cfg(target_os = "linux")] +fn print_disk_sum() -> UResult<()> { + let data = get_disk_sum()?; + + data.iter() + .for_each(|(name, value)| println!("{value:>13} {name}")); + + Ok(()) +} + +#[cfg(target_os = "linux")] +fn print_partition(device: &str) -> UResult<()> { + let disk_data = DiskStat::current() + .map_err(|_| USimpleError::new(1, "Unable to retrieve disk statistics"))?; + + let disk = disk_data + .iter() + .find(|disk| disk.device == device) + .ok_or_else(|| USimpleError::new(1, format!("Disk/Partition {device} not found")))?; + + println!( + "{device:<9} {:>11} {:>17} {:>11} {:>17}", + "reads", "read sectors", "writes", "requested writes" + ); + println!( + "{:>21} {:>17} {:>11} {:>17}", + disk.reads_completed, disk.sectors_read, disk.writes_completed, disk.sectors_written + ); + + Ok(()) +} + #[cfg(target_os = "linux")] fn print_header(pickers: &[Picker]) { let mut section: Vec<&str> = vec![]; @@ -125,13 +303,19 @@ pub fn uu_app() -> Command { .required(false) .value_parser(value_parser!(u64)), arg!(-a --active "Display active and inactive memory"), - // arg!(-f --forks "switch displays the number of forks since boot"), - // arg!(-m --slabs "Display slabinfo"), + arg!(-f --forks "switch displays the number of forks since boot") + .conflicts_with_all(["slabs", "stats", "disk", "disk-sum", "partition"]), + arg!(-m --slabs "Display slabinfo") + .conflicts_with_all(["forks", "stats", "disk", "disk-sum", "partition"]), arg!(-n --"one-header" "Display the header only once rather than periodically"), - // arg!(-s --stats "Displays a table of various event counters and memory statistics"), - // arg!(-d --disk "Report disk statistics"), - // arg!(-D --"disk-sum" "Report some summary statistics about disk activity"), - // arg!(-p --partition "Detailed statistics about partition"), + arg!(-s --stats "Displays a table of various event counters and memory statistics") + .conflicts_with_all(["forks", "slabs", "disk", "disk-sum", "partition"]), + arg!(-d --disk "Report disk statistics") + .conflicts_with_all(["forks", "slabs", "stats", "disk-sum", "partition"]), + arg!(-D --"disk-sum" "Report some summary statistics about disk activity") + .conflicts_with_all(["forks", "slabs", "stats", "disk", "partition"]), + arg!(-p --partition "Detailed statistics about partition") + .conflicts_with_all(["forks", "slabs", "stats", "disk", "disk-sum"]), arg!(-S --unit "Switches outputs between 1000 (k), 1024 (K), 1000000 (m), or 1048576 (M) bytes"), arg!(-t --timestamp "Append timestamp to each line"), arg!(-w --wide "Wide output mode"), diff --git a/src/uu/w/src/w.rs b/src/uu/w/src/w.rs index 8b13e35..1d4b9af 100644 --- a/src/uu/w/src/w.rs +++ b/src/uu/w/src/w.rs @@ -255,6 +255,10 @@ fn fetch_user_info() -> Result, std::io::Error> { Ok(Vec::new()) } +fn truncate_username(user: &str) -> String { + user.chars().take(8).collect::() +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; @@ -281,7 +285,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if short { println!( "{:<9}{:<9}{:<7}{:<}", - user.user, + truncate_username(&user.user), user.terminal, format_time_elapsed(user.idle_time, old_style).unwrap_or_default(), user.command @@ -289,7 +293,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else { println!( "{:<9}{:<10}{:<9}{:<6} {:<7}{:<6}{:<}", - user.user, + truncate_username(&user.user), user.terminal, user.login_time, format_time_elapsed(user.idle_time, old_style).unwrap_or_default(), diff --git a/tests/by-util/test_pkill.rs b/tests/by-util/test_pkill.rs index 4828e90..54a4c17 100644 --- a/tests/by-util/test_pkill.rs +++ b/tests/by-util/test_pkill.rs @@ -74,3 +74,9 @@ fn test_too_long_pattern() { .code_is(1) .stderr_contains("pattern that searches for process name longer than 15 characters will result in zero matches"); } + +#[test] +#[cfg(target_os = "linux")] +fn test_invalid_queue() { + new_ucmd!().args(&["-q"]).fails().code_is(1); +} diff --git a/tests/by-util/test_pmap.rs b/tests/by-util/test_pmap.rs index fab26be..472078c 100644 --- a/tests/by-util/test_pmap.rs +++ b/tests/by-util/test_pmap.rs @@ -20,6 +20,78 @@ fn test_no_args() { new_ucmd!().fails().code_is(1); } +#[test] +#[cfg(target_os = "linux")] +fn test_default_rc() { + if !uutests::util::is_ci() { + return; + } + + let pid = process::id(); + let ts = TestScenario::new(util_name!()); + + // Fails to read before creating rc file + for arg in ["-c", "--read-rc"] { + ts.ucmd().arg(arg).arg(pid.to_string()).fails().code_is(1); + } + + // Create rc file + ts.ucmd().arg("-n").succeeds(); + + // Fails to create because rc file already exists + for arg in ["-n", "--create-rc"] { + ts.ucmd().arg(arg).fails().code_is(1); + } + + // Succeeds to read now + for arg in ["-c", "--read-rc"] { + ts.ucmd().arg(arg).arg(pid.to_string()).succeeds(); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_create_rc_to() { + let ts = TestScenario::new(util_name!()); + + ts.ucmd().args(&["-N", "pmap_rc_file_name"]).succeeds(); + + // Fails to create because rc file already exists + for arg in ["-N", "--create-rc-to"] { + ts.ucmd() + .args(&[arg, "pmap_rc_file_name"]) + .fails() + .code_is(1); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_read_rc_from() { + let pid = process::id(); + let ts = TestScenario::new(util_name!()); + + // Fails to read before creating rc file + for arg in ["-C", "--read-rc-from"] { + ts.ucmd() + .args(&[arg, "pmap_rc_file_name"]) + .arg(pid.to_string()) + .fails() + .code_is(1); + } + + // Create rc file + ts.ucmd().args(&["-N", "pmap_rc_file_name"]).succeeds(); + + // Succeeds to read now + for arg in ["-C", "--read-rc-from"] { + ts.ucmd() + .args(&[arg, "pmap_rc_file_name"]) + .arg(pid.to_string()) + .succeeds(); + } +} + #[test] #[cfg(target_os = "linux")] fn test_existing_pid() { @@ -192,8 +264,8 @@ fn test_device_permission_denied() { fn test_quiet() { let pid = process::id(); - for arg in ["-q", "--quiet"] { - _test_multiple_formats(pid, arg, true, false); + for args in [&["-q"], &["--quiet"]] { + _test_multiple_formats(pid, args, true, false); } } @@ -202,8 +274,8 @@ fn test_quiet() { fn test_showpath() { let pid = process::id(); - for arg in ["-p", "--show-path"] { - _test_multiple_formats(pid, arg, false, true); + for args in [&["-p"], &["--show-path"]] { + _test_multiple_formats(pid, args, false, true); } } @@ -212,16 +284,26 @@ fn test_showpath() { fn test_quiet_showpath() { let pid = process::id(); - for arg in ["-qp", "-pq"] { - _test_multiple_formats(pid, arg, true, true); + for args in [&["-qp"], &["-pq"]] { + _test_multiple_formats(pid, args, true, true); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_range() { + let pid = process::id(); + + for args in [&["-A", ","], &["--range", ","]] { + _test_multiple_formats(pid, args, false, false); } } #[cfg(target_os = "linux")] -fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { +fn _test_multiple_formats(pid: u32, args: &[&str], quiet: bool, show_path: bool) { // default format let result = new_ucmd!() - .arg(arg) + .args(args) .arg(pid.to_string()) .succeeds() .stdout_move_str(); @@ -230,7 +312,7 @@ fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { // extended format let result = new_ucmd!() - .arg(arg) + .args(args) .arg("--extended") .arg(pid.to_string()) .succeeds() @@ -240,7 +322,7 @@ fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { // more-extended format let result = new_ucmd!() - .arg(arg) + .args(args) .arg("-X") .arg(pid.to_string()) .succeeds() @@ -250,7 +332,7 @@ fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { // most-extended format let result = new_ucmd!() - .arg(arg) + .args(args) .arg("--XX") .arg(pid.to_string()) .succeeds() @@ -260,7 +342,7 @@ fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { // device format let result = new_ucmd!() - .arg(arg) + .args(args) .arg("--device") .arg(pid.to_string()) .succeeds() @@ -269,6 +351,61 @@ fn _test_multiple_formats(pid: u32, arg: &str, quiet: bool, show_path: bool) { assert_device_format(pid, &result, quiet, show_path); } +#[test] +#[cfg(target_os = "linux")] +fn test_range_arg() { + let pid_s = process::id().to_string(); + + for opt in ["-A", "--range"] { + // option without an argument + new_ucmd!().arg(&pid_s).arg(opt).fails().code_is(1); + + // valid arguments + for arg in [ + ",", + "c00fee", + "c00fee,", + ",c00fee", + "c00,fee", + "0", + "0,", + ",0", + "0,0", + "ffffffffffffffff", + "ffffffffffffffff,", + ",ffffffffffffffff", + "ffffffffffffffff,ffffffffffffffff", + ] { + new_ucmd!().arg(&pid_s).arg(opt).arg(arg).succeeds(); + } + + // invalid arguments + for arg in [ + // white spaces + ", ", + " ,", + " , ", + "bad ", + " bad", + // multiple commas + ",,", + ",bad,", + // underscore separator + "bad_beef", + // non-numeric value + "someinvalidtext", + "someinvalidtext,", + ",someinvalidtext", + "someinvalidtext,someinvalidtext", + // too large value (> u64) + "f0000000000000000", + "f0000000000000000,f0000000000000000", + ] { + new_ucmd!().arg(&pid_s).arg(opt).arg(arg).fails().code_is(1); + } + } +} + #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); @@ -378,13 +515,13 @@ fn assert_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) { // Ensure `s` has the following more extended format (-X): // // 1234: /some/path -// Address Perm Offset Device Inode Size Rss Pss Pss_Dirty Referenced Anonymous LazyFree ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible Mapping -// 000073eb5f4c7000 r-xp 0000000000036000 008:00008 2274176 1284 1148 1148 0 1148 0 0 0 0 0 0 0 0 0 0 ld-linux-x86-64.so.2 -// 00007ffd588fc000 r--p 0000000000000000 000:00000 2274176 20 20 20 20 20 20 0 0 0 0 0 0 0 0 0 [stack] -// ffffffffff600000 rw-p 0000000000000000 000:00000 2274176 36 36 36 36 36 36 0 0 0 0 0 0 0 0 0 (one intentional trailing space) +// Address Perm Offset Device Inode Size Rss Pss Pss_Dirty Referenced Anonymous LazyFree ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible Mapping +// 73eb5f4c7000 r-xp 00036000 08:08 2274176 1284 1148 1148 0 1148 0 0 0 0 0 0 0 0 0 0 ld-linux-x86-64.so.2 +// 7ffd588fc000 r--p 00000000 00:00 2274176 20 20 20 20 20 20 0 0 0 0 0 0 0 0 0 [stack] +// ffffffffff600000 rw-p 00000000 00:00 2274176 36 36 36 36 36 36 0 0 0 0 0 0 0 0 0 (one intentional trailing space) // ... -// ==== ==== ==== ========= ========== ========= ======== ============== ============= ============== =============== ==== ======= ====== =========== (one intentional trailing space) -// 4164 3448 2826 552 3448 552 0 0 0 0 0 0 0 0 0 KB (one intentional trailing space) +// ==== ==== ==== ========= ========== ========= ======== ============== ============= ============== =============== ==== ======= ====== =========== (one intentional trailing space) +// 4164 3448 2826 552 3448 552 0 0 0 0 0 0 0 0 0 KB (one intentional trailing space) #[cfg(target_os = "linux")] fn assert_more_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) { let lines: Vec<_> = s.lines().collect(); @@ -394,11 +531,11 @@ fn assert_more_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) assert!(re.is_match(lines[0])); if !quiet { - let re = Regex::new(r"^ Address Perm Offset Device Inode +Size +Rss +Pss +Pss_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +Mapping$").unwrap(); + let re = Regex::new(r"^ Address Perm Offset +Device +Inode +Size +Rss +Pss +Pss_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +Mapping$").unwrap(); assert!(re.is_match(lines[1]), "failing line: '{}'", lines[1]); } - let base_pattern = r"^[0-9a-f]{16} (-|r)(-|w)(-|x)(p|s) [0-9a-f]{16} [0-9a-f]{3}:[0-9a-f]{5} +\d+ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)?"; + let base_pattern = r"^[ 0-9a-f]{16} (-|r)(-|w)(-|x)(p|s) [0-9a-f]{8} +[0-9a-f]+:[0-9a-f]+ +\d+ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)?"; let mapping_pattern = if show_path { r" (|\[[a-zA-Z_ ]+\]|/[/a-zA-Z0-9._-]+)$" } else { @@ -415,14 +552,14 @@ fn assert_more_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) assert!(re.is_match(line), "failing line: '{line}'"); } - let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); + let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); assert!( re.is_match(lines[line_count - 2]), "failing line: '{}'", lines[line_count - 2] ); - let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); + let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); assert!( re.is_match(lines[line_count - 1]), "failing line: '{}'", @@ -434,13 +571,13 @@ fn assert_more_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) // Ensure `s` has the following most extended format (--XX): // // 1234: /some/path -// Address Perm Offset Device Inode Size KernelPageSize MMUPageSize Rss Pss Pss_Dirty Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous LazyFree AnonHugePages ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible VmFlags Mapping -// 000073eb5f4c7000 r-xp 0000000000036000 008:00008 2274176 1284 4 4 1148 1148 0 0 0 1148 0 1148 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me ld-linux-x86-64.so.2 -// 00007ffd588fc000 r--p 0000000000000000 000:00000 2274176 20 4 4 20 20 20 0 0 0 20 20 20 0 0 0 0 0 0 0 0 0 0 rd mr mw me ac [stack] -// ffffffffff600000 rw-p 0000000000000000 000:00000 2274176 36 4 4 36 36 36 0 0 0 36 36 36 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac (one intentional trailing space) +// Address Perm Offset Device Inode Size KernelPageSize MMUPageSize Rss Pss Pss_Dirty Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous LazyFree AnonHugePages ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible VmFlags Mapping +// 73eb5f4c7000 r-xp 00036000 08:08 2274176 1284 4 4 1148 1148 0 0 0 1148 0 1148 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me ld-linux-x86-64.so.2 +// 7ffd588fc000 r--p 00000000 00:00 2274176 20 4 4 20 20 20 0 0 0 20 20 20 0 0 0 0 0 0 0 0 0 0 rd mr mw me ac [stack] +// ffffffffff600000 rw-p 00000000 00:00 2274176 36 4 4 36 36 36 0 0 0 36 36 36 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac (one intentional trailing space) // ... -// ==== ============== =========== ==== ==== ========= ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== =========== (one intentional trailing space) -// 4164 92 92 3448 2880 552 1132 0 1764 552 3448 552 0 0 0 0 0 0 0 0 0 0 KB (one intentional trailing space) +// ==== ============== =========== ==== ==== ========= ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== =========== (one intentional trailing space) +// 4164 92 92 3448 2880 552 1132 0 1764 552 3448 552 0 0 0 0 0 0 0 0 0 0 KB (one intentional trailing space) #[cfg(target_os = "linux")] fn assert_most_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) { let lines: Vec<_> = s.lines().collect(); @@ -450,11 +587,11 @@ fn assert_most_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) assert!(re.is_match(lines[0])); if !quiet { - let re = Regex::new(r"^ Address Perm Offset Device Inode +Size +KernelPageSize +MMUPageSize +Rss +Pss +Pss_Dirty +Shared_Clean +Shared_Dirty +Private_Clean +Private_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +AnonHugePages +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +VmFlags +Mapping$").unwrap(); + let re = Regex::new(r"^ Address Perm Offset +Device +Inode +Size +KernelPageSize +MMUPageSize +Rss +Pss +Pss_Dirty +Shared_Clean +Shared_Dirty +Private_Clean +Private_Dirty +Referenced +Anonymous( +KSM)? +LazyFree +AnonHugePages +ShmemPmdMapped +FilePmdMapped +Shared_Hugetlb +Private_Hugetlb +Swap +SwapPss +Locked +THPeligible( +ProtectionKey)? +VmFlags +Mapping$").unwrap(); assert!(re.is_match(lines[1]), "failing line: '{}'", lines[1]); } - let base_pattern = r"^[0-9a-f]{16} (-|r)(-|w)(-|x)(p|s) [0-9a-f]{16} [0-9a-f]{3}:[0-9a-f]{5} +\d+ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +([a-z][a-z] )*"; + let base_pattern = r"^[ 0-9a-f]{16} (-|r)(-|w)(-|x)(p|s) [0-9a-f]{8} +[0-9a-f]+:[0-9a-f]+ +\d+ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +([a-z][a-z] )*"; let mapping_pattern = if show_path { r"(|\[[a-zA-Z_ ]+\]|/[/a-zA-Z0-9._-]+)$" } else { @@ -471,14 +608,14 @@ fn assert_most_extended_format(pid: u32, s: &str, quiet: bool, show_path: bool) assert!(re.is_match(line), "failing line: '{line}'"); } - let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); + let re = Regex::new(r"^ +=+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? =+ =+ =+ =+ =+ =+ =+ =+ =+ =+( =+)? $").unwrap(); assert!( re.is_match(lines[line_count - 2]), "failing line: '{}'", lines[line_count - 2] ); - let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); + let re = Regex::new(r"^ +[1-9][0-9]* +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+( +\d+)? KB $").unwrap(); assert!( re.is_match(lines[line_count - 1]), "failing line: '{}'", diff --git a/tests/by-util/test_skill.rs b/tests/by-util/test_skill.rs new file mode 100644 index 0000000..a9e5f16 --- /dev/null +++ b/tests/by-util/test_skill.rs @@ -0,0 +1,13 @@ +// This file is part of the uutils procps package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use uutests::new_ucmd; +use uutests::util::TestScenario; +use uutests::util_name; + +#[test] +fn test_no_args() { + new_ucmd!().fails().code_is(1); +} diff --git a/tests/by-util/test_vmstat.rs b/tests/by-util/test_vmstat.rs index a37a790..544f087 100644 --- a/tests/by-util/test_vmstat.rs +++ b/tests/by-util/test_vmstat.rs @@ -19,6 +19,11 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } +#[test] +fn test_conflict_arg() { + new_ucmd!().args(&["-s", "-m"]).fails().code_is(1); +} + #[test] fn test_invalid_number() { new_ucmd!().arg("-1").fails().code_is(1); @@ -81,3 +86,21 @@ fn test_timestamp() { .unwrap() .contains("timestamp")); } + +#[test] +#[cfg(target_os = "linux")] +fn test_stats() { + new_ucmd!().arg("-s").succeeds(); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_disk() { + new_ucmd!().arg("-d").succeeds(); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_disk_sum() { + new_ucmd!().arg("-D").succeeds(); +} diff --git a/tests/tests.rs b/tests/tests.rs index 6078611..8df7027 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -63,6 +63,10 @@ mod test_top; #[path = "by-util/test_vmstat.rs"] mod test_vmstat; +#[cfg(feature = "skill")] +#[path = "by-util/test_skill.rs"] +mod test_skill; + #[cfg(feature = "snice")] #[path = "by-util/test_snice.rs"] mod test_snice;