├── .github └── workflows │ ├── ci.yml │ └── code-quality.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── renovate.json ├── src ├── bin │ ├── procps.rs │ └── uudoc.rs └── uu │ ├── free │ ├── Cargo.toml │ ├── free.md │ └── src │ │ ├── free.rs │ │ ├── main.rs │ │ └── windows_util.rs │ ├── pgrep │ ├── Cargo.toml │ ├── pgrep.md │ └── src │ │ ├── main.rs │ │ ├── pgrep.rs │ │ ├── process.rs │ │ └── process_matcher.rs │ ├── pidof │ ├── Cargo.toml │ ├── pidof.md │ └── src │ │ ├── main.rs │ │ └── pidof.rs │ ├── pidwait │ ├── Cargo.toml │ ├── pidwait.md │ └── src │ │ ├── main.rs │ │ ├── pidwait.rs │ │ └── wait.rs │ ├── pkill │ ├── Cargo.toml │ ├── pkill.md │ └── src │ │ ├── main.rs │ │ └── pkill.rs │ ├── pmap │ ├── Cargo.toml │ ├── pmap.md │ └── src │ │ ├── main.rs │ │ ├── maps_format_parser.rs │ │ ├── pmap.rs │ │ ├── pmap_config.rs │ │ └── smaps_format_parser.rs │ ├── ps │ ├── Cargo.toml │ ├── ps.md │ └── src │ │ ├── collector.rs │ │ ├── main.rs │ │ ├── mapping.rs │ │ ├── parser.rs │ │ ├── picker.rs │ │ ├── ps.rs │ │ └── sorting.rs │ ├── pwdx │ ├── Cargo.toml │ ├── pwdx.md │ └── src │ │ ├── main.rs │ │ └── pwdx.rs │ ├── slabtop │ ├── Cargo.toml │ ├── slabtop.md │ └── src │ │ ├── main.rs │ │ ├── parse.rs │ │ └── slabtop.rs │ ├── snice │ ├── Cargo.toml │ ├── snice.md │ └── src │ │ ├── action.rs │ │ ├── main.rs │ │ ├── priority.rs │ │ └── snice.rs │ ├── sysctl │ ├── Cargo.toml │ ├── src │ │ ├── main.rs │ │ └── sysctl.rs │ └── sysctl.md │ ├── tload │ ├── Cargo.toml │ ├── src │ │ ├── main.rs │ │ ├── tload.rs │ │ └── tui.rs │ └── tload.md │ ├── top │ ├── Cargo.toml │ ├── build.rs │ ├── src │ │ ├── field.rs │ │ ├── header.rs │ │ ├── main.rs │ │ ├── picker.rs │ │ └── top.rs │ └── top.md │ ├── vmstat │ ├── Cargo.toml │ ├── src │ │ ├── main.rs │ │ ├── parser.rs │ │ ├── picker.rs │ │ └── vmstat.rs │ └── vmstat.md │ ├── w │ ├── Cargo.toml │ ├── src │ │ ├── main.rs │ │ └── w.rs │ └── w.md │ └── watch │ ├── Cargo.toml │ ├── src │ ├── main.rs │ └── watch.rs │ └── watch.md ├── tests ├── by-util │ ├── test_free.rs │ ├── test_pgrep.rs │ ├── test_pidof.rs │ ├── test_pidwait.rs │ ├── test_pkill.rs │ ├── test_pmap.rs │ ├── test_ps.rs │ ├── test_pwdx.rs │ ├── test_slabtop.rs │ ├── test_snice.rs │ ├── test_sysctl.rs │ ├── test_tload.rs │ ├── test_top.rs │ ├── test_vmstat.rs │ ├── test_w.rs │ └── test_watch.rs ├── fixtures │ └── slabtop │ │ └── data.txt └── tests.rs └── util └── publish.sh /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Basic CI 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | check: 10 | name: cargo check 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macOS-latest, windows-latest] 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: dtolnay/rust-toolchain@stable 18 | - if: ${{ contains(matrix.os, 'ubuntu') }} 19 | run: | 20 | sudo apt-get update -y 21 | sudo apt-get -yq --no-install-suggests --no-install-recommends install libsystemd-dev 22 | - run: cargo check 23 | 24 | test: 25 | name: cargo test 26 | runs-on: ${{ matrix.os }} 27 | strategy: 28 | matrix: 29 | os: [ubuntu-latest, macOS-latest, windows-latest] 30 | steps: 31 | - uses: actions/checkout@v4 32 | - uses: dtolnay/rust-toolchain@stable 33 | - if: ${{ contains(matrix.os, 'ubuntu') }} 34 | run: | 35 | sudo apt-get update -y 36 | sudo apt-get -yq --no-install-suggests --no-install-recommends install libsystemd-dev 37 | - run: cargo test --all 38 | 39 | coverage: 40 | name: Code Coverage 41 | runs-on: ${{ matrix.job.os }} 42 | strategy: 43 | fail-fast: true 44 | matrix: 45 | job: 46 | - { os: ubuntu-latest , features: unix } 47 | - { os: macos-latest , features: macos } 48 | - { os: windows-latest , features: windows } 49 | steps: 50 | - uses: actions/checkout@v4 51 | - name: Initialize workflow variables 52 | id: vars 53 | shell: bash 54 | run: | 55 | ## VARs setup 56 | outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } 57 | # toolchain 58 | TOOLCHAIN="nightly" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support 59 | # * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files 60 | case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac; 61 | # * use requested TOOLCHAIN if specified 62 | if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi 63 | outputs TOOLCHAIN 64 | # target-specific options 65 | # * CODECOV_FLAGS 66 | CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' ) 67 | outputs CODECOV_FLAGS 68 | 69 | - name: rust toolchain ~ install 70 | uses: dtolnay/rust-toolchain@nightly 71 | with: 72 | components: llvm-tools-preview 73 | - if: ${{ contains(matrix.job.os, 'ubuntu') }} 74 | run: | 75 | sudo apt-get update -y 76 | sudo apt-get -yq --no-install-suggests --no-install-recommends install libsystemd-dev 77 | - name: Test 78 | run: cargo test --no-fail-fast 79 | env: 80 | CARGO_INCREMENTAL: "0" 81 | RUSTC_WRAPPER: "" 82 | RUSTFLAGS: "-Cinstrument-coverage -Zcoverage-options=branch -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" 83 | RUSTDOCFLAGS: "-Cpanic=abort" 84 | LLVM_PROFILE_FILE: "procps-%p-%m.profraw" 85 | - name: "`grcov` ~ install" 86 | id: build_grcov 87 | shell: bash 88 | run: | 89 | git clone https://github.com/mozilla/grcov.git ~/grcov/ 90 | cd ~/grcov 91 | cargo install --path . 92 | cd - 93 | # Uncomment when the upstream issue 94 | # https://github.com/mozilla/grcov/issues/849 is fixed 95 | # uses: actions-rs/install@v0.1 96 | # with: 97 | # crate: grcov 98 | # version: latest 99 | # use-tool-cache: false 100 | - name: Generate coverage data (via `grcov`) 101 | id: coverage 102 | shell: bash 103 | run: | 104 | ## Generate coverage data 105 | COVERAGE_REPORT_DIR="target/debug" 106 | COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info" 107 | mkdir -p "${COVERAGE_REPORT_DIR}" 108 | # display coverage files 109 | grcov . --binary-path="${COVERAGE_REPORT_DIR}" --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique 110 | # generate coverage report 111 | grcov . --binary-path="${COVERAGE_REPORT_DIR}" --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" 112 | echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT 113 | - name: Upload coverage results (to Codecov.io) 114 | uses: codecov/codecov-action@v5 115 | with: 116 | token: ${{ secrets.CODECOV_TOKEN }} 117 | files: ${{ steps.coverage.outputs.report }} 118 | ## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }} 119 | flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} 120 | name: codecov-umbrella 121 | fail_ci_if_error: false 122 | 123 | -------------------------------------------------------------------------------- /.github/workflows/code-quality.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality 2 | 3 | # spell-checker:ignore TERMUX reactivecircus Swatinem noaudio pkill swiftshader dtolnay juliangruber 4 | 5 | on: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | 11 | env: 12 | # * style job configuration 13 | STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis 14 | 15 | permissions: 16 | contents: read # to fetch code (actions/checkout) 17 | 18 | # End the current execution if there is a new changeset in the PR. 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 22 | 23 | jobs: 24 | fmt: 25 | name: cargo fmt --all -- --check 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: dtolnay/rust-toolchain@stable 30 | - uses: Swatinem/rust-cache@v2 31 | - run: rustup component add rustfmt 32 | - run: cargo fmt --all -- --check 33 | 34 | style_lint: 35 | name: Style/lint 36 | runs-on: ${{ matrix.job.os }} 37 | env: 38 | SCCACHE_GHA_ENABLED: "true" 39 | RUSTC_WRAPPER: "sccache" 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | job: 44 | - { os: ubuntu-latest } 45 | - { os: macos-latest } 46 | - { os: windows-latest } 47 | steps: 48 | - uses: actions/checkout@v4 49 | - uses: dtolnay/rust-toolchain@master 50 | with: 51 | toolchain: stable 52 | components: clippy 53 | - uses: Swatinem/rust-cache@v2 54 | - name: Run sccache-cache 55 | uses: mozilla-actions/sccache-action@v0.0.9 56 | - if: ${{ contains(matrix.job.os, 'ubuntu') }} 57 | run: | 58 | sudo apt-get update -y 59 | sudo apt-get -yq --no-install-suggests --no-install-recommends install libsystemd-dev 60 | - name: Initialize workflow variables 61 | id: vars 62 | shell: bash 63 | run: | 64 | ## VARs setup 65 | outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } 66 | # failure mode 67 | unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in 68 | ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; 69 | *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; 70 | esac; 71 | outputs FAIL_ON_FAULT FAULT_TYPE 72 | - name: "`cargo clippy` lint testing" 73 | shell: bash 74 | run: | 75 | ## `cargo clippy` lint testing 76 | unset fault 77 | CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity -W clippy::implicit_clone -W clippy::range-plus-one -W clippy::redundant-clone -W clippy::match_bool -W clippy::semicolon_if_nothing_returned" 78 | fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" 79 | fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') 80 | # * convert any warnings to GHA UI annotations; ref: 81 | S=$(cargo clippy --all-targets -pprocps -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } 82 | if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | /.vscode/ 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # procps (uutils) 2 | # * see the repository LICENSE, README, and CONTRIBUTING files for more information 3 | 4 | # spell-checker:ignore (libs) mangen 5 | 6 | [workspace.package] 7 | authors = ["uutils developers"] 8 | categories = ["command-line-utilities"] 9 | edition = "2021" 10 | homepage = "https://github.com/uutils/procps" 11 | keywords = ["procps", "uutils", "cross-platform", "cli", "utility"] 12 | license = "MIT" 13 | version = "0.0.1" 14 | 15 | [package] 16 | name = "procps" 17 | description = "procps ~ implemented as universal (cross-platform) utils, written in Rust" 18 | default-run = "procps" 19 | repository = "https://github.com/uutils/procps" 20 | readme = "README.md" 21 | build = "build.rs" 22 | authors.workspace = true 23 | categories.workspace = true 24 | edition.workspace = true 25 | homepage.workspace = true 26 | keywords.workspace = true 27 | license.workspace = true 28 | version.workspace = true 29 | 30 | [features] 31 | default = ["feat_common_core"] 32 | uudoc = [] 33 | 34 | feat_common_core = [ 35 | "free", 36 | "pgrep", 37 | "pidof", 38 | "pidwait", 39 | "pkill", 40 | "pmap", 41 | "ps", 42 | "pwdx", 43 | "slabtop", 44 | "snice", 45 | "sysctl", 46 | "tload", 47 | "top", 48 | "vmstat", 49 | "w", 50 | "watch", 51 | ] 52 | 53 | [workspace.dependencies] 54 | bytesize = "2.0.0" 55 | chrono = { version = "0.4.38", default-features = false, features = ["clock"] } 56 | clap = { version = "4.5.4", features = ["wrap_help", "cargo"] } 57 | clap_complete = "4.5.2" 58 | clap_mangen = "0.2.20" 59 | crossterm = "0.29.0" 60 | ctor = "0.4.1" 61 | libc = "0.2.154" 62 | nix = { version = "0.30", default-features = false, features = ["process"] } 63 | phf = "0.11.2" 64 | phf_codegen = "0.11.2" 65 | prettytable-rs = "0.10.0" 66 | rand = { version = "0.9.0", features = ["small_rng"] } 67 | ratatui = "0.29.0" 68 | regex = "1.10.4" 69 | sysinfo = "0.35.0" 70 | tempfile = "3.10.1" 71 | terminal_size = "0.4.2" 72 | textwrap = { version = "0.16.1", features = ["terminal_size"] } 73 | thiserror = "2.0.4" 74 | uucore = "0.1.0" 75 | uutests = "0.1.0" 76 | walkdir = "2.5.0" 77 | windows = { version = "0.61.1" } 78 | windows-sys = { version = "0.59.0", default-features = false } 79 | xattr = "1.3.1" 80 | 81 | [dependencies] 82 | clap = { workspace = true } 83 | clap_complete = { workspace = true } 84 | clap_mangen = { workspace = true } 85 | phf = { workspace = true } 86 | regex = { workspace = true } 87 | sysinfo = { workspace = true } 88 | textwrap = { workspace = true } 89 | uucore = { workspace = true } 90 | 91 | # 92 | free = { optional = true, version = "0.0.1", package = "uu_free", path = "src/uu/free" } 93 | pgrep = { optional = true, version = "0.0.1", package = "uu_pgrep", path = "src/uu/pgrep" } 94 | pidof = { optional = true, version = "0.0.1", package = "uu_pidof", path = "src/uu/pidof" } 95 | pidwait = { optional = true, version = "0.0.1", package = "uu_pidwait", path = "src/uu/pidwait" } 96 | pkill = { optional = true, version = "0.0.1", package = "uu_pkill", path = "src/uu/pkill" } 97 | pmap = { optional = true, version = "0.0.1", package = "uu_pmap", path = "src/uu/pmap" } 98 | ps = { optional = true, version = "0.0.1", package = "uu_ps", path = "src/uu/ps" } 99 | pwdx = { optional = true, version = "0.0.1", package = "uu_pwdx", path = "src/uu/pwdx" } 100 | slabtop = { optional = true, version = "0.0.1", package = "uu_slabtop", path = "src/uu/slabtop" } 101 | snice = { optional = true, version = "0.0.1", package = "uu_snice", path = "src/uu/snice" } 102 | sysctl = { optional = true, version = "0.0.1", package = "uu_sysctl", path = "src/uu/sysctl" } 103 | tload = { optional = true, version = "0.0.1", package = "uu_tload", path = "src/uu/tload" } 104 | top = { optional = true, version = "0.0.1", package = "uu_top", path = "src/uu/top" } 105 | vmstat = { optional = true, version = "0.0.1", package = "uu_vmstat", path = "src/uu/vmstat" } 106 | w = { optional = true, version = "0.0.1", package = "uu_w", path = "src/uu/w" } 107 | watch = { optional = true, version = "0.0.1", package = "uu_watch", path = "src/uu/watch" } 108 | 109 | [dev-dependencies] 110 | chrono = { workspace = true } 111 | ctor = { workspace = true } 112 | libc = { workspace = true } 113 | pretty_assertions = "1.4.0" 114 | rand = { workspace = true } 115 | regex = { workspace = true } 116 | tempfile = { workspace = true } 117 | uucore = { workspace = true, features = ["entries", "process", "signals"] } 118 | uutests = { workspace = true } 119 | 120 | [target.'cfg(unix)'.dev-dependencies] 121 | xattr = { workspace = true } 122 | 123 | [target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies] 124 | rlimit = "0.10.1" 125 | 126 | [build-dependencies] 127 | phf_codegen = { workspace = true } 128 | 129 | 130 | [[bin]] 131 | name = "procps" 132 | path = "src/bin/procps.rs" 133 | 134 | [[bin]] 135 | name = "uudoc" 136 | path = "src/bin/uudoc.rs" 137 | required-features = ["uudoc"] 138 | 139 | # The default release profile. It contains all optimizations, without 140 | # sacrificing debug info. With this profile (like in the standard 141 | # release profile), the debug info and the stack traces will still be available. 142 | [profile.release] 143 | lto = true 144 | 145 | # A release-like profile that is tuned to be fast, even when being fast 146 | # compromises on binary size. This includes aborting on panic. 147 | [profile.release-fast] 148 | inherits = "release" 149 | panic = "abort" 150 | 151 | # A release-like profile that is as small as possible. 152 | [profile.release-small] 153 | inherits = "release" 154 | opt-level = "z" 155 | panic = "abort" 156 | strip = true 157 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 uutils 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/procps.svg)](https://crates.io/crates/procps) 2 | [![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ) 3 | [![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/procps/blob/main/LICENSE) 4 | [![dependency status](https://deps.rs/repo/github/uutils/procps/status.svg)](https://deps.rs/repo/github/uutils/procps) 5 | 6 | [![CodeCov](https://codecov.io/gh/uutils/procps/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/procps) 7 | 8 | # procps 9 | 10 | Rust reimplementation of the procps project 11 | 12 | Provides command line and full screen utilities for browsing procfs, a "pseudo" file system dynamically generated by the kernel to provide information about the status of entries in its process table (such as whether the process is running, stopped, or a "zombie"). 13 | 14 | Ongoing: 15 | * `free`: Shows the amount of free and used memory in the system. 16 | * `pgrep`: Searches for processes based on name and other attributes. 17 | * `pidof`: Find the process ID of a running program. 18 | * `pidwait`: Waits for a specific process to terminate. 19 | * `pkill`: Kills processes based on name and other attributes. 20 | * `pmap`: Displays the memory map of a process. 21 | * `ps`: Displays information about active processes. 22 | * `pwdx`: Shows the current working directory of a process. 23 | * `slabtop`: Displays detailed kernel slab cache information in real time. 24 | * `snice`: Changes the scheduling priority of a running process. 25 | * `sysctl`: Read or write kernel parameters at run-time. 26 | * `tload`: Prints a graphical representation of system load average to the terminal. 27 | * `top`: Displays real-time information about system processes. 28 | * `vmstat`: Reports information about processes, memory, paging, block IO, traps, and CPU activity. 29 | * `w`: Shows who is logged on and what they are doing. 30 | * `watch`: Executes a program periodically, showing output fullscreen. 31 | 32 | TODO: 33 | * `hugetop`: Report hugepage usage of processes and the system as a whole. 34 | * `skill`: Sends a signal to processes based on criteria like user, terminal, etc. 35 | 36 | Elsewhere: 37 | 38 | * `kill` is already implemented in https://github.com/uutils/coreutils 39 | * `uptime`: Shows how long the system has been running, including load average. It is already implemented in https://github.com/uutils/coreutils 40 | 41 | ## Installation 42 | 43 | Ensure you have Rust installed on your system. You can install Rust through [rustup](https://rustup.rs/). 44 | 45 | Clone the repository and build the project using Cargo: 46 | 47 | ```bash 48 | git clone https://github.com/uutils/procps.git 49 | cd procps 50 | cargo build --release 51 | cargo run --release 52 | ``` 53 | 54 | ## License 55 | 56 | procps is licensed under the MIT License - see the `LICENSE` file for details 57 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | // spell-checker:ignore (vars) krate 7 | 8 | use std::env; 9 | use std::fs::File; 10 | use std::io::Write; 11 | use std::path::Path; 12 | 13 | pub fn main() { 14 | const ENV_FEATURE_PREFIX: &str = "CARGO_FEATURE_"; 15 | const FEATURE_PREFIX: &str = "feat_"; 16 | 17 | if let Ok(profile) = env::var("PROFILE") { 18 | println!("cargo:rustc-cfg=build={profile:?}"); 19 | } 20 | 21 | let out_dir = env::var("OUT_DIR").unwrap(); 22 | 23 | let mut crates = Vec::new(); 24 | for (key, val) in env::vars() { 25 | if val == "1" && key.starts_with(ENV_FEATURE_PREFIX) { 26 | let krate = key[ENV_FEATURE_PREFIX.len()..].to_lowercase(); 27 | // Allow this as we have a bunch of info in the comments 28 | #[allow(clippy::match_same_arms)] 29 | match krate.as_ref() { 30 | "default" | "macos" | "unix" | "windows" | "selinux" | "zip" => continue, // common/standard feature names 31 | "nightly" | "test_unimplemented" => continue, // crate-local custom features 32 | "uudoc" => continue, // is not a utility 33 | "test" => continue, // over-ridden with 'uu_test' to avoid collision with rust core crate 'test' 34 | s if s.starts_with(FEATURE_PREFIX) => continue, // crate feature sets 35 | _ => {} // util feature name 36 | } 37 | crates.push(krate); 38 | } 39 | } 40 | crates.sort(); 41 | 42 | let mut mf = File::create(Path::new(&out_dir).join("uutils_map.rs")).unwrap(); 43 | 44 | mf.write_all( 45 | "type UtilityMap = phf::OrderedMap<&'static str, (fn(T) -> i32, fn() -> Command)>;\n\ 46 | \n\ 47 | #[allow(clippy::too_many_lines)] 48 | fn util_map() -> UtilityMap {\n" 49 | .as_bytes(), 50 | ) 51 | .unwrap(); 52 | 53 | let mut phf_map = phf_codegen::OrderedMap::<&str>::new(); 54 | for krate in &crates { 55 | let map_value = format!("({krate}::uumain, {krate}::uu_app)"); 56 | phf_map.entry(krate, &map_value); 57 | } 58 | write!(mf, "{}", phf_map.build()).unwrap(); 59 | mf.write_all(b"\n}\n").unwrap(); 60 | 61 | mf.flush().unwrap(); 62 | } 63 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/bin/procps.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | // spell-checker:ignore manpages mangen 7 | 8 | use clap::{Arg, Command}; 9 | use clap_complete::Shell; 10 | use std::cmp; 11 | use std::ffi::OsStr; 12 | use std::ffi::OsString; 13 | use std::io::{self, Write}; 14 | use std::path::{Path, PathBuf}; 15 | use std::process; 16 | use uucore::display::Quotable; 17 | 18 | const VERSION: &str = env!("CARGO_PKG_VERSION"); 19 | 20 | include!(concat!(env!("OUT_DIR"), "/uutils_map.rs")); 21 | 22 | fn usage(utils: &UtilityMap, name: &str) { 23 | println!("{name} {VERSION} (multi-call binary)\n"); 24 | println!("Usage: {name} [function [arguments...]]\n"); 25 | println!("Currently defined functions:\n"); 26 | #[allow(clippy::map_clone)] 27 | let mut utils: Vec<&str> = utils.keys().map(|&s| s).collect(); 28 | utils.sort_unstable(); 29 | let display_list = utils.join(", "); 30 | let width = cmp::min(textwrap::termwidth(), 100) - 4 * 2; // (opinion/heuristic) max 100 chars wide with 4 character side indentions 31 | println!( 32 | "{}", 33 | textwrap::indent(&textwrap::fill(&display_list, width), " ") 34 | ); 35 | } 36 | 37 | fn binary_path(args: &mut impl Iterator) -> PathBuf { 38 | match args.next() { 39 | Some(ref s) if !s.is_empty() => PathBuf::from(s), 40 | _ => std::env::current_exe().unwrap(), 41 | } 42 | } 43 | 44 | fn name(binary_path: &Path) -> Option<&str> { 45 | binary_path.file_stem()?.to_str() 46 | } 47 | 48 | #[allow(clippy::cognitive_complexity)] 49 | fn main() { 50 | uucore::panic::mute_sigpipe_panic(); 51 | 52 | let utils = util_map(); 53 | let mut args = uucore::args_os(); 54 | 55 | let binary = binary_path(&mut args); 56 | let binary_as_util = name(&binary).unwrap_or_else(|| { 57 | usage(&utils, ""); 58 | process::exit(0); 59 | }); 60 | 61 | // binary name equals util name? 62 | if let Some(&(uumain, _)) = utils.get(binary_as_util) { 63 | process::exit(uumain((vec![binary.into()].into_iter()).chain(args))); 64 | } 65 | 66 | // binary name equals prefixed util name? 67 | // * prefix/stem may be any string ending in a non-alphanumeric character 68 | let util_name = if let Some(util) = utils.keys().find(|util| { 69 | binary_as_util.ends_with(*util) 70 | && !binary_as_util[..binary_as_util.len() - (*util).len()] 71 | .ends_with(char::is_alphanumeric) 72 | }) { 73 | // prefixed util => replace 0th (aka, executable name) argument 74 | Some(OsString::from(*util)) 75 | } else { 76 | // unmatched binary name => regard as multi-binary container and advance argument list 77 | uucore::set_utility_is_second_arg(); 78 | args.next() 79 | }; 80 | 81 | // 0th argument equals util name? 82 | if let Some(util_os) = util_name { 83 | fn not_found(util: &OsStr) -> ! { 84 | println!("{}: function/utility not found", util.maybe_quote()); 85 | process::exit(1); 86 | } 87 | 88 | let Some(util) = util_os.to_str() else { 89 | not_found(&util_os) 90 | }; 91 | 92 | if util == "completion" { 93 | gen_completions(args, &utils); 94 | } 95 | 96 | if util == "manpage" { 97 | gen_manpage(args, &utils); 98 | } 99 | 100 | match utils.get(util) { 101 | Some(&(uumain, _)) => { 102 | process::exit(uumain((vec![util_os].into_iter()).chain(args))); 103 | } 104 | None => { 105 | if util == "--help" || util == "-h" { 106 | // see if they want help on a specific util 107 | if let Some(util_os) = args.next() { 108 | let Some(util) = util_os.to_str() else { 109 | not_found(&util_os) 110 | }; 111 | 112 | match utils.get(util) { 113 | Some(&(uumain, _)) => { 114 | let code = uumain( 115 | (vec![util_os, OsString::from("--help")].into_iter()) 116 | .chain(args), 117 | ); 118 | io::stdout().flush().expect("could not flush stdout"); 119 | process::exit(code); 120 | } 121 | None => not_found(&util_os), 122 | } 123 | } 124 | usage(&utils, binary_as_util); 125 | process::exit(0); 126 | } else { 127 | not_found(&util_os); 128 | } 129 | } 130 | } 131 | } else { 132 | // no arguments provided 133 | usage(&utils, binary_as_util); 134 | process::exit(0); 135 | } 136 | } 137 | 138 | /// Prints completions for the utility in the first parameter for the shell in the second parameter to stdout 139 | fn gen_completions( 140 | args: impl Iterator, 141 | util_map: &UtilityMap, 142 | ) -> ! { 143 | let all_utilities: Vec<_> = std::iter::once("procps") 144 | .chain(util_map.keys().copied()) 145 | .collect(); 146 | 147 | let matches = Command::new("completion") 148 | .about("Prints completions to stdout") 149 | .arg( 150 | Arg::new("utility") 151 | .value_parser(clap::builder::PossibleValuesParser::new(all_utilities)) 152 | .required(true), 153 | ) 154 | .arg( 155 | Arg::new("shell") 156 | .value_parser(clap::builder::EnumValueParser::::new()) 157 | .required(true), 158 | ) 159 | .get_matches_from(std::iter::once(OsString::from("completion")).chain(args)); 160 | 161 | let utility = matches.get_one::("utility").unwrap(); 162 | let shell = *matches.get_one::("shell").unwrap(); 163 | 164 | let mut command = if utility == "procps" { 165 | gen_procps_app(util_map) 166 | } else { 167 | util_map.get(utility).unwrap().1() 168 | }; 169 | let bin_name = std::env::var("PROG_PREFIX").unwrap_or_default() + utility; 170 | 171 | clap_complete::generate(shell, &mut command, bin_name, &mut io::stdout()); 172 | io::stdout().flush().unwrap(); 173 | process::exit(0); 174 | } 175 | 176 | /// Generate the manpage for the utility in the first parameter 177 | fn gen_manpage( 178 | args: impl Iterator, 179 | util_map: &UtilityMap, 180 | ) -> ! { 181 | let all_utilities: Vec<_> = std::iter::once("procps") 182 | .chain(util_map.keys().copied()) 183 | .collect(); 184 | 185 | let matches = Command::new("manpage") 186 | .about("Prints manpage to stdout") 187 | .arg( 188 | Arg::new("utility") 189 | .value_parser(clap::builder::PossibleValuesParser::new(all_utilities)) 190 | .required(true), 191 | ) 192 | .get_matches_from(std::iter::once(OsString::from("manpage")).chain(args)); 193 | 194 | let utility = matches.get_one::("utility").unwrap(); 195 | 196 | let command = if utility == "procps" { 197 | gen_procps_app(util_map) 198 | } else { 199 | util_map.get(utility).unwrap().1() 200 | }; 201 | 202 | let man = clap_mangen::Man::new(command); 203 | man.render(&mut io::stdout()) 204 | .expect("Man page generation failed"); 205 | io::stdout().flush().unwrap(); 206 | process::exit(0); 207 | } 208 | 209 | fn gen_procps_app(util_map: &UtilityMap) -> Command { 210 | let mut command = Command::new("procps"); 211 | for (name, (_, sub_app)) in util_map { 212 | // Recreate a small subcommand with only the relevant info 213 | // (name & short description) 214 | let about = sub_app() 215 | .get_about() 216 | .expect("Could not get the 'about'") 217 | .to_string(); 218 | let sub_app = Command::new(name).about(about); 219 | command = command.subcommand(sub_app); 220 | } 221 | command 222 | } 223 | -------------------------------------------------------------------------------- /src/uu/free/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_free" 3 | description = "free ~ (uutils) Display amount of free and used memory in the system" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/free" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | bytesize = { workspace = true } 15 | clap = { workspace = true } 16 | sysinfo = { workspace = true } 17 | uucore = { workspace = true } 18 | 19 | [target.'cfg(target_os="windows")'.dependencies] 20 | windows = { workspace = true, features = ["Wdk_System_SystemInformation", "Win32_System_ProcessStatus", "Win32_System_SystemInformation"] } 21 | 22 | [lib] 23 | path = "src/free.rs" 24 | 25 | [[bin]] 26 | name = "free" 27 | path = "src/main.rs" 28 | -------------------------------------------------------------------------------- /src/uu/free/free.md: -------------------------------------------------------------------------------- 1 | # free 2 | 3 | ``` 4 | free [options] 5 | ``` 6 | 7 | Display amount of free and used memory in the system 8 | -------------------------------------------------------------------------------- /src/uu/free/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_free); 2 | -------------------------------------------------------------------------------- /src/uu/free/src/windows_util.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | 3 | use windows::{ 4 | core::Result, 5 | Wdk::System::SystemInformation::{NtQuerySystemInformation, SYSTEM_INFORMATION_CLASS}, 6 | Win32::Foundation::{STATUS_INFO_LENGTH_MISMATCH, STATUS_SUCCESS, UNICODE_STRING}, 7 | }; 8 | 9 | #[repr(C)] 10 | #[derive(Default, Debug)] 11 | #[allow(non_snake_case)] 12 | struct SYSTEM_PAGEFILE_INFORMATION { 13 | NextEntryOffset: u32, 14 | TotalSize: u32, 15 | TotalInUse: u32, 16 | PeakUsage: u32, 17 | PageFileName: UNICODE_STRING, 18 | } 19 | 20 | /// Get the usage and total size of all page files. 21 | pub(crate) fn get_pagefile_usage() -> Result<(u32, u32)> { 22 | let mut buf: Vec = Vec::new(); 23 | 24 | let mut return_len: u32 = 0; 25 | 26 | loop { 27 | let status = unsafe { 28 | NtQuerySystemInformation( 29 | // SystemPageFileInformation 30 | SYSTEM_INFORMATION_CLASS(0x12), 31 | buf.as_mut_ptr() as *mut c_void, 32 | buf.len() as u32, 33 | &mut return_len, 34 | ) 35 | }; 36 | 37 | debug_assert!(buf.len() <= return_len as usize); 38 | 39 | if status == STATUS_INFO_LENGTH_MISMATCH { 40 | debug_assert!(return_len > buf.len() as u32); 41 | buf.resize(return_len as usize, 0); 42 | continue; 43 | } else if status == STATUS_SUCCESS { 44 | debug_assert!(return_len == buf.len() as u32); 45 | break; 46 | } else { 47 | return Err(status.into()); 48 | } 49 | } 50 | 51 | let mut usage = 0; 52 | let mut total = 0; 53 | 54 | if return_len > 0 { 55 | let ptr = buf.as_ptr(); 56 | let mut offset = 0; 57 | loop { 58 | let ptr_offset = 59 | unsafe { ptr.byte_offset(offset) } as *const SYSTEM_PAGEFILE_INFORMATION; 60 | let record = unsafe { std::ptr::read(ptr_offset) }; 61 | 62 | usage += record.TotalInUse; 63 | total += record.TotalSize; 64 | 65 | offset = record.NextEntryOffset as isize; 66 | 67 | if offset == 0 { 68 | break; 69 | } 70 | } 71 | } 72 | 73 | Ok((usage, total)) 74 | } 75 | -------------------------------------------------------------------------------- /src/uu/pgrep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_pgrep" 3 | description = "pgrep ~ (uutils) look up, signal, or wait for processes based on name and other attributes" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/pgrep" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true, features = ["entries"] } 15 | clap = { workspace = true } 16 | walkdir = { workspace = true } 17 | regex = { workspace = true } 18 | 19 | [lib] 20 | path = "src/pgrep.rs" 21 | 22 | [[bin]] 23 | name = "pgrep" 24 | path = "src/main.rs" 25 | -------------------------------------------------------------------------------- /src/uu/pgrep/pgrep.md: -------------------------------------------------------------------------------- 1 | # pgrep 2 | 3 | ``` 4 | pgrep [options] 5 | ``` 6 | 7 | look up, signal, or wait for processes based on name and other attributes 8 | -------------------------------------------------------------------------------- /src/uu/pgrep/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_pgrep); 2 | -------------------------------------------------------------------------------- /src/uu/pgrep/src/pgrep.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | // Pid utils 7 | pub mod process; 8 | pub mod process_matcher; 9 | 10 | use clap::{arg, crate_version, Command}; 11 | use uucore::{error::UResult, format_usage, help_about, help_usage}; 12 | 13 | const ABOUT: &str = help_about!("pgrep.md"); 14 | const USAGE: &str = help_usage!("pgrep.md"); 15 | 16 | /// # Conceptual model of `pgrep` 17 | /// 18 | /// At first, `pgrep` command will check the patterns is legal. 19 | /// In this stage, `pgrep` will construct regex if `--exact` argument was passed. 20 | /// 21 | /// Then, `pgrep` will collect all *matched* pids, and filtering them. 22 | /// In this stage `pgrep` command will collect all the pids and its information from __/proc/__ 23 | /// file system. At the same time, `pgrep` will construct filters from command 24 | /// line arguments to filter the collected pids. Note that the "-o" and "-n" flag filters works 25 | /// if them enabled and based on general collecting result. 26 | /// 27 | /// Last, `pgrep` will construct output format from arguments, and print the processed result. 28 | #[uucore::main] 29 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 30 | let matches = uu_app().try_get_matches_from(args)?; 31 | let mut settings = process_matcher::get_match_settings(&matches)?; 32 | settings.threads = matches.get_flag("lightweight"); 33 | 34 | // Collect pids 35 | let pids = process_matcher::find_matching_pids(&settings)?; 36 | 37 | // Processing output 38 | let output = if matches.get_flag("count") { 39 | format!("{}", pids.len()) 40 | } else { 41 | let delimiter = matches.get_one::("delimiter").unwrap(); 42 | 43 | let formatted: Vec<_> = if matches.get_flag("list-full") { 44 | pids.into_iter() 45 | .map(|it| { 46 | // pgrep from procps-ng outputs the process name inside square brackets 47 | // if /proc//cmdline is empty 48 | if it.cmdline.is_empty() { 49 | format!("{} [{}]", it.pid, it.clone().name().unwrap()) 50 | } else { 51 | format!("{} {}", it.pid, it.cmdline) 52 | } 53 | }) 54 | .collect() 55 | } else if matches.get_flag("list-name") { 56 | pids.into_iter() 57 | .map(|it| format!("{} {}", it.pid, it.clone().name().unwrap())) 58 | .collect() 59 | } else { 60 | pids.into_iter().map(|it| format!("{}", it.pid)).collect() 61 | }; 62 | 63 | formatted.join(delimiter) 64 | }; 65 | 66 | if !output.is_empty() { 67 | println!("{}", output); 68 | }; 69 | 70 | Ok(()) 71 | } 72 | 73 | #[allow(clippy::cognitive_complexity)] 74 | pub fn uu_app() -> Command { 75 | Command::new(uucore::util_name()) 76 | .version(crate_version!()) 77 | .about(ABOUT) 78 | .override_usage(format_usage(USAGE)) 79 | .args_override_self(true) 80 | .args([ 81 | arg!(-d --delimiter "specify output delimiter") 82 | .default_value("\n") 83 | .hide_default_value(true), 84 | arg!(-l --"list-name" "list PID and process name"), 85 | arg!(-a --"list-full" "list PID and full command line"), 86 | arg!(-w --lightweight "list all TID"), 87 | ]) 88 | .args(process_matcher::clap_args( 89 | "Name of the program to find the PID of", 90 | true, 91 | )) 92 | } 93 | -------------------------------------------------------------------------------- /src/uu/pidof/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_pidof" 3 | description = "pidof ~ (uutils) Find the process ID of a running program" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/pidof" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true, features = ["process"] } 15 | clap = { workspace = true } 16 | uu_pgrep = { path = "../pgrep" } 17 | 18 | [lib] 19 | path = "src/pidof.rs" 20 | 21 | [[bin]] 22 | name = "pidof" 23 | path = "src/main.rs" 24 | -------------------------------------------------------------------------------- /src/uu/pidof/pidof.md: -------------------------------------------------------------------------------- 1 | # pidof 2 | 3 | ``` 4 | pidof [options] [program [...]] 5 | ``` 6 | 7 | Find the process ID of a running program 8 | -------------------------------------------------------------------------------- /src/uu/pidof/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_pidof); 2 | -------------------------------------------------------------------------------- /src/uu/pidof/src/pidof.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use std::path::PathBuf; 7 | 8 | use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; 9 | use uu_pgrep::process::{walk_process, ProcessInformation}; 10 | #[cfg(unix)] 11 | use uucore::process::geteuid; 12 | use uucore::{error::UResult, format_usage, help_about, help_usage}; 13 | 14 | const ABOUT: &str = help_about!("pidof.md"); 15 | const USAGE: &str = help_usage!("pidof.md"); 16 | 17 | #[uucore::main] 18 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 19 | let matches = uu_app().try_get_matches_from(args)?; 20 | 21 | let arg_program_name = matches.get_many::("program-name"); 22 | let arg_separator = matches.get_one::("S").unwrap(); 23 | 24 | if arg_program_name.is_none() { 25 | uucore::error::set_exit_code(1); 26 | return Ok(()); 27 | }; 28 | 29 | let collected = collect_matched_pids(&matches); 30 | 31 | if collected.is_empty() { 32 | uucore::error::set_exit_code(1); 33 | return Ok(()); 34 | }; 35 | 36 | let output = collected 37 | .into_iter() 38 | .map(|it| it.to_string()) 39 | .collect::>() 40 | .join(arg_separator); 41 | 42 | let flag_quiet = matches.get_flag("q"); 43 | if !flag_quiet { 44 | println!("{output}"); 45 | } 46 | 47 | Ok(()) 48 | } 49 | 50 | fn match_process_name( 51 | process: &mut ProcessInformation, 52 | name_to_match: &str, 53 | with_workers: bool, 54 | match_scripts: bool, 55 | ) -> bool { 56 | let binding = process.cmdline.split(' ').collect::>(); 57 | let path = binding.first().unwrap().to_string(); 58 | 59 | if path.is_empty() { 60 | if !with_workers { 61 | return false; 62 | } 63 | return process.name().unwrap() == name_to_match; 64 | }; 65 | 66 | if PathBuf::from(path).file_name().unwrap().to_str().unwrap() == name_to_match { 67 | return true; 68 | } 69 | 70 | // When a script (ie. file starting with e.g. #!/bin/sh) is run like `./script.sh`, then 71 | // its cmdline will look like `/bin/sh ./script.sh` but its .name() will be `script.sh`. 72 | // As name() gets truncated to 15 characters, the original pidof seems to always do a prefix match. 73 | if match_scripts && binding.len() > 1 { 74 | return PathBuf::from(binding[1]) 75 | .file_name() 76 | .map(|f| f.to_str().unwrap()) 77 | .is_some_and(|f| f == name_to_match && f.starts_with(&process.name().unwrap())); 78 | } 79 | 80 | false 81 | } 82 | 83 | fn collect_matched_pids(matches: &ArgMatches) -> Vec { 84 | let program_names: Vec<_> = matches 85 | .get_many::("program-name") 86 | .unwrap() 87 | .cloned() 88 | .collect(); 89 | let with_workers = matches.get_flag("with-workers"); 90 | let match_scripts = matches.get_flag("x"); 91 | 92 | let collected = walk_process().collect::>(); 93 | let arg_omit_pid = matches 94 | .get_many::("o") 95 | .unwrap_or_default() 96 | .copied() 97 | .collect::>(); 98 | 99 | // Original pidof silently ignores the check-root option if the user is not root. 100 | #[cfg(unix)] 101 | let check_root = matches.get_flag("check-root") && geteuid() == 0; 102 | #[cfg(not(unix))] 103 | let check_root = false; 104 | let our_root = ProcessInformation::current_process_info() 105 | .unwrap() 106 | .root() 107 | .unwrap(); 108 | 109 | program_names 110 | .into_iter() 111 | .flat_map(|program| { 112 | let mut processed = Vec::new(); 113 | for mut process in collected.clone() { 114 | if !match_process_name(&mut process, &program, with_workers, match_scripts) { 115 | continue; 116 | } 117 | if arg_omit_pid.contains(&process.pid) { 118 | continue; 119 | } 120 | if check_root && process.root().unwrap() != our_root { 121 | continue; 122 | } 123 | 124 | if matches.get_flag("t") { 125 | processed.extend_from_slice(&process.thread_ids()); 126 | } else { 127 | processed.push(process.pid); 128 | } 129 | } 130 | 131 | processed.sort_by(|a, b| b.cmp(a)); 132 | 133 | let flag_s = matches.get_flag("s"); 134 | if flag_s { 135 | match processed.first() { 136 | Some(first) => vec![*first], 137 | None => Vec::new(), 138 | } 139 | } else { 140 | processed 141 | } 142 | }) 143 | .collect() 144 | } 145 | 146 | #[allow(clippy::cognitive_complexity)] 147 | pub fn uu_app() -> Command { 148 | Command::new(uucore::util_name()) 149 | .version(crate_version!()) 150 | .about(ABOUT) 151 | .override_usage(format_usage(USAGE)) 152 | .infer_long_args(true) 153 | .arg( 154 | Arg::new("program-name") 155 | .help("Program name.") 156 | .index(1) 157 | .action(ArgAction::Append), 158 | ) 159 | .arg( 160 | Arg::new("check-root") 161 | .short('c') 162 | .long("check-root") 163 | .help("Only return PIDs with the same root directory") 164 | .action(ArgAction::SetTrue), 165 | ) 166 | .arg( 167 | Arg::new("S") 168 | .short('S') 169 | // the pidof bundled with Debian uses -d instead of -S 170 | .visible_short_alias('d') 171 | .long("separator") 172 | .help("Use SEP as separator between PIDs") 173 | .action(ArgAction::Set) 174 | .value_name("SEP") 175 | .default_value(" ") 176 | .hide_default_value(true), 177 | ) 178 | // .arg( 179 | // Arg::new("n") 180 | // .short('n') 181 | // .help("Avoid using stat system function on network shares") 182 | // .action(ArgAction::SetTrue), 183 | // ) 184 | .arg( 185 | Arg::new("o") 186 | .short('o') 187 | .long("omit-pid") 188 | .help("Omit results with a given PID") 189 | .value_delimiter(',') 190 | .action(ArgAction::Append) 191 | .value_parser(clap::value_parser!(usize)) 192 | .value_name("PID"), 193 | ) 194 | .arg( 195 | Arg::new("q") 196 | .short('q') 197 | .help("Quiet mode. Do not display output") 198 | .action(ArgAction::SetTrue), 199 | ) 200 | .arg( 201 | Arg::new("s") 202 | .short('s') 203 | .long("single-shot") 204 | .help("Only return one PID") 205 | .action(ArgAction::SetTrue), 206 | ) 207 | .arg( 208 | Arg::new("t") 209 | .short('t') 210 | .long("lightweight") 211 | .help("Show thread ids instead of process ids") 212 | .action(ArgAction::SetTrue), 213 | ) 214 | .arg( 215 | Arg::new("with-workers") 216 | .short('w') 217 | .long("with-workers") 218 | .help("Show kernel worker threads as well") 219 | .action(ArgAction::SetTrue), 220 | ) 221 | .arg( 222 | Arg::new("x") 223 | .short('x') 224 | .help("Return PIDs of shells running scripts with a matching name") 225 | .action(ArgAction::SetTrue), 226 | ) 227 | } 228 | -------------------------------------------------------------------------------- /src/uu/pidwait/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_pidwait" 3 | description = "pidwait ~ (uutils) Wait for processes based on name" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/pidwait" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | nix = { workspace = true } 15 | uucore = { workspace = true, features = ["entries"] } 16 | clap = { workspace = true } 17 | regex = { workspace = true } 18 | uu_pgrep = { path = "../pgrep" } 19 | 20 | [lib] 21 | path = "src/pidwait.rs" 22 | 23 | [[bin]] 24 | name = "pidwait" 25 | path = "src/main.rs" 26 | -------------------------------------------------------------------------------- /src/uu/pidwait/pidwait.md: -------------------------------------------------------------------------------- 1 | # pidwait 2 | 3 | ``` 4 | pidwait [options] pattern 5 | ``` 6 | 7 | Wait for processes based on name. 8 | -------------------------------------------------------------------------------- /src/uu/pidwait/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_pidwait); 2 | -------------------------------------------------------------------------------- /src/uu/pidwait/src/pidwait.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use clap::{arg, crate_version, Command}; 7 | use uu_pgrep::process_matcher; 8 | use uucore::{error::UResult, format_usage, help_about, help_usage}; 9 | use wait::wait; 10 | 11 | mod wait; 12 | 13 | const ABOUT: &str = help_about!("pidwait.md"); 14 | const USAGE: &str = help_usage!("pidwait.md"); 15 | 16 | #[uucore::main] 17 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 18 | let matches = uu_app().try_get_matches_from(args)?; 19 | 20 | let settings = process_matcher::get_match_settings(&matches)?; 21 | let mut proc_infos = process_matcher::find_matching_pids(&settings)?; 22 | 23 | // For empty result 24 | if proc_infos.is_empty() { 25 | uucore::error::set_exit_code(1); 26 | } 27 | 28 | // Process outputs 29 | if matches.get_flag("count") { 30 | println!("{}", proc_infos.len()); 31 | } 32 | 33 | if matches.get_flag("echo") { 34 | if settings.newest || settings.oldest { 35 | for ele in &proc_infos { 36 | println!("waiting for (pid {})", ele.pid); 37 | } 38 | } else { 39 | for ele in proc_infos.iter_mut() { 40 | println!("waiting for {} (pid {})", ele.status()["Name"], ele.pid); 41 | } 42 | } 43 | } 44 | 45 | wait(&proc_infos); 46 | 47 | Ok(()) 48 | } 49 | 50 | pub fn uu_app() -> Command { 51 | Command::new(env!("CARGO_PKG_NAME")) 52 | .version(crate_version!()) 53 | .about(ABOUT) 54 | .override_usage(format_usage(USAGE)) 55 | .infer_long_args(true) 56 | .args([arg!(-e --echo "display PIDs before waiting")]) 57 | .args(process_matcher::clap_args( 58 | "Name of the program to wait for", 59 | true, 60 | )) 61 | } 62 | -------------------------------------------------------------------------------- /src/uu/pidwait/src/wait.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use uu_pgrep::process::ProcessInformation; 7 | 8 | // Dirty, but it works. 9 | // TODO: Use better implementation instead 10 | #[cfg(target_os = "linux")] 11 | pub(crate) fn wait(procs: &[ProcessInformation]) { 12 | use std::{thread::sleep, time::Duration}; 13 | 14 | let mut list = procs.to_vec(); 15 | 16 | loop { 17 | for proc in &list.clone() { 18 | // Check is running 19 | if !is_running(proc.pid) { 20 | list.retain(|it| it.pid != proc.pid); 21 | } 22 | } 23 | 24 | if list.is_empty() { 25 | return; 26 | } 27 | 28 | sleep(Duration::from_millis(50)); 29 | } 30 | } 31 | #[cfg(target_os = "linux")] 32 | fn is_running(pid: usize) -> bool { 33 | use std::{path::PathBuf, str::FromStr}; 34 | use uu_pgrep::process::RunState; 35 | 36 | let proc = PathBuf::from_str(&format!("/proc/{}", pid)).unwrap(); 37 | 38 | if !proc.exists() { 39 | return false; 40 | } 41 | 42 | match ProcessInformation::try_new(proc) { 43 | Ok(mut proc) => proc 44 | .run_state() 45 | .map(|it| it != RunState::Stopped) 46 | .unwrap_or(false), 47 | Err(_) => false, 48 | } 49 | } 50 | 51 | // Just for passing compile on other system. 52 | #[cfg(not(target_os = "linux"))] 53 | pub(crate) fn wait(_procs: &[ProcessInformation]) {} 54 | -------------------------------------------------------------------------------- /src/uu/pkill/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_pkill" 3 | description = "pgrep ~ (uutils) Kills processes based on name and other attributes." 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/pkill" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true, features = ["entries"] } 15 | clap = { workspace = true } 16 | walkdir = { workspace = true } 17 | regex = { workspace = true } 18 | nix = { workspace = true, features = ["signal"] } 19 | 20 | uu_pgrep = { path = "../pgrep" } 21 | 22 | [lib] 23 | path = "src/pkill.rs" 24 | 25 | [[bin]] 26 | name = "pkill" 27 | path = "src/main.rs" 28 | -------------------------------------------------------------------------------- /src/uu/pkill/pkill.md: -------------------------------------------------------------------------------- 1 | # pgrep 2 | 3 | ``` 4 | pkill [options] 5 | ``` 6 | 7 | Kills processes based on name and other attributes. 8 | -------------------------------------------------------------------------------- /src/uu/pkill/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_pkill); 2 | -------------------------------------------------------------------------------- /src/uu/pkill/src/pkill.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | // Pid utils 7 | use clap::{arg, crate_version, Command}; 8 | #[cfg(unix)] 9 | use nix::{ 10 | sys::signal::{self, Signal}, 11 | unistd::Pid, 12 | }; 13 | #[cfg(unix)] 14 | use std::io::Error; 15 | #[cfg(unix)] 16 | use uu_pgrep::process::ProcessInformation; 17 | use uu_pgrep::process_matcher; 18 | #[cfg(unix)] 19 | use uucore::{ 20 | error::FromIo, 21 | show, 22 | signals::{signal_by_name_or_value, signal_name_by_value}, 23 | }; 24 | use uucore::{error::UResult, format_usage, help_about, help_usage}; 25 | 26 | const ABOUT: &str = help_about!("pkill.md"); 27 | const USAGE: &str = help_usage!("pkill.md"); 28 | 29 | #[uucore::main] 30 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 31 | #[cfg(unix)] 32 | let mut args = args.collect_ignore(); 33 | #[cfg(target_os = "windows")] 34 | let args = args.collect_ignore(); 35 | #[cfg(unix)] 36 | handle_obsolete(&mut args); 37 | 38 | let matches = uu_app().try_get_matches_from(&args)?; 39 | let settings = process_matcher::get_match_settings(&matches)?; 40 | 41 | #[cfg(unix)] 42 | let sig_name = signal_name_by_value(settings.signal); 43 | // Signal does not support converting from EXIT 44 | // Instead, nix::signal::kill expects Option::None to properly handle EXIT 45 | #[cfg(unix)] 46 | let sig: Option = if sig_name.is_some_and(|name| name == "EXIT") { 47 | None 48 | } else { 49 | let sig = (settings.signal as i32) 50 | .try_into() 51 | .map_err(|e| std::io::Error::from_raw_os_error(e as i32))?; 52 | Some(sig) 53 | }; 54 | 55 | // Collect pids 56 | let pids = process_matcher::find_matching_pids(&settings)?; 57 | 58 | // Send signal 59 | // TODO: Implement -q 60 | #[cfg(unix)] 61 | let echo = matches.get_flag("echo"); 62 | #[cfg(unix)] 63 | kill(&pids, sig, echo); 64 | 65 | if matches.get_flag("count") { 66 | println!("{}", pids.len()); 67 | } 68 | 69 | Ok(()) 70 | } 71 | 72 | #[cfg(unix)] 73 | fn handle_obsolete(args: &mut [String]) { 74 | // Sanity check 75 | if args.len() > 2 { 76 | // Old signal can only be in the first argument position 77 | let slice = args[1].as_str(); 78 | if let Some(signal) = slice.strip_prefix('-') { 79 | // Check if it is a valid signal 80 | let opt_signal = signal_by_name_or_value(signal); 81 | if opt_signal.is_some() { 82 | // Replace with long option that clap can parse 83 | args[1] = format!("--signal={}", signal); 84 | } 85 | } 86 | } 87 | } 88 | 89 | #[cfg(unix)] 90 | fn kill(pids: &Vec, sig: Option, echo: bool) { 91 | for pid in pids { 92 | if let Err(e) = signal::kill(Pid::from_raw(pid.pid as i32), sig) { 93 | show!(Error::from_raw_os_error(e as i32) 94 | .map_err_context(|| format!("killing pid {} failed", pid.pid))); 95 | } else if echo { 96 | println!( 97 | "{} killed (pid {})", 98 | pid.cmdline.split(" ").next().unwrap_or(""), 99 | pid.pid 100 | ); 101 | } 102 | } 103 | } 104 | 105 | #[allow(clippy::cognitive_complexity)] 106 | pub fn uu_app() -> Command { 107 | Command::new(uucore::util_name()) 108 | .version(crate_version!()) 109 | .about(ABOUT) 110 | .override_usage(format_usage(USAGE)) 111 | .args_override_self(true) 112 | .args([ 113 | // arg!(- "signal to send (either number or name)"), 114 | // arg!(-q --queue "integer value to be sent with the signal"), 115 | arg!(-e --echo "display what is killed"), 116 | ]) 117 | .args(process_matcher::clap_args( 118 | "Name of the process to kill", 119 | false, 120 | )) 121 | } 122 | -------------------------------------------------------------------------------- /src/uu/pmap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_pmap" 3 | description = "pmap ~ (uutils) Report memory map of a process" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/pmap" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true } 15 | clap = { workspace = true } 16 | 17 | [lib] 18 | path = "src/pmap.rs" 19 | 20 | [[bin]] 21 | name = "pmap" 22 | path = "src/main.rs" 23 | -------------------------------------------------------------------------------- /src/uu/pmap/pmap.md: -------------------------------------------------------------------------------- 1 | # pmap 2 | 3 | ``` 4 | pmap [options] pid [...] 5 | ``` 6 | 7 | Report memory map of a process 8 | -------------------------------------------------------------------------------- /src/uu/pmap/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_pmap); 2 | -------------------------------------------------------------------------------- /src/uu/ps/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_ps" 3 | description = "ps - (uutils) Report a snapshot of the current processes" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/ps" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true, features = ["utmpx"] } 15 | clap = { workspace = true } 16 | chrono = { workspace = true, default-features = false, features = ["clock"] } 17 | libc = { workspace = true } 18 | prettytable-rs = { workspace = true } 19 | nix = { workspace = true } 20 | 21 | uu_pgrep = { path = "../pgrep" } 22 | 23 | [lib] 24 | path = "src/ps.rs" 25 | 26 | [[bin]] 27 | name = "ps" 28 | path = "src/main.rs" 29 | -------------------------------------------------------------------------------- /src/uu/ps/ps.md: -------------------------------------------------------------------------------- 1 | # ps 2 | 3 | ``` 4 | ps [options] 5 | ``` 6 | 7 | Report a snapshot of the current processes. 8 | -------------------------------------------------------------------------------- /src/uu/ps/src/collector.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use clap::ArgMatches; 7 | #[cfg(target_family = "unix")] 8 | use nix::errno::Errno; 9 | use std::{cell::RefCell, path::PathBuf, rc::Rc, str::FromStr}; 10 | use uu_pgrep::process::{ProcessInformation, Teletype}; 11 | 12 | // TODO: Temporary add to this file, this function will add to uucore. 13 | #[cfg(not(target_os = "redox"))] 14 | #[cfg(target_family = "unix")] 15 | fn getsid(pid: i32) -> Option { 16 | unsafe { 17 | let result = libc::getsid(pid); 18 | if Errno::last() == Errno::UnknownErrno { 19 | Some(result) 20 | } else { 21 | None 22 | } 23 | } 24 | } 25 | 26 | // TODO: Temporary add to this file, this function will add to uucore. 27 | #[cfg(target_family = "windows")] 28 | fn getsid(_pid: i32) -> Option { 29 | Some(0) 30 | } 31 | 32 | // Guessing it matches the current terminal 33 | pub(crate) fn basic_collector( 34 | proc_snapshot: &[Rc>], 35 | ) -> Vec>> { 36 | let mut result = Vec::new(); 37 | 38 | let current_tty = { 39 | // SAFETY: The `libc::getpid` always return i32 40 | let proc_path = 41 | PathBuf::from_str(&format!("/proc/{}/", unsafe { libc::getpid() })).unwrap(); 42 | let current_proc_info = ProcessInformation::try_new(proc_path).unwrap(); 43 | 44 | current_proc_info.tty() 45 | }; 46 | 47 | for proc_info in proc_snapshot { 48 | let proc_ttys = proc_info.borrow().tty(); 49 | 50 | if proc_ttys == current_tty { 51 | result.push(proc_info.clone()); 52 | } 53 | } 54 | 55 | result 56 | } 57 | 58 | /// Filter for processes 59 | /// 60 | /// - `-A` Select all processes. Identical to `-e`. 61 | pub(crate) fn process_collector( 62 | matches: &ArgMatches, 63 | proc_snapshot: &[Rc>], 64 | ) -> Vec>> { 65 | let mut result = Vec::new(); 66 | 67 | // flag `-A` 68 | if matches.get_flag("A") { 69 | result.extend(proc_snapshot.iter().map(Rc::clone)); 70 | } 71 | 72 | result 73 | } 74 | 75 | /// Filter for session 76 | /// 77 | /// - `-d` Select all processes except session leaders. 78 | /// - `-a` Select all processes except both session leaders (see getsid(2)) and processes not associated with a terminal. 79 | pub(crate) fn session_collector( 80 | matches: &ArgMatches, 81 | proc_snapshot: &[Rc>], 82 | ) -> Vec>> { 83 | let mut result = Vec::new(); 84 | 85 | let tty = |proc: &Rc>| proc.borrow_mut().tty(); 86 | 87 | // flag `-d` 88 | // TODO: Implementation this collection, guessing it pid=sid 89 | if matches.get_flag("d") { 90 | proc_snapshot.iter().for_each(|_| {}); 91 | } 92 | 93 | // flag `-a` 94 | // Guessing it pid=sid, and associated terminal. 95 | if matches.get_flag("a") { 96 | for it in proc_snapshot { 97 | let pid = it.borrow().pid; 98 | 99 | if let Some(sid) = getsid(pid as i32) { 100 | // Check is session leader 101 | if sid != (pid as i32) && tty(it) != Teletype::Unknown { 102 | result.push(it.clone()); 103 | } 104 | } 105 | } 106 | } 107 | 108 | result 109 | } 110 | -------------------------------------------------------------------------------- /src/uu/ps/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_ps); 2 | -------------------------------------------------------------------------------- /src/uu/ps/src/mapping.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use crate::parser::OptionalKeyValue; 7 | use std::collections::HashMap; 8 | 9 | pub(crate) fn collect_code_mapping(formats: &[OptionalKeyValue]) -> Vec<(String, String)> { 10 | let mapping = default_mapping(); 11 | 12 | formats 13 | .iter() 14 | .map(|it| { 15 | let key = it.key().to_string(); 16 | match it.value() { 17 | Some(value) => (key, value.clone()), 18 | None => (key.clone(), mapping.get(&key).unwrap().to_string()), 19 | } 20 | }) 21 | .collect() 22 | } 23 | 24 | /// Returns the default codes. 25 | pub(crate) fn default_codes() -> Vec { 26 | ["pid", "tname", "time", "ucmd"].map(Into::into).to_vec() 27 | } 28 | 29 | /// Collect mapping from argument 30 | pub(crate) fn default_mapping() -> HashMap { 31 | let mut mapping = HashMap::new(); 32 | let mut append = |code: &str, header: &str| mapping.insert(code.into(), header.into()); 33 | 34 | // Those mapping generated from manpage 35 | append("%cpu", "%CPU"); 36 | append("%mem", "%MEM"); 37 | append("ag_id", "AGID"); 38 | append("ag_nice", "AGNI"); 39 | append("args", "COMMAND"); 40 | append("blocked", "BLOCKED"); 41 | append("bsdstart", "START"); 42 | append("bsdtime", "TIME"); 43 | append("c", "C"); 44 | append("caught", "CAUGHT"); 45 | append("cgname", "CGNAME"); 46 | append("cgroup", "CGROUP"); 47 | append("cgroupns", "CGROUPNS"); 48 | append("class", "CLS"); 49 | append("cls", "CLS"); 50 | append("cmd", "CMD"); 51 | append("comm", "COMMAND"); 52 | append("command", "COMMAND"); 53 | append("cp", "CP"); 54 | append("cputime", "TIME"); 55 | append("cputimes", "TIME"); 56 | append("cuc", "%CUC"); 57 | append("cuu", "%CUU"); 58 | append("drs", "DRS"); 59 | append("egid", "EGID"); 60 | append("egroup", "EGROUP"); 61 | append("eip", "EIP"); 62 | append("esp", "ESP"); 63 | append("etime", "ELAPSED"); 64 | append("etimes", "ELAPSED"); 65 | append("euid", "EUID"); 66 | append("euser", "EUSER"); 67 | append("exe", "EXE"); 68 | append("f", "F"); 69 | append("fgid", "FGID"); 70 | append("fgroup", "FGROUP"); 71 | append("flag", "F"); 72 | append("flags", "F"); 73 | append("fname", "COMMAND"); 74 | append("fuid", "FUID"); 75 | append("fuser", "FUSER"); 76 | append("gid", "GID"); 77 | append("group", "GROUP"); 78 | append("ignored", "IGNORED"); 79 | append("ipcns", "IPCNS"); 80 | append("label", "LABEL"); 81 | append("lstart", "STARTED"); 82 | append("lsession", "SESSION"); 83 | append("luid", "LUID"); 84 | append("lwp", "LWP"); 85 | append("lxc", "LXC"); 86 | append("machine", "MACHINE"); 87 | append("maj_flt", "MAJFLT"); 88 | append("min_flt", "MINFLT"); 89 | append("mntns", "MNTNS"); 90 | append("netns", "NETNS"); 91 | append("ni", "NI"); 92 | append("nice", "NI"); 93 | append("nlwp", "NLWP"); 94 | append("numa", "NUMA"); 95 | append("nwchan", "WCHAN"); 96 | append("oom", "OOM"); 97 | append("oomadj", "OOMADJ"); 98 | append("ouid", "OWNER"); 99 | append("pcpu", "%CPU"); 100 | append("pending", "PENDING"); 101 | append("pgid", "PGID"); 102 | append("pgrp", "PGRP"); 103 | append("pid", "PID"); 104 | append("pidns", "PIDNS"); 105 | append("pmem", "%MEM"); 106 | append("policy", "POL"); 107 | append("ppid", "PPID"); 108 | append("pri", "PRI"); 109 | append("psr", "PSR"); 110 | append("pss", "PSS"); 111 | append("rbytes", "RBYTES"); 112 | append("rchars", "RCHARS"); 113 | append("rgid", "RGID"); 114 | append("rgroup", "RGROUP"); 115 | append("rops", "ROPS"); 116 | append("rss", "RSS"); 117 | append("rssize", "RSS"); 118 | append("rsz", "RSZ"); 119 | append("rtprio", "RTPRIO"); 120 | append("ruid", "RUID"); 121 | append("ruser", "RUSER"); 122 | append("s", "S"); 123 | append("sched", "SCH"); 124 | append("seat", "SEAT"); 125 | append("sess", "SESS"); 126 | append("sgi_p", "P"); 127 | append("sgid", "SGID"); 128 | append("sgroup", "SGROUP"); 129 | append("sid", "SID"); 130 | append("sig", "PENDING"); 131 | append("sigcatch", "CAUGHT"); 132 | append("sigignore", "IGNORED"); 133 | append("sigmask", "BLOCKED"); 134 | append("size", "SIZE"); 135 | append("slice", "SLICE"); 136 | append("spid", "SPID"); 137 | append("stackp", "STACKP"); 138 | append("start", "STARTED"); 139 | append("start_time", "START"); 140 | append("stat", "STAT"); 141 | append("state", "S"); 142 | append("stime", "STIME"); 143 | append("suid", "SUID"); 144 | append("supgid", "SUPGID"); 145 | append("supgrp", "SUPGRP"); 146 | append("suser", "SUSER"); 147 | append("svgid", "SVGID"); 148 | append("svuid", "SVUID"); 149 | append("sz", "SZ"); 150 | append("tgid", "TGID"); 151 | append("thcount", "THCNT"); 152 | append("tid", "TID"); 153 | append("time", "TIME"); 154 | append("timens", "TIMENS"); 155 | append("times", "TIME"); 156 | append("tname", "TTY"); 157 | append("tpgid", "TPGID"); 158 | append("trs", "TRS"); 159 | append("tt", "TT"); 160 | append("tty", "TT"); 161 | append("ucmd", "CMD"); 162 | append("ucomm", "COMMAND"); 163 | append("uid", "UID"); 164 | append("uname", "USER"); 165 | append("unit", "UNIT"); 166 | append("user", "USER"); 167 | append("userns", "USERNS"); 168 | append("uss", "USS"); 169 | append("utsns", "UTSNS"); 170 | append("uunit", "UUNIT"); 171 | append("vsize", "VSZ"); 172 | append("vsz", "VSZ"); 173 | append("wbytes", "WBYTES"); 174 | append("wcbytes", "WCBYTES"); 175 | append("wchan", "WCHAN"); 176 | append("wchars", "WCHARS"); 177 | append("wops", "WOPS"); 178 | 179 | mapping 180 | } 181 | -------------------------------------------------------------------------------- /src/uu/ps/src/parser.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use std::convert::Infallible; 7 | 8 | /// Parsing _**optional**_ key-value arguments 9 | /// 10 | /// There are two formats 11 | /// 12 | /// - `cmd` -> key: `cmd`, value: None 13 | /// - `cmd=CMD` -> key: `cmd`, value: `CMD` 14 | /// 15 | /// Other formats can also be parsed: 16 | /// 17 | /// - `cmd=` -> key: `cmd`, value: (empty, no space there) 18 | /// - `cmd=abcd123~~~~` -> key: `cmd`, value: `abcd123~~~~` 19 | /// - `cmd======?` -> key: `cmd`, value: `=====?` 20 | #[derive(Debug, Clone)] 21 | pub struct OptionalKeyValue { 22 | key: String, 23 | value: Option, 24 | } 25 | 26 | impl OptionalKeyValue { 27 | pub fn new(value: T) -> Self 28 | where 29 | T: Into, 30 | { 31 | let value: String = value.into(); 32 | 33 | if let Some((key, value)) = value.split_once('=') { 34 | Self { 35 | key: key.into(), 36 | value: Some(value.into()), 37 | } 38 | } else { 39 | Self { 40 | key: value, 41 | value: None, 42 | } 43 | } 44 | } 45 | 46 | pub fn key(&self) -> &str { 47 | &self.key 48 | } 49 | 50 | pub fn value(&self) -> &Option { 51 | &self.value 52 | } 53 | } 54 | 55 | // clap value parser wrapper 56 | pub(crate) fn parser(value: &str) -> Result { 57 | Ok(OptionalKeyValue::new(value)) 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | 64 | #[inline(always)] 65 | fn new(value: T) -> OptionalKeyValue 66 | where 67 | T: Into, 68 | { 69 | OptionalKeyValue::new(value) 70 | } 71 | 72 | #[test] 73 | fn test_get_key() { 74 | assert_eq!(new("value").key(), "value"); 75 | assert_eq!(new("value=").key(), "value"); 76 | assert_eq!(new("value=?").key(), "value"); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/uu/ps/src/picker.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use std::cell::RefCell; 7 | 8 | use uu_pgrep::process::{ProcessInformation, Teletype}; 9 | 10 | pub(crate) fn collect_pickers( 11 | code_order: &[String], 12 | ) -> Vec) -> String>> { 13 | let mut pickers = Vec::new(); 14 | 15 | for code in code_order { 16 | match code.as_str() { 17 | "pid" | "tgid" => pickers.push(helper(pid)), 18 | "tname" | "tt" | "tty" => pickers.push(helper(tty)), 19 | "time" | "cputime" => pickers.push(helper(time)), 20 | "ucmd" => pickers.push(helper(ucmd)), 21 | "cmd" => pickers.push(helper(cmd)), 22 | _ => {} 23 | } 24 | } 25 | 26 | pickers 27 | } 28 | 29 | #[inline] 30 | fn helper( 31 | f: impl Fn(RefCell) -> String + 'static, 32 | ) -> Box) -> String> { 33 | Box::new(f) 34 | } 35 | 36 | fn pid(proc_info: RefCell) -> String { 37 | format!("{}", proc_info.borrow().pid) 38 | } 39 | 40 | fn tty(proc_info: RefCell) -> String { 41 | match proc_info.borrow().tty() { 42 | Teletype::Tty(tty) => format!("tty{}", tty), 43 | Teletype::TtyS(ttys) => format!("ttyS{}", ttys), 44 | Teletype::Pts(pts) => format!("pts/{}", pts), 45 | Teletype::Unknown => "?".to_owned(), 46 | } 47 | } 48 | 49 | fn time(proc_info: RefCell) -> String { 50 | // https://docs.kernel.org/filesystems/proc.html#id10 51 | // Index of 13 14 52 | 53 | let cumulative_cpu_time = { 54 | let utime = proc_info.borrow_mut().stat()[13].parse::().unwrap(); 55 | let stime = proc_info.borrow_mut().stat()[14].parse::().unwrap(); 56 | (utime + stime) / 100 57 | }; 58 | 59 | format_time(cumulative_cpu_time) 60 | } 61 | 62 | fn format_time(seconds: i64) -> String { 63 | let day = seconds / (3600 * 24); 64 | let hour = (seconds % (3600 * 24)) / 3600; 65 | let minute = (seconds % 3600) / 60; 66 | let second = seconds % 60; 67 | 68 | if day != 0 { 69 | format!("{:02}-{:02}:{:02}:{:02}", day, hour, minute, second) 70 | } else { 71 | format!("{:02}:{:02}:{:02}", hour, minute, second) 72 | } 73 | } 74 | 75 | fn cmd(proc_info: RefCell) -> String { 76 | proc_info.borrow().cmdline.clone() 77 | } 78 | 79 | fn ucmd(proc_info: RefCell) -> String { 80 | proc_info.borrow_mut().status().get("Name").unwrap().into() 81 | } 82 | 83 | #[test] 84 | fn test_time() { 85 | let formatted = { 86 | let time = { 87 | let utime = 29i64; 88 | let stime = 18439i64; 89 | (utime + stime) / 100 90 | }; 91 | format_time(time) 92 | }; 93 | assert_eq!(formatted, "00:03:04"); 94 | 95 | let formatted = { 96 | let time = { 97 | let utime = 12345678i64; 98 | let stime = 90i64; 99 | (utime + stime) / 100 100 | }; 101 | format_time(time) 102 | }; 103 | assert_eq!(formatted, "01-10:17:37"); 104 | } 105 | -------------------------------------------------------------------------------- /src/uu/ps/src/ps.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | mod collector; 7 | mod mapping; 8 | mod parser; 9 | mod picker; 10 | mod sorting; 11 | 12 | use clap::crate_version; 13 | use clap::{Arg, ArgAction, ArgMatches, Command}; 14 | use mapping::{collect_code_mapping, default_codes, default_mapping}; 15 | use parser::{parser, OptionalKeyValue}; 16 | use prettytable::{format::consts::FORMAT_CLEAN, Row, Table}; 17 | use std::{cell::RefCell, rc::Rc}; 18 | use uu_pgrep::process::walk_process; 19 | use uucore::{ 20 | error::{UError, UResult, USimpleError}, 21 | format_usage, help_about, help_usage, 22 | }; 23 | 24 | const ABOUT: &str = help_about!("ps.md"); 25 | const USAGE: &str = help_usage!("ps.md"); 26 | 27 | #[uucore::main] 28 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 29 | let matches = uu_app().try_get_matches_from(args)?; 30 | 31 | let snapshot = walk_process() 32 | .map(|it| Rc::new(RefCell::new(it))) 33 | .collect::>(); 34 | let mut proc_infos = Vec::new(); 35 | 36 | proc_infos.extend(collector::basic_collector(&snapshot)); 37 | proc_infos.extend(collector::process_collector(&matches, &snapshot)); 38 | proc_infos.extend(collector::session_collector(&matches, &snapshot)); 39 | 40 | proc_infos.dedup_by(|a, b| a.borrow().pid == b.borrow().pid); 41 | 42 | sorting::sort(&mut proc_infos, &matches); 43 | 44 | let arg_formats = collect_format(&matches); 45 | let Ok(arg_formats) = arg_formats else { 46 | return Err(arg_formats.err().unwrap()); 47 | }; 48 | 49 | // Collect codes with order 50 | let codes = if arg_formats.is_empty() { 51 | default_codes() 52 | } else { 53 | arg_formats.iter().map(|it| it.key().to_owned()).collect() 54 | }; 55 | 56 | // Collect pickers ordered by codes 57 | let pickers = picker::collect_pickers(&codes); 58 | 59 | // Constructing table 60 | let mut rows = Vec::new(); 61 | for proc in proc_infos { 62 | let picked = pickers 63 | .iter() 64 | .map(|picker| picker(Rc::unwrap_or_clone(proc.clone()))); 65 | rows.push(Row::from_iter(picked)); 66 | } 67 | 68 | // Apply header mapping 69 | let code_mapping = if arg_formats.is_empty() { 70 | let default_mapping = default_mapping(); 71 | default_codes(); 72 | codes 73 | .into_iter() 74 | .map(|code| (code.clone(), default_mapping[&code].to_string())) 75 | .collect::>() 76 | } else { 77 | collect_code_mapping(&arg_formats) 78 | }; 79 | 80 | let header = code_mapping 81 | .iter() 82 | .map(|(_, header)| header) 83 | .map(Into::into) 84 | .collect::>(); 85 | 86 | // Apply header 87 | let mut table = Table::from_iter([Row::from_iter(header)]); 88 | table.set_format(*FORMAT_CLEAN); 89 | table.extend(rows); 90 | 91 | print!("{}", table); 92 | 93 | Ok(()) 94 | } 95 | 96 | fn collect_format( 97 | matches: &ArgMatches, 98 | ) -> Result, Box> { 99 | let arg_format = matches.get_many::("format"); 100 | 101 | let collect = arg_format.unwrap_or_default().cloned().collect::>(); 102 | 103 | let default_mapping = default_mapping(); 104 | 105 | // Validate key is exist 106 | for key in collect.iter().map(OptionalKeyValue::key) { 107 | if !default_mapping.contains_key(key) { 108 | return Err(USimpleError::new( 109 | 1, 110 | format!("error: unknown user-defined format specifier \"{key}\""), 111 | )); 112 | } 113 | } 114 | 115 | Ok(collect) 116 | } 117 | 118 | #[allow(clippy::cognitive_complexity)] 119 | pub fn uu_app() -> Command { 120 | Command::new(uucore::util_name()) 121 | .version(crate_version!()) 122 | .about(ABOUT) 123 | .override_usage(format_usage(USAGE)) 124 | .infer_long_args(true) 125 | .disable_help_flag(true) 126 | .arg(Arg::new("help").long("help").action(ArgAction::Help)) 127 | .args([ 128 | Arg::new("A") 129 | .short('A') 130 | .help("all processes") 131 | .visible_short_alias('e') 132 | .action(ArgAction::SetTrue), 133 | Arg::new("a") 134 | .short('a') 135 | .help("all with tty, except session leaders") 136 | .action(ArgAction::SetTrue), 137 | // Arg::new("a_") 138 | // .short('a') 139 | // .help("all with tty, including other users") 140 | // .action(ArgAction::SetTrue) 141 | // .allow_hyphen_values(true), 142 | Arg::new("d") 143 | .short('d') 144 | .help("all except session leaders") 145 | .action(ArgAction::SetTrue), 146 | Arg::new("deselect") 147 | .long("deselect") 148 | .short('N') 149 | .help("negate selection") 150 | .action(ArgAction::SetTrue), 151 | // Arg::new("r") 152 | // .short('r') 153 | // .action(ArgAction::SetTrue) 154 | // .help("only running processes") 155 | // .allow_hyphen_values(true), 156 | // Arg::new("T") 157 | // .short('T') 158 | // .action(ArgAction::SetTrue) 159 | // .help("all processes on this terminal") 160 | // .allow_hyphen_values(true), 161 | // Arg::new("x") 162 | // .short('x') 163 | // .action(ArgAction::SetTrue) 164 | // .help("processes without controlling ttys") 165 | // .allow_hyphen_values(true), 166 | ]) 167 | .arg( 168 | Arg::new("format") 169 | .short('o') 170 | .long("format") 171 | .action(ArgAction::Append) 172 | .value_delimiter(',') 173 | .value_parser(parser) 174 | .help("user-defined format"), 175 | ) 176 | // .args([ 177 | // Arg::new("command").short('c').help("command name"), 178 | // Arg::new("GID") 179 | // .short('G') 180 | // .long("Group") 181 | // .help("real group id or name"), 182 | // Arg::new("group") 183 | // .short('g') 184 | // .long("group") 185 | // .help("session or effective group name"), 186 | // Arg::new("PID").short('p').long("pid").help("process id"), 187 | // Arg::new("pPID").long("ppid").help("parent process id"), 188 | // Arg::new("qPID") 189 | // .short('q') 190 | // .long("quick-pid") 191 | // .help("process id"), 192 | // Arg::new("session") 193 | // .short('s') 194 | // .long("sid") 195 | // .help("session id"), 196 | // Arg::new("t").short('t').long("tty").help("terminal"), 197 | // Arg::new("eUID") 198 | // .short('u') 199 | // .long("user") 200 | // .help("effective user id or name"), 201 | // Arg::new("rUID") 202 | // .short('U') 203 | // .long("User") 204 | // .help("real user id or name"), 205 | // ]) 206 | } 207 | -------------------------------------------------------------------------------- /src/uu/ps/src/sorting.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use clap::ArgMatches; 7 | use std::{cell::RefCell, rc::Rc}; 8 | use uu_pgrep::process::ProcessInformation; 9 | 10 | // TODO: Implementing sorting flags. 11 | pub(crate) fn sort(input: &mut [Rc>], _matches: &ArgMatches) { 12 | sort_by_pid(input); 13 | } 14 | 15 | /// Sort by pid. (Default) 16 | fn sort_by_pid(input: &mut [Rc>]) { 17 | input.sort_by(|a, b| a.borrow().pid.cmp(&b.borrow().pid)); 18 | } 19 | -------------------------------------------------------------------------------- /src/uu/pwdx/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_pwdx" 3 | description = "pwdx ~ (uutils) Report current working directory of a process" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/pwdx" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true } 15 | clap = { workspace = true } 16 | sysinfo = { workspace = true } 17 | 18 | [lib] 19 | path = "src/pwdx.rs" 20 | 21 | [[bin]] 22 | name = "pwdx" 23 | path = "src/main.rs" 24 | -------------------------------------------------------------------------------- /src/uu/pwdx/pwdx.md: -------------------------------------------------------------------------------- 1 | # pwdx 2 | 3 | ``` 4 | pwdx [options] pid [...] 5 | ``` 6 | 7 | Report current working directory of a process -------------------------------------------------------------------------------- /src/uu/pwdx/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_pwdx); 2 | -------------------------------------------------------------------------------- /src/uu/pwdx/src/pwdx.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use clap::{crate_version, Arg, Command}; 7 | use std::env; 8 | use sysinfo::{Pid, System}; 9 | use uucore::error::{set_exit_code, UResult, USimpleError}; 10 | use uucore::{format_usage, help_about, help_usage}; 11 | 12 | const ABOUT: &str = help_about!("pwdx.md"); 13 | const USAGE: &str = help_usage!("pwdx.md"); 14 | 15 | #[uucore::main] 16 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 17 | let matches = uu_app().try_get_matches_from(args)?; 18 | 19 | let pids = matches.get_many::("pid").unwrap(); 20 | let sys = System::new_all(); 21 | 22 | for pid_str in pids { 23 | let pid = match pid_str.parse::() { 24 | // PIDs start at 1, hence 0 is invalid 25 | Ok(0) | Err(_) => { 26 | return Err(USimpleError::new( 27 | 1, 28 | format!("invalid process id: {pid_str}"), 29 | )) 30 | } 31 | Ok(pid) => pid, 32 | }; 33 | 34 | match sys.process(Pid::from(pid)) { 35 | Some(process) => match process.cwd() { 36 | Some(cwd) => println!("{pid}: {}", cwd.display()), 37 | None => { 38 | set_exit_code(1); 39 | eprintln!("{pid}: Permission denied"); 40 | } 41 | }, 42 | None => { 43 | set_exit_code(1); 44 | eprintln!("{pid}: No such process"); 45 | } 46 | } 47 | } 48 | 49 | Ok(()) 50 | } 51 | 52 | pub fn uu_app() -> Command { 53 | Command::new(uucore::util_name()) 54 | .version(crate_version!()) 55 | .about(ABOUT) 56 | .override_usage(format_usage(USAGE)) 57 | .infer_long_args(true) 58 | .arg( 59 | Arg::new("pid") 60 | .value_name("PID") 61 | .help("Process ID") 62 | .required(true) 63 | .num_args(1..) 64 | .index(1), 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /src/uu/slabtop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_slabtop" 3 | description = "slabtop ~ (uutils) Display kernel slab cache information in real time" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/slabtop" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true } 15 | clap = { workspace = true } 16 | 17 | [lib] 18 | path = "src/slabtop.rs" 19 | 20 | [[bin]] 21 | name = "slabtop" 22 | path = "src/main.rs" 23 | -------------------------------------------------------------------------------- /src/uu/slabtop/slabtop.md: -------------------------------------------------------------------------------- 1 | # slabtop 2 | 3 | ``` 4 | slabtop [options] 5 | ``` 6 | 7 | Display kernel slab cache information in real time 8 | 9 | ## After Help 10 | 11 | The following are valid sort criteria: 12 | 13 | * `a` sort by number of active objects 14 | * `b` sort by objects per slab 15 | * `c` sort by cache size 16 | * `l` sort by number of slabs 17 | * `v` sort by (non display) number of active slabs 18 | * `n` sort by name 19 | * `o` sort by number of objects (the default) 20 | * `p` sort by (non display) pages per slab 21 | * `s` sort by object size 22 | * `u` sort by cache utilization 23 | -------------------------------------------------------------------------------- /src/uu/slabtop/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_slabtop); 2 | -------------------------------------------------------------------------------- /src/uu/slabtop/src/slabtop.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use crate::parse::SlabInfo; 7 | use clap::{arg, crate_version, ArgAction, Command}; 8 | use uucore::{error::UResult, format_usage, help_about, help_section, help_usage}; 9 | 10 | const ABOUT: &str = help_about!("slabtop.md"); 11 | const AFTER_HELP: &str = help_section!("after help", "slabtop.md"); 12 | const USAGE: &str = help_usage!("slabtop.md"); 13 | 14 | mod parse; 15 | 16 | #[uucore::main] 17 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 18 | let matches = uu_app().try_get_matches_from(args)?; 19 | 20 | let sort_flag = matches 21 | .try_get_one::("sort") 22 | .ok() 23 | .unwrap_or(Some(&'o')) 24 | .unwrap_or(&'o'); 25 | 26 | let slabinfo = SlabInfo::new()?.sort(*sort_flag, false); 27 | 28 | if matches.get_flag("once") { 29 | output_header(&slabinfo); 30 | println!(); 31 | output_list(&slabinfo); 32 | } else { 33 | // TODO: implement TUI 34 | output_header(&slabinfo); 35 | println!(); 36 | output_list(&slabinfo); 37 | } 38 | 39 | Ok(()) 40 | } 41 | 42 | fn to_kb(byte: u64) -> f64 { 43 | byte as f64 / 1024.0 44 | } 45 | 46 | fn percentage(numerator: u64, denominator: u64) -> f64 { 47 | if denominator == 0 { 48 | return 0.0; 49 | } 50 | 51 | let numerator = numerator as f64; 52 | let denominator = denominator as f64; 53 | 54 | (numerator / denominator) * 100.0 55 | } 56 | 57 | fn output_header(slabinfo: &SlabInfo) { 58 | println!( 59 | r" Active / Total Objects (% used) : {} / {} ({:.1}%)", 60 | slabinfo.total_active_objs(), 61 | slabinfo.total_objs(), 62 | percentage(slabinfo.total_active_objs(), slabinfo.total_objs()) 63 | ); 64 | 65 | println!( 66 | r" Active / Total Slabs (% used) : {} / {} ({:.1}%)", 67 | slabinfo.total_active_slabs(), 68 | slabinfo.total_slabs(), 69 | percentage(slabinfo.total_active_slabs(), slabinfo.total_slabs(),) 70 | ); 71 | 72 | // TODO: I don't know the 'cache' meaning. 73 | println!( 74 | r" Active / Total Caches (% used) : {} / {} ({:.1}%)", 75 | slabinfo.total_active_cache(), 76 | slabinfo.total_cache(), 77 | percentage(slabinfo.total_active_cache(), slabinfo.total_cache()) 78 | ); 79 | 80 | println!( 81 | r" Active / Total Size (% used) : {:.2}K / {:.2}K ({:.1}%)", 82 | to_kb(slabinfo.total_active_size()), 83 | to_kb(slabinfo.total_size()), 84 | percentage(slabinfo.total_active_size(), slabinfo.total_size()) 85 | ); 86 | 87 | println!( 88 | r" Minimum / Average / Maximum Object : {:.2}K / {:.2}K / {:.2}K", 89 | to_kb(slabinfo.object_minimum()), 90 | to_kb(slabinfo.object_avg()), 91 | to_kb(slabinfo.object_maximum()) 92 | ); 93 | } 94 | 95 | fn output_list(info: &SlabInfo) { 96 | let title = format!( 97 | "{:>6} {:>6} {:>4} {:>8} {:>6} {:>8} {:>10} {:<}", 98 | "OBJS", "ACTIVE", "USE", "OBJ SIZE", "SLABS", "OBJ/SLAB", "CACHE SIZE", "NAME" 99 | ); 100 | println!("{}", title); 101 | 102 | for name in info.names() { 103 | let objs = info.fetch(name, "num_objs").unwrap_or_default(); 104 | let active = info.fetch(name, "active_objs").unwrap_or_default(); 105 | let used = format!("{:.0}%", percentage(active, objs)); 106 | let objsize = { 107 | let size = info.fetch(name, "objsize").unwrap_or_default(); // Byte to KB :1024 108 | size as f64 / 1024.0 109 | }; 110 | let slabs = info.fetch(name, "num_slabs").unwrap_or_default(); 111 | let obj_per_slab = info.fetch(name, "objperslab").unwrap_or_default(); 112 | 113 | let cache_size = (objsize * (objs as f64)) as u64; 114 | let objsize = format!("{:.2}", objsize); 115 | 116 | let content = format!( 117 | "{:>6} {:>6} {:>4} {:>7}K {:>6} {:>8} {:>10} {:<}", 118 | objs, active, used, objsize, slabs, obj_per_slab, cache_size, name 119 | ); 120 | 121 | println!("{}", content); 122 | } 123 | } 124 | 125 | #[allow(clippy::cognitive_complexity)] 126 | pub fn uu_app() -> Command { 127 | Command::new(uucore::util_name()) 128 | .version(crate_version!()) 129 | .about(ABOUT) 130 | .override_usage(format_usage(USAGE)) 131 | .infer_long_args(true) 132 | .args([ 133 | // arg!(-d --delay "delay updates"), 134 | arg!(-o --once "only display once, then exit").action(ArgAction::SetTrue), 135 | arg!(-s --sort "specify sort criteria by character (see below)"), 136 | ]) 137 | .after_help(AFTER_HELP) 138 | } 139 | -------------------------------------------------------------------------------- /src/uu/snice/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_snice" 3 | description = "snice - (uutils) send a signal or report process status" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/snice" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true, features = ["signals"] } 15 | clap = { workspace = true } 16 | libc = { workspace = true } 17 | nix = { workspace = true } 18 | prettytable-rs = { workspace = true } 19 | thiserror = { workspace = true } 20 | sysinfo = { workspace = true } 21 | 22 | uu_pgrep = { path = "../pgrep" } 23 | 24 | [lib] 25 | path = "src/snice.rs" 26 | 27 | [[bin]] 28 | name = "snice" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /src/uu/snice/snice.md: -------------------------------------------------------------------------------- 1 | # snice 2 | 3 | ``` 4 | snice [priority] [options] expression 5 | ``` 6 | 7 | Send a signal or report process status 8 | -------------------------------------------------------------------------------- /src/uu/snice/src/action.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use crate::priority::Priority; 7 | use std::{ 8 | fmt::{self, Display, Formatter}, 9 | sync::OnceLock, 10 | }; 11 | use sysinfo::{System, Users}; 12 | use uu_pgrep::process::Teletype; 13 | 14 | pub(crate) fn process_snapshot() -> &'static sysinfo::System { 15 | static SNAPSHOT: OnceLock = OnceLock::new(); 16 | 17 | SNAPSHOT.get_or_init(System::new_all) 18 | } 19 | 20 | pub(crate) fn users() -> &'static Users { 21 | static SNAPSHOT: OnceLock = OnceLock::new(); 22 | 23 | SNAPSHOT.get_or_init(Users::new_with_refreshed_list) 24 | } 25 | 26 | #[derive(Debug)] 27 | pub(crate) enum SelectedTarget { 28 | Command(String), 29 | Pid(u32), 30 | Tty(Teletype), 31 | User(String), 32 | } 33 | 34 | #[allow(unused)] 35 | impl SelectedTarget { 36 | pub(crate) fn to_pids(&self) -> Vec { 37 | match self { 38 | Self::Command(cmd) => Self::from_cmd(cmd), 39 | Self::Pid(pid) => vec![*pid], 40 | Self::Tty(tty) => Self::from_tty(tty), 41 | Self::User(user) => Self::from_user(user), 42 | } 43 | } 44 | 45 | fn from_cmd(cmd: &str) -> Vec { 46 | process_snapshot() 47 | .processes_by_name(cmd.as_ref()) 48 | .map(|it| it.pid().as_u32()) 49 | .collect() 50 | } 51 | 52 | #[cfg(target_os = "linux")] 53 | fn from_tty(tty: &Teletype) -> Vec { 54 | use std::{path::PathBuf, str::FromStr}; 55 | use uu_pgrep::process::ProcessInformation; 56 | 57 | process_snapshot() 58 | .processes() 59 | .iter() 60 | .filter(|(pid, _)| { 61 | let pid = pid.as_u32(); 62 | let path = PathBuf::from_str(&format!("/proc/{}/", pid)).unwrap(); 63 | 64 | ProcessInformation::try_new(path).unwrap().tty() == *tty 65 | }) 66 | .map(|(pid, _)| pid.as_u32()) 67 | .collect() 68 | } 69 | 70 | // TODO: issues:#179 https://github.com/uutils/procps/issues/179 71 | #[cfg(not(target_os = "linux"))] 72 | fn from_tty(_tty: &Teletype) -> Vec { 73 | Vec::new() 74 | } 75 | 76 | fn from_user(user: &str) -> Vec { 77 | let Some(uid) = users().iter().find(|it| it.name() == user) else { 78 | return Vec::new(); 79 | }; 80 | let uid = uid.id(); 81 | 82 | process_snapshot() 83 | .processes() 84 | .iter() 85 | .filter(|(_, process)| match process.user_id() { 86 | Some(p_uid) => p_uid == uid, 87 | None => false, 88 | }) 89 | .map(|(pid, _)| pid.as_u32()) 90 | .collect() 91 | } 92 | } 93 | 94 | #[allow(unused)] 95 | #[derive(Debug, Clone)] 96 | pub(crate) enum ActionResult { 97 | PermissionDenied, 98 | Success, 99 | } 100 | 101 | impl Display for ActionResult { 102 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 103 | match self { 104 | Self::PermissionDenied => write!(f, "Permission Denied"), 105 | Self::Success => write!(f, "Success"), 106 | } 107 | } 108 | } 109 | 110 | /// Set priority of process. 111 | /// 112 | /// But we don't know if the process of pid are exist, if [None], the process doesn't exist 113 | #[cfg(target_os = "linux")] 114 | fn set_priority(pid: u32, prio: &Priority) -> Option { 115 | use libc::{getpriority, setpriority, PRIO_PROCESS}; 116 | use nix::errno::Errno; 117 | 118 | // Very dirty. 119 | let current_priority = { 120 | // Clear errno 121 | Errno::clear(); 122 | 123 | let prio = unsafe { getpriority(PRIO_PROCESS, pid) }; 124 | // prio == -1 might be error. 125 | if prio == -1 && Errno::last() != Errno::UnknownErrno { 126 | // Must clear errno. 127 | Errno::clear(); 128 | 129 | // I don't know but, just considering it just caused by permission. 130 | // https://manpages.debian.org/bookworm/manpages-dev/getpriority.2.en.html#ERRORS 131 | return match Errno::last() { 132 | Errno::ESRCH => Some(ActionResult::PermissionDenied), 133 | _ => None, 134 | }; 135 | } 136 | prio 137 | }; 138 | 139 | let prio = match prio { 140 | Priority::Increase(prio) => current_priority + *prio as i32, 141 | Priority::Decrease(prio) => current_priority - *prio as i32, 142 | Priority::To(prio) => *prio as i32, 143 | }; 144 | 145 | // result only 0, -1 146 | Errno::clear(); 147 | let result = unsafe { setpriority(PRIO_PROCESS, pid, prio) }; 148 | 149 | // https://manpages.debian.org/bookworm/manpages-dev/setpriority.2.en.html#ERRORS 150 | if result == -1 { 151 | match Errno::last() { 152 | Errno::ESRCH => Some(ActionResult::PermissionDenied), 153 | _ => None, 154 | } 155 | } else { 156 | Some(ActionResult::Success) 157 | } 158 | } 159 | 160 | // TODO: Implemented this on other platform 161 | #[cfg(not(target_os = "linux"))] 162 | fn set_priority(_pid: u32, _prio: &Priority) -> Option { 163 | None 164 | } 165 | 166 | pub(crate) fn perform_action(pids: &[u32], prio: &Priority) -> Vec> { 167 | let f = |pid: &u32| set_priority(*pid, prio); 168 | pids.iter().map(f).collect() 169 | } 170 | -------------------------------------------------------------------------------- /src/uu/snice/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_snice); 2 | -------------------------------------------------------------------------------- /src/uu/snice/src/priority.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use std::fmt::Display; 7 | use thiserror::Error; 8 | 9 | #[derive(Debug, Error, PartialEq, Eq)] 10 | pub enum Error { 11 | #[error("failed to parse argument: '{0}'")] 12 | ParsingFailed(String), 13 | } 14 | 15 | #[derive(Debug, PartialEq, Eq)] 16 | pub(crate) enum Priority { 17 | // The default priority is +4. (snice +4 ...) 18 | Increase(u32), 19 | Decrease(u32), 20 | To(u32), 21 | } 22 | 23 | impl TryFrom for Priority { 24 | type Error = Error; 25 | 26 | fn try_from(value: String) -> Result { 27 | Self::try_from(value.as_str()) 28 | } 29 | } 30 | 31 | impl TryFrom<&str> for Priority { 32 | type Error = Error; 33 | 34 | fn try_from(value: &str) -> Result { 35 | if let Some(stripped_value) = value.strip_prefix("-") { 36 | stripped_value 37 | .parse::() 38 | .map_err(|_| Error::ParsingFailed(value.into())) 39 | .map(Priority::Decrease) 40 | } else if let Some(stripped_value) = value.strip_prefix("+") { 41 | stripped_value 42 | .parse::() 43 | .map_err(|_| Error::ParsingFailed(value.into())) 44 | .map(Priority::Increase) 45 | } else { 46 | value 47 | .parse::() 48 | .map_err(|_| Error::ParsingFailed(value.into())) 49 | .map(Priority::To) 50 | } 51 | } 52 | } 53 | 54 | impl Default for Priority { 55 | fn default() -> Self { 56 | Priority::Increase(4) 57 | } 58 | } 59 | 60 | impl Display for Priority { 61 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 62 | match self { 63 | Self::Increase(prio) => write!(f, "+{prio}"), 64 | Self::Decrease(prio) => write!(f, "-{prio}"), 65 | Self::To(prio) => write!(f, "{prio}"), 66 | } 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | 74 | #[test] 75 | fn test_try_from() { 76 | assert!(Priority::try_from("4").is_ok()); 77 | assert!(Priority::try_from(String::from("4")).is_ok()); 78 | 79 | assert_eq!(Priority::try_from("-4"), Ok(Priority::Decrease(4))); 80 | assert_eq!(Priority::try_from("+4"), Ok(Priority::Increase(4))); 81 | assert_eq!(Priority::try_from("4"), Ok(Priority::To(4))); 82 | 83 | assert_eq!( 84 | Priority::try_from("-4-"), 85 | Err(Error::ParsingFailed("-4-".into())) 86 | ); 87 | assert_eq!( 88 | Priority::try_from("+4+"), 89 | Err(Error::ParsingFailed("+4+".into())) 90 | ); 91 | } 92 | 93 | #[test] 94 | fn test_to_string() { 95 | assert_eq!(Priority::Decrease(4).to_string(), "-4"); 96 | assert_eq!(Priority::Increase(4).to_string(), "+4"); 97 | assert_eq!(Priority::To(4).to_string(), "4"); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/uu/sysctl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_sysctl" 3 | description = "sysctl ~ (uutils) Show or modify kernel parameters at runtime" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/sysctl" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true } 15 | clap = { workspace = true } 16 | sysinfo = { workspace = true } 17 | walkdir = { workspace = true } 18 | 19 | [lib] 20 | path = "src/sysctl.rs" 21 | 22 | [[bin]] 23 | name = "sysctl" 24 | path = "src/main.rs" 25 | -------------------------------------------------------------------------------- /src/uu/sysctl/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_sysctl); 2 | -------------------------------------------------------------------------------- /src/uu/sysctl/src/sysctl.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use clap::{crate_version, Arg, ArgAction, Command}; 7 | use std::env; 8 | use uucore::error::UResult; 9 | use uucore::{format_usage, help_about, help_usage}; 10 | 11 | const ABOUT: &str = help_about!("sysctl.md"); 12 | const USAGE: &str = help_usage!("sysctl.md"); 13 | 14 | #[cfg(target_os = "linux")] 15 | mod linux { 16 | use std::path::{Path, PathBuf}; 17 | use uucore::error::{FromIo, UIoError}; 18 | use walkdir::WalkDir; 19 | 20 | const PROC_SYS_ROOT: &str = "/proc/sys"; 21 | 22 | pub fn get_all_sysctl_variables() -> Vec { 23 | let mut ret = vec![]; 24 | for entry in WalkDir::new(PROC_SYS_ROOT) { 25 | match entry { 26 | Ok(e) => { 27 | if e.file_type().is_file() { 28 | let var = e 29 | .path() 30 | .strip_prefix(PROC_SYS_ROOT) 31 | .expect("Always should be ancestor of of sysctl root"); 32 | if let Some(s) = var.as_os_str().to_str() { 33 | ret.push(s.to_owned()); 34 | } 35 | } 36 | } 37 | Err(e) => { 38 | uucore::show_error!("{}", e); 39 | } 40 | } 41 | } 42 | ret 43 | } 44 | 45 | pub fn normalize_var(var: &str) -> String { 46 | var.replace('/', ".") 47 | } 48 | 49 | pub fn variable_path(var: &str) -> PathBuf { 50 | Path::new(PROC_SYS_ROOT).join(var.replace('.', "/")) 51 | } 52 | 53 | pub fn get_sysctl(var: &str) -> std::io::Result { 54 | Ok(std::fs::read_to_string(variable_path(var))? 55 | .trim_end() 56 | .to_string()) 57 | } 58 | 59 | pub fn set_sysctl(var: &str, value: &str) -> std::io::Result<()> { 60 | std::fs::write(variable_path(var), value) 61 | } 62 | 63 | pub fn handle_one_arg( 64 | var_or_assignment: &str, 65 | quiet: bool, 66 | ) -> Result, Box> { 67 | let mut split = var_or_assignment.splitn(2, '='); 68 | let var = normalize_var(split.next().expect("Split always returns at least 1 value")); 69 | 70 | if let Some(value_to_set) = split.next() { 71 | set_sysctl(&var, value_to_set) 72 | .map_err(|e| e.map_err_context(|| format!("error writing key '{}'", var)))?; 73 | if quiet { 74 | Ok(None) 75 | } else { 76 | Ok(Some((var, value_to_set.to_string()))) 77 | } 78 | } else { 79 | let value = get_sysctl(&var) 80 | .map_err(|e| e.map_err_context(|| format!("error reading key '{}'", var)))?; 81 | Ok(Some((var, value))) 82 | } 83 | } 84 | } 85 | #[cfg(target_os = "linux")] 86 | use linux::*; 87 | 88 | #[cfg(target_os = "linux")] 89 | #[uucore::main] 90 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 91 | let matches = uu_app().try_get_matches_from(args)?; 92 | 93 | let vars = if matches.get_flag("all") { 94 | get_all_sysctl_variables() 95 | } else if let Some(vars) = matches.get_many::("variables") { 96 | vars.cloned().collect() 97 | } else { 98 | uu_app().print_help()?; 99 | return Ok(()); 100 | }; 101 | 102 | for var_or_assignment in vars { 103 | match handle_one_arg(&var_or_assignment, matches.get_flag("quiet")) { 104 | Ok(None) => (), 105 | Ok(Some((var, value_to_print))) => { 106 | for line in value_to_print.split('\n') { 107 | if matches.get_flag("names") { 108 | println!("{}", var); 109 | } else if matches.get_flag("values") { 110 | println!("{}", line); 111 | } else { 112 | println!("{} = {}", var, line); 113 | } 114 | } 115 | } 116 | Err(e) => { 117 | if !matches.get_flag("ignore") { 118 | uucore::show!(e); 119 | } 120 | } 121 | } 122 | } 123 | 124 | Ok(()) 125 | } 126 | 127 | #[cfg(not(target_os = "linux"))] 128 | #[uucore::main] 129 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 130 | let _matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?; 131 | 132 | Err(uucore::error::USimpleError::new( 133 | 1, 134 | "`sysctl` currently only supports Linux.", 135 | )) 136 | } 137 | 138 | pub fn uu_app() -> Command { 139 | Command::new(uucore::util_name()) 140 | .version(crate_version!()) 141 | .about(ABOUT) 142 | .override_usage(format_usage(USAGE)) 143 | .infer_long_args(true) 144 | .arg( 145 | Arg::new("variables") 146 | .value_name("VARIABLE[=VALUE]") 147 | .action(ArgAction::Append), 148 | ) 149 | .arg( 150 | Arg::new("all") 151 | .short('a') 152 | .visible_short_aliases(['A', 'X']) 153 | .long("all") 154 | .action(ArgAction::SetTrue) 155 | .help("Display all variables"), 156 | ) 157 | .arg( 158 | Arg::new("names") 159 | .short('N') 160 | .long("names") 161 | .action(ArgAction::SetTrue) 162 | .help("Only print names"), 163 | ) 164 | .arg( 165 | Arg::new("values") 166 | .short('n') 167 | .long("values") 168 | .action(ArgAction::SetTrue) 169 | .help("Only print values"), 170 | ) 171 | .arg( 172 | Arg::new("ignore") 173 | .short('e') 174 | .long("ignore") 175 | .action(ArgAction::SetTrue) 176 | .help("Ignore errors"), 177 | ) 178 | .arg( 179 | Arg::new("quiet") 180 | .short('q') 181 | .long("quiet") 182 | .action(ArgAction::SetTrue) 183 | .help("Do not print when setting variables"), 184 | ) 185 | .arg( 186 | Arg::new("noop_o") 187 | .short('o') 188 | .help("Does nothing, for BSD compatibility"), 189 | ) 190 | .arg( 191 | Arg::new("noop_x") 192 | .short('x') 193 | .help("Does nothing, for BSD compatibility"), 194 | ) 195 | } 196 | -------------------------------------------------------------------------------- /src/uu/sysctl/sysctl.md: -------------------------------------------------------------------------------- 1 | # sysctl 2 | 3 | ``` 4 | sysctl [options] [variable[=value]]... 5 | ``` 6 | 7 | Show or modify kernel parameters at runtime. 8 | -------------------------------------------------------------------------------- /src/uu/tload/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_tload" 3 | description = "tload ~ (uutils) graphic representation of system load average" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/tload" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | clap = { workspace = true } 15 | crossterm = { workspace = true } 16 | ratatui = { workspace = true } 17 | uucore = { workspace = true } 18 | 19 | [lib] 20 | path = "src/tload.rs" 21 | 22 | [[bin]] 23 | name = "tload" 24 | path = "src/main.rs" 25 | -------------------------------------------------------------------------------- /src/uu/tload/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_tload); 2 | -------------------------------------------------------------------------------- /src/uu/tload/src/tload.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use std::collections::VecDeque; 7 | use std::sync::{Arc, RwLock}; 8 | use std::thread::{self, sleep}; 9 | use std::time::Duration; 10 | 11 | use clap::{arg, crate_version, value_parser, ArgAction, ArgMatches, Command}; 12 | use crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers}; 13 | use tui::{LegacyTui, ModernTui}; 14 | use uucore::{error::UResult, format_usage, help_about, help_usage}; 15 | 16 | const ABOUT: &str = help_about!("tload.md"); 17 | const USAGE: &str = help_usage!("tload.md"); 18 | 19 | mod tui; 20 | 21 | #[derive(Debug, Default, Clone)] 22 | struct SystemLoadAvg { 23 | pub(crate) last_1: f32, 24 | pub(crate) last_5: f32, 25 | pub(crate) last_10: f32, 26 | } 27 | 28 | impl SystemLoadAvg { 29 | #[cfg(target_os = "linux")] 30 | fn new() -> UResult { 31 | use std::fs; 32 | use uucore::error::USimpleError; 33 | 34 | let result = fs::read_to_string("/proc/loadavg")?; 35 | let split = result.split(" ").collect::>(); 36 | 37 | // Helper function to keep code clean 38 | fn f(s: &str) -> UResult { 39 | s.parse::() 40 | .map_err(|e| USimpleError::new(1, e.to_string())) 41 | } 42 | 43 | Ok(SystemLoadAvg { 44 | last_1: f(split[0])?, 45 | last_5: f(split[1])?, 46 | last_10: f(split[2])?, 47 | }) 48 | } 49 | 50 | #[cfg(not(target_os = "linux"))] 51 | fn new() -> UResult { 52 | Ok(SystemLoadAvg::default()) 53 | } 54 | } 55 | 56 | #[allow(unused)] 57 | #[derive(Debug)] 58 | struct Settings { 59 | delay: u64, 60 | scale: usize, // Not used 61 | 62 | is_modern: bool, // For modern display 63 | } 64 | 65 | impl Settings { 66 | fn new(matches: &ArgMatches) -> Settings { 67 | Settings { 68 | delay: matches.get_one("delay").cloned().unwrap(), 69 | scale: matches.get_one("scale").cloned().unwrap(), 70 | is_modern: matches.get_flag("modern"), 71 | } 72 | } 73 | } 74 | 75 | #[uucore::main] 76 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 77 | let matches = uu_app().try_get_matches_from(args)?; 78 | let settings = Settings::new(&matches); 79 | 80 | let mut terminal = ratatui::init(); 81 | 82 | let data = { 83 | // Why 10240? 84 | // 85 | // Emm, maybe there will be some terminal can display more than 10000 char? 86 | let data = Arc::new(RwLock::new(VecDeque::with_capacity(10240))); 87 | data.write() 88 | .unwrap() 89 | .push_back(SystemLoadAvg::new().unwrap()); 90 | data 91 | }; 92 | let cloned_data = data.clone(); 93 | thread::spawn(move || loop { 94 | sleep(Duration::from_secs(settings.delay)); 95 | 96 | let mut data = cloned_data.write().unwrap(); 97 | if data.iter().len() >= 10240 { 98 | // Keep this VecDeque smaller than 10240 99 | data.pop_front(); 100 | } 101 | data.push_back(SystemLoadAvg::new().unwrap()); 102 | }); 103 | 104 | loop { 105 | // Now only accept `Ctrl+C` for compatibility with the original implementation 106 | // 107 | // Use `event::poll` for non-blocking event reading 108 | if let Ok(true) = event::poll(Duration::from_millis(10)) { 109 | // If event available, break this loop 110 | if let Ok(event::Event::Key(KeyEvent { 111 | code: KeyCode::Char('c'), 112 | modifiers: KeyModifiers::CONTROL, 113 | .. 114 | })) = event::read() 115 | { 116 | // compatibility with the original implementation 117 | uucore::error::set_exit_code(130); 118 | break; 119 | } 120 | } 121 | 122 | terminal.draw(|frame| { 123 | let data = &data.read().unwrap(); 124 | let data = data.iter().cloned().collect::>(); 125 | frame.render_widget( 126 | if settings.is_modern { 127 | ModernTui::new(&data) 128 | } else { 129 | LegacyTui::new(&data) 130 | }, 131 | frame.area(), 132 | ); 133 | })?; 134 | 135 | std::thread::sleep(Duration::from_millis(10)); 136 | } 137 | 138 | ratatui::restore(); 139 | Ok(()) 140 | } 141 | 142 | #[allow(clippy::cognitive_complexity)] 143 | pub fn uu_app() -> Command { 144 | Command::new(uucore::util_name()) 145 | .version(crate_version!()) 146 | .about(ABOUT) 147 | .override_usage(format_usage(USAGE)) 148 | .infer_long_args(true) 149 | .args([ 150 | arg!(-d --delay "update delay in seconds") 151 | .value_parser(value_parser!(u64)) 152 | .default_value("5") 153 | .hide_default_value(true), 154 | arg!(-m --modern "modern look").action(ArgAction::SetTrue), 155 | // TODO: Implement this arg 156 | arg!(-s --scale "vertical scale") 157 | .value_parser(value_parser!(usize)) 158 | .default_value("5") 159 | .hide_default_value(true), 160 | ]) 161 | } 162 | 163 | #[cfg(test)] 164 | mod tests { 165 | use super::*; 166 | 167 | // It's just a test to make sure if can parsing correctly. 168 | #[test] 169 | fn test_system_load_avg() { 170 | let _ = SystemLoadAvg::new().expect("SystemLoadAvg::new"); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/uu/tload/src/tui.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use ratatui::{ 7 | buffer::Buffer, 8 | layout::{Constraint, Direction, Layout, Rect}, 9 | style::{Style, Stylize}, 10 | symbols::Marker, 11 | text::{Line, Text}, 12 | widgets::{Axis, Block, Borders, Chart, Dataset, GraphType, Paragraph, Widget}, 13 | }; 14 | 15 | use crate::SystemLoadAvg; 16 | 17 | pub(crate) struct ModernTui<'a>(&'a [SystemLoadAvg]); 18 | 19 | impl ModernTui<'_> { 20 | fn render_header(&self, area: Rect, buf: &mut Buffer) { 21 | let text = Text::from(vec![ 22 | Line::from(format!( 23 | "Last 1 min load: {:>5}", 24 | self.0.last().unwrap().last_1 25 | )), 26 | Line::from(format!( 27 | "Last 5 min load: {:>5}", 28 | self.0.last().unwrap().last_5 29 | )), 30 | Line::from(format!( 31 | "Last 10 min load: {:>5}", 32 | self.0.last().unwrap().last_10 33 | )), 34 | ]); 35 | 36 | Paragraph::new(text) 37 | .style(Style::default().bold().italic()) 38 | .block( 39 | Block::new() 40 | .borders(Borders::ALL) 41 | .title("System load history"), 42 | ) 43 | .render(area, buf); 44 | } 45 | 46 | fn render_chart(&self, area: Rect, buf: &mut Buffer) { 47 | let result = &self.0[self.0.len().saturating_sub(area.width.into())..] 48 | .iter() 49 | .enumerate() 50 | .map(|(index, load)| (index as f64, load.last_1 as f64)) 51 | .collect::>(); 52 | 53 | let data = Dataset::default() 54 | .graph_type(GraphType::Line) 55 | .marker(Marker::Braille) 56 | .data(result); 57 | 58 | let x_axis = { 59 | let start = Line::from("0"); 60 | let middle = Line::from((area.width / 2).to_string()); 61 | let end = Line::from(area.width.to_string()); 62 | Axis::default() 63 | .title("Time(per delay)") 64 | .bounds([0.0, area.width.into()]) 65 | .labels(vec![start, middle, end]) 66 | }; 67 | 68 | // Why this tweak? 69 | // 70 | // Sometime the chart cannot display all the line because of max height are equals the max 71 | // load of system in the history, so I add 0.2*{max_load} to the height of chart make it 72 | // display beautiful 73 | let y_axis_upper_bound = result.iter().map(|it| it.1).reduce(f64::max).unwrap_or(0.0); 74 | let y_axis_upper_bound = y_axis_upper_bound + y_axis_upper_bound * 0.2; 75 | let label = { 76 | let min = "0.0".to_owned(); 77 | let mid = format!("{:.1}", y_axis_upper_bound / 2.0); 78 | let max = format!("{:.1}", y_axis_upper_bound); 79 | vec![min, mid, max] 80 | }; 81 | let y_axis = Axis::default() 82 | .bounds([0.0, y_axis_upper_bound]) 83 | .labels(label) 84 | .title("System Load"); 85 | 86 | Chart::new(vec![data]) 87 | .x_axis(x_axis) 88 | .y_axis(y_axis) 89 | .render(area, buf); 90 | } 91 | } 92 | 93 | impl ModernTui<'_> { 94 | pub(crate) fn new(input: &[SystemLoadAvg]) -> ModernTui<'_> { 95 | ModernTui(input) 96 | } 97 | } 98 | 99 | impl Widget for ModernTui<'_> { 100 | fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) 101 | where 102 | Self: Sized, 103 | { 104 | let layout = Layout::new( 105 | Direction::Vertical, 106 | [Constraint::Length(5), Constraint::Min(0)], 107 | ) 108 | .split(area); 109 | 110 | let header = layout[0]; 111 | let chart = layout[1]; 112 | 113 | self.render_header(header, buf); 114 | self.render_chart(chart, buf); 115 | } 116 | } 117 | 118 | // TODO: Implemented LegacyTui 119 | pub(crate) type LegacyTui<'a> = ModernTui<'a>; 120 | -------------------------------------------------------------------------------- /src/uu/tload/tload.md: -------------------------------------------------------------------------------- 1 | # tload 2 | 3 | ``` 4 | tload [options] [tty] 5 | ``` 6 | 7 | tload prints a graph of the current system load average to the specified tty (or the tty of the tload process if none is specified). 8 | -------------------------------------------------------------------------------- /src/uu/top/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_top" 3 | description = "top ~ (uutils) Display Linux processes" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/top" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true, features = ["utmpx", "uptime"] } 15 | clap = { workspace = true } 16 | libc = { workspace = true } 17 | nix = { workspace = true } 18 | prettytable-rs = { workspace = true } 19 | sysinfo = { workspace = true } 20 | chrono = { workspace = true } 21 | bytesize = { workspace = true } 22 | 23 | uu_vmstat = { path = "../vmstat" } 24 | uu_w = { path = "../w" } 25 | 26 | [target.'cfg(target_os="windows")'.dependencies] 27 | windows-sys = { workspace = true, features = [ 28 | "Win32_System_RemoteDesktop", 29 | "Win32_System_SystemInformation", 30 | ] } 31 | 32 | 33 | [target.'cfg(target_os="linux")'.build-dependencies] 34 | pkg-config = "0.3.31" 35 | 36 | [lib] 37 | path = "src/top.rs" 38 | 39 | [[bin]] 40 | name = "top" 41 | path = "src/main.rs" 42 | -------------------------------------------------------------------------------- /src/uu/top/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | #[cfg(target_os = "linux")] 3 | pkg_config::find_library("libsystemd").unwrap(); 4 | } 5 | -------------------------------------------------------------------------------- /src/uu/top/src/field.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use std::{ 7 | collections::{HashMap, HashSet}, 8 | sync::OnceLock, 9 | }; 10 | 11 | // This static field will used in future 12 | #[allow(unused)] 13 | static FIELDS: OnceLock> = OnceLock::new(); 14 | 15 | // Generated from manpage 16 | #[allow(unused)] 17 | pub(crate) fn fields() -> HashSet { 18 | FIELDS 19 | .get_or_init(|| { 20 | vec![ 21 | ("%CPU", "CPU Usage"), 22 | ("%CUC", "CPU Utilization"), 23 | ("%CUU", "CPU Utilization"), 24 | ("%MEM", "Memory Usage (RES)"), 25 | ("AGID", "Autogroup Identifier"), 26 | ("AGNI", "Autogroup Nice Value"), 27 | ("CGNAME", "Control Group Name"), 28 | ("CGROUPS", "Control Groups"), 29 | ("CODE", "Code Size (KiB)"), 30 | ("COMMAND", "Command Name or Command Line"), 31 | ("DATA", "Data + Stack Size (KiB)"), 32 | ("ELAPSED", "Elapsed Running Time"), 33 | ("ENVIRON", "Environment variables"), 34 | ("EXE", "Executable Path"), 35 | ("Flags", "Task Flags"), 36 | ("GID", "Group Id"), 37 | ("GROUP", "Group Name"), 38 | ("LOGID", "Login User Id"), 39 | ("LXC", "Lxc Container Name"), 40 | ("NI", "Nice Value"), 41 | ("NU", "Last known NUMA node"), 42 | ("OOMa", "Out of Memory Adjustment Factor"), 43 | ("OOMs", "Out of Memory Score"), 44 | ("P", "Last used CPU (SMP)"), 45 | ("PGRP", "Process Group Id"), 46 | ("PID", "Process Id"), 47 | ("PPID", "Parent Process Id"), 48 | ("PR", "Priority"), 49 | ("PSS", "Proportional Resident Memory, smaps (KiB)"), 50 | ] 51 | .iter() 52 | .map(|(key, value)| (key.to_string(), value.to_string())) 53 | .collect::>() 54 | }) 55 | .keys() 56 | .cloned() 57 | .collect() 58 | } 59 | 60 | #[allow(unused)] 61 | pub(crate) fn description_of(field: T) -> Option 62 | where 63 | T: Into, 64 | { 65 | let field: String = field.into(); 66 | fields().get(&field).cloned() 67 | } 68 | -------------------------------------------------------------------------------- /src/uu/top/src/header.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use crate::picker::sysinfo; 7 | use bytesize::ByteSize; 8 | use uu_w::get_formatted_uptime_procps; 9 | use uucore::uptime::{get_formatted_loadavg, get_formatted_nusers, get_formatted_time}; 10 | 11 | pub(crate) fn header(scale_summary_mem: Option<&String>) -> String { 12 | format!( 13 | "top - {time} {uptime}, {user}, {load_average}\n\ 14 | {task}\n\ 15 | {cpu}\n\ 16 | {memory}", 17 | time = get_formatted_time(), 18 | uptime = uptime(), 19 | user = user(), 20 | load_average = load_average(), 21 | task = task(), 22 | cpu = cpu(), 23 | memory = memory(scale_summary_mem), 24 | ) 25 | } 26 | 27 | #[cfg(target_os = "linux")] 28 | extern "C" { 29 | pub fn sd_booted() -> libc::c_int; 30 | pub fn sd_get_sessions(sessions: *mut *mut *mut libc::c_char) -> libc::c_int; 31 | pub fn sd_session_get_class( 32 | session: *const libc::c_char, 33 | class: *mut *mut libc::c_char, 34 | ) -> libc::c_int; 35 | } 36 | 37 | fn format_memory(memory_b: u64, unit: u64) -> f64 { 38 | ByteSize::b(memory_b).0 as f64 / unit as f64 39 | } 40 | 41 | #[inline] 42 | fn uptime() -> String { 43 | get_formatted_uptime_procps().unwrap_or_default() 44 | } 45 | 46 | #[cfg(target_os = "linux")] 47 | pub fn get_nusers_systemd() -> uucore::error::UResult { 48 | use std::ffi::CStr; 49 | use std::ptr; 50 | use uucore::error::USimpleError; 51 | use uucore::libc::*; 52 | 53 | // SAFETY: sd_booted to check if system is booted with systemd. 54 | unsafe { 55 | // systemd 56 | if sd_booted() > 0 { 57 | let mut sessions_list: *mut *mut c_char = ptr::null_mut(); 58 | let mut num_user = 0; 59 | let sessions = sd_get_sessions(&mut sessions_list); 60 | 61 | if sessions > 0 { 62 | for i in 0..sessions { 63 | let mut class: *mut c_char = ptr::null_mut(); 64 | 65 | if sd_session_get_class( 66 | *sessions_list.add(i as usize) as *const c_char, 67 | &mut class, 68 | ) < 0 69 | { 70 | continue; 71 | } 72 | if CStr::from_ptr(class).to_str().unwrap().starts_with("user") { 73 | num_user += 1; 74 | } 75 | free(class as *mut c_void); 76 | } 77 | } 78 | 79 | for i in 0..sessions { 80 | free(*sessions_list.add(i as usize) as *mut c_void); 81 | } 82 | free(sessions_list as *mut c_void); 83 | 84 | return Ok(num_user); 85 | } 86 | } 87 | Err(USimpleError::new( 88 | 1, 89 | "could not retrieve number of logged users", 90 | )) 91 | } 92 | 93 | // see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115 94 | fn user() -> String { 95 | #[cfg(target_os = "linux")] 96 | if let Ok(nusers) = get_nusers_systemd() { 97 | return uucore::uptime::format_nusers(nusers); 98 | } 99 | 100 | get_formatted_nusers() 101 | } 102 | 103 | fn load_average() -> String { 104 | get_formatted_loadavg().unwrap_or_default() 105 | } 106 | 107 | fn task() -> String { 108 | let binding = sysinfo().read().unwrap(); 109 | 110 | let process = binding.processes(); 111 | let mut running_process = 0; 112 | let mut sleeping_process = 0; 113 | let mut stopped_process = 0; 114 | let mut zombie_process = 0; 115 | 116 | for (_, process) in process.iter() { 117 | match process.status() { 118 | sysinfo::ProcessStatus::Run => running_process += 1, 119 | sysinfo::ProcessStatus::Sleep => sleeping_process += 1, 120 | sysinfo::ProcessStatus::Stop => stopped_process += 1, 121 | sysinfo::ProcessStatus::Zombie => zombie_process += 1, 122 | _ => {} 123 | }; 124 | } 125 | 126 | format!( 127 | "Tasks: {} total, {} running, {} sleeping, {} stopped, {} zombie", 128 | process.len(), 129 | running_process, 130 | sleeping_process, 131 | stopped_process, 132 | zombie_process, 133 | ) 134 | } 135 | 136 | #[cfg(target_os = "linux")] 137 | fn cpu() -> String { 138 | let cpu_load = uu_vmstat::CpuLoad::current(); 139 | 140 | format!( 141 | "%Cpu(s): {:.1} us, {:.1} sy, {:.1} ni, {:.1} id, {:.1} wa, {:.1} hi, {:.1} si, {:.1} st", 142 | cpu_load.user, 143 | cpu_load.system, 144 | cpu_load.nice, 145 | cpu_load.idle, 146 | cpu_load.io_wait, 147 | cpu_load.hardware_interrupt, 148 | cpu_load.software_interrupt, 149 | cpu_load.steal_time, 150 | ) 151 | } 152 | 153 | #[cfg(target_os = "windows")] 154 | fn cpu() -> String { 155 | use libc::malloc; 156 | use windows_sys::Wdk::System::SystemInformation::NtQuerySystemInformation; 157 | 158 | #[repr(C)] 159 | #[derive(Debug)] 160 | struct SystemProcessorPerformanceInformation { 161 | idle_time: i64, // LARGE_INTEGER 162 | kernel_time: i64, // LARGE_INTEGER 163 | user_time: i64, // LARGE_INTEGER 164 | dpc_time: i64, // LARGE_INTEGER 165 | interrupt_time: i64, // LARGE_INTEGER 166 | interrupt_count: u32, // ULONG 167 | } 168 | 169 | let n_cpu = sysinfo().read().unwrap().cpus().len(); 170 | let mut cpu_load = SystemProcessorPerformanceInformation { 171 | idle_time: 0, 172 | kernel_time: 0, 173 | user_time: 0, 174 | dpc_time: 0, 175 | interrupt_time: 0, 176 | interrupt_count: 0, 177 | }; 178 | // SAFETY: malloc is safe to use here. We free the memory after we are done with it. If action fails, all "time" will be 0. 179 | unsafe { 180 | let len = n_cpu * size_of::(); 181 | let data = malloc(len); 182 | let status = NtQuerySystemInformation( 183 | windows_sys::Wdk::System::SystemInformation::SystemProcessorPerformanceInformation, 184 | data, 185 | (n_cpu * size_of::()) as u32, 186 | std::ptr::null_mut(), 187 | ); 188 | if status == 0 { 189 | for i in 0..n_cpu { 190 | let cpu = data.add(i * size_of::()) 191 | as *const SystemProcessorPerformanceInformation; 192 | let cpu = cpu.as_ref().unwrap(); 193 | cpu_load.idle_time += cpu.idle_time; 194 | cpu_load.kernel_time += cpu.kernel_time; 195 | cpu_load.user_time += cpu.user_time; 196 | cpu_load.dpc_time += cpu.dpc_time; 197 | cpu_load.interrupt_time += cpu.interrupt_time; 198 | cpu_load.interrupt_count += cpu.interrupt_count; 199 | } 200 | } 201 | } 202 | let total = cpu_load.idle_time 203 | + cpu_load.kernel_time 204 | + cpu_load.user_time 205 | + cpu_load.dpc_time 206 | + cpu_load.interrupt_time; 207 | format!( 208 | "%Cpu(s): {:.1} us, {:.1} sy, {:.1} id, {:.1} hi, {:.1} si", 209 | cpu_load.user_time as f64 / total as f64 * 100.0, 210 | cpu_load.kernel_time as f64 / total as f64 * 100.0, 211 | cpu_load.idle_time as f64 / total as f64 * 100.0, 212 | cpu_load.interrupt_time as f64 / total as f64 * 100.0, 213 | cpu_load.dpc_time as f64 / total as f64 * 100.0, 214 | ) 215 | } 216 | 217 | //TODO: Implement for macos 218 | #[cfg(target_os = "macos")] 219 | fn cpu() -> String { 220 | "TODO".into() 221 | } 222 | 223 | fn memory(scale_summary_mem: Option<&String>) -> String { 224 | let binding = sysinfo().read().unwrap(); 225 | let (unit, unit_name) = match scale_summary_mem { 226 | Some(scale) => match scale.as_str() { 227 | "k" => (bytesize::KIB, "KiB"), 228 | "m" => (bytesize::MIB, "MiB"), 229 | "g" => (bytesize::GIB, "GiB"), 230 | "t" => (bytesize::TIB, "TiB"), 231 | "p" => (bytesize::PIB, "PiB"), 232 | "e" => (1_152_921_504_606_846_976, "EiB"), 233 | _ => (bytesize::MIB, "MiB"), 234 | }, 235 | None => (bytesize::MIB, "MiB"), 236 | }; 237 | 238 | format!( 239 | "{unit_name} Mem : {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} buff/cache\n\ 240 | {unit_name} Swap: {:8.1} total, {:8.1} free, {:8.1} used, {:8.1} avail Mem", 241 | format_memory(binding.total_memory(), unit), 242 | format_memory(binding.free_memory(), unit), 243 | format_memory(binding.used_memory(), unit), 244 | format_memory(binding.available_memory() - binding.free_memory(), unit), 245 | format_memory(binding.total_swap(), unit), 246 | format_memory(binding.free_swap(), unit), 247 | format_memory(binding.used_swap(), unit), 248 | format_memory(binding.available_memory(), unit), 249 | unit_name = unit_name 250 | ) 251 | } 252 | -------------------------------------------------------------------------------- /src/uu/top/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_top); 2 | -------------------------------------------------------------------------------- /src/uu/top/src/picker.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use std::{ 7 | ffi::OsString, 8 | fs::File, 9 | io::read_to_string, 10 | path::PathBuf, 11 | str::FromStr, 12 | sync::{OnceLock, RwLock}, 13 | }; 14 | use sysinfo::{Pid, System, Users}; 15 | 16 | static SYSINFO: OnceLock> = OnceLock::new(); 17 | 18 | pub fn sysinfo() -> &'static RwLock { 19 | SYSINFO.get_or_init(|| RwLock::new(System::new_all())) 20 | } 21 | 22 | pub(crate) fn pickers(fields: &[String]) -> Vec String>> { 23 | fields 24 | .iter() 25 | .map(|field| match field.as_str() { 26 | "PID" => helper(pid), 27 | "USER" => helper(user), 28 | "PR" => helper(pr), 29 | "RES" => helper(res), 30 | "SHR" => helper(shr), 31 | "S" => helper(s), 32 | "%CPU" => helper(cpu), 33 | "TIME+" => helper(time_plus), 34 | "%MEM" => helper(mem), 35 | "COMMAND" => helper(command), 36 | _ => helper(todo), 37 | }) 38 | .collect() 39 | } 40 | 41 | #[inline] 42 | fn helper(f: impl Fn(u32) -> String + 'static) -> Box String> { 43 | Box::new(f) 44 | } 45 | 46 | fn todo(_pid: u32) -> String { 47 | "TODO".into() 48 | } 49 | 50 | fn cpu(pid: u32) -> String { 51 | let binding = sysinfo().read().unwrap(); 52 | let Some(proc) = binding.process(Pid::from_u32(pid)) else { 53 | return "0.0".into(); 54 | }; 55 | 56 | let usage = proc.cpu_usage(); 57 | 58 | format!("{:.2}", usage) 59 | } 60 | 61 | fn pid(pid: u32) -> String { 62 | pid.to_string() 63 | } 64 | 65 | fn user(pid: u32) -> String { 66 | let binding = sysinfo().read().unwrap(); 67 | let Some(proc) = binding.process(Pid::from_u32(pid)) else { 68 | return "0.0".into(); 69 | }; 70 | 71 | let users = Users::new_with_refreshed_list(); 72 | match proc.user_id() { 73 | Some(uid) => users.get_user_by_id(uid).map(|it| it.name()).unwrap_or("?"), 74 | None => "?", 75 | } 76 | .to_string() 77 | } 78 | 79 | #[cfg(not(target_os = "windows"))] 80 | fn pr(pid: u32) -> String { 81 | use libc::{getpriority, PRIO_PROCESS}; 82 | use nix::errno::Errno; 83 | 84 | let result = unsafe { getpriority(PRIO_PROCESS, pid) }; 85 | 86 | let result = if Errno::last() == Errno::UnknownErrno { 87 | result 88 | } else { 89 | Errno::clear(); 90 | 0 91 | }; 92 | 93 | format!("{}", result) 94 | } 95 | 96 | // TODO: Implement this function for Windows 97 | #[cfg(target_os = "windows")] 98 | fn pr(_pid: u32) -> String { 99 | "0".into() 100 | } 101 | 102 | fn res(_pid: u32) -> String { 103 | "TODO".into() 104 | } 105 | 106 | fn shr(_pid: u32) -> String { 107 | "TODO".into() 108 | } 109 | 110 | fn s(pid: u32) -> String { 111 | let binding = sysinfo().read().unwrap(); 112 | let Some(proc) = binding.process(Pid::from_u32(pid)) else { 113 | return "?".into(); 114 | }; 115 | 116 | proc.status() 117 | .to_string() 118 | .chars() 119 | .collect::>() 120 | .first() 121 | .unwrap() 122 | .to_string() 123 | } 124 | 125 | fn time_plus(pid: u32) -> String { 126 | let binding = sysinfo().read().unwrap(); 127 | let Some(proc) = binding.process(Pid::from_u32(pid)) else { 128 | return "0:00.00".into(); 129 | }; 130 | 131 | let (hour, min, sec) = { 132 | let total = proc.run_time(); 133 | let hour = total / 3600; 134 | let minute = (total % 3600) / 60; 135 | let second = total % 60; 136 | 137 | (hour, minute, second) 138 | }; 139 | 140 | format!("{}:{:0>2}.{:0>2}", hour, min, sec) 141 | } 142 | 143 | fn mem(pid: u32) -> String { 144 | let binding = sysinfo().read().unwrap(); 145 | let Some(proc) = binding.process(Pid::from_u32(pid)) else { 146 | return "0.0".into(); 147 | }; 148 | 149 | format!( 150 | "{:.1}", 151 | proc.memory() as f32 / sysinfo().read().unwrap().total_memory() as f32 152 | ) 153 | } 154 | 155 | fn command(pid: u32) -> String { 156 | let f = |cmd: &[OsString]| -> String { 157 | let binding = cmd 158 | .iter() 159 | .map(|os_str| os_str.to_string_lossy().into_owned()) 160 | .collect::>() 161 | .join(" "); 162 | let trimmed = binding.trim(); 163 | 164 | let result: String = trimmed.into(); 165 | 166 | if cfg!(target_os = "linux") && result.is_empty() { 167 | { 168 | match PathBuf::from_str(&format!("/proc/{}/status", pid)) { 169 | Ok(path) => { 170 | let file = File::open(path).unwrap(); 171 | let content = read_to_string(file).unwrap(); 172 | let line = content 173 | .lines() 174 | .collect::>() 175 | .first() 176 | .unwrap() 177 | .split(':') 178 | .collect::>(); 179 | 180 | line[1].trim().to_owned() 181 | } 182 | Err(_) => String::new(), 183 | } 184 | } 185 | } else { 186 | result 187 | } 188 | }; 189 | 190 | let binding = sysinfo().read().unwrap(); 191 | let Some(proc) = binding.process(Pid::from_u32(pid)) else { 192 | return "?".into(); 193 | }; 194 | 195 | proc.exe() 196 | .and_then(|it| it.iter().next_back()) 197 | .map(|it| it.to_str().unwrap()) 198 | .unwrap_or(&f(proc.cmd())) 199 | .into() 200 | } 201 | -------------------------------------------------------------------------------- /src/uu/top/src/top.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use clap::{arg, crate_version, value_parser, ArgAction, ArgGroup, ArgMatches, Command}; 7 | use header::header; 8 | use picker::pickers; 9 | use picker::sysinfo; 10 | use prettytable::{format::consts::FORMAT_CLEAN, Row, Table}; 11 | use std::{thread::sleep, time::Duration}; 12 | use sysinfo::{Pid, Users}; 13 | use uucore::{ 14 | error::{UResult, USimpleError}, 15 | format_usage, help_about, help_usage, 16 | }; 17 | 18 | const ABOUT: &str = help_about!("top.md"); 19 | const USAGE: &str = help_usage!("top.md"); 20 | 21 | mod field; 22 | mod header; 23 | mod picker; 24 | 25 | #[allow(unused)] 26 | #[derive(Debug)] 27 | enum Filter { 28 | Pid(Vec), 29 | User(String), 30 | EUser(String), 31 | } 32 | 33 | #[derive(Debug)] 34 | struct Settings { 35 | // batch:bool 36 | filter: Option, 37 | width: Option, 38 | } 39 | 40 | impl Settings { 41 | fn new(matches: &ArgMatches) -> Self { 42 | let width = matches.get_one::("width").cloned(); 43 | 44 | Self { 45 | width, 46 | filter: None, 47 | } 48 | } 49 | } 50 | 51 | #[uucore::main] 52 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 53 | let matches = uu_app().try_get_matches_from(args)?; 54 | 55 | // Must refresh twice. 56 | // https://docs.rs/sysinfo/0.31.2/sysinfo/struct.System.html#method.refresh_cpu_usage 57 | picker::sysinfo().write().unwrap().refresh_all(); 58 | sleep(Duration::from_millis(200)); 59 | picker::sysinfo().write().unwrap().refresh_all(); 60 | 61 | let settings = Settings::new(&matches); 62 | 63 | let settings = { 64 | let filter = matches 65 | .get_many::("pid") 66 | .map(|pidlist| Filter::Pid(pidlist.cloned().collect())) 67 | .or_else(|| { 68 | matches 69 | .get_one::("filter-any-user") 70 | .map(|user| Filter::User(user.clone())) 71 | }) 72 | .or_else(|| { 73 | matches 74 | .get_one::("filter-only-euser") 75 | .map(|euser| Filter::EUser(euser.clone())) 76 | }); 77 | 78 | let filter = match filter { 79 | Some(Filter::User(data)) => Some(Filter::User(try_into_uid(data)?)), 80 | // TODO: Make sure this working 81 | Some(Filter::EUser(data)) => Some(Filter::EUser(try_into_uid(data)?)), 82 | _ => filter, 83 | }; 84 | 85 | Settings { filter, ..settings } 86 | }; 87 | 88 | let fields = selected_fields(); 89 | let collected = collect(&settings, &fields); 90 | 91 | let table = { 92 | let mut table = Table::new(); 93 | 94 | table.set_format(*FORMAT_CLEAN); 95 | 96 | table.add_row(Row::from_iter(fields)); 97 | table.extend(collected.iter().map(Row::from_iter)); 98 | 99 | table 100 | }; 101 | 102 | println!("{}", header(matches.get_one::("scale-summary-mem"))); 103 | println!("\n"); 104 | 105 | let cutter = { 106 | #[inline] 107 | fn f(f: impl Fn(&str) -> String + 'static) -> Box String> { 108 | Box::new(f) 109 | } 110 | 111 | if let Some(width) = settings.width { 112 | f(move |line: &str| apply_width(line, width)) 113 | } else { 114 | f(|line: &str| line.to_string()) 115 | } 116 | }; 117 | 118 | table 119 | .to_string() 120 | .lines() 121 | .map(cutter) 122 | .for_each(|it| println!("{}", it)); 123 | 124 | Ok(()) 125 | } 126 | 127 | fn try_into_uid(input: T) -> UResult 128 | where 129 | T: Into, 130 | { 131 | let into: String = input.into(); 132 | 133 | if into.parse::().is_ok() { 134 | return Ok(into); 135 | } 136 | 137 | let user_name = into; 138 | let users = Users::new_with_refreshed_list(); 139 | 140 | users 141 | .iter() 142 | .find(|it| it.name() == user_name) 143 | .map(|it| it.id().to_string()) 144 | .ok_or(USimpleError::new(1, "Invalid user")) 145 | } 146 | 147 | fn apply_width(input: T, width: usize) -> String 148 | where 149 | T: Into, 150 | { 151 | let input: String = input.into(); 152 | 153 | if input.len() > width { 154 | input.chars().take(width).collect() 155 | } else { 156 | let mut result = String::from(&input); 157 | result.extend(std::iter::repeat_n(' ', width - input.len())); 158 | result 159 | } 160 | } 161 | 162 | // TODO: Implement fields selecting 163 | fn selected_fields() -> Vec { 164 | vec![ 165 | "PID", "USER", "PR", "NI", "VIRT", "RES", "SHR", "S", "%CPU", "%MEM", "TIME+", "COMMAND", 166 | ] 167 | .into_iter() 168 | .map(Into::into) 169 | .collect() 170 | } 171 | 172 | fn collect(settings: &Settings, fields: &[String]) -> Vec> { 173 | let pickers = pickers(fields); 174 | 175 | let pids = sysinfo() 176 | .read() 177 | .unwrap() 178 | .processes() 179 | .iter() 180 | .map(|(it, _)| it.as_u32()) 181 | .collect::>(); 182 | 183 | let filter = construct_filter(settings); 184 | 185 | pids.into_iter() 186 | .filter(|pid| filter(*pid)) 187 | .map(|it| { 188 | pickers 189 | .iter() 190 | .map(move |picker| picker(it)) 191 | .collect::>() 192 | }) 193 | .collect() 194 | } 195 | 196 | /// Constructing filter from `Settings` 197 | fn construct_filter(settings: &Settings) -> Box bool> { 198 | let Some(ref filter) = settings.filter else { 199 | return Box::new(|_: u32| true); 200 | }; 201 | 202 | fn helper(f: impl Fn(u32) -> bool + 'static) -> Box bool> { 203 | Box::new(f) 204 | } 205 | 206 | match filter { 207 | Filter::Pid(pids) => { 208 | let pids = pids.clone(); 209 | helper(move |pid: u32| pids.contains(&pid)) 210 | } 211 | 212 | Filter::User(user) => { 213 | let user = user.to_owned(); 214 | 215 | helper(move |pid| { 216 | let binding = sysinfo().read().unwrap(); 217 | let Some(proc) = binding.process(Pid::from_u32(pid)) else { 218 | return false; 219 | }; 220 | 221 | let Some(uid) = proc.user_id() else { 222 | return false; 223 | }; 224 | 225 | uid.to_string() == user 226 | }) 227 | } 228 | 229 | // Doesn't work on Windows. 230 | // https://docs.rs/sysinfo/0.31.3/sysinfo/struct.Process.html#method.effective_user_id 231 | Filter::EUser(euser) => { 232 | let euser = euser.to_owned(); 233 | 234 | helper(move |pid| { 235 | let binding = sysinfo().read().unwrap(); 236 | let Some(proc) = binding.process(Pid::from_u32(pid)) else { 237 | return false; 238 | }; 239 | 240 | let Some(euid) = proc.effective_user_id() else { 241 | return false; 242 | }; 243 | 244 | euid.to_string() == euser 245 | }) 246 | } 247 | } 248 | } 249 | 250 | #[allow(clippy::cognitive_complexity)] 251 | pub fn uu_app() -> Command { 252 | Command::new(uucore::util_name()) 253 | .version(crate_version!()) 254 | .about(ABOUT) 255 | .override_usage(format_usage(USAGE)) 256 | .infer_long_args(true) 257 | .args([ 258 | // arg!(-b --"batch-mode" "run in non-interactive batch mode"), 259 | // arg!(-c --"cmdline-toggle" "reverse last remembered 'c' state"), 260 | // arg!(-d --delay "iterative delay as SECS [.TENTHS]"), 261 | arg!(-E --"scale-summary-mem" "set mem as: k,m,g,t,p,e for SCALE"), 262 | // arg!(-e --"scale-task-mem" "set mem with: k,m,g,t,p for SCALE"), 263 | // arg!(-H --"threads-show" "show tasks plus all their threads"), 264 | // arg!(-i --"idle-toggle" "reverse last remembered 'i' state"), 265 | // arg!(-n --iterations "exit on maximum iterations NUMBER"), 266 | arg!(-O --"list-fields" "output all field names, then exit"), 267 | // arg!(-o --"sort-override" "force sorting on this named FIELD"), 268 | arg!(-p --pid "monitor only the tasks in PIDLIST") 269 | .action(ArgAction::Append) 270 | .value_parser(value_parser!(u32)) 271 | .value_delimiter(','), 272 | // arg!(-S --"accum-time-toggle" "reverse last remembered 'S' state"), 273 | // arg!(-s --"secure-mode" "run with secure mode restrictions"), 274 | arg!(-U --"filter-any-user" "show only processes owned by USER"), 275 | arg!(-u --"filter-only-euser" "show only processes owned by USER"), 276 | arg!(-w --width "change print width [,use COLUMNS]"), 277 | // arg!(-1 --single-cpu-toggle "reverse last remembered '1' state"), 278 | ]) 279 | .group(ArgGroup::new("filter").args(["pid", "filter-any-user", "filter-only-euser"])) 280 | } 281 | -------------------------------------------------------------------------------- /src/uu/top/top.md: -------------------------------------------------------------------------------- 1 | # top 2 | 3 | ``` 4 | top [options] 5 | ``` 6 | 7 | Display Linux processes 8 | -------------------------------------------------------------------------------- /src/uu/vmstat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_vmstat" 3 | description = "vmstat ~ (uutils) Report virtual memory statistics." 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/vmstat" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | bytesize = { workspace = true } 15 | chrono = { workspace = true, default-features = false, features = ["clock"] } 16 | clap = { workspace = true } 17 | terminal_size = { workspace = true } 18 | uucore = { workspace = true, features = ["custom-tz-fmt"] } 19 | 20 | [lib] 21 | path = "src/vmstat.rs" 22 | 23 | [[bin]] 24 | name = "vmstat" 25 | path = "src/main.rs" 26 | -------------------------------------------------------------------------------- /src/uu/vmstat/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_vmstat); 2 | -------------------------------------------------------------------------------- /src/uu/vmstat/src/parser.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | #[cfg(target_os = "linux")] 7 | use std::collections::HashMap; 8 | 9 | #[cfg(target_os = "linux")] 10 | pub fn parse_proc_file(path: &str) -> HashMap { 11 | let file = std::fs::File::open(std::path::Path::new(path)).unwrap(); 12 | let content = std::io::read_to_string(file).unwrap(); 13 | let mut map: HashMap = HashMap::new(); 14 | 15 | for line in content.lines() { 16 | let parts = line.split_once(char::is_whitespace); 17 | if let Some(parts) = parts { 18 | map.insert( 19 | parts.0.strip_suffix(":").unwrap_or(parts.0).to_string(), 20 | parts.1.trim_start().to_string(), 21 | ); 22 | } 23 | } 24 | 25 | map 26 | } 27 | 28 | #[cfg(target_os = "linux")] 29 | pub struct ProcData { 30 | pub uptime: (f64, f64), 31 | pub stat: HashMap, 32 | pub meminfo: HashMap, 33 | pub vmstat: HashMap, 34 | } 35 | #[cfg(target_os = "linux")] 36 | impl Default for ProcData { 37 | fn default() -> Self { 38 | Self::new() 39 | } 40 | } 41 | #[cfg(target_os = "linux")] 42 | impl ProcData { 43 | pub fn new() -> Self { 44 | let uptime = Self::get_uptime(); 45 | let stat = parse_proc_file("/proc/stat"); 46 | let meminfo = parse_proc_file("/proc/meminfo"); 47 | let vmstat = parse_proc_file("/proc/vmstat"); 48 | Self { 49 | uptime, 50 | stat, 51 | meminfo, 52 | vmstat, 53 | } 54 | } 55 | 56 | fn get_uptime() -> (f64, f64) { 57 | let file = std::fs::File::open(std::path::Path::new("/proc/uptime")).unwrap(); 58 | let content = std::io::read_to_string(file).unwrap(); 59 | let mut parts = content.split_whitespace(); 60 | let uptime = parts.next().unwrap().parse::().unwrap(); 61 | let idle_time = parts.next().unwrap().parse::().unwrap(); 62 | (uptime, idle_time) 63 | } 64 | } 65 | 66 | #[cfg(target_os = "linux")] 67 | pub struct CpuLoad { 68 | pub user: f64, 69 | pub nice: f64, 70 | pub system: f64, 71 | pub idle: f64, 72 | pub io_wait: f64, 73 | pub hardware_interrupt: f64, 74 | pub software_interrupt: f64, 75 | pub steal_time: f64, 76 | pub guest: f64, 77 | pub guest_nice: f64, 78 | } 79 | 80 | #[cfg(target_os = "linux")] 81 | impl CpuLoad { 82 | pub fn current() -> Self { 83 | 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 84 | let content = std::io::read_to_string(file).unwrap(); 85 | let load_str = content.lines().next().unwrap().strip_prefix("cpu").unwrap(); 86 | Self::from_str(load_str) 87 | } 88 | 89 | pub fn from_proc_map(proc_map: &HashMap) -> Self { 90 | let load_str = proc_map.get("cpu").unwrap(); 91 | Self::from_str(load_str) 92 | } 93 | 94 | fn from_str(s: &str) -> Self { 95 | let load = s.split(' ').filter(|s| !s.is_empty()).collect::>(); 96 | let user = load[0].parse::().unwrap(); 97 | let nice = load[1].parse::().unwrap(); 98 | let system = load[2].parse::().unwrap(); 99 | let idle = load[3].parse::().unwrap_or_default(); // since 2.5.41 100 | let io_wait = load[4].parse::().unwrap_or_default(); // since 2.5.41 101 | let hardware_interrupt = load[5].parse::().unwrap_or_default(); // since 2.6.0 102 | let software_interrupt = load[6].parse::().unwrap_or_default(); // since 2.6.0 103 | let steal_time = load[7].parse::().unwrap_or_default(); // since 2.6.11 104 | let guest = load[8].parse::().unwrap_or_default(); // since 2.6.24 105 | let guest_nice = load[9].parse::().unwrap_or_default(); // since 2.6.33 106 | let total = user 107 | + nice 108 | + system 109 | + idle 110 | + io_wait 111 | + hardware_interrupt 112 | + software_interrupt 113 | + steal_time 114 | + guest 115 | + guest_nice; 116 | Self { 117 | user: user / total * 100.0, 118 | system: system / total * 100.0, 119 | nice: nice / total * 100.0, 120 | idle: idle / total * 100.0, 121 | io_wait: io_wait / total * 100.0, 122 | hardware_interrupt: hardware_interrupt / total * 100.0, 123 | software_interrupt: software_interrupt / total * 100.0, 124 | steal_time: steal_time / total * 100.0, 125 | guest: guest / total * 100.0, 126 | guest_nice: guest_nice / total * 100.0, 127 | } 128 | } 129 | } 130 | 131 | #[cfg(target_os = "linux")] 132 | pub struct Meminfo { 133 | pub mem_total: bytesize::ByteSize, 134 | pub mem_free: bytesize::ByteSize, 135 | pub mem_available: bytesize::ByteSize, 136 | pub buffers: bytesize::ByteSize, 137 | pub cached: bytesize::ByteSize, 138 | pub swap_cached: bytesize::ByteSize, 139 | pub active: bytesize::ByteSize, 140 | pub inactive: bytesize::ByteSize, 141 | pub swap_total: bytesize::ByteSize, 142 | pub swap_free: bytesize::ByteSize, 143 | } 144 | #[cfg(target_os = "linux")] 145 | impl Meminfo { 146 | pub fn current() -> Self { 147 | let meminfo = parse_proc_file("/proc/meminfo"); 148 | Self::from_proc_map(&meminfo) 149 | } 150 | 151 | pub fn from_proc_map(proc_map: &HashMap) -> Self { 152 | use std::str::FromStr; 153 | 154 | let mem_total = bytesize::ByteSize::from_str(proc_map.get("MemTotal").unwrap()).unwrap(); 155 | let mem_free = bytesize::ByteSize::from_str(proc_map.get("MemFree").unwrap()).unwrap(); 156 | let mem_available = 157 | bytesize::ByteSize::from_str(proc_map.get("MemAvailable").unwrap()).unwrap(); 158 | let buffers = bytesize::ByteSize::from_str(proc_map.get("Buffers").unwrap()).unwrap(); 159 | let cached = bytesize::ByteSize::from_str(proc_map.get("Cached").unwrap()).unwrap(); 160 | let swap_cached = 161 | bytesize::ByteSize::from_str(proc_map.get("SwapCached").unwrap()).unwrap(); 162 | let active = bytesize::ByteSize::from_str(proc_map.get("Active").unwrap()).unwrap(); 163 | let inactive = bytesize::ByteSize::from_str(proc_map.get("Inactive").unwrap()).unwrap(); 164 | let swap_total = bytesize::ByteSize::from_str(proc_map.get("SwapTotal").unwrap()).unwrap(); 165 | let swap_free = bytesize::ByteSize::from_str(proc_map.get("SwapFree").unwrap()).unwrap(); 166 | Self { 167 | mem_total, 168 | mem_free, 169 | mem_available, 170 | buffers, 171 | cached, 172 | swap_cached, 173 | active, 174 | inactive, 175 | swap_total, 176 | swap_free, 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/uu/vmstat/src/vmstat.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | mod parser; 7 | mod picker; 8 | 9 | #[cfg(target_os = "linux")] 10 | use crate::picker::{get_pickers, Picker}; 11 | use clap::value_parser; 12 | #[allow(unused_imports)] 13 | use clap::{arg, crate_version, ArgMatches, Command}; 14 | #[allow(unused_imports)] 15 | pub use parser::*; 16 | #[allow(unused_imports)] 17 | use uucore::error::{UResult, USimpleError}; 18 | use uucore::{format_usage, help_about, help_usage}; 19 | 20 | const ABOUT: &str = help_about!("vmstat.md"); 21 | const USAGE: &str = help_usage!("vmstat.md"); 22 | 23 | #[uucore::main] 24 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 25 | #[allow(unused)] 26 | let matches = uu_app().try_get_matches_from(args)?; 27 | #[cfg(target_os = "linux")] 28 | { 29 | // validate unit 30 | if let Some(unit) = matches.get_one::("unit") { 31 | if !["k", "K", "m", "M"].contains(&unit.as_str()) { 32 | Err(USimpleError::new( 33 | 1, 34 | "-S requires k, K, m or M (default is KiB)", 35 | ))?; 36 | } 37 | } 38 | 39 | let one_header = matches.get_flag("one-header"); 40 | let no_first = matches.get_flag("no-first"); 41 | 42 | let delay = matches.get_one::("delay"); 43 | let count = matches.get_one::("count"); 44 | let mut count = count.copied().map(|c| if c == 0 { 1 } else { c }); 45 | let delay = delay.copied().unwrap_or_else(|| { 46 | count.get_or_insert(1); 47 | 1 48 | }); 49 | 50 | let pickers = get_pickers(&matches); 51 | let mut proc_data = ProcData::new(); 52 | 53 | let mut line_count = 0; 54 | print_header(&pickers); 55 | if !no_first { 56 | print_data(&pickers, &proc_data, None, &matches); 57 | line_count += 1; 58 | } 59 | 60 | let term_height = terminal_size::terminal_size() 61 | .map(|size| size.1 .0) 62 | .unwrap_or(0); 63 | 64 | while count.is_none() || line_count < count.unwrap() { 65 | std::thread::sleep(std::time::Duration::from_secs(delay)); 66 | let proc_data_now = ProcData::new(); 67 | if !one_header && term_height > 0 && ((line_count + 3) % term_height as u64 == 0) { 68 | print_header(&pickers); 69 | } 70 | print_data(&pickers, &proc_data_now, Some(&proc_data), &matches); 71 | line_count += 1; 72 | proc_data = proc_data_now; 73 | } 74 | } 75 | 76 | Ok(()) 77 | } 78 | 79 | #[cfg(target_os = "linux")] 80 | fn print_header(pickers: &[Picker]) { 81 | let mut section: Vec<&str> = vec![]; 82 | let mut title: Vec<&str> = vec![]; 83 | 84 | pickers.iter().for_each(|p| { 85 | section.push(p.0 .0.as_str()); 86 | title.push(p.0 .1.as_str()); 87 | }); 88 | println!("{}", section.join(" ")); 89 | println!("{}", title.join(" ")); 90 | } 91 | 92 | #[cfg(target_os = "linux")] 93 | fn print_data( 94 | pickers: &[Picker], 95 | proc_data: &ProcData, 96 | proc_data_before: Option<&ProcData>, 97 | matches: &ArgMatches, 98 | ) { 99 | let mut data: Vec = vec![]; 100 | let mut data_len_excess = 0; 101 | pickers.iter().for_each(|f| { 102 | f.1( 103 | proc_data, 104 | proc_data_before, 105 | matches, 106 | &mut data, 107 | &mut data_len_excess, 108 | ); 109 | }); 110 | println!("{}", data.join(" ")); 111 | } 112 | 113 | #[allow(clippy::cognitive_complexity)] 114 | pub fn uu_app() -> Command { 115 | Command::new(uucore::util_name()) 116 | .version(crate_version!()) 117 | .about(ABOUT) 118 | .override_usage(format_usage(USAGE)) 119 | .infer_long_args(true) 120 | .args([ 121 | arg!( "The delay between updates in seconds") 122 | .required(false) 123 | .value_parser(value_parser!(u64).range(1..)), 124 | arg!( "Number of updates") 125 | .required(false) 126 | .value_parser(value_parser!(u64)), 127 | arg!(-a --active "Display active and inactive memory"), 128 | // arg!(-f --forks "switch displays the number of forks since boot"), 129 | // arg!(-m --slabs "Display slabinfo"), 130 | arg!(-n --"one-header" "Display the header only once rather than periodically"), 131 | // arg!(-s --stats "Displays a table of various event counters and memory statistics"), 132 | // arg!(-d --disk "Report disk statistics"), 133 | // arg!(-D --"disk-sum" "Report some summary statistics about disk activity"), 134 | // arg!(-p --partition "Detailed statistics about partition"), 135 | arg!(-S --unit "Switches outputs between 1000 (k), 1024 (K), 1000000 (m), or 1048576 (M) bytes"), 136 | arg!(-t --timestamp "Append timestamp to each line"), 137 | arg!(-w --wide "Wide output mode"), 138 | arg!(-y --"no-first" "Omits first report with statistics since system boot"), 139 | ]) 140 | } 141 | -------------------------------------------------------------------------------- /src/uu/vmstat/vmstat.md: -------------------------------------------------------------------------------- 1 | # vmstat 2 | 3 | usage: 4 | 5 | ```bash 6 | vmstat [options] 7 | ``` 8 | 9 | Report virtual memory statistics -------------------------------------------------------------------------------- /src/uu/w/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_w" 3 | description = "w ~ (uutils) Show who is logged on and what they are doing" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/w" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true, features = ["utmpx", "uptime"] } 15 | clap = { workspace = true } 16 | chrono = { workspace = true, default-features = false, features = [ 17 | "clock", 18 | ] } 19 | libc = { workspace = true } 20 | 21 | [lib] 22 | path = "src/w.rs" 23 | 24 | [[bin]] 25 | name = "w" 26 | path = "src/main.rs" 27 | -------------------------------------------------------------------------------- /src/uu/w/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_w); 2 | -------------------------------------------------------------------------------- /src/uu/w/w.md: -------------------------------------------------------------------------------- 1 | # w 2 | 3 | ``` 4 | w [options] [user] 5 | ``` 6 | 7 | Show who is logged on and what they are doing 8 | -------------------------------------------------------------------------------- /src/uu/watch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uu_watch" 3 | description = "watch ~ (uutils) Execute a program periodically, showing output fullscreen" 4 | repository = "https://github.com/uutils/procps/tree/main/src/uu/watch" 5 | authors.workspace = true 6 | categories.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | keywords.workspace = true 10 | license.workspace = true 11 | version.workspace = true 12 | 13 | [dependencies] 14 | uucore = { workspace = true } 15 | clap = { workspace = true } 16 | 17 | [lib] 18 | path = "src/watch.rs" 19 | 20 | [[bin]] 21 | name = "watch" 22 | path = "src/main.rs" 23 | -------------------------------------------------------------------------------- /src/uu/watch/src/main.rs: -------------------------------------------------------------------------------- 1 | uucore::bin!(uu_watch); 2 | -------------------------------------------------------------------------------- /src/uu/watch/src/watch.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use clap::crate_version; 7 | use clap::{Arg, Command}; 8 | use std::io::{Error, ErrorKind}; 9 | use std::num::ParseIntError; 10 | use std::process::{Command as SystemCommand, Stdio}; 11 | use std::thread::sleep; 12 | use std::time::Duration; 13 | use uucore::{error::UResult, format_usage, help_about, help_usage}; 14 | 15 | const ABOUT: &str = help_about!("watch.md"); 16 | const USAGE: &str = help_usage!("watch.md"); 17 | 18 | fn parse_interval(input: &str) -> Result { 19 | // Find index where to split string into seconds and nanos 20 | let Some(index) = input.find([',', '.']) else { 21 | let seconds: u64 = input.parse()?; 22 | return Ok(Duration::new(seconds, 0)); 23 | }; 24 | 25 | // If the seconds string is empty, set seconds to 0 26 | let seconds: u64 = if index > 0 { 27 | input[..index].parse()? 28 | } else { 29 | 0 30 | }; 31 | 32 | let nanos_string = &input[index + 1..]; 33 | let nanos: u32 = match nanos_string.len() { 34 | // If nanos string is empty, set nanos to 0 35 | 0 => 0, 36 | 1..=9 => { 37 | let nanos: u32 = nanos_string.parse()?; 38 | nanos * 10u32.pow((9 - nanos_string.len()) as u32) 39 | } 40 | _ => { 41 | // This parse is used to validate if the rest of the string is indeed numeric 42 | if nanos_string.find(|c: char| !c.is_numeric()).is_some() { 43 | "a".parse::()?; 44 | } 45 | // We can have only 9 digits of accuracy, trim the rest 46 | nanos_string[..9].parse()? 47 | } 48 | }; 49 | 50 | let duration = Duration::new(seconds, nanos); 51 | // Minimum duration of sleep to 0.1 s 52 | Ok(std::cmp::max(duration, Duration::from_millis(100))) 53 | } 54 | 55 | #[uucore::main] 56 | pub fn uumain(args: impl uucore::Args) -> UResult<()> { 57 | let matches = uu_app().try_get_matches_from(args)?; 58 | 59 | let command_to_watch = matches 60 | .get_one::("command") 61 | .expect("required argument"); 62 | let interval = match matches.get_one::("interval") { 63 | None => Duration::from_secs(2), 64 | Some(input) => match parse_interval(input) { 65 | Ok(interval) => interval, 66 | Err(_) => { 67 | return Err(Box::from(Error::new( 68 | ErrorKind::InvalidInput, 69 | format!("watch: failed to parse argument: '{input}': Invalid argument"), 70 | ))); 71 | } 72 | }, 73 | }; 74 | 75 | loop { 76 | #[cfg(windows)] 77 | let mut command = 78 | SystemCommand::new(std::env::var_os("COMSPEC").unwrap_or_else(|| "cmd.exe".into())); 79 | #[cfg(not(windows))] 80 | let mut command = SystemCommand::new("sh"); 81 | 82 | #[cfg(windows)] 83 | command.arg("/c"); 84 | #[cfg(not(windows))] 85 | command.arg("-c"); 86 | 87 | let output = command 88 | .arg(command_to_watch) 89 | .stdout(Stdio::inherit()) 90 | .stderr(Stdio::inherit()) 91 | .output()?; 92 | 93 | if !output.status.success() { 94 | eprintln!("watch: command failed: {:?}", output.status); 95 | break; 96 | } 97 | 98 | sleep(interval); 99 | } 100 | 101 | Ok(()) 102 | } 103 | 104 | pub fn uu_app() -> Command { 105 | Command::new(uucore::util_name()) 106 | .version(crate_version!()) 107 | .about(ABOUT) 108 | .override_usage(format_usage(USAGE)) 109 | .infer_long_args(true) 110 | .arg( 111 | Arg::new("command") 112 | .required(true) 113 | .help("Command to be executed"), 114 | ) 115 | .arg( 116 | Arg::new("interval") 117 | .short('n') 118 | .long("interval") 119 | .help("Seconds to wait between updates") 120 | .default_value("2") 121 | .env("WATCH_INTERVAL") 122 | .value_name("SECONDS"), 123 | ) 124 | .arg( 125 | Arg::new("beep") 126 | .short('b') 127 | .long("beep") 128 | .help("Beep if command has a non-zero exit"), 129 | ) 130 | .arg( 131 | Arg::new("color") 132 | .short('c') 133 | .long("color") 134 | .help("Interpret ANSI color and style sequences"), 135 | ) 136 | .arg( 137 | Arg::new("no-color") 138 | .short('C') 139 | .long("no-color") 140 | .help("Do not interpret ANSI color and style sequences"), 141 | ) 142 | .arg( 143 | Arg::new("differences") 144 | .short('d') 145 | .long("differences") 146 | .value_name("permanent") 147 | .help("Highlight changes between updates"), 148 | ) 149 | .arg( 150 | Arg::new("errexit") 151 | .short('e') 152 | .long("errexit") 153 | .help("Exit if command has a non-zero exit"), 154 | ) 155 | .arg( 156 | Arg::new("chgexit") 157 | .short('g') 158 | .long("chgexit") 159 | .help("Exit when output from command changes"), 160 | ) 161 | .arg( 162 | Arg::new("equexit") 163 | .short('q') 164 | .long("equexit") 165 | .value_name("CYCLES") 166 | .help("Exit when output from command does not change"), 167 | ) 168 | .arg( 169 | Arg::new("precise") 170 | .short('p') 171 | .long("precise") 172 | .help("Attempt to run command in precise intervals"), 173 | ) 174 | .arg( 175 | Arg::new("no-rerun") 176 | .short('r') 177 | .long("no-rerun") 178 | .help("Do not rerun program on window resize"), 179 | ) 180 | .arg( 181 | Arg::new("no-title") 182 | .short('t') 183 | .long("no-title") 184 | .help("Turn off header"), 185 | ) 186 | .arg( 187 | Arg::new("no-wrap") 188 | .short('w') 189 | .long("no-wrap") 190 | .help("Turn off line wrapping"), 191 | ) 192 | .arg( 193 | Arg::new("exec") 194 | .short('x') 195 | .long("exec") 196 | .help("Pass command to exec instead of 'sh -c'"), 197 | ) 198 | } 199 | 200 | #[cfg(test)] 201 | mod parse_interval_tests { 202 | use super::*; 203 | 204 | #[test] 205 | fn test_comma_parse() { 206 | let interval = parse_interval("1,5"); 207 | assert_eq!(Ok(Duration::from_millis(1500)), interval); 208 | } 209 | 210 | #[test] 211 | fn test_different_nanos_length() { 212 | let interval = parse_interval("1.12345"); 213 | assert_eq!(Ok(Duration::new(1, 123450000)), interval); 214 | let interval = parse_interval("1.1234"); 215 | assert_eq!(Ok(Duration::new(1, 123400000)), interval); 216 | } 217 | 218 | #[test] 219 | fn test_period_parse() { 220 | let interval = parse_interval("1.5"); 221 | assert_eq!(Ok(Duration::from_millis(1500)), interval); 222 | } 223 | 224 | #[test] 225 | fn test_empty_seconds_interval() { 226 | let interval = parse_interval(".5"); 227 | assert_eq!(Ok(Duration::from_millis(500)), interval); 228 | } 229 | 230 | #[test] 231 | fn test_seconds_only() { 232 | let interval = parse_interval("7"); 233 | assert_eq!(Ok(Duration::from_secs(7)), interval); 234 | } 235 | 236 | #[test] 237 | fn test_empty_nanoseconds_interval() { 238 | let interval = parse_interval("1."); 239 | assert_eq!(Ok(Duration::from_millis(1000)), interval); 240 | } 241 | 242 | #[test] 243 | fn test_too_many_nanos() { 244 | let interval = parse_interval("1.00000000009"); 245 | assert_eq!(Ok(Duration::from_secs(1)), interval); 246 | } 247 | 248 | #[test] 249 | fn test_invalid_nano() { 250 | let interval = parse_interval("1.00000000000a"); 251 | assert!(interval.is_err()) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/uu/watch/watch.md: -------------------------------------------------------------------------------- 1 | # watch 2 | 3 | ``` 4 | watch [options] command 5 | ``` 6 | 7 | Execute a program periodically, showing output fullscreen -------------------------------------------------------------------------------- /tests/by-util/test_free.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use pretty_assertions::assert_eq; 7 | use regex::Regex; 8 | 9 | use uutests::new_ucmd; 10 | use uutests::util::TestScenario; 11 | use uutests::util_name; 12 | 13 | // TODO: make tests combineable (e.g. test --total --human) 14 | 15 | #[test] 16 | fn test_invalid_arg() { 17 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 18 | } 19 | 20 | #[test] 21 | fn test_no_args() { 22 | let output = new_ucmd!().succeeds().stdout_move_str(); 23 | 24 | assert_eq!(output.len(), 207); 25 | assert_default_format(&output); 26 | } 27 | 28 | #[test] 29 | fn test_line() { 30 | let pattern = Regex::new(r"^SwapUse +\d+ CachUse +\d+ {2}MemUse +\d+ MemFree +\d+\n$").unwrap(); 31 | 32 | for arg in ["-L", "--line"] { 33 | new_ucmd!().arg(arg).succeeds().stdout_matches(&pattern); 34 | } 35 | 36 | // ensure --line "wins" 37 | for arg in ["--lohi", "--total", "--committed", "--wide"] { 38 | new_ucmd!() 39 | .arg(arg) 40 | .arg("--line") 41 | .arg(arg) 42 | .succeeds() 43 | .stdout_matches(&pattern); 44 | } 45 | } 46 | 47 | #[test] 48 | fn test_wide() { 49 | let header_pattern = r"^ {15}total {8}used {8}free {6}shared {5}buffers {7}cache {3}available$"; 50 | let mem_pattern = r"^Mem:( +\d+){7}$"; 51 | let swap_pattern = r"^Swap: ( +\d+){3}$"; 52 | 53 | let patterns = vec![ 54 | Regex::new(header_pattern).unwrap(), 55 | Regex::new(mem_pattern).unwrap(), 56 | Regex::new(swap_pattern).unwrap(), 57 | ]; 58 | 59 | for arg in ["-w", "--wide"] { 60 | let binding = new_ucmd!().arg(arg).succeeds(); 61 | let output = binding.stdout_str(); 62 | 63 | // The total number of character is always fixed 64 | assert_eq!(output.len(), 231); 65 | 66 | // Check the format for each line output 67 | let mut lines = output.lines(); 68 | for pattern in &patterns { 69 | assert!(pattern.is_match(lines.next().unwrap())); 70 | } 71 | } 72 | } 73 | 74 | #[test] 75 | fn test_total() { 76 | for arg in ["-t", "--total"] { 77 | let result = new_ucmd!().arg(arg).succeeds(); 78 | assert_eq!(result.stdout_str().lines().count(), 4); 79 | assert!(result 80 | .stdout_str() 81 | .lines() 82 | .last() 83 | .unwrap() 84 | .starts_with("Total:")); 85 | } 86 | } 87 | 88 | #[test] 89 | fn test_count() { 90 | for arg in ["-c", "--count"] { 91 | let output = new_ucmd!() 92 | // without -s, there would be a delay of 1s between the output of the 93 | // two blocks 94 | .args(&[arg, "2", "-s", "0.00001"]) 95 | .succeeds() 96 | .stdout_move_str(); 97 | 98 | let lines: Vec<&str> = output.lines().collect(); 99 | 100 | assert_default_format(&lines[..3].join("\n")); 101 | assert!(lines[3].is_empty()); 102 | assert_default_format(&lines[4..].join("\n")); 103 | } 104 | } 105 | 106 | #[test] 107 | fn test_count_line() { 108 | let re = Regex::new(r"^SwapUse +\d+ CachUse +\d+ {2}MemUse +\d+ MemFree +\d+$").unwrap(); 109 | 110 | let output = new_ucmd!() 111 | .args(&["--count", "2", "--line", "-s", "0.00001"]) 112 | .succeeds() 113 | .stdout_move_str(); 114 | 115 | let lines: Vec<&str> = output.lines().collect(); 116 | 117 | assert_eq!(2, lines.len()); 118 | 119 | assert!(re.is_match(lines[0])); 120 | assert!(re.is_match(lines[1])); 121 | } 122 | 123 | #[test] 124 | fn test_count_zero() { 125 | new_ucmd!() 126 | .arg("--count=0") 127 | .fails() 128 | .code_is(1) 129 | .stderr_only("free: count argument must be greater than 0\n"); 130 | } 131 | 132 | #[test] 133 | fn test_lohi() { 134 | for arg in ["-l", "--lohi"] { 135 | let result = new_ucmd!().arg(arg).succeeds(); 136 | assert_eq!(result.stdout_str().lines().count(), 5); 137 | let lines = result.stdout_str().lines().collect::>(); 138 | assert!(lines[2].starts_with("Low:")); 139 | assert!(lines[3].starts_with("High:")); 140 | } 141 | } 142 | 143 | #[test] 144 | fn test_committed() { 145 | for arg in ["-v", "--committed"] { 146 | let result = new_ucmd!().arg(arg).succeeds(); 147 | assert_eq!(result.stdout_str().lines().count(), 4); 148 | assert!(result 149 | .stdout_str() 150 | .lines() 151 | .last() 152 | .unwrap() 153 | .starts_with("Comm:")); 154 | } 155 | } 156 | 157 | #[test] 158 | fn test_seconds_zero() { 159 | for arg in ["-s", "--seconds"] { 160 | new_ucmd!() 161 | .arg(arg) 162 | .arg("0") 163 | .fails() 164 | .code_is(1) 165 | .stderr_only("free: seconds argument must be greater than 0\n"); 166 | } 167 | } 168 | 169 | #[test] 170 | fn test_unit() { 171 | fn extract_total(re: &Regex, output: &str) -> u64 { 172 | re.captures(output) 173 | .unwrap() 174 | .get(1) 175 | .unwrap() 176 | .as_str() 177 | .parse::() 178 | .unwrap() 179 | } 180 | 181 | let kibi_output = new_ucmd!().succeeds().stdout_move_str(); 182 | let total_mem_re = Regex::new(r"Mem:\s+(\d{1,12})").unwrap(); 183 | let total_swap_re = Regex::new(r"Swap:\s+(\d{1,12})").unwrap(); 184 | let total_mem_bytes = extract_total(&total_mem_re, &kibi_output) * 1024; 185 | let total_swap_bytes = extract_total(&total_swap_re, &kibi_output) * 1024; 186 | 187 | let base: u64 = 1024; 188 | let base_si: u64 = 1000; 189 | for (args, divisor) in vec![ 190 | (vec!["--kilo"], base_si), 191 | (vec!["--mega"], base_si.pow(2)), 192 | (vec!["--giga"], base_si.pow(3)), 193 | (vec!["--tera"], base_si.pow(4)), 194 | (vec!["--peta"], base_si.pow(5)), 195 | (vec!["--kilo", "--si"], base_si), 196 | (vec!["--mega", "--si"], base_si.pow(2)), 197 | (vec!["--giga", "--si"], base_si.pow(3)), 198 | (vec!["--tera", "--si"], base_si.pow(4)), 199 | (vec!["--peta", "--si"], base_si.pow(5)), 200 | (vec!["--kibi"], base), 201 | (vec!["--mebi"], base.pow(2)), 202 | (vec!["--gibi"], base.pow(3)), 203 | (vec!["--tebi"], base.pow(4)), 204 | (vec!["--pebi"], base.pow(5)), 205 | (vec!["--kibi", "--si"], base_si), 206 | (vec!["--mebi", "--si"], base_si.pow(2)), 207 | (vec!["--gibi", "--si"], base_si.pow(3)), 208 | (vec!["--tebi", "--si"], base_si.pow(4)), 209 | (vec!["--pebi", "--si"], base_si.pow(5)), 210 | (vec![], base), 211 | (vec!["--si"], base_si), 212 | ] { 213 | let output = new_ucmd!().args(&args).succeeds().stdout_move_str(); 214 | let total_mem = extract_total(&total_mem_re, &output); 215 | let total_swap = extract_total(&total_swap_re, &output); 216 | assert_eq!(total_mem, total_mem_bytes / divisor); 217 | assert_eq!(total_swap, total_swap_bytes / divisor); 218 | } 219 | } 220 | 221 | fn assert_default_format(s: &str) { 222 | let header_pattern = r"^ {15}total {8}used {8}free {6}shared {2}buff/cache {3}available$"; 223 | let mem_pattern = r"^Mem:( +\d+){6}$"; 224 | let swap_pattern = r"^Swap: ( +\d+){3}$"; 225 | 226 | let patterns = vec![ 227 | Regex::new(header_pattern).unwrap(), 228 | Regex::new(mem_pattern).unwrap(), 229 | Regex::new(swap_pattern).unwrap(), 230 | ]; 231 | 232 | // Check the format for each line output 233 | let mut lines = s.lines(); 234 | for pattern in patterns { 235 | assert!(pattern.is_match(lines.next().unwrap())); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /tests/by-util/test_pidof.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use uutests::new_ucmd; 7 | use uutests::util::TestScenario; 8 | use uutests::util_name; 9 | 10 | #[test] 11 | fn test_no_args() { 12 | new_ucmd!().fails().code_is(1).no_output(); 13 | } 14 | 15 | #[test] 16 | fn test_invalid_arg() { 17 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 18 | } 19 | 20 | #[test] 21 | #[cfg(target_os = "linux")] 22 | #[ignore = "fails in CI"] 23 | fn test_find_init() { 24 | new_ucmd!().arg("init").succeeds(); 25 | } 26 | 27 | #[test] 28 | #[cfg(target_os = "linux")] 29 | fn test_find_kthreadd_only_with_w_flag() { 30 | new_ucmd!().arg("kthreadd").fails(); 31 | new_ucmd!().arg("-w").arg("kthreadd").succeeds(); 32 | } 33 | 34 | #[test] 35 | #[cfg(target_os = "linux")] 36 | fn test_no_pid_found() { 37 | new_ucmd!().arg("NO_THIS_PROGRAM").fails().code_is(1); 38 | } 39 | 40 | #[test] 41 | #[cfg(target_os = "linux")] 42 | fn test_quiet() { 43 | new_ucmd!() 44 | .arg("kthreadd") 45 | .arg("-q") 46 | .arg("-w") 47 | .succeeds() 48 | .no_output(); 49 | } 50 | 51 | #[test] 52 | #[cfg(target_os = "linux")] 53 | fn test_check_root_accepted() { 54 | new_ucmd!() 55 | .arg("-w") 56 | .arg("--check-root") 57 | .arg("kthreadd") 58 | .succeeds(); 59 | } 60 | 61 | #[test] 62 | #[cfg(target_os = "linux")] 63 | fn test_single_shot() { 64 | for arg in ["-s", "--single-shot"] { 65 | let binding = new_ucmd!() 66 | .args(&[arg, "-w", "kthreadd", "kthreadd", "kthreadd"]) 67 | .succeeds(); 68 | let output = binding.stdout_str().trim_end(); 69 | 70 | let pids = output.split(' ').collect::>(); 71 | let first = pids[0]; 72 | 73 | let result = pids.iter().all(|it| *it == first); 74 | 75 | assert!(result); 76 | assert_eq!(pids.len(), 3); 77 | } 78 | } 79 | 80 | #[test] 81 | #[cfg(target_os = "linux")] 82 | fn test_omit_pid() { 83 | for arg in ["-o=1000", "--omit-pid=1000"] { 84 | new_ucmd!().arg(arg).arg("-w").arg("kthreadd").succeeds(); 85 | } 86 | } 87 | 88 | #[test] 89 | #[cfg(target_os = "linux")] 90 | fn test_separator() { 91 | use regex::Regex; 92 | 93 | let re = &Regex::new("^[1-9][0-9]*separator[1-9][0-9]*\n$").unwrap(); 94 | 95 | for arg in ["-S", "-d", "--separator"] { 96 | new_ucmd!() 97 | .args(&[arg, "separator", "-w", "kthreadd", "kthreadd"]) 98 | .succeeds() 99 | .stdout_matches(re); 100 | } 101 | } 102 | 103 | #[test] 104 | #[cfg(target_os = "linux")] 105 | fn test_threads() { 106 | let main_tid = unsafe { uucore::libc::gettid() }; 107 | std::thread::spawn(move || { 108 | let argv0 = std::env::args().next().unwrap(); 109 | let our_name = std::path::Path::new(argv0.as_str()) 110 | .file_name() 111 | .unwrap() 112 | .to_str() 113 | .unwrap(); 114 | 115 | let new_thread_tid = unsafe { uucore::libc::gettid() }; 116 | new_ucmd!() 117 | .arg("-t") 118 | .arg(our_name) 119 | .succeeds() 120 | .stdout_contains(main_tid.to_string()) 121 | .stdout_contains(new_thread_tid.to_string()); 122 | }) 123 | .join() 124 | .unwrap(); 125 | } 126 | 127 | #[test] 128 | #[cfg(target_os = "linux")] 129 | fn test_script() { 130 | use std::os::unix::fs::PermissionsExt; 131 | 132 | let temp_dir = tempfile::tempdir().unwrap(); 133 | let script_path = temp_dir 134 | .path() 135 | .join("dummy_test_script_with_very_long_name"); 136 | std::fs::write(&script_path, "#!/bin/sh\nsleep 2").unwrap(); 137 | std::fs::set_permissions(&script_path, std::fs::Permissions::from_mode(0o755)).unwrap(); 138 | 139 | let mut directly_executed_child = std::process::Command::new(&script_path).spawn().unwrap(); 140 | new_ucmd!() 141 | .arg("dummy_test_script_with_very_long_name") 142 | .fails(); 143 | new_ucmd!() 144 | .args(&["-x", "dummy_test_script_with_very_long_name"]) 145 | .succeeds() 146 | .stdout_contains(directly_executed_child.id().to_string()); 147 | directly_executed_child.kill().unwrap(); 148 | directly_executed_child.wait().unwrap(); 149 | 150 | let mut executed_via_sh_child = std::process::Command::new("/bin/sh") 151 | .arg(&script_path) 152 | .spawn() 153 | .unwrap(); 154 | new_ucmd!() 155 | .arg("dummy_test_script_with_very_long_name") 156 | .fails(); 157 | new_ucmd!() 158 | .args(&["-x", "dummy_test_script_with_very_long_name"]) 159 | .fails(); 160 | executed_via_sh_child.kill().unwrap(); 161 | executed_via_sh_child.wait().unwrap(); 162 | } 163 | -------------------------------------------------------------------------------- /tests/by-util/test_pidwait.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use uutests::new_ucmd; 7 | use uutests::util::TestScenario; 8 | use uutests::util_name; 9 | 10 | #[test] 11 | fn test_invalid_arg() { 12 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 13 | } 14 | 15 | #[test] 16 | fn test_non_matching_pattern() { 17 | new_ucmd!() 18 | .arg("THIS_PATTERN_DOES_NOT_MATCH") 19 | .fails() 20 | .code_is(1) 21 | .stderr_contains("pidwait: pattern that searches for process name longer than 15 characters will result in zero matches"); 22 | 23 | new_ucmd!() 24 | .arg("DOES_NOT_MATCH") 25 | .fails() 26 | .code_is(1) 27 | .no_output(); 28 | } 29 | 30 | #[test] 31 | fn test_no_args() { 32 | new_ucmd!() 33 | .fails() 34 | .code_is(2) 35 | .no_stdout() 36 | .stderr_contains("no matching criteria specified"); 37 | } 38 | 39 | #[test] 40 | fn test_too_many_patterns() { 41 | new_ucmd!() 42 | .arg("sh") 43 | .arg("sh") 44 | .fails() 45 | .code_is(2) 46 | .no_stdout() 47 | .stderr_contains("only one pattern can be provided"); 48 | } 49 | -------------------------------------------------------------------------------- /tests/by-util/test_pkill.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | #[cfg(unix)] 7 | use uutests::new_ucmd; 8 | #[cfg(unix)] 9 | use uutests::util::TestScenario; 10 | #[cfg(unix)] 11 | use uutests::util_name; 12 | 13 | #[cfg(unix)] 14 | #[test] 15 | fn test_no_args() { 16 | new_ucmd!() 17 | .fails() 18 | .code_is(2) 19 | .no_stdout() 20 | .stderr_contains("no matching criteria specified"); 21 | } 22 | 23 | #[cfg(unix)] 24 | #[test] 25 | fn test_non_matching_pattern() { 26 | new_ucmd!() 27 | .arg("NONMATCHING") 28 | .fails() 29 | .code_is(1) 30 | .no_output(); 31 | } 32 | 33 | #[cfg(unix)] 34 | #[test] 35 | fn test_too_many_patterns() { 36 | new_ucmd!() 37 | .arg("sh") 38 | .arg("sh") 39 | .fails() 40 | .code_is(2) 41 | .no_stdout() 42 | .stderr_contains("only one pattern can be provided"); 43 | } 44 | 45 | #[cfg(unix)] 46 | #[test] 47 | fn test_invalid_arg() { 48 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 49 | } 50 | 51 | #[cfg(target_os = "linux")] 52 | #[test] 53 | fn test_inverse() { 54 | new_ucmd!() 55 | .arg("-0") 56 | .arg("--inverse") 57 | .arg("NONEXISTENT") 58 | .fails() 59 | .stderr_contains("Permission denied"); 60 | } 61 | 62 | #[cfg(unix)] 63 | #[test] 64 | fn test_help() { 65 | new_ucmd!().arg("--help").succeeds(); 66 | } 67 | 68 | #[test] 69 | #[cfg(target_os = "linux")] 70 | fn test_too_long_pattern() { 71 | new_ucmd!() 72 | .arg("THIS_IS_OVER_16_CHARS") 73 | .fails() 74 | .code_is(1) 75 | .stderr_contains("pattern that searches for process name longer than 15 characters will result in zero matches"); 76 | } 77 | -------------------------------------------------------------------------------- /tests/by-util/test_ps.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use uutests::new_ucmd; 7 | use uutests::util::TestScenario; 8 | use uutests::util_name; 9 | 10 | #[test] 11 | #[cfg(target_os = "linux")] 12 | fn test_select_all_processes() { 13 | for arg in ["-A", "-e"] { 14 | // TODO ensure the output format is correct 15 | new_ucmd!().arg(arg).succeeds(); 16 | } 17 | } 18 | 19 | #[test] 20 | fn test_invalid_arg() { 21 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 22 | } 23 | 24 | #[test] 25 | #[cfg(target_os = "linux")] 26 | fn test_code_mapping() { 27 | new_ucmd!() 28 | .args(&["-o", "cmd=CCMD"]) 29 | .succeeds() 30 | .stdout_contains("CCMD"); 31 | 32 | new_ucmd!().args(&["-o", "cmd= "]).succeeds(); 33 | 34 | new_ucmd!().args(&["-o", "ccmd=CCMD"]).fails().code_is(1); 35 | 36 | new_ucmd!() 37 | .args(&["-o", "cmd=CMD1", "-o", "cmd=CMD2"]) 38 | .succeeds() 39 | .stdout_contains("CMD1") 40 | .stdout_contains("CMD2"); 41 | 42 | new_ucmd!() 43 | .args(&["-o", "cmd=CMD1,cmd=CMD2"]) 44 | .succeeds() 45 | .stdout_contains("CMD1") 46 | .stdout_contains("CMD2"); 47 | 48 | new_ucmd!() 49 | .args(&["-o", "ucmd=CMD1", "-o", "ucmd=CMD2"]) 50 | .succeeds() 51 | .stdout_contains("CMD1") 52 | .stdout_contains("CMD2"); 53 | 54 | new_ucmd!() 55 | .args(&["-o", "ucmd=CMD1,ucmd=CMD2"]) 56 | .succeeds() 57 | .stdout_contains("CMD1") 58 | .stdout_contains("CMD2"); 59 | } 60 | -------------------------------------------------------------------------------- /tests/by-util/test_pwdx.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use std::process; 7 | 8 | use regex::Regex; 9 | 10 | use uutests::new_ucmd; 11 | use uutests::util::TestScenario; 12 | use uutests::util_name; 13 | 14 | #[test] 15 | fn test_no_args() { 16 | new_ucmd!().fails().code_is(1); 17 | } 18 | 19 | #[test] 20 | fn test_valid_pid() { 21 | let pid = process::id(); 22 | 23 | new_ucmd!() 24 | .arg(pid.to_string()) 25 | .succeeds() 26 | .stdout_matches(&Regex::new(&format!("^{pid}: .+\n$")).unwrap()); 27 | } 28 | 29 | #[test] 30 | fn test_multiple_valid_pids() { 31 | let pid = process::id(); 32 | 33 | new_ucmd!() 34 | .arg(pid.to_string()) 35 | .arg(pid.to_string()) 36 | .succeeds() 37 | // (?m) enables multi-line mode 38 | .stdout_matches(&Regex::new(&format!("(?m)^{pid}: .+$")).unwrap()); 39 | } 40 | 41 | #[test] 42 | fn test_non_existing_pid() { 43 | let non_existing_pid = "999999"; 44 | 45 | new_ucmd!() 46 | .arg(non_existing_pid) 47 | .fails() 48 | .code_is(1) 49 | .no_stdout() 50 | .stderr_is(format!("{non_existing_pid}: No such process\n")); 51 | } 52 | 53 | #[test] 54 | fn test_non_existing_and_existing_pid() { 55 | let pid = process::id(); 56 | let non_existing_pid = "999999"; 57 | 58 | new_ucmd!() 59 | .arg(non_existing_pid) 60 | .arg(pid.to_string()) 61 | .fails() 62 | .code_is(1) 63 | .stdout_matches(&Regex::new(&format!("^{pid}: .+\n$")).unwrap()) 64 | .stderr_is(format!("{non_existing_pid}: No such process\n")); 65 | 66 | new_ucmd!() 67 | .arg(pid.to_string()) 68 | .arg(non_existing_pid) 69 | .fails() 70 | .code_is(1) 71 | .stdout_matches(&Regex::new(&format!("^{pid}: .+\n$")).unwrap()) 72 | .stderr_is(format!("{non_existing_pid}: No such process\n")); 73 | } 74 | 75 | #[test] 76 | fn test_invalid_pid() { 77 | for invalid_pid in ["0", "invalid"] { 78 | new_ucmd!() 79 | .arg(invalid_pid) 80 | .fails() 81 | .code_is(1) 82 | .no_stdout() 83 | .stderr_contains(format!("invalid process id: {invalid_pid}")); 84 | } 85 | } 86 | 87 | #[test] 88 | fn test_invalid_and_valid_pid() { 89 | let pid = process::id(); 90 | let invalid_pid = "invalid"; 91 | 92 | new_ucmd!() 93 | .arg(invalid_pid) 94 | .arg(pid.to_string()) 95 | .fails() 96 | .code_is(1) 97 | .no_stdout() 98 | .stderr_contains(format!("invalid process id: {invalid_pid}")); 99 | 100 | new_ucmd!() 101 | .arg(pid.to_string()) 102 | .arg(invalid_pid) 103 | .fails() 104 | .code_is(1) 105 | .stdout_matches(&Regex::new(&format!("^{pid}: .+\n$")).unwrap()) 106 | .stderr_contains(format!("invalid process id: {invalid_pid}")); 107 | } 108 | 109 | #[test] 110 | fn test_invalid_arg() { 111 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 112 | } 113 | -------------------------------------------------------------------------------- /tests/by-util/test_slabtop.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | #[cfg(target_os = "linux")] 7 | use uutests::util::run_ucmd_as_root; 8 | 9 | use uutests::new_ucmd; 10 | use uutests::util::TestScenario; 11 | use uutests::util_name; 12 | 13 | #[test] 14 | fn test_invalid_arg() { 15 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 16 | } 17 | 18 | #[test] 19 | fn test_help() { 20 | new_ucmd!().arg("--help").succeeds().code_is(0); 21 | } 22 | 23 | #[cfg(target_os = "linux")] 24 | #[test] 25 | fn test_without_args_as_non_root() { 26 | new_ucmd!() 27 | .fails() 28 | .code_is(1) 29 | .stderr_contains("Permission denied"); 30 | } 31 | 32 | // TODO: tests some temporary behavior; in the future a TUI should be used 33 | // if there are no args 34 | #[cfg(target_os = "linux")] 35 | #[test] 36 | fn test_without_args_as_root() { 37 | let ts = TestScenario::new(util_name!()); 38 | 39 | if let Ok(result) = run_ucmd_as_root(&ts, &[]) { 40 | result 41 | .success() 42 | .stdout_contains("Active / Total Objects") 43 | .stdout_contains("OBJS"); 44 | } else { 45 | print!("Test skipped; requires root user"); 46 | } 47 | } 48 | 49 | #[cfg(target_os = "linux")] 50 | #[test] 51 | fn test_once_as_non_root() { 52 | for arg in ["-o", "--once"] { 53 | new_ucmd!() 54 | .arg(arg) 55 | .fails() 56 | .code_is(1) 57 | .stderr_contains("Permission denied"); 58 | } 59 | } 60 | 61 | #[cfg(target_os = "linux")] 62 | #[test] 63 | fn test_once_as_root() { 64 | let ts = TestScenario::new(util_name!()); 65 | 66 | for arg in ["-o", "--once"] { 67 | if let Ok(result) = run_ucmd_as_root(&ts, &[arg]) { 68 | result 69 | .success() 70 | .stdout_contains("Active / Total Objects") 71 | .stdout_contains("OBJS"); 72 | } else { 73 | print!("Test skipped; requires root user"); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/by-util/test_snice.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use uutests::new_ucmd; 7 | use uutests::util::TestScenario; 8 | use uutests::util_name; 9 | 10 | #[test] 11 | fn test_no_args() { 12 | new_ucmd!().fails().code_is(1); 13 | } 14 | 15 | #[test] 16 | fn test_no_process_selected() { 17 | new_ucmd!().arg("-u=invalid_user").fails().code_is(1); 18 | } 19 | -------------------------------------------------------------------------------- /tests/by-util/test_sysctl.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use uutests::new_ucmd; 7 | use uutests::util::TestScenario; 8 | use uutests::util_name; 9 | 10 | #[test] 11 | fn test_invalid_arg() { 12 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 13 | } 14 | 15 | #[cfg(target_os = "linux")] 16 | mod linux { 17 | 18 | use uutests::new_ucmd; 19 | use uutests::util::TestScenario; 20 | use uutests::util_name; 21 | 22 | #[test] 23 | fn test_get_simple() { 24 | new_ucmd!() 25 | .arg("kernel.ostype") 26 | .arg("fs.overflowuid") 27 | .succeeds() 28 | .stdout_is("kernel.ostype = Linux\nfs.overflowuid = 65534\n"); 29 | } 30 | 31 | #[test] 32 | fn test_get_value_only() { 33 | new_ucmd!() 34 | .arg("-n") 35 | .arg("kernel.ostype") 36 | .arg("fs.overflowuid") 37 | .succeeds() 38 | .stdout_is("Linux\n65534\n"); 39 | } 40 | 41 | #[test] 42 | fn test_get_key_only() { 43 | new_ucmd!() 44 | .arg("-N") 45 | .arg("kernel.ostype") 46 | .arg("fs.overflowuid") 47 | .succeeds() 48 | .stdout_is("kernel.ostype\nfs.overflowuid\n"); 49 | } 50 | 51 | #[test] 52 | fn test_continues_on_error() { 53 | new_ucmd!() 54 | .arg("nonexisting") 55 | .arg("kernel.ostype") 56 | .fails() 57 | .stdout_is("kernel.ostype = Linux\n") 58 | .stderr_is("sysctl: error reading key 'nonexisting': No such file or directory\n"); 59 | } 60 | 61 | #[test] 62 | fn test_ignoring_errors() { 63 | new_ucmd!() 64 | .arg("-e") 65 | .arg("nonexisting") 66 | .arg("nonexisting2=foo") 67 | .arg("kernel.ostype") 68 | .succeeds() 69 | .stdout_is("kernel.ostype = Linux\n") 70 | .stderr_is(""); 71 | } 72 | } 73 | 74 | #[cfg(not(target_os = "linux"))] 75 | mod non_linux { 76 | 77 | use uutests::new_ucmd; 78 | use uutests::util::TestScenario; 79 | use uutests::util_name; 80 | 81 | #[test] 82 | fn test_fails_on_unsupported_platforms() { 83 | new_ucmd!() 84 | .arg("-a") 85 | .fails() 86 | .code_is(1) 87 | .stderr_is("sysctl: `sysctl` currently only supports Linux.\n"); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/by-util/test_tload.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use uutests::new_ucmd; 7 | use uutests::util::TestScenario; 8 | use uutests::util_name; 9 | 10 | #[test] 11 | fn test_invalid_arg() { 12 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 13 | } 14 | -------------------------------------------------------------------------------- /tests/by-util/test_top.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use uutests::new_ucmd; 7 | use uutests::util::TestScenario; 8 | use uutests::util_name; 9 | 10 | #[test] 11 | fn test_invalid_arg() { 12 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 13 | } 14 | 15 | #[test] 16 | fn test_conflict_arg() { 17 | new_ucmd!().arg("-p=0").arg("-U=0").fails().code_is(1); 18 | } 19 | 20 | #[test] 21 | fn test_flag_user() { 22 | let check = |output: &str| { 23 | assert!(output 24 | .lines() 25 | .map(|it| it.split_whitespace().collect::>()) 26 | .filter(|it| it.len() >= 2) 27 | .filter(|it| it[0].parse::().is_ok()) 28 | .all(|it| it[1] == "root")); 29 | }; 30 | 31 | #[cfg(target_family = "unix")] 32 | check( 33 | new_ucmd!() 34 | .arg("-U=root") 35 | .succeeds() 36 | .code_is(0) 37 | .stdout_str(), 38 | ); 39 | 40 | check(new_ucmd!().arg("-U=0").succeeds().code_is(0).stdout_str()); 41 | 42 | new_ucmd!().arg("-U=19999").succeeds().code_is(0); 43 | 44 | new_ucmd!().arg("-U=NOT_EXIST").fails().code_is(1); 45 | } 46 | 47 | #[test] 48 | fn test_arg_p() { 49 | new_ucmd!().arg("-p=1").succeeds().code_is(0); 50 | new_ucmd!().arg("-p=1,2,3").succeeds().code_is(0); 51 | new_ucmd!() 52 | .arg("-p=1") 53 | .arg("-p=2") 54 | .arg("-p=3") 55 | .succeeds() 56 | .code_is(0); 57 | } 58 | -------------------------------------------------------------------------------- /tests/by-util/test_vmstat.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | #[cfg(target_os = "linux")] 7 | use std::time::Duration; 8 | use uutests::new_ucmd; 9 | use uutests::util::TestScenario; 10 | use uutests::util_name; 11 | 12 | #[test] 13 | fn test_simple() { 14 | new_ucmd!().succeeds(); 15 | } 16 | 17 | #[test] 18 | fn test_invalid_arg() { 19 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 20 | } 21 | 22 | #[test] 23 | fn test_invalid_number() { 24 | new_ucmd!().arg("-1").fails().code_is(1); 25 | new_ucmd!().arg("0").fails().code_is(1); 26 | } 27 | 28 | #[test] 29 | fn test_unit() { 30 | new_ucmd!().args(&["-S", "M"]).succeeds(); 31 | } 32 | 33 | #[test] 34 | #[cfg(target_os = "linux")] 35 | fn test_invalid_unit() { 36 | new_ucmd!().args(&["-S", "x"]).fails().code_is(1); 37 | } 38 | 39 | #[test] 40 | #[cfg(target_os = "linux")] 41 | fn test_header() { 42 | let result = new_ucmd!().succeeds(); 43 | assert!(result.stdout_str().starts_with("procs")); 44 | } 45 | 46 | #[test] 47 | #[cfg(target_os = "linux")] 48 | fn test_wide_mode() { 49 | let result = new_ucmd!().arg("-w").succeeds(); 50 | assert!(result.stdout_str().starts_with("--procs--")); 51 | } 52 | 53 | #[test] 54 | #[cfg(target_os = "linux")] 55 | fn test_no_first() { 56 | let time = std::time::Instant::now(); 57 | new_ucmd!().arg("-y").succeeds(); 58 | assert!(time.elapsed() >= Duration::from_secs(1)); 59 | } 60 | 61 | #[test] 62 | #[cfg(target_os = "linux")] 63 | fn test_active() { 64 | let result = new_ucmd!().arg("-a").succeeds(); 65 | assert!(result 66 | .stdout_str() 67 | .lines() 68 | .nth(1) 69 | .unwrap() 70 | .contains("active")); 71 | } 72 | 73 | #[test] 74 | #[cfg(target_os = "linux")] 75 | fn test_timestamp() { 76 | let result = new_ucmd!().arg("-t").succeeds(); 77 | assert!(result 78 | .stdout_str() 79 | .lines() 80 | .next() 81 | .unwrap() 82 | .contains("timestamp")); 83 | } 84 | -------------------------------------------------------------------------------- /tests/by-util/test_w.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use uutests::new_ucmd; 7 | use uutests::util::TestScenario; 8 | use uutests::util_name; 9 | 10 | #[test] 11 | fn test_invalid_arg() { 12 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 13 | } 14 | 15 | #[test] 16 | fn test_help() { 17 | new_ucmd!() 18 | .arg("--help") 19 | .succeeds() 20 | .stdout_contains("Usage") 21 | .stdout_contains("Options"); 22 | } 23 | 24 | #[test] 25 | fn test_no_header() { 26 | for arg in ["-h", "--no-header"] { 27 | let cmd = new_ucmd!().arg(arg).succeeds(); 28 | 29 | let result = cmd.stdout_str(); 30 | 31 | assert!(match result.lines().next() { 32 | None => true, 33 | Some(line) => !line.contains("user") && !line.contains("load average"), 34 | }); 35 | assert!(!result.contains("USER TTY LOGIN@ IDLE JCPU PCPU WHAT")); 36 | } 37 | } 38 | 39 | #[test] 40 | // As of now, --short is only implemented for Linux 41 | #[cfg(target_os = "linux")] 42 | fn test_option_short() { 43 | use std::io::IsTerminal; 44 | 45 | use regex::Regex; 46 | let cmd = new_ucmd!().arg("--short").succeeds(); 47 | 48 | let cmd_output = cmd.stdout_str(); 49 | let cmd_output_lines: Vec<&str> = cmd_output.split('\n').collect(); 50 | let line_output_header = cmd_output_lines[1]; 51 | let line_output_data_words: Vec<&str> = cmd_output_lines[1] 52 | .trim() 53 | .split(' ') 54 | .filter(|it| !it.is_empty()) 55 | .collect(); 56 | 57 | assert!(line_output_header.contains("USER TTY IDLE WHAT")); 58 | assert!(!line_output_header.contains("USER TTY LOGIN@ IDLE JCPU PCPU WHAT")); 59 | 60 | if std::io::stdout().is_terminal() { 61 | let pattern: Vec = vec![ 62 | Regex::new(r"^(\S+)").unwrap(), // USER 63 | Regex::new(r"(\S+)").unwrap(), // TERMINAL 64 | // Regex::new(r"(^$)").unwrap(), // IDLE_TIME => empty str until IDLE_TIME implemented 65 | Regex::new(r"(\d+\.\d+s)?").unwrap(), // COMMAND 66 | ]; 67 | 68 | assert!(pattern[0].is_match(line_output_data_words[0])); 69 | assert!(pattern[1].is_match(line_output_data_words[1])); 70 | // assert!(pattern[2].is_match(line_output_data_words[2])); 71 | assert!(pattern[2].is_match(line_output_data_words[3])); 72 | } 73 | } 74 | 75 | #[test] 76 | // As of now, output is only implemented for Linux 77 | #[cfg(target_os = "linux")] 78 | fn test_output_format() { 79 | // Use no header to simplify testing 80 | let cmd = new_ucmd!().arg("--no-header").succeeds(); 81 | let output_lines = cmd.stdout_str().lines(); 82 | 83 | for line in output_lines { 84 | let line_vec: Vec = line.split_whitespace().map(String::from).collect(); 85 | // Check the time formatting, this should be the third entry in list 86 | // For now, we are just going to check that that length of time is 5 and it has a colon, else 87 | // it is possible that a time can look like Fri13, so it can start with a letter and end 88 | // with a number 89 | assert!( 90 | (line_vec[2].contains(':') && line_vec[2].chars().count() == 5) 91 | || (line_vec[2].starts_with(char::is_alphabetic) 92 | && line_vec[2].ends_with(char::is_numeric) 93 | && line_vec[2].chars().count() == 5) 94 | ); 95 | // Assert that there is something in the JCPU and PCPU slots, 96 | // this will need to be changed when IDLE is implemented 97 | assert!(!line_vec[3].is_empty() && !line_vec[4].is_empty()); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/by-util/test_watch.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | use uutests::new_ucmd; 7 | use uutests::util::TestScenario; 8 | use uutests::util_name; 9 | 10 | // runddl32.exe has no console window, no side effects, 11 | // and no arguments are required. 12 | // The full path must be provided since tests are ran with clear_env 13 | #[cfg(windows)] 14 | const TRUE_CMD: &str = "%SYSTEMROOT%\\System32\\rundll32.exe"; 15 | 16 | #[cfg(not(windows))] 17 | const TRUE_CMD: &str = "true"; 18 | 19 | #[cfg(windows)] 20 | const ECHO_HELLO_CMD: &str = "echo | set /p dummyName=hello"; 21 | 22 | #[cfg(not(windows))] 23 | const ECHO_HELLO_CMD: &str = "printf hello"; 24 | 25 | #[test] 26 | fn test_invalid_arg() { 27 | new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 28 | } 29 | 30 | #[test] 31 | fn test_invalid_interval() { 32 | let args = vec!["-n", "definitely-not-valid", TRUE_CMD]; 33 | new_ucmd!() 34 | .args(&args) 35 | .fails() 36 | .stderr_contains("Invalid argument"); 37 | } 38 | 39 | #[test] 40 | fn test_no_interval() { 41 | let mut p = new_ucmd!().arg(TRUE_CMD).run_no_wait(); 42 | p.make_assertion_with_delay(500).is_alive(); 43 | p.kill() 44 | .make_assertion() 45 | .with_all_output() 46 | .no_stderr() 47 | .no_stdout(); 48 | } 49 | 50 | #[test] 51 | fn test_valid_interval() { 52 | let args = vec!["-n", "1.5", TRUE_CMD]; 53 | let mut p = new_ucmd!().args(&args).run_no_wait(); 54 | p.make_assertion_with_delay(500).is_alive(); 55 | p.kill() 56 | .make_assertion() 57 | .with_all_output() 58 | .no_stderr() 59 | .no_stdout(); 60 | } 61 | 62 | #[test] 63 | fn test_valid_interval_comma() { 64 | let args = vec!["-n", "1,5", TRUE_CMD]; 65 | let mut p = new_ucmd!().args(&args).run_no_wait(); 66 | p.make_assertion_with_delay(1000).is_alive(); 67 | p.kill() 68 | .make_assertion() 69 | .with_all_output() 70 | .no_stderr() 71 | .no_stdout(); 72 | } 73 | 74 | #[test] 75 | fn test_interval_environment_variable() { 76 | let mut p = new_ucmd!() 77 | .arg(ECHO_HELLO_CMD) 78 | .env("WATCH_INTERVAL", "0.3") 79 | .run_no_wait(); 80 | // With 0.5 seconds runtime, the watched command is called twice if 81 | // `WATCH_INTERVAL` (0.3 seconds) is taken into account, but only once if the default 82 | // interval (2 seconds) is used. 83 | p.make_assertion_with_delay(500).is_alive(); 84 | p.kill() 85 | .make_assertion() 86 | .with_all_output() 87 | .no_stderr() 88 | .stdout_is_bytes(b"hellohello"); 89 | } 90 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | // This file is part of the uutils procps package. 2 | // 3 | // For the full copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | use std::env; 6 | 7 | pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_procps"); 8 | 9 | // Use the ctor attribute to run this function before any tests 10 | #[ctor::ctor] 11 | fn init() { 12 | unsafe { 13 | // Necessary for uutests to be able to find the binary 14 | std::env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); 15 | } 16 | } 17 | 18 | #[cfg(feature = "pwdx")] 19 | #[path = "by-util/test_pwdx.rs"] 20 | mod test_pwdx; 21 | 22 | #[cfg(feature = "free")] 23 | #[path = "by-util/test_free.rs"] 24 | mod test_free; 25 | 26 | #[cfg(feature = "w")] 27 | #[path = "by-util/test_w.rs"] 28 | mod test_w; 29 | 30 | #[cfg(feature = "watch")] 31 | #[path = "by-util/test_watch.rs"] 32 | mod test_watch; 33 | 34 | #[cfg(feature = "pmap")] 35 | #[path = "by-util/test_pmap.rs"] 36 | mod test_pmap; 37 | 38 | #[cfg(feature = "slabtop")] 39 | #[path = "by-util/test_slabtop.rs"] 40 | mod test_slabtop; 41 | 42 | #[cfg(feature = "pgrep")] 43 | #[path = "by-util/test_pgrep.rs"] 44 | mod test_pgrep; 45 | 46 | #[cfg(feature = "pidof")] 47 | #[path = "by-util/test_pidof.rs"] 48 | mod test_pidof; 49 | 50 | #[cfg(feature = "ps")] 51 | #[path = "by-util/test_ps.rs"] 52 | mod test_ps; 53 | 54 | #[cfg(feature = "pidwait")] 55 | #[path = "by-util/test_pidwait.rs"] 56 | mod test_pidwait; 57 | 58 | #[cfg(feature = "top")] 59 | #[path = "by-util/test_top.rs"] 60 | mod test_top; 61 | 62 | #[cfg(feature = "vmstat")] 63 | #[path = "by-util/test_vmstat.rs"] 64 | mod test_vmstat; 65 | 66 | #[cfg(feature = "snice")] 67 | #[path = "by-util/test_snice.rs"] 68 | mod test_snice; 69 | 70 | #[cfg(feature = "pkill")] 71 | #[path = "by-util/test_pkill.rs"] 72 | mod test_pkill; 73 | 74 | #[cfg(feature = "sysctl")] 75 | #[path = "by-util/test_sysctl.rs"] 76 | mod test_sysctl; 77 | 78 | #[cfg(feature = "tload")] 79 | #[path = "by-util/test_tload.rs"] 80 | mod test_tload; 81 | -------------------------------------------------------------------------------- /util/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # spell-checker:ignore uuhelp 3 | ARG="" 4 | if test "$1" != "--do-it"; then 5 | ARG="--dry-run --allow-dirty" 6 | fi 7 | 8 | # Function to check if the crate is already published 9 | is_already_published() { 10 | local crate_name=$1 11 | local crate_version=$2 12 | 13 | # Use the crates.io API to get the latest version of the crate 14 | local latest_published_version 15 | latest_published_version=$(curl -s https://crates.io/api/v1/crates/$crate_name | jq -r '.crate.max_version') 16 | 17 | if [ "$latest_published_version" = "$crate_version" ]; then 18 | return 0 19 | else 20 | return 1 21 | fi 22 | } 23 | 24 | # Figure out any dependencies between the util via Cargo.toml 25 | # We store this as edges in a graph with each line: 26 | # [dependent] [dependency] 27 | # We use ROOT as a the node that should come before all other nodes. 28 | PROGS=$(ls -1d src/uu/*/) 29 | PARTIAL_ORDER="" 30 | for p in $PROGS; do 31 | DEPENDENCIES=$(grep -oE "^uu_[a-z0-9]+" ${p}Cargo.toml) 32 | 33 | # Turn "src/uu/util/" into "util" 34 | p=${p#src/uu/} 35 | p=${p%/} 36 | 37 | PARTIAL_ORDER+="$p ROOT\n" 38 | while read d; do 39 | if [ $d ]; then 40 | # Remove "uu_" prefix 41 | d=${d#uu_} 42 | 43 | PARTIAL_ORDER+="$p $d\n" 44 | fi 45 | done <<<"$DEPENDENCIES" 46 | done 47 | 48 | # Apply tsort to get the order in which to publish the crates 49 | TOTAL_ORDER=$(echo -e $PARTIAL_ORDER | tsort | tac) 50 | 51 | # Remove the ROOT node from the start 52 | TOTAL_ORDER=${TOTAL_ORDER#ROOT} 53 | 54 | CRATE_VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2) 55 | 56 | set -e 57 | 58 | for p in $TOTAL_ORDER; do 59 | ( 60 | cd "src/uu/$p" 61 | CRATE_NAME=$(grep '^name =' "Cargo.toml" | head -n1 | cut -d '"' -f2) 62 | #shellcheck disable=SC2086 63 | if ! is_already_published "$CRATE_NAME" "$CRATE_VERSION"; then 64 | cargo publish $ARG 65 | else 66 | echo "Skip: $CRATE_NAME $CRATE_VERSION already published" 67 | fi 68 | ) 69 | done 70 | 71 | #shellcheck disable=SC2086 72 | cargo publish $ARG 73 | --------------------------------------------------------------------------------