├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── clippy_build_test.yml ├── .gitignore ├── CONTRIBUTORS.md ├── Cargo.toml ├── FUNDING.json ├── LICENSE ├── Makefile ├── README.md ├── build.rs ├── doc ├── MacosVersions.md ├── References.md └── TODO.md ├── docs_rs └── osx_libproc_bindings.rs ├── examples ├── dmesg.rs └── procinfo.rs └── src ├── lib.rs ├── libproc ├── bsd_info.rs ├── file_info.rs ├── helpers.rs ├── kmesg_buffer.rs ├── mod.rs ├── net_info.rs ├── pid_rusage.rs ├── proc_pid.rs ├── sys │ ├── linux.rs │ ├── macos.rs │ └── mod.rs ├── task_info.rs ├── thread_info.rs └── work_queue_info.rs └── processes.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: andrew 2 | patreon: andrewmackenzie 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/clippy_build_test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: # Build every day at 5PM UTC 9 | - cron: '0 17 * * *' 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | RUST_BACKTRACE: full 14 | 15 | jobs: 16 | msrv: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: taiki-e/install-action@cargo-hack 21 | - run: cargo hack check --rust-version --workspace --all-targets --ignore-private 22 | 23 | build: 24 | runs-on: ${{ matrix.os }} 25 | strategy: 26 | matrix: 27 | os: [ ubuntu-latest, macos-14, macos-15 ] 28 | rust: [ stable, beta, 1.72.0 ] # Minimum Rust Version Supported = 1.72.0 29 | experimental: [ false ] 30 | include: 31 | - os: ubuntu-latest 32 | rust: nightly 33 | experimental: true 34 | - os: macos-14 35 | rust: nightly 36 | experimental: true 37 | - os: macos-15 38 | rust: nightly 39 | experimental: true 40 | 41 | continue-on-error: ${{ matrix.experimental }} 42 | 43 | steps: 44 | - uses: actions/checkout@v2 45 | 46 | - name: Install toolchain 47 | uses: actions-rs/toolchain@v1 48 | with: 49 | profile: minimal 50 | toolchain: ${{ matrix.rust }} 51 | override: true 52 | components: clippy 53 | 54 | - name: InstallLinuxDependencies 55 | if: runner.os == 'Linux' 56 | run: | 57 | sudo apt-get update && sudo apt-get -y install lcov 58 | 59 | - name: InstallMacDependencies 60 | if: runner.os == 'macOS' 61 | run: brew install lcov 62 | 63 | - name: ConfigureCoverage 64 | if: matrix.rust == 'stable' 65 | run: | 66 | cargo install grcov 67 | rustup component add llvm-tools-preview 68 | echo RUSTFLAGS="-C instrument-coverage" >> "$GITHUB_ENV" 69 | echo LLVM_PROFILE_FILE="libproc-%p-%m.profraw" >> "$GITHUB_ENV" 70 | 71 | - name: Run clippy (installed toolchain version as per matrix) 72 | run: | 73 | rustc --version 74 | cargo clippy --all --tests --no-deps --all-targets --all-features -- --warn clippy::pedantic -D warnings 75 | 76 | - name: Run Tests on Linux 77 | if: runner.os == 'Linux' 78 | run: env "PATH=$PATH" cargo test 79 | 80 | - name: Run Tests as Root on Mac 81 | if: runner.os == 'macOS' 82 | run: sudo env "PATH=$PATH" cargo test 83 | 84 | - name: UploadCoverage 85 | if: matrix.rust == 'stable' 86 | continue-on-error: true 87 | run: | 88 | grcov . --binary-path target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*" -o coverage.info 89 | lcov --remove coverage.info lcov --ignore-errors unused '/Applications/*' 'target/debug/build/**' 'target/release/build/**' '/usr*' '**/errors.rs' '**/build.rs' 'examples/**' '*tests/*' -o coverage.info 90 | bash <(curl -s https://codecov.io/bash) -f coverage.info 91 | 92 | 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target 3 | Cargo.lock 4 | src/osx_libproc_bindings.rs 5 | coverage.info 6 | *.profraw -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Contributors to libproc-rs 2 | = 3 | 4 | [andrewdavidmackenzie]() 5 | 6 | [andrewdavidmackenzie](https://github.com/andrewdavidmackenzie) 7 | 8 | [lambda-fairy]() 9 | 10 | [lambda-fairy](https://github.com/lambda-fairy) 11 | 12 | [LuoZijun]() 13 | 14 | [LuoZijun](https://github.com/LuoZijun) -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libproc" 3 | version = "0.14.10" 4 | description = "A library to get information about running processes - for Mac OS X and Linux" 5 | authors = ["Andrew Mackenzie "] 6 | repository = "https://github.com/andrewdavidmackenzie/libproc-rs" 7 | documentation = "https://docs.rs/libproc/latest/libproc" 8 | readme = "README.md" 9 | license = "MIT" 10 | edition = "2018" 11 | rust-version = "1.72" 12 | 13 | [dependencies] 14 | errno = "0.3.0" 15 | libc = "^0.2.62" 16 | 17 | [lib] 18 | name = "libproc" 19 | path = "src/lib.rs" 20 | 21 | [build-dependencies] 22 | bindgen = { version = "0.71.1", default-features = false, features = ["runtime"] } 23 | 24 | # NOTE: This assumes that there is a procfs compatible FS in redox and the procfs crate 25 | # supports it. It's quite probably that neither of those two things ever happen. 26 | # But making this assumption for now so that everything compiles at least for redox 27 | [target.'cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))'.dev-dependencies] 28 | procfs = "0.17.0" 29 | tempfile = "3.3.0" 30 | 31 | # Build docs for macos and linux 32 | [package.metadata.docs.rs] 33 | targets = ["aarch64-apple-darwin", "x86_64-unknown-linux-gnu"] -------------------------------------------------------------------------------- /FUNDING.json: -------------------------------------------------------------------------------- 1 | { 2 | "drips": { 3 | "ethereum": { 4 | "ownedBy": "0xC27a11573340854C83C1B5eAD6449E096477F184" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 3scale networks S.L. 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 13 | all 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 SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RUST_MIN_VERSION := 1.72.0 2 | ACT := $(shell command -v act 2> /dev/null) 3 | UNAME := $(shell uname -s) 4 | 5 | .PHONY: all 6 | all: clippy test build-docs 7 | 8 | .PHONY: clippy 9 | clippy: 10 | @cargo clippy --all --tests --no-deps --all-targets --all-features -- --warn clippy::pedantic -D warnings 11 | 12 | .PHONY: test 13 | test: 14 | ifeq ($(UNAME),Darwin) 15 | @echo "On macos, process tests are required to be run as root - so please enter your password at the prompt" 16 | @sudo env "PATH=$$PATH" cargo test 17 | else 18 | @env "PATH=$$PATH" cargo test 19 | endif 20 | 21 | .PHONY: coverage 22 | coverage: test-with-coverage gen-coverage view-coverage 23 | 24 | .PHONY: test-with-coverage 25 | test-with-coverage: 26 | @RUSTFLAGS="-C instrument-coverage" LLVM_PROFILE_FILE="libproc-%p-%m.profraw" cargo build 27 | ifeq ($(UNAME),Darwin) 28 | @echo "On macos, process tests are required to be run as root - so please enter your password at the prompt" 29 | @RUSTFLAGS="-C instrument-coverage" LLVM_PROFILE_FILE="libproc-%p-%m.profraw" sudo cargo test 30 | else 31 | @RUSTFLAGS="-C instrument-coverage" LLVM_PROFILE_FILE="libproc-%p-%m.profraw" cargo test 32 | endif 33 | 34 | .PHONY: gen-coverage 35 | gen-coverage: 36 | @grcov . --binary-path target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*" -o coverage.info 37 | @lcov --remove coverage.info '/Applications/*' 'target/debug/build/**' 'target/release/build/**' '/usr*' '**/errors.rs' '**/build.rs' 'examples/**' '*tests/*' -o coverage.info 38 | @find . -name "*.profraw" | xargs rm -f 39 | 40 | .PHONY: view-coverage 41 | view-coverage: 42 | @genhtml -o target/coverage --quiet coverage.info 43 | @echo "View coverage report using 'open target/coverage/index.html'" 44 | 45 | .PHONY: build-docs 46 | build-docs: 47 | cargo doc --workspace --quiet --all-features --no-deps --target-dir=target 48 | 49 | .PHONY: matrix 50 | matrix: 51 | @for rust_version in stable beta nightly $(RUST_MIN_VERSION) ; do \ 52 | echo rust: $$rust_version ; \ 53 | rustup override set $$rust_version ; \ 54 | make clippy ; \ 55 | make test ; \ 56 | done 57 | ifeq ($(UNAME),Darwin) 58 | ifneq ($(ACT),) 59 | @echo "Running Linux GH Action workflow using `act` on macos" 60 | @act -W .github/workflows/clippy_build_test.yml 61 | else 62 | @echo "`act` is not installed so skipping running Linux matrix" 63 | endif 64 | else 65 | @echo "Cannot run Linux parts of matrix on macos, create PR and make sure all checks pass" 66 | endif 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://travis-ci.org/andrewdavidmackenzie/libproc-rs.svg?branch=master "Mac OS X") 2 | [![codecov](https://codecov.io/gh/andrewdavidmackenzie/libproc-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/andrewdavidmackenzie/libproc-rs) 3 | 4 | # libproc-rs 5 | This is a library for getting information about running processes for Mac OS X and Linux. 6 | 7 | Add it to your project's `Cargo.toml`: 8 | ```toml 9 | libproc = "0.14.4" 10 | ``` 11 | 12 | And then use it in your code: 13 | ```rust 14 | use libproc::libproc::proc_pid; 15 | 16 | match proc_pid::pidpath(pid) { 17 | Ok(path) => println!("PID {}: has path {}", pid, path), 18 | Err(err) => writeln!(&mut std::io::stderr(), "Error: {}", err).unwrap() 19 | } 20 | ``` 21 | 22 | You can find the latest published release on [crates.io](https://crates.io/crates/libproc) 23 | 24 | You can find the browseable docs for the latest release on [docs.rs](https://docs.rs/libproc/latest/libproc/). 25 | 26 | NOTE: `master` branch (code and docs) can differ from those docs prior to a new release. 27 | 28 | # Minimum rust version 29 | The minimum rust version required, by version: 30 | * libproc-rs: 0.14.6 --> 1.74.1 31 | * libproc-rs: 0.14.7 --> 1.72.0 32 | 33 | This is tested in CI and must pass. 34 | 35 | # Test Matrix 36 | The GitHub Actions CI test `libproc-rs` on : 37 | 38 | rust versions: 39 | * `stable` (must pass) 40 | * `beta` (must pass) 41 | * `1.72.0` (currently the minimum rust version supported) (must pass) 42 | * `nightly` (allowed to fail) 43 | 44 | on the following platforms: 45 | * `ubuntu-latest` 46 | * `macos-12` (Monterey) (Intel) 47 | * `macos-13` (Ventura) (Intel) 48 | * `macos-14` (Sonoma) (Arm64) 49 | 50 | # Examples 51 | Two simple examples are included to show libproc-rs working. 52 | 53 | - `procinfo` that takes a PID as an optional argument (uses its own pid if none supplied) and returns 54 | information about the process on stdout 55 | - `dmesg` is a version of dmesg implemented in rust that uses libproc-rs. 56 | 57 | These can be run thus: 58 | `sudo cargo run --example procinfo` or 59 | `sudo cargo run --example dmesg` 60 | 61 | # Contributing 62 | You are welcome to fork this repo and make a pull request, or write an issue. 63 | 64 | ## Experiment in OSS funding 65 | I am exploring the ideas around Open Source Software funding from [RadWorks Foundation]([https://radworks.org/) via the [Drips Project](https://www.drips.network/) 66 | 67 | This project is in Drips [here](https://www.drips.network/app/projects/github/andrewdavidmackenzie/libproc-rs) 68 | 69 | ## Input Requested 70 | * Suggestions for API, module re-org and cross-platform abstractions are welcome. 71 | * How to do error reporting? Define own new Errors, or keep simple with Strings? 72 | * Would like Path/PathBuf returned when it makes sense instead of String? 73 | 74 | ## TODO 75 | See the [list of issues](https://github.com/andrewdavidmackenzie/libproc-rs/issues). 76 | I put the "help wanted" label where I need help from others. 77 | 78 | - Look at what similar methods could be implemented on Linux 79 | - Complete the API on Mac OS X - figuring out all the Mac OS X / Darwin version mess.... 80 | - Add more documentation (including samples with documentation test) 81 | - Add own custom error type and implement From::from to ease reporting of multiple error types in clients 82 | 83 | ## Build and Test Locally 84 | If you're feeling lucky today, start with `make` that will run `clippy`, `test` and will build docs also. 85 | 86 | If you want to stay "pure rust" : `cargo test` will build and test as usual. 87 | 88 | However, as some functions need to be run as `root` to work, CI tests are run as `root`. 89 | So, when developing in local it's best if you use `sudo cargo test`. 90 | 91 | [!NOTE] This can get you into permissions problems when switching back and for 92 | between using `cargo test` and `sudo cargo test`. 93 | To fix that run `sudo cargo clean` and then build or test as you prefer. 94 | 95 | In order to have tests pass when run as `root` or not, some tests need to check if they are `root` 96 | at run-time (using our own `am_root()` function is handy) and avoid failing if *not* run as `root`. 97 | 98 | ### Using `act` to run GH Actions locally 99 | If you develop on macOS but want to ensure code builds and tests pass on linux while making changes, 100 | you can use the [act](https://github.com/nektos/act) tool to run the GitHub Actions Workflows on 101 | the test matrix. 102 | 103 | Just install `act` (`brew install act` on macOS) (previously install docker if you don't have it already, 104 | and make sure the daemon is running) then run `act push`. You can test a subset of the rust 105 | and os versions of the matrix with something like `act push --matrix os:ubuntu-latest` 106 | 107 | ## Enter the matrix 108 | If you want to test locally as much of the test matrix as possible (different OS and 109 | versions of rust), that you can use `make matrix`. On macOS, if you have `act` 110 | installed, it will use it to run the linux part of the matrix. 111 | 112 | ### macOS: clang detection and header file finding 113 | Newer versions of `bindgen` have improved the detection of `clang` and hence macOS header files. 114 | If you also have llvm/clang installed directly or via `brew` this may cause the build to fail saying it 115 | cannot find `libproc.h`. This can be fixed by setting `CLANG_PATH="/usr/bin/clang"` so that `bindgen` 116 | detects the Xcode version and hence can find the correct header files. 117 | 118 | # Other docs 119 | * [Reference docs](doc/References.md) used to build and document libproc-rs 120 | * Details on methods available in different [macOS versions](doc/MacosVersions.md) 121 | 122 | # LICENSE 123 | This code is licensed under MIT license (see LICENCE). 124 | 125 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "macos")] 2 | fn main() { 3 | use bindgen::{RustEdition, RustTarget}; 4 | use std::env; 5 | use std::path::Path; 6 | 7 | match RustTarget::stable(72, 0) { 8 | Ok(rust_target) => { 9 | let bindings = bindgen::builder() 10 | .header_contents("libproc_rs.h", "#include ") 11 | .rust_target(rust_target) 12 | .rust_edition(RustEdition::Edition2018) 13 | .layout_tests(false) 14 | .clang_args(&["-x", "c++", "-I", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/"]) 15 | .generate() 16 | .expect("Failed to build libproc bindings"); 17 | 18 | let output_path = Path::new(&env::var("OUT_DIR") 19 | .expect("OUT_DIR env var was not defined")) 20 | .join("osx_libproc_bindings.rs"); 21 | 22 | bindings 23 | .write_to_file(output_path) 24 | .expect("Failed to write libproc bindings"); 25 | } 26 | _ => eprintln!("Error executing bindgen") 27 | } 28 | } 29 | 30 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 31 | fn main() {} 32 | -------------------------------------------------------------------------------- /doc/MacosVersions.md: -------------------------------------------------------------------------------- 1 | # Macos Versions 2 | This doc lists the different versions of macos released (and still supported and tested by `libproc-rs`) 3 | along with the corresponding versions of `Darwin` and `XNU used in each. 4 | 5 | It also lists the public (via `libproc.h` header file) functions provided by `libproc` in each version, and 6 | shows in what version new functions were added. 7 | 8 | ## What is XNU 9 | XNU kernel is part of the Darwin operating system for use in macOS and iOS operating systems. 10 | 11 | [Here](https://github.com/apple-oss-distributions/xnu/tags) you can find the XNU version tags that apple has tagged in 12 | the GH project 13 | 14 | ## What is Darwin 15 | Darwin is the core Unix operating system of macOS (previously OS X and Mac OS X), iOS, watchOS, tvOS, iPadOS, visionOS, 16 | and bridgeOS. It previously existed as an independent open-source operating system, first released by Apple Inc. 17 | in 2000. It is composed of code derived from NeXTSTEP, FreeBSD,[3] other BSD operating systems,[6] Mach, and other 18 | free software projects' code, as well as code developed by Apple. 19 | 20 | # Mapping of macos versions to xnu versions 21 | ## macos 14 22 | * 14.0 -> [xnu-10002.1.13](https://newosxbook.com/src.jl?tree=&file=/xnu-10002.1.13) 23 | * 14.1 -> xnu-10002.41.9 24 | * 14.1.1 -> xnu-10002.41.9 25 | * 14.1.2 -> xnu-10002.41.9 26 | * 14.2 -> xnu-10002.61.3 27 | * 14.2.1 -> xnu-10002.61.3 28 | * 14.3 -> xnu-10002.81.5 29 | * 14.3.1 -> xnu-10002.81.5 30 | * 14.4 -> xnu-10063.101.15 31 | 32 | ## macos 13 33 | * 13.0 -> xnu-8792.41.9 34 | * 13.1 -> xnu-8792.61.2 35 | * 13.2 -> [xnu-8792.81.2](https://newosxbook.com/src.jl?tree=&file=/xnu-8792.81.2) 36 | * Added 37 | * `proc_terminate_all_rsr` (NOTE: Not implemented by libproc-rs) 38 | * 13.2.1 -> xnu-8792.81.3 39 | * 13.3 -> xnu-8796.101.5 40 | * 13.4 -> xnu-8796.121.2 41 | * 13.4.1 -> xnu-8796.121.3 42 | * 13.5 -> xnu-8796.141.3 43 | * 13.6 -> xnu-8796.141.3.700.8 44 | * 13.6.1 -> xnu-8796.141.3.701.17 45 | * 13.6.2 -> xnu-8796.141.3.701.17 46 | * 13.6.3 -> xnu-8796.141.3.702.9 47 | * 13.6.4 -> xnu-8796.141.3.703.2 48 | * 13.6.5 -> xnu-8796.141.3.704.6 49 | 50 | ## macos 12 51 | 2021 - Darwin 21, macOS Monterey (Version 12.0) 52 | 53 | * 12.0 -> [xnu-8019.30.61 (generic 8019)](https://newosxbook.com/src.jl?tree=&file=/xnu-8019) 54 | * Added 55 | * `proc_pidpath_audittoken` (NOTE: Not implemented by libproc-rs) 56 | * 12.0.1 -> xnu-8019.41.5 57 | * 12.1 -> xnu-8019.61.5 58 | * 12.2 -> xnu-8019.80.24 59 | * 12.3 -> xnu-8020.101.4 60 | * 12.3.1 -> xnu-8020.101.4 61 | * 12.4 -> xnu-8020.121.3 62 | * 12.5 -> xnu-8020.140.41 63 | * 12.5.1 -> xnu-8020.141.5 64 | * 12.6 -> xnu-8020.140.49 65 | * 12.6.1 -> xnu-8020.240.7 66 | * 12.6.2 -> xnu-8020.240.14 67 | * 12.6.3 -> xnu-8020.240.18 68 | * 12.6.4 -> xnu-8020.240.18.700.8 69 | * 12.6.6 -> xnu-8020.240.18.701.5 70 | * 12.6.7 -> xnu-8020.240.18.701.6 71 | * 12.6.8 -> xnu-8020.240.18.702.13 72 | * 12.7 -> xnu-8020.240.18.703.5 73 | * 12.7.1 -> xnu-8020.240.18.704.15 74 | * 12.7.2 -> xnu-8020.240.18.705.10 75 | * 12.7.3 -> xnu-8020.240.18.706.2 76 | * 12.7.4 -> xnu-8020.240.18.707.4 77 | 78 | ## macos 11 79 | 2020 - Darwin 20, macOS Big Sur (Version 11.0) 80 | 81 | * 11.0 -> xnu-7195.41.8 82 | * 11.0.1 -> xnu-7195.50.7 83 | * 11.1 -> xnu-7195.60.75 84 | * 11.2 -> [xnu-7195.81.3](https://opensource.apple.com/source/xnu/xnu-7195.81.3/) 85 | * Added 86 | * `proc_set_no_smt` (NO_SMT means that on an SMT CPU, this thread must be scheduled alone, 87 | with the paired CPU idle. Set NO_SMT on the current proc (all existing and future threads). 88 | This attribute is inherited on fork and exec. (NOTE: Not implemented by libproc-rs) 89 | * `proc_setthread_no_smt` (Set NO_SMT on the current thread) (NOTE: Not implemented by libproc-rs) 90 | * `proc_set_csm` (CPU Security Mitigation APIs - Set CPU security mitigation on the current proc 91 | (all existing and future threads). This attribute is inherited on fork and exec) (NOTE: Not implemented by libproc-rs) 92 | * `proc_setthread_csm (Set CPU security mitigation on the current thread) (NOTE: Not implemented by libproc-rs) 93 | * 11.3 -> xnu-7195.101.1 94 | * 11.3.1 -> xnu-7195.101.2 95 | * 11.4 -> xnu-7195.121.3 96 | * 11.5 -> xnu-7195.141.2 97 | * 11.6 -> xnu-7195.141.6 98 | * 11.6.1 -> xnu-7195.141.8 99 | * 11.6.2 -> xnu-7195.141.14 100 | * 11.6.3 -> xnu-7195.141.19 101 | * 11.6.5 -> xnu-7195.141.26 102 | * 11.6.6 -> xnu-7195.141.29 103 | * 11.6.8 -> xnu-7195.141.32 104 | * 11.7 -> xnu-7195.141.39 105 | * 11.7.1 -> xnu-7195.141.42 106 | * 11.7.2 -> xnu-7195.141.46 107 | * 11.7.3 -> xnu-7195.141.49 108 | * 11.7.5 -> xnu-7195.141.49.700.6 109 | * 11.7.7 -> xnu-7195.141.49.701.3 110 | * 11.7.8 -> xnu-7195.141.49.701.4 111 | * 11.7.9 -> xnu-7195.141.49.702.12 112 | 113 | [!NOTE] Versions below here (prior to macOS 11) are not supported in GitHub Actions and hence are not tested 114 | as part of `libproc-rs` CI process. 115 | 116 | ## macos 10.15 117 | 2019 - Darwin 19, macOS Catalina (Version 10.15) 118 | 119 | 10.15.1 -> [xnu-6153.41.3 (closest 6153.11.26)](https://newosxbook.com/src.jl?tree=&file=/xnu-6153.11.26) 120 | 10.15.2 -> xnu-6153.61.1 121 | 10.15.3 ->xnu-6153.81.5 122 | 10.15.4 -> xnu-6153.101.6 123 | 10.15.5 -> xnu-6153.121.1 124 | 10.15.6 -> [xnu-6153.141.1](https://opensource.apple.com/source/xnu/xnu-6153.141.1/) 125 | 10.15.7 -> xnu-6153.141.2 126 | 127 | ## macos 10.14 128 | 2018 - Darwin 18, macOS Mojave (Version 10.14) 129 | 130 | * 10.14.1 -> [xnu-4903.221.2](https://opensource.apple.com/source/xnu/xnu-4903.221.2/) 131 | * No additions, as xnu-4570.71.2 below 132 | * 10.14.2 -> [xnu-4903.231.4](https://opensource.apple.com/source/xnu/xnu-4903.231.4/) 133 | * No additions, as xnu-4570.71.2 below 134 | * 10.14.3 -> [xnu-4903.241.1](https://opensource.apple.com/source/xnu/xnu-4903.241.1/) 135 | * No additions, as xnu-4570.71.2 below 136 | * 10.14.4 -> xnu-4903.251.3 137 | * 10.14.5 -> xnu-4903.261.4 138 | * 10.14.6 -> [xnu-4903.270.47](https://opensource.apple.com/source/xnu/xnu-4903.270.47/) 139 | * No additions, as xnu-4570.71.2 below 140 | 141 | ## macos 10.13 142 | 2017 - Darwin 17, macOS High Sierra (Version 10.13) 143 | 144 | 10.13.6 -> xnu-4570.71.46 - xnu-4570.71.82.8 (approx [xnu-4570.71.2](https://opensource.apple.com/source/xnu/xnu-4570.71.2/)) 145 | 146 | Methods inherited from previous versions of XNU, Darwin and macOS that 147 | are present in 10.13.6: 148 | * proc_listpidspath 149 | * proc_listpids 150 | * proc_listallpids 151 | * proc_listpgrppids 152 | * proc_listchildpids 153 | * proc_pidinfo 154 | * proc_pidfdinfo 155 | * proc_pidfileportinfo 156 | * proc_name 157 | * proc_regionfilename 158 | * proc_kmsgbuf 159 | * proc_pidpath 160 | * proc_libversion 161 | * proc_pid_rusage 162 | * proc_setpcontrol 163 | * proc_track_dirty 164 | * proc_set_dirty 165 | * proc_get_dirty 166 | * proc_clear_dirty 167 | * proc_terminate 168 | * proc_udata_info 169 | 170 | # Reference docs used 171 | * [Darwin page on operating-system.org](https://www.operating-system.org/betriebssystem/_english/bs-darwin.htm) 172 | * [Darwin/XNU Github Project](https://github.com/apple/darwin-xnu) 173 | * [Apple OS Distributions Github Project](https://github.com/apple-oss-distributions/xnu) 174 | * [Darwin Wikipedia page](https://en.wikipedia.org/wiki/Darwin_(operating_system)) -------------------------------------------------------------------------------- /doc/References.md: -------------------------------------------------------------------------------- 1 | # Reference Docs 2 | 3 | ## Apple Docs 4 | https://developer.apple.com/documentation/ 5 | 6 | ## Linux Docs 7 | https://manned.org/libproc/9ae74aa3 libproc man page 8 | https://docs.oracle.com/cd/E88353_01/html/E37842/libproc-3lib.html 9 | https://manned.org/libproc_dev.3 name_to_dev, dev_to_name - lookup device names and numbers tty_to_dev, 10 | dev_to_tty - lookup tty names and numbers 11 | https://manned.org/libproc_output.3 12 | https://manned.org/libproc_pw.3 user_from_uid, group_from_gid - lookup ids in /etc/passwd and /etc/group 13 | https://manned.org/readproc.3 read information from next /proc/## entry 14 | https://manned.org/readproctab.3 read information for all current processes at once 15 | https://manned.org/openproc.3 16 | 17 | https://gitlab.com/procps-ng/procps Command line and full screen utilities for browsing procfs, a "pseudo" file 18 | system dynamically generated by the kernel to provide information about the 19 | status of entries in its process table. 20 | -------------------------------------------------------------------------------- /doc/TODO.md: -------------------------------------------------------------------------------- 1 | TODO 2 | = 3 | These are just notes I've gathered and a list of the status of different calls 4 | and ideas for others. 5 | 6 | Cross-Platform 7 | == 8 | * am_root - libc::geteuid() is unstable still. 9 | * name - Returns the name of the process with the specified pid 10 | * listpids - Only implements listing all pid types 11 | * libversion - Just returns error message on linux as no lib used 12 | * cwdself - just wraps env::current_dir() of rust so not so useful 13 | * pidpath - Returns the path of the file being run as the process with specified pid 14 | * kmsgbuf - get the contents of the kernel message buffer 15 | 16 | Pending 17 | == 18 | Mac OS 19 | === 20 | * pidcwd Can't see yet how to do this in Mac OS 21 | 22 | Linux 23 | === 24 | * listpidinfo 25 | * regionfilename 26 | * pidinfo 27 | * pidfdinfo 28 | 29 | Ideas 30 | == 31 | Here is a lits of things I see would be easy to do on Linux. I still need 32 | to look into how they could be done on Mac. 33 | 34 | * command line arguments 35 | * environment vars 36 | * uid running a process 37 | * parent pid (ppid) -------------------------------------------------------------------------------- /examples/dmesg.rs: -------------------------------------------------------------------------------- 1 | //! A `dmesg` command that is a simple demonstration program for using 2 | //! the `libproc` library 3 | //! 4 | //! Usage 5 | //! = 6 | //! 7 | //! `> dmesg` 8 | //! 9 | 10 | use libproc::kmesg_buffer; 11 | 12 | fn main() { 13 | match kmesg_buffer::kmsgbuf() { 14 | Ok(message) => print!("{message}"), 15 | Err(e) => eprintln!("{e}"), 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/procinfo.rs: -------------------------------------------------------------------------------- 1 | //! `procinfo` is a simple program to demonstrate the use of the `libproc` library. 2 | //! 3 | //! It prints out info about a process specified by its pid, or the current process if no pid 4 | //! specified. 5 | //! 6 | //! Usage 7 | //! = 8 | //!``` 9 | //! procinfo [pid] 10 | //! 11 | //!``` 12 | //! 13 | //! Which will produce output similar to: 14 | //! ``` 15 | //! Libversion: 1.1 16 | //! Pid: 8484 17 | //! Path: /Users/andrew/workspace/libproc-rs/target/debug/procinfo 18 | //! Name: procinfo 19 | //! Region Filename (at address 0): /Users/andrew/workspace/libproc-rs/target/debug/procinfo 20 | //! There are currently 454 processes active 21 | //! 8496 22 | //! ... 23 | //! ``` 24 | 25 | use libproc::pid_rusage::{pidrusage, RUsageInfoV0}; 26 | use libproc::proc_pid; 27 | use libproc::processes; 28 | use std::env; 29 | use std::io::Write; 30 | use std::process; 31 | 32 | fn procinfo(pid: i32) { 33 | match proc_pid::libversion() { 34 | Ok((major, minor)) => println!("Libversion: {major}.{minor}"), 35 | Err(err) => writeln!(&mut std::io::stderr(), "Error: {err}").unwrap(), 36 | } 37 | 38 | println!("Pid: {pid}"); 39 | 40 | match proc_pid::pidpath(pid) { 41 | Ok(path) => println!("Path: {path}"), 42 | Err(err) => writeln!(&mut std::io::stderr(), "Error: {err}").unwrap(), 43 | } 44 | 45 | match pidrusage::(pid) { 46 | Ok(resource_usage) => println!("Memory Used: {} Bytes", resource_usage.ri_resident_size), 47 | Err(err) => writeln!(&mut std::io::stderr(), "Error: {err}").unwrap(), 48 | } 49 | 50 | match proc_pid::name(pid) { 51 | Ok(name) => println!("Name: {name}"), 52 | Err(err) => writeln!(&mut std::io::stderr(), "Error: {err}").unwrap(), 53 | } 54 | 55 | match proc_pid::regionfilename(pid, 0) { 56 | Ok(regionfilename) => println!("Region Filename (at address 0): {regionfilename}"), 57 | Err(err) => writeln!(&mut std::io::stderr(), "Error: {err}").unwrap(), 58 | } 59 | 60 | match processes::pids_by_type(processes::ProcFilter::All) { 61 | Ok(pids) => { 62 | println!("There are currently {} processes active", pids.len()); 63 | for pid in pids { 64 | println!("{pid}"); 65 | } 66 | } 67 | Err(err) => writeln!(&mut std::io::stderr(), "Error: {err}").unwrap(), 68 | } 69 | } 70 | 71 | /// Print out some information about the current process (if no arguments provided) 72 | /// or a particular process (by passing that process's PID as the first argument 73 | fn main() { 74 | let args: Vec = env::args().collect(); 75 | 76 | let pid = if args.len() == 1 { 77 | process::id() 78 | } else { 79 | args[1].clone().parse::().unwrap() 80 | }; 81 | 82 | #[allow(clippy::cast_possible_wrap)] 83 | procinfo(pid as i32); 84 | } 85 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | #![warn(clippy::unwrap_used)] 3 | 4 | //! `libproc` is a library for getting information about running processes on Mac and Linux. 5 | //! 6 | //! Not all methods are available on both Operating Systems yet, but more will be made 7 | //! cross-platform over time. 8 | //! 9 | //! Get information (such as name, path, process info, fd) about running processes by pid, process type, etc. 10 | //! 11 | //! At the moment these methods have been implemented, most of which have examples in their docs: 12 | //! 13 | 14 | /// List processes by type, path or by type and path. 15 | pub mod processes; 16 | 17 | #[doc(inline)] 18 | /// Get information about processes using mainly the `pid` 19 | pub use libproc::proc_pid; 20 | 21 | #[doc(inline)] 22 | /// Read messages from the Kernel Message Buffer 23 | pub use libproc::kmesg_buffer; 24 | 25 | #[doc(inline)] 26 | /// Get information about resource usage of processes 27 | pub use libproc::pid_rusage; 28 | 29 | #[cfg(any(target_os = "macos", doc))] 30 | #[doc(inline)] 31 | /// Get information specific to BSD/Darwin on macos 32 | pub use libproc::bsd_info; 33 | 34 | #[cfg(any(target_os = "macos", doc))] 35 | #[doc(inline)] 36 | /// Get information about a process's use of different types of file descriptors 37 | pub use libproc::file_info; 38 | 39 | #[cfg(any(target_os = "macos", doc))] 40 | #[doc(inline)] 41 | /// Get information about a processes use of network, sockets etc. 42 | pub use libproc::net_info; 43 | 44 | #[cfg(any(target_os = "macos", doc))] 45 | #[doc(inline)] 46 | /// Get information about a process's BSD Tasks 47 | pub use libproc::task_info; 48 | 49 | #[cfg(any(target_os = "macos", doc))] 50 | #[doc(inline)] 51 | /// Get information about threads within a process 52 | pub use libproc::thread_info; 53 | 54 | #[cfg(any(target_os = "macos", doc))] 55 | #[doc(inline)] 56 | /// Get information about Work Queues 57 | pub use libproc::work_queue_info; 58 | 59 | // Not documenting this as this export is legacy, and replaced by all the re-exports of 60 | // sub-modules above 61 | #[doc(hidden)] 62 | pub mod libproc; 63 | 64 | #[cfg(target_os = "macos")] 65 | #[allow(warnings, missing_docs)] 66 | mod osx_libproc_bindings { 67 | #[cfg(not(docsrs))] 68 | include!(concat!(env!("OUT_DIR"), "/osx_libproc_bindings.rs")); 69 | #[cfg(docsrs)] 70 | include!("../docs_rs/osx_libproc_bindings.rs"); 71 | } 72 | -------------------------------------------------------------------------------- /src/libproc/bsd_info.rs: -------------------------------------------------------------------------------- 1 | use crate::libproc::proc_pid::{PIDInfo, PidInfoFlavor}; 2 | #[cfg(target_os = "macos")] 3 | pub use crate::osx_libproc_bindings::proc_bsdinfo as BSDInfo; 4 | 5 | #[cfg(target_os = "macos")] 6 | impl PIDInfo for BSDInfo { 7 | fn flavor() -> PidInfoFlavor { 8 | PidInfoFlavor::TBSDInfo 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/libproc/file_info.rs: -------------------------------------------------------------------------------- 1 | use crate::libproc::helpers; 2 | use crate::libproc::proc_pid::{ListPIDInfo, PidInfoFlavor}; 3 | use std::mem; 4 | 5 | #[cfg(target_os = "macos")] 6 | use crate::osx_libproc_bindings::proc_pidfdinfo; 7 | #[cfg(target_os = "macos")] 8 | use libc::c_void; 9 | 10 | /// Flavor of Pid `FileDescriptor` info for different types of File Descriptors 11 | pub enum PIDFDInfoFlavor { 12 | /// `VNodeInfo` 13 | VNodeInfo = 1, 14 | /// `VNodePathInfo` 15 | VNodePathInfo = 2, 16 | /// `SocketInfo` 17 | SocketInfo = 3, 18 | /// `PSEMInfo` 19 | PSEMInfo = 4, 20 | /// `PSHMInfo` 21 | PSHMInfo = 5, 22 | /// `PipeInfo` 23 | PipeInfo = 6, 24 | /// `KQueueInfo` 25 | KQueueInfo = 7, 26 | /// `AppleTalkInfo` 27 | ATalkInfo = 8, 28 | } 29 | 30 | /// Struct for Listing File Descriptors 31 | pub struct ListFDs; 32 | 33 | impl ListPIDInfo for ListFDs { 34 | type Item = ProcFDInfo; 35 | fn flavor() -> PidInfoFlavor { 36 | PidInfoFlavor::ListFDs 37 | } 38 | } 39 | 40 | /// Struct to hold info about a Processes `FileDescriptor` Info 41 | #[repr(C)] 42 | pub struct ProcFDInfo { 43 | /// `FileDescriptor` 44 | pub proc_fd: i32, 45 | /// `FileDescriptor` type 46 | pub proc_fdtype: u32, 47 | } 48 | 49 | /// Enum for different `FileDescriptor` types 50 | #[derive(Copy, Clone, Debug)] 51 | pub enum ProcFDType { 52 | /// `AppleTalk` 53 | ATalk = 0, 54 | /// Vnode 55 | VNode = 1, 56 | /// Socket 57 | Socket = 2, 58 | /// POSIX shared memory 59 | PSHM = 3, 60 | /// POSIX semaphore 61 | PSEM = 4, 62 | /// Kqueue 63 | KQueue = 5, 64 | /// Pipe 65 | Pipe = 6, 66 | /// `FSEvents` 67 | FSEvents = 7, 68 | /// `NetPolicy` 69 | NetPolicy = 9, 70 | /// Unknown 71 | Unknown, 72 | } 73 | 74 | impl From for ProcFDType { 75 | fn from(value: u32) -> ProcFDType { 76 | match value { 77 | 0 => ProcFDType::ATalk, 78 | 1 => ProcFDType::VNode, 79 | 2 => ProcFDType::Socket, 80 | 3 => ProcFDType::PSHM, 81 | 4 => ProcFDType::PSEM, 82 | 5 => ProcFDType::KQueue, 83 | 6 => ProcFDType::Pipe, 84 | 7 => ProcFDType::FSEvents, 85 | _ => ProcFDType::Unknown, 86 | } 87 | } 88 | } 89 | 90 | /// The `PIDFDInfo` trait is needed for polymorphism on pidfdinfo types, also abstracting flavor 91 | /// in order to provide type-guaranteed flavor correctness 92 | pub trait PIDFDInfo: Default { 93 | /// Return the Pid File Descriptor Info flavor of the implementing struct 94 | fn flavor() -> PIDFDInfoFlavor; 95 | } 96 | 97 | /// Returns the information about file descriptors of the process that match pid passed in. 98 | /// 99 | /// # Errors 100 | /// 101 | /// Will return `Err`if the underlying Darwin method `proc_pidfdinfo` returns 0 102 | /// 103 | /// # Examples 104 | /// 105 | /// ``` 106 | /// use std::io::Write; 107 | /// use std::net::TcpListener; 108 | /// use libproc::libproc::proc_pid::{listpidinfo, pidinfo}; 109 | /// use libproc::libproc::bsd_info::{BSDInfo}; 110 | /// use libproc::libproc::net_info::{SocketFDInfo, SocketInfoKind}; 111 | /// use libproc::libproc::file_info::{pidfdinfo, ListFDs, ProcFDType}; 112 | /// use std::process; 113 | /// 114 | /// let pid = process::id() as i32; 115 | /// 116 | /// // Open TCP port:8000 to test. 117 | /// let _listener = TcpListener::bind("127.0.0.1:8000"); 118 | /// 119 | /// let info = pidinfo::(pid, 0).expect("Could not get BSDInfo on {pid}");/// 120 | /// let fds = listpidinfo::(pid, info.pbi_nfiles as usize) 121 | /// .expect("Could not list FD of {pid}"); 122 | /// for fd in &fds { 123 | /// if let(ProcFDType::Socket) = fd.proc_fdtype.into() { 124 | /// let socket = pidfdinfo::(pid, fd.proc_fd) 125 | /// .expect("Could not get SocketFDInfo"); 126 | /// if let(SocketInfoKind::Tcp) = socket.psi.soi_kind.into() { 127 | /// // access to the member of `soi_proto` is unsafe becasuse of union type. 128 | /// let info = unsafe { socket.psi.soi_proto.pri_tcp }; 129 | /// 130 | /// // change endian and cut off because insi_lport is network endian and 16bit witdh. 131 | /// let mut port = 0; 132 | /// port |= info.tcpsi_ini.insi_lport >> 8 & 0x00ff; 133 | /// port |= info.tcpsi_ini.insi_lport << 8 & 0xff00; 134 | /// 135 | /// // access to the member of `insi_laddr` is unsafe becasuse of union type. 136 | /// let s_addr = unsafe { info.tcpsi_ini.insi_laddr.ina_46.i46a_addr4.s_addr }; 137 | /// 138 | /// // change endian because insi_laddr is network endian. 139 | /// let mut addr = 0; 140 | /// addr |= s_addr >> 24 & 0x000000ff; 141 | /// addr |= s_addr >> 8 & 0x0000ff00; 142 | /// addr |= s_addr << 8 & 0x00ff0000; 143 | /// addr |= s_addr << 24 & 0xff000000; 144 | /// 145 | /// println!("{}.{}.{}.{}:{}", addr >> 24 & 0xff, addr >> 16 & 0xff, addr >> 8 & 0xff, 146 | /// addr & 0xff, port); 147 | /// } 148 | /// } 149 | /// } 150 | /// ``` 151 | #[cfg(target_os = "macos")] 152 | pub fn pidfdinfo(pid: i32, fd: i32) -> Result { 153 | let flavor = T::flavor() as i32; 154 | // No `T` will have size greater than `i32::MAX` so no truncation 155 | #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] 156 | let buffer_size = mem::size_of::() as i32; 157 | let mut pidinfo = T::default(); 158 | #[allow(clippy::pedantic)] 159 | let buffer_ptr = &mut pidinfo as *mut _ as *mut c_void; 160 | let ret: i32; 161 | 162 | unsafe { 163 | ret = proc_pidfdinfo(pid, fd, flavor, buffer_ptr, buffer_size); 164 | }; 165 | 166 | if ret <= 0 { 167 | Err(helpers::get_errno_with_message(ret)) 168 | } else { 169 | Ok(pidinfo) 170 | } 171 | } 172 | 173 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 174 | pub fn pidfdinfo(_pid: i32, _fd: i32) -> Result { 175 | unimplemented!() 176 | } 177 | 178 | #[cfg(all(test, target_os = "macos"))] 179 | mod test { 180 | use crate::libproc::bsd_info::BSDInfo; 181 | use crate::libproc::file_info::{ListFDs, ProcFDType}; 182 | use crate::libproc::net_info::{SocketFDInfo, SocketInfoKind}; 183 | use crate::libproc::proc_pid::{listpidinfo, pidinfo}; 184 | 185 | use super::pidfdinfo; 186 | 187 | #[test] 188 | #[allow(clippy::cast_possible_wrap)] 189 | fn pidfdinfo_test() { 190 | use std::net::TcpListener; 191 | use std::process; 192 | let pid = process::id() as i32; 193 | 194 | let _listener = TcpListener::bind("127.0.0.1:65535"); 195 | 196 | let info = pidinfo::(pid, 0).expect("pidinfo() failed"); 197 | let fds = 198 | listpidinfo::(pid, info.pbi_nfiles as usize).expect("listpidinfo() failed"); 199 | for fd in fds { 200 | if let ProcFDType::Socket = fd.proc_fdtype.into() { 201 | let socket = 202 | pidfdinfo::(pid, fd.proc_fd).expect("pidfdinfo() failed"); 203 | if let SocketInfoKind::Tcp = socket.psi.soi_kind.into() { 204 | unsafe { 205 | let info = socket.psi.soi_proto.pri_tcp; 206 | assert_eq!(socket.psi.soi_protocol, libc::IPPROTO_TCP); 207 | assert_eq!(info.tcpsi_ini.insi_lport, 65535); 208 | } 209 | } 210 | } 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/libproc/helpers.rs: -------------------------------------------------------------------------------- 1 | use errno::errno; 2 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 3 | use std::fs::File; 4 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 5 | use std::io::{BufRead, BufReader}; 6 | 7 | /// Helper function to get errno and return a String with the passed in `return_code`, the error 8 | /// number and a possible message 9 | pub fn get_errno_with_message(return_code: i32) -> String { 10 | let e = errno(); 11 | let code = e.0; 12 | format!("return code = {return_code}, errno = {code}, message = '{e}'") 13 | } 14 | 15 | /// Helper function that depending on the `ret` value: 16 | /// - is negative or 0, then form an error message from the `errno` value 17 | /// - is positive, take `ret` as the length of the success message in `buf` in bytes 18 | pub fn check_errno(ret: i32, buf: &mut Vec) -> Result { 19 | if ret <= 0 { 20 | Err(get_errno_with_message(ret)) 21 | } else { 22 | // `ret` mucg be greater than 0 here so no sign-loss 23 | #[allow(clippy::cast_sign_loss)] 24 | unsafe { 25 | buf.set_len(ret as usize); 26 | } 27 | 28 | match String::from_utf8(buf.clone()) { 29 | Ok(return_value) => Ok(return_value), 30 | Err(e) => Err(format!("Invalid UTF-8 sequence: {e}")), 31 | } 32 | } 33 | } 34 | 35 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 36 | /// A helper function for finding named fields in specific /proc FS files for processes 37 | /// This will be more useful when implementing more linux functions 38 | pub(crate) fn procfile_field(filename: &str, field_name: &str) -> Result { 39 | const SEPARATOR: &str = ":"; 40 | let line_header = format!("{field_name}{SEPARATOR}"); 41 | 42 | // Open the file in read-only mode (ignoring errors). 43 | let file = 44 | File::open(filename).map_err(|_| format!("Could not open /proc file '{filename}'"))?; 45 | let reader = BufReader::new(file); 46 | 47 | // Read the file line by line using the lines() iterator from std::io::BufRead. 48 | for line in reader.lines() { 49 | let line = line.map_err(|_| "Could not read file contents")?; 50 | if line.starts_with(&line_header) { 51 | let parts: Vec<&str> = line.split(SEPARATOR).collect(); 52 | return Ok(parts[1].trim().to_owned()); 53 | } 54 | } 55 | 56 | Err(format!( 57 | "Could not find the field named '{field_name}' in the /proc FS file name '{filename}'" 58 | )) 59 | } 60 | 61 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 62 | /// Parse a memory amount string into integer number of bytes 63 | /// e.g. 220844 kB --> 64 | pub fn parse_memory_string(line: &str) -> Result { 65 | let parts: Vec<&str> = line.trim().split(' ').collect(); 66 | if parts.is_empty() { 67 | return Err(format!("Could not parse Memory String: {line}")); 68 | } 69 | let multiplier: u64 = if parts.len() == 2 { 70 | match parts.get(1) { 71 | Some(&"MB") => 1024 * 1024, 72 | Some(&"kB") => 1024, 73 | Some(&"B") => 1, 74 | _ => return Err(format!("Could not parse units of Memory String: {line}")), 75 | } 76 | } else { 77 | 1 78 | }; 79 | 80 | let value: u64 = parts[0] 81 | .parse() 82 | .map_err(|_| "Could not parse value as integer")?; 83 | 84 | Ok(value * multiplier) 85 | } 86 | 87 | #[cfg(test)] 88 | mod test { 89 | use super::check_errno; 90 | use errno::{set_errno, Errno}; 91 | 92 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 93 | mod linux { 94 | use crate::libproc::helpers::parse_memory_string; 95 | 96 | #[test] 97 | fn test_valid_memory_string() { 98 | assert_eq!(parse_memory_string("220844 kB"), Ok(226_144_256)); 99 | } 100 | 101 | #[test] 102 | fn test_valid_memory_string_spaces() { 103 | assert_eq!(parse_memory_string(" 220844 kB "), Ok(226_144_256)); 104 | } 105 | 106 | #[test] 107 | fn test_invalid_memory_string_units() { 108 | assert!(parse_memory_string(" 220844 THz ").is_err()); 109 | } 110 | 111 | #[test] 112 | fn test_invalid_memory_string() { 113 | assert!(parse_memory_string(" ").is_err()); 114 | } 115 | 116 | #[test] 117 | fn test_invalid_memory_string_empty() { 118 | assert!(parse_memory_string("gobble dee gook").is_err()); 119 | } 120 | } 121 | 122 | #[test] 123 | fn invalid_utf8() { 124 | let mut buf: Vec = vec![255, 0, 0]; 125 | 126 | // Test - small test buffer so no problem truncating 127 | #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] 128 | if let Err(msg) = check_errno(buf.len() as i32, &mut buf) { 129 | assert!(msg.contains( 130 | "Invalid UTF-8 sequence: invalid utf-8 sequence of 1 bytes from index 0" 131 | )); 132 | } 133 | } 134 | 135 | #[test] 136 | fn positive_ret() { 137 | let message = "custom message"; 138 | let mut buf: Vec = Vec::from(message.as_bytes()); 139 | 140 | // Test - small test buffer so no problem truncating 141 | #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] 142 | if let Ok(msg) = check_errno(buf.len() as i32, &mut buf) { 143 | assert!(msg.contains(message)); 144 | } 145 | } 146 | 147 | #[test] 148 | fn negative_ret() { 149 | let mut buf: Vec = vec![]; 150 | set_errno(Errno(-1)); 151 | 152 | // Test 153 | if let Err(mes) = check_errno(-1, &mut buf) { 154 | assert!(mes.contains("return code = -1, errno = -1")); 155 | } 156 | } 157 | 158 | #[test] 159 | fn zero_ret() { 160 | let mut buf: Vec = vec![]; 161 | set_errno(Errno(2)); 162 | 163 | // Test 164 | if let Err(mes) = check_errno(0, &mut buf) { 165 | assert!(mes.contains("return code = 0, errno = 2")); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/libproc/kmesg_buffer.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "macos")] 2 | use std::str; 3 | 4 | #[cfg(target_os = "macos")] 5 | use libc::c_void; 6 | 7 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 8 | use std::fs::File; 9 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 10 | use std::io::{BufRead, BufReader}; 11 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 12 | use std::sync::mpsc; 13 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 14 | use std::sync::mpsc::Receiver; 15 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 16 | use std::{thread, time}; 17 | 18 | #[cfg(target_os = "macos")] 19 | use crate::osx_libproc_bindings::{proc_kmsgbuf, MAXBSIZE as MAX_MSG_BSIZE}; 20 | 21 | #[cfg(any(target_os = "macos", doc))] 22 | /// Read messages from the kernel message buffer 23 | /// 24 | /// Entries are in the format: 25 | /// faclev,seqnum,timestamp[optional, ...];message\n 26 | /// TAGNAME=value (0 or more Tags) 27 | /// See 28 | /// 29 | /// On linux: 30 | /// Turns out that reading to the end of an "infinite file" like "/dev/kmsg" with standard file 31 | /// reading methods will block at the end of file, so a workaround is required. Do the blocking 32 | /// reads on a thread that sends lines read back through a channel, and then return when the thread 33 | /// has blocked and can't send anymore. Returning will end the thread and the channel. 34 | /// 35 | /// # Errors 36 | /// 37 | /// An `Err` will be returned if `/dev/kmsg` device cannot be read 38 | #[cfg(target_os = "macos")] 39 | pub fn kmsgbuf() -> Result { 40 | let mut message_buffer: Vec = Vec::with_capacity(MAX_MSG_BSIZE as _); 41 | let buffer_ptr = message_buffer.as_mut_ptr().cast::(); 42 | let ret: i32; 43 | 44 | unsafe { 45 | // This assumes that MAX_MSG_BSIZE < u32::MAX - but compile time asserts are experimental 46 | #[allow(clippy::cast_possible_truncation)] 47 | let buffersize = message_buffer.capacity() as u32; 48 | ret = proc_kmsgbuf(buffer_ptr, buffersize); 49 | if ret > 0 { 50 | // `ret` cannot be negative here - so cannot lose the sign 51 | #[allow(clippy::cast_sign_loss)] 52 | message_buffer.set_len(ret as usize - 1); 53 | } 54 | } 55 | 56 | if message_buffer.is_empty() { 57 | Err("Could not read kernel message buffer".to_string()) 58 | } else { 59 | let msg = str::from_utf8(&message_buffer) 60 | .map_err(|_| "Could not convert kernel message buffer from utf8".to_string())? 61 | .parse() 62 | .map_err(|_| "Could not parse kernel message")?; 63 | Ok(msg) 64 | } 65 | } 66 | 67 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 68 | pub fn kmsgbuf() -> Result { 69 | let mut file = File::open("/dev/kmsg"); 70 | if file.is_err() { 71 | file = File::open("/dev/console"); 72 | } 73 | let file = file.map_err(|_| "Could not open /dev/kmsg nor /dev/console file '{}'")?; 74 | let kmsg_channel = spawn_kmsg_channel(file); 75 | let duration = time::Duration::from_millis(1); 76 | let mut buf = String::new(); 77 | while let Ok(line) = kmsg_channel.recv_timeout(duration) { 78 | buf.push_str(&line); 79 | } 80 | 81 | Ok(buf) 82 | } 83 | 84 | // Create a channel to return lines read from a file on, then create a thread that reads the lines 85 | // and sends them back on the channel one by one. Eventually it will get to EOF or block 86 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 87 | fn spawn_kmsg_channel(file: File) -> Receiver { 88 | let mut reader = BufReader::new(file); 89 | let (tx, rx) = mpsc::channel::(); 90 | thread::spawn(move || loop { 91 | let mut line = String::new(); 92 | match reader.read_line(&mut line) { 93 | Ok(_) => { 94 | if tx.send(line).is_err() { 95 | break; 96 | } 97 | } 98 | _ => break, 99 | } 100 | }); 101 | 102 | rx 103 | } 104 | 105 | #[cfg(test)] 106 | mod test { 107 | use crate::libproc::proc_pid::am_root; 108 | 109 | use super::kmsgbuf; 110 | 111 | #[test] 112 | fn kmessage_buffer_test() { 113 | if am_root() { 114 | match kmsgbuf() { 115 | Ok(_) => {} 116 | Err(message) => panic!("{}", message), 117 | } 118 | } else { 119 | println!("test skipped as it needs to be run as root"); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/libproc/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(target_os = "macos", doc))] 2 | /// BSD specific information - very macos specific 3 | pub mod bsd_info; 4 | 5 | #[cfg(any(target_os = "macos", doc))] 6 | /// Information about Files and File Descriptors used by processes 7 | pub mod file_info; 8 | 9 | /// Get messages from the kernel message buffer 10 | pub mod kmesg_buffer; 11 | 12 | /// Information about Process Resource Usage - added in Mac OS X 10.9 13 | pub mod pid_rusage; 14 | 15 | /// Get basic information about processes by PID 16 | pub mod proc_pid; 17 | 18 | #[cfg(any(target_os = "macos", doc))] 19 | /// Information about Tasks - very macos specific 20 | pub mod task_info; 21 | 22 | #[cfg(any(target_os = "macos", doc))] 23 | /// Information about Threads running inside processes 24 | pub mod thread_info; 25 | 26 | #[cfg(any(target_os = "macos", doc))] 27 | /// Information about Work Queues - very macos specific 28 | pub mod work_queue_info; 29 | 30 | #[cfg(any(target_os = "macos", doc))] 31 | /// Information about Network usage by a process 32 | pub mod net_info; 33 | 34 | mod helpers; 35 | pub(crate) mod sys; 36 | -------------------------------------------------------------------------------- /src/libproc/net_info.rs: -------------------------------------------------------------------------------- 1 | use crate::libproc::file_info::{PIDFDInfo, PIDFDInfoFlavor}; 2 | 3 | #[cfg(target_os = "macos")] 4 | use libc::SOCK_MAXADDRLEN; 5 | use libc::{ 6 | c_char, c_int, c_short, c_uchar, c_ushort, gid_t, in6_addr, in_addr, off_t, sockaddr_un, uid_t, 7 | IF_NAMESIZE, 8 | }; 9 | #[cfg(target_os = "linux")] 10 | pub const SOCK_MAXADDRLEN: c_int = 255; 11 | 12 | /// Socket File Descriptor Info 13 | #[repr(C)] 14 | #[derive(Default)] 15 | pub struct SocketFDInfo { 16 | /// Proc File Info 17 | pub pfi: ProcFileInfo, 18 | /// Socket Info 19 | pub psi: SocketInfo, 20 | } 21 | 22 | /// Proc File Info 23 | #[repr(C)] 24 | #[derive(Default)] 25 | pub struct ProcFileInfo { 26 | /// Open flags 27 | pub fi_openflags: u32, 28 | /// Status 29 | pub fi_status: u32, 30 | /// Offset 31 | pub fi_offset: off_t, 32 | /// Type 33 | pub fi_type: i32, 34 | /// Reserved for future use 35 | pub rfu_1: i32, 36 | } 37 | 38 | impl PIDFDInfo for SocketFDInfo { 39 | fn flavor() -> PIDFDInfoFlavor { 40 | PIDFDInfoFlavor::SocketInfo 41 | } 42 | } 43 | 44 | /// Socket Info Kind 45 | #[derive(Copy, Clone, Debug)] 46 | pub enum SocketInfoKind { 47 | /// Generic 48 | Generic = 0, 49 | /// IPv4 and IPv6 Sockets 50 | In = 1, 51 | /// TCP Sockets 52 | Tcp = 2, 53 | /// Unix Domain Sockets 54 | Un = 3, 55 | /// Net Drive Sockets 56 | Ndrv = 4, 57 | /// Kernel Event Sockets 58 | KernEvent = 5, 59 | /// Kernel Control Sockets 60 | KernCtl = 6, 61 | /// Unknown 62 | Unknown, 63 | } 64 | 65 | impl From for SocketInfoKind { 66 | fn from(value: c_int) -> SocketInfoKind { 67 | match value { 68 | 0 => SocketInfoKind::Generic, 69 | 1 => SocketInfoKind::In, 70 | 2 => SocketInfoKind::Tcp, 71 | 3 => SocketInfoKind::Un, 72 | 4 => SocketInfoKind::Ndrv, 73 | 5 => SocketInfoKind::KernEvent, 74 | 6 => SocketInfoKind::KernCtl, 75 | _ => SocketInfoKind::Unknown, 76 | } 77 | } 78 | } 79 | 80 | /// Socket Info 81 | #[repr(C)] 82 | #[derive(Default)] 83 | pub struct SocketInfo { 84 | /// Stat 85 | pub soi_stat: VInfoStat, 86 | /// SO 87 | pub soi_so: u64, 88 | /// PCB 89 | pub soi_pcb: u64, 90 | /// Type 91 | pub soi_type: c_int, 92 | /// Protocol 93 | pub soi_protocol: c_int, 94 | /// Family 95 | pub soi_family: c_int, 96 | /// Options 97 | pub soi_options: c_short, 98 | /// Linger 99 | pub soi_linger: c_short, 100 | /// State 101 | pub soi_state: c_short, 102 | /// Queue Length 103 | pub soi_qlen: c_short, 104 | /// Incremental Queue Length 105 | pub soi_incqlen: c_short, 106 | /// Queue Limit 107 | pub soi_qlimit: c_short, 108 | /// Time O 109 | pub soi_timeo: c_short, 110 | /// Error 111 | pub soi_error: c_ushort, 112 | /// OOB Mark 113 | pub soi_oobmark: u32, 114 | /// Receive 115 | pub soi_rcv: SockBufInfo, 116 | /// Send 117 | pub soi_snd: SockBufInfo, 118 | /// Kind 119 | pub soi_kind: c_int, 120 | /// Reserved for future use 121 | pub rfu_1: u32, 122 | /// Proto 123 | pub soi_proto: SocketInfoProto, 124 | } 125 | 126 | /// Struct for V Info Stat 127 | #[repr(C)] 128 | #[derive(Default)] 129 | pub struct VInfoStat { 130 | /// ID of device containing file 131 | pub vst_dev: u32, 132 | /// Mode of file (see below) 133 | pub vst_mode: u16, 134 | /// Number of hard links 135 | pub vst_nlink: u16, 136 | /// File serial number 137 | pub vst_ino: u64, 138 | /// User ID of the file 139 | pub vst_uid: uid_t, 140 | /// Group ID of the file 141 | pub vst_gid: gid_t, 142 | /// Time of last access 143 | pub vst_atime: i64, 144 | /// Time of last access in nano seconds 145 | pub vst_atimensec: i64, 146 | /// Last data modification time 147 | pub vst_mtime: i64, 148 | /// Last data modification time in nano seconds 149 | pub vst_mtimensec: i64, 150 | /// Time of last status change 151 | pub vst_ctime: i64, 152 | /// Time of last status change in nano seconds 153 | pub vst_ctimensec: i64, 154 | /// File creation time(birth) 155 | pub vst_birthtime: i64, 156 | /// File creation time(birth) in nano seconds 157 | pub vst_birthtimensec: i64, 158 | /// file size, in bytes 159 | pub vst_size: off_t, 160 | /// blocks allocated for file 161 | pub vst_blocks: i64, 162 | /// optimal blocksize for I/O 163 | pub vst_blksize: i32, 164 | /// user defined flags for file 165 | pub vst_flags: u32, 166 | /// file generation number 167 | pub vst_gen: u32, 168 | /// Device ID 169 | pub vst_rdev: u32, 170 | /// RESERVED: DO NOT USE! 171 | pub vst_qspare: [i64; 2], 172 | } 173 | 174 | /// Socket Buffer Info 175 | #[repr(C)] 176 | #[derive(Default)] 177 | pub struct SockBufInfo { 178 | /// CC 179 | pub sbi_cc: u32, 180 | /// Hiwat 181 | pub sbi_hiwat: u32, 182 | /// MB Count 183 | pub sbi_mbcnt: u32, 184 | /// MB Max 185 | pub sbi_mbmax: u32, 186 | /// Lowat 187 | pub sbi_lowat: u32, 188 | /// Flags 189 | pub sbi_flags: c_short, 190 | /// Timeo 191 | pub sbi_timeo: c_short, 192 | } 193 | 194 | /// Socket Info Proto 195 | #[repr(C)] 196 | pub union SocketInfoProto { 197 | /// In socket info 198 | pub pri_in: InSockInfo, 199 | /// TCP Socket Info 200 | pub pri_tcp: TcpSockInfo, 201 | /// Un socket info 202 | pub pri_un: UnSockInfo, 203 | /// N Drive Info 204 | pub pri_ndrv: NdrvInfo, 205 | /// Kern Event Info 206 | pub pri_kern_event: KernEventInfo, 207 | /// Kernel Control Info 208 | pub pri_kern_ctl: KernCtlInfo, 209 | } 210 | 211 | impl Default for SocketInfoProto { 212 | fn default() -> SocketInfoProto { 213 | SocketInfoProto { 214 | pri_in: InSockInfo::default(), 215 | } 216 | } 217 | } 218 | 219 | /// struct for holding IP4 or IP6 addresses 220 | #[repr(C)] 221 | #[derive(Copy, Clone)] 222 | pub struct In4In6Addr { 223 | /// Padding 224 | pub i46a_pad32: [u32; 3], 225 | /// Address 226 | pub i46a_addr4: in_addr, 227 | } 228 | 229 | impl Default for In4In6Addr { 230 | fn default() -> In4In6Addr { 231 | In4In6Addr { 232 | i46a_pad32: [0; 3], 233 | i46a_addr4: in_addr { s_addr: 0 }, 234 | } 235 | } 236 | } 237 | 238 | /// `InSocketInfo` struct 239 | #[repr(C)] 240 | #[derive(Copy, Clone, Default)] 241 | pub struct InSockInfo { 242 | /// Foreign Port 243 | pub insi_fport: c_int, 244 | /// Local Port 245 | pub insi_lport: c_int, 246 | /// generation count of this instance 247 | pub insi_gencnt: u64, 248 | /// generic IP/datagram flags 249 | pub insi_flags: u32, 250 | /// Flow 251 | pub insi_flow: u32, 252 | /// In Socket Info IPV4 or IPV6 253 | pub insi_vflag: u8, 254 | /// time to live proto 255 | pub insi_ip_ttl: u8, 256 | /// Reserved for future use 257 | pub rfu_1: u32, 258 | /// foreign host table entry 259 | pub insi_faddr: InSIAddr, 260 | /// local host table entry 261 | pub insi_laddr: InSIAddr, 262 | /// V4 info 263 | pub insi_v4: InSIV4, 264 | /// V6 info 265 | pub insi_v6: InSIV6, 266 | } 267 | 268 | /// In Socket Info `InSIV4` struct 269 | #[repr(C)] 270 | #[derive(Copy, Clone, Default)] 271 | pub struct InSIV4 { 272 | /// Input socket V4 type of service 273 | pub in4_top: c_uchar, // NOTE: Should be in4_tos! 274 | } 275 | 276 | /// In Socket Info `InSIV6` struct 277 | #[repr(C)] 278 | #[derive(Copy, Clone, Default)] 279 | pub struct InSIV6 { 280 | /// `Hlim` 281 | pub in6_hlim: u8, 282 | /// Checksum 283 | pub in6_cksum: c_int, 284 | /// Interface Index 285 | pub in6_ifindex: c_ushort, 286 | /// Hops 287 | pub in6_hops: c_short, 288 | } 289 | 290 | /// In Socket Info `InSIAddr` union for v4 and v6 addresses 291 | #[repr(C)] 292 | #[derive(Copy, Clone)] 293 | pub union InSIAddr { 294 | /// v4 address 295 | pub ina_46: In4In6Addr, 296 | /// v6 address 297 | pub ina_6: in6_addr, 298 | } 299 | 300 | impl Default for InSIAddr { 301 | fn default() -> InSIAddr { 302 | InSIAddr { 303 | ina_46: In4In6Addr::default(), 304 | } 305 | } 306 | } 307 | 308 | /// TCP SI State struct 309 | #[derive(Copy, Clone, Debug)] 310 | pub enum TcpSIState { 311 | /// Closed 312 | Closed = 0, 313 | /// Listening for connection 314 | Listen = 1, 315 | /// Active, have sent syn 316 | SynSent = 2, 317 | /// Have send and received syn 318 | SynReceived = 3, 319 | /// Established 320 | Established = 4, 321 | /// Rcvd fin, waiting for close 322 | CloseWait = 5, 323 | /// Have closed, sent fin 324 | FinWait1 = 6, 325 | /// Closed xchd FIN; await FIN ACK 326 | Closing = 7, 327 | /// Had fin and close; await FIN ACK 328 | LastAck = 8, 329 | /// Have closed, fin is acked 330 | FinWait2 = 9, 331 | /// In 2*msl quiet wait after close 332 | TimeWait = 10, 333 | /// Pseudo state: reserved 334 | Reserved = 11, 335 | /// Unknown 336 | Unknown, 337 | } 338 | 339 | impl From for TcpSIState { 340 | fn from(value: c_int) -> TcpSIState { 341 | match value { 342 | 0 => TcpSIState::Closed, 343 | 1 => TcpSIState::Listen, 344 | 2 => TcpSIState::SynSent, 345 | 3 => TcpSIState::SynReceived, 346 | 4 => TcpSIState::Established, 347 | 5 => TcpSIState::CloseWait, 348 | 6 => TcpSIState::FinWait1, 349 | 7 => TcpSIState::Closing, 350 | 8 => TcpSIState::LastAck, 351 | 9 => TcpSIState::FinWait2, 352 | 10 => TcpSIState::TimeWait, 353 | 11 => TcpSIState::Reserved, 354 | _ => TcpSIState::Unknown, 355 | } 356 | } 357 | } 358 | 359 | const TSI_T_NTIMERS: usize = 4; 360 | 361 | /// TCP Socket Info struct 362 | #[repr(C)] 363 | #[derive(Copy, Clone, Default)] 364 | pub struct TcpSockInfo { 365 | /// In Socket Info 366 | pub tcpsi_ini: InSockInfo, 367 | /// State 368 | pub tcpsi_state: c_int, 369 | /// Timer 370 | pub tcpsi_timer: [c_int; TSI_T_NTIMERS], 371 | /// MSS 372 | pub tcpsi_mss: c_int, 373 | /// Flags 374 | pub tcpsi_flags: u32, 375 | /// Reserved for future use 376 | pub rfu_1: u32, 377 | /// TP 378 | pub tcpsi_tp: u64, 379 | } 380 | 381 | /// Unix Domain Socket Info `UnSockInfo` struct 382 | #[repr(C)] 383 | #[derive(Copy, Clone, Default)] 384 | pub struct UnSockInfo { 385 | /// opaque handle of connected socket 386 | pub unsi_conn_so: u64, 387 | /// opaque handle of connected protocol control block 388 | pub unsi_conn_pcb: u64, 389 | /// bound address 390 | pub unsi_addr: UnSIAddr, 391 | /// address of socket connected to 392 | pub unsi_caddr: UnSIAddr, 393 | } 394 | 395 | /// Unix Socket Info Address `UnSIAddr` union 396 | #[repr(C)] 397 | #[derive(Copy, Clone)] 398 | pub union UnSIAddr { 399 | /// Socket address 400 | pub ua_sun: sockaddr_un, 401 | /// Dummy for padding 402 | pub ua_dummy: [c_char; SOCK_MAXADDRLEN as usize], 403 | } 404 | 405 | impl Default for UnSIAddr { 406 | fn default() -> UnSIAddr { 407 | UnSIAddr { 408 | ua_dummy: [0; SOCK_MAXADDRLEN as usize], 409 | } 410 | } 411 | } 412 | 413 | /// `NDrvInfo` struct for `PF_NDRV Sockets` 414 | #[repr(C)] 415 | #[derive(Copy, Clone, Default)] 416 | pub struct NdrvInfo { 417 | /// Interface Family 418 | pub ndrvsi_if_family: u32, 419 | /// Interface Unit 420 | pub ndrvsi_if_unit: u32, 421 | /// Interface name 422 | pub ndrvsi_if_name: [c_char; IF_NAMESIZE], 423 | } 424 | 425 | /// Kernel Event Info struct 426 | #[repr(C)] 427 | #[derive(Copy, Clone, Default)] 428 | pub struct KernEventInfo { 429 | /// Vendor code filter 430 | pub kesi_vendor_code_filter: u32, 431 | /// Class filter 432 | pub kesi_class_filter: u32, 433 | /// Subclass filter 434 | pub kesi_subclass_filter: u32, 435 | } 436 | 437 | const MAX_KCTL_NAME: usize = 96; 438 | 439 | /// Kernel Control Info struct 440 | #[repr(C)] 441 | #[derive(Copy, Clone)] 442 | pub struct KernCtlInfo { 443 | /// ID 444 | pub kcsi_id: u32, 445 | /// Reg Unit 446 | pub kcsi_reg_unit: u32, 447 | /// Flags 448 | pub kcsi_flags: u32, 449 | /// Receive Buffer Size 450 | pub kcsi_recvbufsize: u32, 451 | /// Send Buffer Size 452 | pub kcsi_sendbufsize: u32, 453 | /// Unit 454 | pub kcsi_unit: u32, 455 | /// Name 456 | pub kcsi_name: [c_char; MAX_KCTL_NAME], 457 | } 458 | 459 | impl Default for KernCtlInfo { 460 | fn default() -> KernCtlInfo { 461 | KernCtlInfo { 462 | kcsi_id: 0, 463 | kcsi_reg_unit: 0, 464 | kcsi_flags: 0, 465 | kcsi_recvbufsize: 0, 466 | kcsi_sendbufsize: 0, 467 | kcsi_unit: 0, 468 | kcsi_name: [0; MAX_KCTL_NAME], 469 | } 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /src/libproc/pid_rusage.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "macos")] 2 | use crate::libproc::helpers; 3 | 4 | #[cfg(target_os = "macos")] 5 | use libc::c_void; 6 | 7 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 8 | use crate::libproc::helpers::{parse_memory_string, procfile_field}; 9 | #[cfg(target_os = "macos")] 10 | use crate::osx_libproc_bindings::proc_pid_rusage; 11 | 12 | /// The `PIDRUsage` trait is needed for polymorphism on pidrusage types, also abstracting flavor in order to provide 13 | /// type-guaranteed flavor correctness 14 | pub trait PIDRUsage: Default { 15 | /// Return the `PidRUsageFlavor` for the implementing struct 16 | fn flavor() -> PidRUsageFlavor; 17 | /// Memory used in bytes 18 | fn memory_used(&self) -> u64; 19 | /// Memory used in bytes 20 | fn set_memory_used(&mut self, used: u64); 21 | } 22 | 23 | /// `PidRUsageFlavor` From 24 | pub enum PidRUsageFlavor { 25 | /// Version 0 26 | V0 = 0, 27 | /// Version 1 28 | V1 = 1, 29 | /// Version 2 30 | V2 = 2, 31 | /// Version 3 32 | V3 = 3, 33 | /// Version 4 34 | V4 = 4, 35 | } 36 | 37 | /// C struct for Resource Usage Version 0 38 | #[repr(C)] 39 | #[derive(Default)] 40 | pub struct RUsageInfoV0 { 41 | /// Unique user id 42 | pub ri_uuid: [u8; 16], 43 | /// User time used 44 | pub ri_user_time: u64, 45 | /// System time used 46 | pub ri_system_time: u64, 47 | /// Wakeups from idle 48 | pub ri_pkg_idle_wkups: u64, 49 | /// Interrupt wakeups 50 | pub ri_interrupt_wkups: u64, 51 | /// Number of pageins 52 | pub ri_pageins: u64, 53 | /// Wired size 54 | pub ri_wired_size: u64, 55 | /// Resident size 56 | pub ri_resident_size: u64, 57 | /// Physical footprint 58 | pub ri_phys_footprint: u64, 59 | /// Process start time 60 | pub ri_proc_start_abstime: u64, 61 | /// Process exit time 62 | pub ri_proc_exit_abstime: u64, 63 | } 64 | 65 | impl PIDRUsage for RUsageInfoV0 { 66 | fn flavor() -> PidRUsageFlavor { 67 | PidRUsageFlavor::V0 68 | } 69 | 70 | fn memory_used(&self) -> u64 { 71 | self.ri_resident_size 72 | } 73 | 74 | fn set_memory_used(&mut self, used: u64) { 75 | self.ri_resident_size = used; 76 | } 77 | } 78 | 79 | /// C struct for Resource Usage Version 1 80 | #[repr(C)] 81 | #[derive(Default)] 82 | pub struct RUsageInfoV1 { 83 | /// Unique user id 84 | pub ri_uuid: [u8; 16], 85 | /// User time used 86 | pub ri_user_time: u64, 87 | /// System time used 88 | pub ri_system_time: u64, 89 | /// Wakeups from idle 90 | pub ri_pkg_idle_wkups: u64, 91 | /// Interrupt wakeups 92 | pub ri_interrupt_wkups: u64, 93 | /// Number of pageins 94 | pub ri_pageins: u64, 95 | /// Wired size 96 | pub ri_wired_size: u64, 97 | /// Resident size 98 | pub ri_resident_size: u64, 99 | /// Physical footprint 100 | pub ri_phys_footprint: u64, 101 | /// Process start time 102 | pub ri_proc_start_abstime: u64, 103 | /// Process exit time 104 | pub ri_proc_exit_abstime: u64, 105 | /// Child user time 106 | pub ri_child_user_time: u64, 107 | /// Child system time 108 | pub ri_child_system_time: u64, 109 | /// Child wakeups from idle 110 | pub ri_child_pkg_idle_wkups: u64, 111 | /// Child interrupt wakeups 112 | pub ri_child_interrupt_wkups: u64, 113 | /// Child pageins 114 | pub ri_child_pageins: u64, 115 | /// Child elapse time 116 | pub ri_child_elapsed_abstime: u64, 117 | } 118 | 119 | impl PIDRUsage for RUsageInfoV1 { 120 | fn flavor() -> PidRUsageFlavor { 121 | PidRUsageFlavor::V1 122 | } 123 | 124 | fn memory_used(&self) -> u64 { 125 | self.ri_resident_size 126 | } 127 | 128 | fn set_memory_used(&mut self, used: u64) { 129 | self.ri_resident_size = used; 130 | } 131 | } 132 | 133 | /// C struct for Resource Usage Version 2 134 | #[repr(C)] 135 | #[derive(Debug, Default)] 136 | pub struct RUsageInfoV2 { 137 | /// Unique user id 138 | pub ri_uuid: [u8; 16], 139 | /// User time used 140 | pub ri_user_time: u64, 141 | /// System time used 142 | pub ri_system_time: u64, 143 | /// Wakeups from idle 144 | pub ri_pkg_idle_wkups: u64, 145 | /// Interrupt wakeups 146 | pub ri_interrupt_wkups: u64, 147 | /// Number of pageins 148 | pub ri_pageins: u64, 149 | /// Wired size 150 | pub ri_wired_size: u64, 151 | /// Resident size 152 | pub ri_resident_size: u64, 153 | /// Physical footprint 154 | pub ri_phys_footprint: u64, 155 | /// Process start time 156 | pub ri_proc_start_abstime: u64, 157 | /// Process exit time 158 | pub ri_proc_exit_abstime: u64, 159 | /// Child user time 160 | pub ri_child_user_time: u64, 161 | /// Child system time 162 | pub ri_child_system_time: u64, 163 | /// Child wakeups from idle 164 | pub ri_child_pkg_idle_wkups: u64, 165 | /// Child interrupt wakeups 166 | pub ri_child_interrupt_wkups: u64, 167 | /// Child pageins 168 | pub ri_child_pageins: u64, 169 | /// Child elapse time 170 | pub ri_child_elapsed_abstime: u64, 171 | /// Disk IO bytes read 172 | pub ri_diskio_bytesread: u64, 173 | /// Disk IO bytes written 174 | pub ri_diskio_byteswritten: u64, 175 | } 176 | 177 | impl PIDRUsage for RUsageInfoV2 { 178 | fn flavor() -> PidRUsageFlavor { 179 | PidRUsageFlavor::V2 180 | } 181 | 182 | fn memory_used(&self) -> u64 { 183 | self.ri_resident_size 184 | } 185 | 186 | fn set_memory_used(&mut self, used: u64) { 187 | self.ri_resident_size = used; 188 | } 189 | } 190 | 191 | /// C struct for Resource Usage Version 3 192 | #[repr(C)] 193 | #[derive(Default)] 194 | pub struct RUsageInfoV3 { 195 | /// Unique user id 196 | pub ri_uuid: [u8; 16], 197 | /// User time used 198 | pub ri_user_time: u64, 199 | /// System time used 200 | pub ri_system_time: u64, 201 | /// Wakeups from idle 202 | pub ri_pkg_idle_wkups: u64, 203 | /// Interrupt wakeups 204 | pub ri_interrupt_wkups: u64, 205 | /// Number of pageins 206 | pub ri_pageins: u64, 207 | /// Wired size 208 | pub ri_wired_size: u64, 209 | /// Resident size 210 | pub ri_resident_size: u64, 211 | /// Physical footprint 212 | pub ri_phys_footprint: u64, 213 | /// Process start time 214 | pub ri_proc_start_abstime: u64, 215 | /// Process exit time 216 | pub ri_proc_exit_abstime: u64, 217 | /// Child user time 218 | pub ri_child_user_time: u64, 219 | /// Child system time 220 | pub ri_child_system_time: u64, 221 | /// Child wakeups from idle 222 | pub ri_child_pkg_idle_wkups: u64, 223 | /// Child interrupt wakeups 224 | pub ri_child_interrupt_wkups: u64, 225 | /// Child pageins 226 | pub ri_child_pageins: u64, 227 | /// Child elapse time 228 | pub ri_child_elapsed_abstime: u64, 229 | /// Disk IO bytes read 230 | pub ri_diskio_bytesread: u64, 231 | /// Disk IO bytes written 232 | pub ri_diskio_byteswritten: u64, 233 | /// CPU time QOS default 234 | pub ri_cpu_time_qos_default: u64, 235 | /// CPU time QOS maintenance 236 | pub ri_cpu_time_qos_maintenance: u64, 237 | /// CPU time QOS background 238 | pub ri_cpu_time_qos_background: u64, 239 | /// CPU time QOS utility 240 | pub ri_cpu_time_qos_utility: u64, 241 | /// CPU time QOS legacy 242 | pub ri_cpu_time_qos_legacy: u64, 243 | /// CPU time QOS user initiated 244 | pub ri_cpu_time_qos_user_initiated: u64, 245 | /// CPU tim QOS user interactive 246 | pub ri_cpu_time_qos_user_interactive: u64, 247 | /// Billed system time 248 | pub ri_billed_system_time: u64, 249 | /// Serviced system time 250 | pub ri_serviced_system_time: u64, 251 | } 252 | 253 | impl PIDRUsage for RUsageInfoV3 { 254 | fn flavor() -> PidRUsageFlavor { 255 | PidRUsageFlavor::V3 256 | } 257 | 258 | fn memory_used(&self) -> u64 { 259 | self.ri_resident_size 260 | } 261 | 262 | fn set_memory_used(&mut self, used: u64) { 263 | self.ri_resident_size = used; 264 | } 265 | } 266 | 267 | /// C struct for Resource Usage Version 4 268 | #[repr(C)] 269 | #[derive(Default)] 270 | pub struct RUsageInfoV4 { 271 | /// Unique user id 272 | pub ri_uuid: [u8; 16], 273 | /// User time used 274 | pub ri_user_time: u64, 275 | /// System time used 276 | pub ri_system_time: u64, 277 | /// Wakeups from idle 278 | pub ri_pkg_idle_wkups: u64, 279 | /// Child interrupt wakeups 280 | pub ri_interrupt_wkups: u64, 281 | /// Number of pageins 282 | pub ri_pageins: u64, 283 | /// Wired size 284 | pub ri_wired_size: u64, 285 | /// Resident size 286 | pub ri_resident_size: u64, 287 | /// Physical footprint 288 | pub ri_phys_footprint: u64, 289 | /// Process start time 290 | pub ri_proc_start_abstime: u64, 291 | /// Process exit time 292 | pub ri_proc_exit_abstime: u64, 293 | /// Child user time 294 | pub ri_child_user_time: u64, 295 | /// Child system time 296 | pub ri_child_system_time: u64, 297 | /// Child wakeups from idle 298 | pub ri_child_pkg_idle_wkups: u64, 299 | /// Child interrupt wakeups 300 | pub ri_child_interrupt_wkups: u64, 301 | /// Child pageins 302 | pub ri_child_pageins: u64, 303 | /// Child elapse time 304 | pub ri_child_elapsed_abstime: u64, 305 | /// Disk IO bytes read 306 | pub ri_diskio_bytesread: u64, 307 | /// Disk IO bytes written 308 | pub ri_diskio_byteswritten: u64, 309 | /// CPU time QOS default 310 | pub ri_cpu_time_qos_default: u64, 311 | /// CPU time QOS maintenance 312 | pub ri_cpu_time_qos_maintenance: u64, 313 | /// CPU time QOS background 314 | pub ri_cpu_time_qos_background: u64, 315 | /// CPU time QOS utility 316 | pub ri_cpu_time_qos_utility: u64, 317 | /// CPU time QOS legacy 318 | pub ri_cpu_time_qos_legacy: u64, 319 | /// CPU time QOS user initiated 320 | pub ri_cpu_time_qos_user_initiated: u64, 321 | /// CPU tim QOS user interactive 322 | pub ri_cpu_time_qos_user_interactive: u64, 323 | /// Billed system time 324 | pub ri_billed_system_time: u64, 325 | /// Serviced system time 326 | pub ri_serviced_system_time: u64, 327 | /// Logical writes 328 | pub ri_logical_writes: u64, 329 | /// Lifetime maximum physical footprint 330 | pub ri_lifetime_max_phys_footprint: u64, 331 | /// instructions 332 | pub ri_instructions: u64, 333 | /// cycles 334 | pub ri_cycles: u64, 335 | /// billed energy 336 | pub ri_billed_energy: u64, 337 | /// services energy 338 | pub ri_serviced_energy: u64, 339 | /// interval maximum physical footprint 340 | pub ri_interval_max_phys_footprint: u64, 341 | /// unused 342 | pub ri_unused: [u64; 1], 343 | } 344 | 345 | impl PIDRUsage for RUsageInfoV4 { 346 | fn flavor() -> PidRUsageFlavor { 347 | PidRUsageFlavor::V4 348 | } 349 | 350 | fn memory_used(&self) -> u64 { 351 | self.ri_resident_size 352 | } 353 | 354 | fn set_memory_used(&mut self, used: u64) { 355 | self.ri_resident_size = used; 356 | } 357 | } 358 | 359 | #[cfg(target_os = "macos")] 360 | /// Returns the information about resources of the process that match pid passed in. 361 | /// 362 | /// # Errors 363 | /// 364 | /// Will return an `Err` if Darwin's underlying method `proc_pid_rusage` returns an error and 365 | /// set `errno` 366 | /// 367 | /// # Examples 368 | /// 369 | /// ``` 370 | /// use std::io::Write; 371 | /// use libproc::libproc::pid_rusage::{pidrusage, RUsageInfoV2}; 372 | /// 373 | /// fn pidrusage_test() { 374 | /// use std::process; 375 | /// let pid = process::id() as i32; 376 | /// 377 | /// #[cfg(target_os = "macos")] 378 | /// if let Ok(res) = pidrusage::(pid) { 379 | /// println!("UUID: {:?}, Disk Read: {}, Disk Write: {}", res.ri_uuid, res.ri_diskio_bytesread, res.ri_diskio_byteswritten); 380 | /// } 381 | /// } 382 | /// ``` 383 | pub fn pidrusage(pid: i32) -> Result { 384 | let flavor = T::flavor() as i32; 385 | let mut pidrusage = T::default(); 386 | #[allow(clippy::pedantic)] 387 | let buffer_ptr = &mut pidrusage as *mut _ as *mut c_void; 388 | let ret: i32; 389 | 390 | unsafe { 391 | ret = proc_pid_rusage(pid, flavor, buffer_ptr.cast()); 392 | }; 393 | 394 | if ret < 0 { 395 | Err(helpers::get_errno_with_message(ret)) 396 | } else { 397 | Ok(pidrusage) 398 | } 399 | } 400 | 401 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 402 | /// Returns the information about resources of the process that match pid passed in. 403 | /// 404 | /// # Errors 405 | /// 406 | /// Will return `Err` if no process with PID `pid` exists, if the procfs file system cannot be 407 | /// read or the information `VmSize` cannot be read from it for the process in question 408 | /// 409 | /// # Examples 410 | /// 411 | /// ``` 412 | /// use std::io::Write; 413 | /// use libproc::libproc::pid_rusage::{pidrusage, RUsageInfoV2, RUsageInfoV0, PIDRUsage}; 414 | /// 415 | /// fn pidrusage_test() { 416 | /// use std::process; 417 | /// let pid = process::id() as i32; 418 | /// 419 | /// if let Ok(res) = pidrusage::(pid) { 420 | /// println!("VmSize (resident_size): {}", res.memory_used() ); 421 | /// } 422 | /// } 423 | /// ``` 424 | pub fn pidrusage(pid: i32) -> Result { 425 | let mut pidrusage = T::default(); 426 | let vm_size = procfile_field(&format!("/proc/{pid}/status"), "VmSize")?; 427 | pidrusage.set_memory_used(parse_memory_string(&vm_size)?); 428 | 429 | Ok(pidrusage) 430 | } 431 | 432 | #[cfg(test)] 433 | #[allow(clippy::cast_possible_wrap)] 434 | mod test { 435 | use super::pidrusage; 436 | use crate::libproc::pid_rusage::RUsageInfoV0; 437 | 438 | #[test] 439 | fn pidrusage_test() { 440 | let usage: RUsageInfoV0 = pidrusage(std::process::id() as i32).expect("pidrusage() failed"); 441 | assert!(usage.ri_resident_size > 0, "Resident size reports 0"); 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /src/libproc/proc_pid.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 3 | use std::ffi::CString; 4 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 5 | use std::fs; 6 | #[cfg(target_os = "macos")] 7 | use std::mem; 8 | use std::path::PathBuf; 9 | 10 | #[cfg(target_os = "macos")] 11 | use libc::c_int; 12 | use libc::pid_t; 13 | 14 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 15 | use libc::PATH_MAX; 16 | 17 | #[cfg(target_os = "macos")] 18 | use crate::libproc::bsd_info::BSDInfo; 19 | use crate::libproc::helpers; 20 | #[cfg(target_os = "macos")] 21 | use crate::libproc::task_info::{TaskAllInfo, TaskInfo}; 22 | #[cfg(target_os = "macos")] 23 | use crate::libproc::thread_info::ThreadInfo; 24 | #[cfg(target_os = "macos")] 25 | use crate::libproc::work_queue_info::WorkQueueInfo; 26 | #[cfg(target_os = "macos")] 27 | use crate::osx_libproc_bindings::{ 28 | proc_libversion, proc_name, proc_pidinfo, proc_pidpath, proc_regionfilename, 29 | PROC_PIDPATHINFO_MAXSIZE, 30 | }; 31 | 32 | #[cfg(target_os = "macos")] 33 | use libc::c_void; 34 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 35 | use libc::{c_char, readlink}; 36 | 37 | use crate::processes; 38 | 39 | /// The `ProcType` type. Used to specify what type of processes you are interested 40 | /// in in other calls, such as `listpids`. 41 | #[derive(Copy, Clone)] 42 | pub enum ProcType { 43 | /// All processes 44 | ProcAllPIDS = 1, 45 | /// Only PGRP Processes 46 | ProcPGRPOnly = 2, 47 | /// Only TTY Processes 48 | ProcTTYOnly = 3, 49 | /// Only UID Processes 50 | ProcUIDOnly = 4, 51 | /// Only RUID Processes 52 | ProcRUIDOnly = 5, 53 | /// Only PPID Processes 54 | ProcPPIDOnly = 6, 55 | } 56 | 57 | /// The `PIDInfo` trait is needed for polymorphism on pidinfo types, also abstracting flavor in order to provide 58 | /// type-guaranteed flavor correctness 59 | pub trait PIDInfo { 60 | /// Return the `PidInfoFlavor` of the implementing struct 61 | fn flavor() -> PidInfoFlavor; 62 | } 63 | 64 | /// An enum used to specify what type of information about a process is referenced 65 | /// See 66 | pub enum PidInfoFlavor { 67 | /// List of File Descriptors 68 | ListFDs = 1, 69 | /// struct `proc_taskallinfo` 70 | TaskAllInfo = 2, 71 | /// struct `proc_bsdinfo` 72 | TBSDInfo = 3, 73 | /// struct `proc_taskinfo` 74 | TaskInfo = 4, 75 | /// struct `proc_threadinfo` 76 | ThreadInfo = 5, 77 | /// list thread ids 78 | ListThreads = 6, 79 | /// TBD what type `RegionInfo` is - string? 80 | RegionInfo = 7, 81 | /// Region Path info strings 82 | RegionPathInfo = 8, 83 | /// Strings 84 | VNodePathInfo = 9, 85 | /// Strings 86 | ThreadPathInfo = 10, 87 | /// Strings 88 | PathInfo = 11, 89 | /// struct `proc_workqueueinfo` 90 | WorkQueueInfo = 12, 91 | } 92 | 93 | /// The `PidInfo` enum contains a piece of information about a processes 94 | #[allow(clippy::large_enum_variant)] 95 | pub enum PidInfo { 96 | /// File Descriptors used by Process 97 | ListFDs(Vec), 98 | /// Get all Task Info 99 | #[cfg(target_os = "macos")] 100 | TaskAllInfo(TaskAllInfo), 101 | /// Get `TBSDInfo` 102 | #[cfg(target_os = "macos")] 103 | TBSDInfo(BSDInfo), 104 | /// Single Task Info 105 | #[cfg(target_os = "macos")] 106 | TaskInfo(TaskInfo), 107 | /// `ThreadInfo` 108 | #[cfg(target_os = "macos")] 109 | ThreadInfo(ThreadInfo), 110 | /// A list of Thread IDs 111 | ListThreads(Vec), 112 | /// `RegionInfo` 113 | RegionInfo(String), 114 | /// `RegionPathInfo` 115 | RegionPathInfo(String), 116 | /// `VNodePathInfo` 117 | VNodePathInfo(String), 118 | /// `ThreadPathInfo` 119 | ThreadPathInfo(String), 120 | /// `PathInfo` of the executable being run as the process 121 | PathInfo(String), 122 | /// `WorkQueueInfo` 123 | #[cfg(target_os = "macos")] 124 | WorkQueueInfo(WorkQueueInfo), 125 | } 126 | 127 | /// The `ListPIDInfo` trait is needed for polymorphism on listpidinfo types, also abstracting flavor in order to provide 128 | /// type-guaranteed flavor correctness 129 | pub trait ListPIDInfo { 130 | /// Item 131 | type Item; 132 | /// Return the `PidInfoFlavor` of the implementing struct 133 | fn flavor() -> PidInfoFlavor; 134 | } 135 | 136 | /// Struct for List of Threads 137 | pub struct ListThreads; 138 | 139 | impl ListPIDInfo for ListThreads { 140 | type Item = u64; 141 | fn flavor() -> PidInfoFlavor { 142 | PidInfoFlavor::ListThreads 143 | } 144 | } 145 | 146 | /// Map `ProcType` to the new `ProcFilter` enum; the values match the now 147 | /// deprecated implementation of `listpids` 148 | impl From for processes::ProcFilter { 149 | fn from(proc_type: ProcType) -> Self { 150 | use processes::ProcFilter; 151 | 152 | match proc_type { 153 | ProcType::ProcAllPIDS => ProcFilter::All, 154 | ProcType::ProcPGRPOnly => ProcFilter::ByProgramGroup { pgrpid: 0 }, 155 | ProcType::ProcTTYOnly => ProcFilter::ByTTY { tty: 0 }, 156 | ProcType::ProcUIDOnly => ProcFilter::ByUID { uid: 0 }, 157 | ProcType::ProcRUIDOnly => ProcFilter::ByRealUID { ruid: 0 }, 158 | ProcType::ProcPPIDOnly => ProcFilter::ByParentProcess { ppid: 0 }, 159 | } 160 | } 161 | } 162 | 163 | /// Returns the PIDs of the active processes that match the `proc_types` parameter 164 | /// 165 | /// # Note 166 | /// 167 | /// This function is deprecated in favor of 168 | /// [`libproc::processes::pids_by_type()`][crate::processes::pids_by_type], 169 | /// which lets you specify what PGRP / TTY / UID / RUID / PPID you want to filter by 170 | #[allow(clippy::missing_errors_doc)] 171 | #[deprecated( 172 | since = "0.13.0", 173 | note = "Please use `libproc::processes::pids_by_type()` instead." 174 | )] 175 | pub fn listpids(proc_types: ProcType) -> Result, String> { 176 | processes::pids_by_type(proc_types.into()).map_err(|e| { 177 | e.raw_os_error() 178 | .map_or_else(|| e.to_string(), helpers::get_errno_with_message) 179 | }) 180 | } 181 | 182 | /// Search through the current processes looking for open file references which match 183 | /// a specified path or volume. 184 | /// 185 | /// # Note 186 | /// 187 | /// This function is deprecated in favor of 188 | /// [`libproc::processes::pids_by_type_and_path()`][crate::processes::pids_by_type_and_path], 189 | /// which lets you specify what PGRP / TTY / UID / RUID / PPID you want to 190 | /// filter by. 191 | /// 192 | #[cfg(target_os = "macos")] 193 | #[allow(clippy::missing_errors_doc)] 194 | #[deprecated( 195 | since = "0.13.0", 196 | note = "Please use `libproc::processes::pids_by_type_and_path()` instead." 197 | )] 198 | pub fn listpidspath(proc_types: ProcType, path: &str) -> Result, String> { 199 | processes::pids_by_type_and_path(proc_types.into(), &PathBuf::from(path), false, false).map_err( 200 | |e| { 201 | e.raw_os_error() 202 | .map_or_else(|| e.to_string(), helpers::get_errno_with_message) 203 | }, 204 | ) 205 | } 206 | 207 | /// Get info about a process, task, thread or work queue by specifying the appropriate type for `T`: 208 | /// - `BSDInfo` - see struct `proc_bsdinfo` in generated `osx_libproc_bindings.rs` 209 | /// - `TaskInfo` - see struct `proc_taskinfo` in generated `osx_libproc_bindings.rs` 210 | /// - `TaskAllInfo` - see struct `TaskAllInfo` in `task_info.rs` that contains the two structs above 211 | /// - `ThreadInfo` - see struct `proc_threadinfo` in generated `osx_libproc_bindings.rs` 212 | /// - `WorkQueueInfo` - see struct `proc_workqueueinfo` in generated `osx_libproc_bindings.rs` 213 | /// 214 | /// # Errors 215 | /// 216 | /// Will return an error if underlying Darwin `proc_pidinfo` returns an error or sets `errno` 217 | /// 218 | /// # Examples 219 | /// 220 | /// ``` 221 | /// use std::io::Write; 222 | /// use libproc::proc_pid::pidinfo; 223 | /// use libproc::bsd_info::BSDInfo; 224 | /// use libproc::task_info::{TaskAllInfo, TaskInfo}; 225 | /// use std::process; 226 | /// use libproc::thread_info::ThreadInfo; 227 | /// use libproc::work_queue_info::WorkQueueInfo; 228 | /// 229 | /// let pid = process::id() as i32; 230 | /// 231 | /// // Get the `BSDInfo` for process with pid 0 232 | /// match pidinfo::(pid, 0) { 233 | /// Ok(info) => assert_eq!(info.pbi_pid as i32, pid), 234 | /// Err(err) => eprintln!("Error retrieving process info: {}", err) 235 | /// }; 236 | /// 237 | /// // Get the `TaskInfo` for process with pid 0 238 | /// match pidinfo::(pid, 0) { 239 | /// Ok(info) => assert!(info.pti_threadnum > 0), 240 | /// Err(err) => eprintln!("Error retrieving process info: {}", err) 241 | /// }; 242 | /// 243 | /// // Get the `TaskAllInfo` for process with pid 0 244 | /// match pidinfo::(pid, 0) { 245 | /// Ok(info) => { 246 | /// assert_eq!(info.pbsd.pbi_pid as i32, pid); 247 | /// assert!(info.ptinfo.pti_threadnum > 0); 248 | /// } 249 | /// Err(err) => eprintln!("Error retrieving process info: {}", err) 250 | /// }; 251 | /// 252 | /// // Get the `ThreadInfo` for process with pid 0 253 | /// match pidinfo::(pid, 0) { 254 | /// Ok(info) => assert!(!info.pth_name.is_empty()), 255 | /// Err(err) => eprintln!("Error retrieving process info: {}", err) 256 | /// }; 257 | /// 258 | /// // Get the `WorkQueueInfo` for process with pid 0 259 | /// match pidinfo::(pid, 0) { 260 | /// Ok(info) => assert!(info.pwq_nthreads > 0), 261 | /// Err(err) => eprintln!("Error retrieving process info: {}", err) 262 | /// }; 263 | /// ``` 264 | #[cfg(target_os = "macos")] 265 | pub fn pidinfo(pid: i32, arg: u64) -> Result { 266 | let flavor = T::flavor() as i32; 267 | // No type `T` will be bigger than `i32::MAX`!! 268 | #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] 269 | let buffer_size = mem::size_of::() as c_int; 270 | let mut pidinfo = unsafe { mem::zeroed() }; 271 | #[allow(clippy::pedantic)] 272 | let buffer_ptr = &mut pidinfo as *mut _ as *mut c_void; 273 | let ret: i32; 274 | 275 | unsafe { 276 | ret = proc_pidinfo(pid, flavor, arg, buffer_ptr, buffer_size); 277 | }; 278 | 279 | if ret <= 0 { 280 | Err(helpers::get_errno_with_message(ret)) 281 | } else { 282 | Ok(pidinfo) 283 | } 284 | } 285 | 286 | #[cfg(any(target_os = "macos", doc))] 287 | /// Get the filename associated with a memory region 288 | /// 289 | /// # Errors 290 | /// 291 | /// Will return an error if underlying Darwin function `proc_regionfilename` returns an error. 292 | /// 293 | /// # Examples 294 | /// 295 | /// ``` 296 | /// use libproc::libproc::proc_pid::regionfilename; 297 | /// 298 | /// // This checks that it can find the regionfilename of the region at address 0, of the init process with PID 1 299 | /// use libproc::libproc::proc_pid::am_root; 300 | /// 301 | /// if am_root() { 302 | /// match regionfilename(1, 0) { 303 | /// Ok(regionfilename) => println!("Region Filename (at address = 0) of init process PID = 1 is '{}'", regionfilename), 304 | /// Err(err) => eprintln!("Error: {}", err) 305 | /// } 306 | /// } 307 | /// ``` 308 | #[cfg(target_os = "macos")] 309 | pub fn regionfilename(pid: i32, address: u64) -> Result { 310 | let mut buf: Vec = Vec::with_capacity((PROC_PIDPATHINFO_MAXSIZE - 1) as _); 311 | let buffer_ptr = buf.as_mut_ptr().cast::(); 312 | // PROC_PIDPATHINFO_MAXSIZE will be smaller than `u32::MAX` 313 | #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] 314 | let buffer_size = buf.capacity() as u32; 315 | let ret: i32; 316 | 317 | unsafe { 318 | ret = proc_regionfilename(pid, address, buffer_ptr, buffer_size); 319 | }; 320 | 321 | helpers::check_errno(ret, &mut buf) 322 | } 323 | 324 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 325 | pub fn regionfilename(_pid: i32, _address: u64) -> Result { 326 | Err("'regionfilename' not implemented on linux".to_owned()) 327 | } 328 | 329 | /// Get the path of the executable file being run for a process 330 | /// 331 | /// # Errors 332 | /// 333 | /// Will return an error if underlying Darwin function `proc_pidpath` returns an error. 334 | /// 335 | /// # Examples 336 | /// 337 | /// ``` 338 | /// use libproc::libproc::proc_pid::pidpath; 339 | /// 340 | /// match pidpath(1) { 341 | /// Ok(path) => println!("Path of init process with PID = 1 is '{}'", path), 342 | /// Err(err) => eprintln!("Error: {}", err) 343 | /// } 344 | /// ``` 345 | #[cfg(target_os = "macos")] 346 | pub fn pidpath(pid: i32) -> Result { 347 | let mut buf: Vec = Vec::with_capacity((PROC_PIDPATHINFO_MAXSIZE - 1) as _); 348 | let buffer_ptr = buf.as_mut_ptr().cast::(); 349 | // PROC_PIDPATHINFO_MAXSIZE will be smaller than `u32::MAX` 350 | #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] 351 | let buffer_size = buf.capacity() as u32; 352 | let ret: i32; 353 | 354 | unsafe { 355 | ret = proc_pidpath(pid, buffer_ptr, buffer_size as _); 356 | }; 357 | 358 | helpers::check_errno(ret, &mut buf) 359 | } 360 | 361 | /// Get the path of the executable file being run for a process 362 | /// 363 | /// # Errors 364 | /// 365 | /// Will return `Err` if not run as root or the underlying linux `readlink` method returns 366 | /// a non-zero value and sets `errno` 367 | /// 368 | /// # Examples 369 | /// 370 | /// ``` 371 | /// use libproc::libproc::proc_pid::{pidpath, am_root}; 372 | /// 373 | /// match pidpath(1) { 374 | /// Ok(path) => println!("Path of init process with PID = 1 is '{}'", path), 375 | /// Err(_) if !am_root() => println!("pidpath() needs to be run as root"), 376 | /// Err(err) if am_root() => eprintln!("Error: {}", err), 377 | /// _ => panic!("Unknown error") 378 | /// } 379 | /// ``` 380 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 381 | pub fn pidpath(pid: i32) -> Result { 382 | let exe_path = 383 | CString::new(format!("/proc/{pid}/exe")).map_err(|_| "Could not create CString")?; 384 | let mut buf: Vec = Vec::with_capacity(PATH_MAX as usize - 1); 385 | let buffer_ptr = buf.as_mut_ptr().cast::(); 386 | let buffer_size = buf.capacity(); 387 | let ret = unsafe { readlink(exe_path.as_ptr(), buffer_ptr, buffer_size) }; 388 | 389 | #[allow(clippy::cast_possible_truncation)] 390 | helpers::check_errno(ret as i32, &mut buf) 391 | } 392 | 393 | #[cfg(any(target_os = "macos", doc))] 394 | /// Get the major and minor version numbers of the native libproc library (Mac OS X) 395 | /// 396 | /// # Errors 397 | /// 398 | /// Should never return an error, but the `Result` return type is used for consistency with 399 | /// other methods, and potential future use. 400 | /// 401 | /// # Examples 402 | /// 403 | /// ``` 404 | /// use libproc::libproc::proc_pid; 405 | /// 406 | /// match proc_pid::libversion() { 407 | /// Ok((major, minor)) => println!("Libversion: {}.{}", major, minor), 408 | /// Err(err) => eprintln!("Error: {}", err) 409 | /// } 410 | /// ``` 411 | #[cfg(target_os = "macos")] 412 | pub fn libversion() -> Result<(i32, i32), String> { 413 | let mut major = 0; 414 | let mut minor = 0; 415 | let ret: i32; 416 | 417 | unsafe { 418 | ret = proc_libversion(&mut major, &mut minor); 419 | }; 420 | 421 | // return value of 0 indicates success (inconsistent with other functions... :-( ) 422 | if ret == 0 { 423 | Ok((major, minor)) 424 | } else { 425 | Err(helpers::get_errno_with_message(ret)) 426 | } 427 | } 428 | 429 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 430 | pub fn libversion() -> Result<(i32, i32), String> { 431 | Err("Linux does not use a library, so no library version number".to_owned()) 432 | } 433 | 434 | /// Get the name of a process, using it's process id (pid) 435 | /// 436 | /// # Errors 437 | /// 438 | /// Will return an error if Darwin's `proc_pidinfo` returns 0 439 | /// 440 | /// # Examples 441 | /// 442 | /// ``` 443 | /// use libproc::libproc::proc_pid; 444 | /// 445 | /// match proc_pid::name(1) { 446 | /// Ok(name) => println!("Name: {}", name), 447 | /// Err(err) => eprintln!("Error: {}", err) 448 | /// } 449 | /// ``` 450 | #[cfg(target_os = "macos")] 451 | pub fn name(pid: i32) -> Result { 452 | let mut namebuf: Vec = Vec::with_capacity((PROC_PIDPATHINFO_MAXSIZE - 1) as _); 453 | let buffer_ptr = namebuf.as_ptr() as *mut c_void; 454 | // No type `T` will be bigger than `i32::MAX`!! 455 | #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] 456 | let buffer_size = namebuf.capacity() as u32; 457 | let ret: c_int; 458 | 459 | unsafe { 460 | ret = proc_name(pid, buffer_ptr, buffer_size); 461 | }; 462 | 463 | if ret <= 0 { 464 | Err(helpers::get_errno_with_message(ret)) 465 | } else { 466 | unsafe { 467 | // `ret` must be greater than 0 here, so no sign-loss 468 | #[allow(clippy::cast_sign_loss)] 469 | namebuf.set_len(ret as usize); 470 | } 471 | 472 | match String::from_utf8(namebuf) { 473 | Ok(name) => Ok(name), 474 | Err(e) => Err(format!("Invalid UTF-8 sequence: {e}")), 475 | } 476 | } 477 | } 478 | 479 | /// Get the name of a process, using it's process id (pid) 480 | /// 481 | /// # Errors 482 | /// 483 | /// An `Err` is returned if the information cannot be read from the procfs file system 484 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 485 | pub fn name(pid: i32) -> Result { 486 | helpers::procfile_field(&format!("/proc/{pid}/status"), "Name") 487 | } 488 | 489 | /// Get information on all running processes. 490 | /// 491 | /// `max_len` is the maximum number of array to return. 492 | /// The length of return value: `Vec` may be less than `max_len`. 493 | /// 494 | /// # Errors 495 | /// 496 | /// Will return an error if Darwin's `proc_pidinfo` returns 0 497 | /// 498 | /// # Examples 499 | /// 500 | /// ``` 501 | /// use libproc::libproc::proc_pid::{listpidinfo, pidinfo}; 502 | /// use libproc::libproc::task_info::TaskAllInfo; 503 | /// use libproc::libproc::file_info::{ListFDs, ProcFDType}; 504 | /// use std::process; 505 | /// 506 | /// let pid = process::id() as i32; 507 | /// 508 | /// if let Ok(info) = pidinfo::(pid, 0) { 509 | /// if let Ok(fds) = listpidinfo::(pid, info.pbsd.pbi_nfiles as usize) { 510 | /// for fd in &fds { 511 | /// let fd_type = ProcFDType::from(fd.proc_fdtype); 512 | /// println!("File Descriptor: {}, Type: {:?}", fd.proc_fd, fd_type); 513 | /// } 514 | /// } 515 | /// } 516 | /// ``` 517 | #[cfg(target_os = "macos")] 518 | pub fn listpidinfo(pid: i32, max_len: usize) -> Result, String> { 519 | let flavor = T::flavor() as i32; 520 | // No type `T` will be bigger than `c_int::MAX`!! 521 | #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] 522 | let buffer_size = mem::size_of::() as c_int * max_len as c_int; 523 | let mut buffer = Vec::::with_capacity(max_len); 524 | let buffer_ptr = unsafe { 525 | buffer.set_len(max_len); 526 | buffer.as_mut_ptr().cast::() 527 | }; 528 | 529 | let ret: i32; 530 | 531 | unsafe { 532 | ret = proc_pidinfo(pid, flavor, 0, buffer_ptr, buffer_size); 533 | }; 534 | 535 | if ret <= 0 { 536 | Err(helpers::get_errno_with_message(ret)) 537 | } else { 538 | // `ret` must be greater than 0 here, so no sign-loss 539 | #[allow(clippy::cast_sign_loss)] 540 | let actual_len = ret as usize / mem::size_of::(); 541 | buffer.truncate(actual_len); 542 | Ok(buffer) 543 | } 544 | } 545 | 546 | #[cfg(target_os = "macos")] 547 | /// Gets the path of current working directory for the process with the provided pid. 548 | /// 549 | /// # Errors 550 | /// 551 | /// Currently always returns an error as this is not implemented yet for macos 552 | /// 553 | /// # Examples 554 | /// 555 | /// ``` 556 | /// use libproc::libproc::proc_pid::pidcwd; 557 | /// 558 | /// match pidcwd(1) { 559 | /// Ok(cwd) => println!("The CWD of the process with pid=1 is '{}'", cwd.display()), 560 | /// Err(err) => eprintln!("Error: {}", err) 561 | /// } 562 | /// ``` 563 | pub fn pidcwd(_pid: pid_t) -> Result { 564 | Err("pidcwd is not implemented for macos".into()) 565 | } 566 | 567 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 568 | /// Gets the path of current working directory for the process with the provided pid. 569 | /// 570 | /// # Errors 571 | /// 572 | /// An `Err` is returned if process with PID `pid` does not exist, or the information 573 | /// about it in the procfs file system cannot be read or parsed 574 | /// 575 | /// # Examples 576 | /// 577 | /// ``` 578 | /// use libproc::libproc::proc_pid::pidcwd; 579 | /// 580 | /// match pidcwd(1) { 581 | /// Ok(cwd) => println!("The CWD of the process with pid=1 is '{}'", cwd.display()), 582 | /// Err(err) => eprintln!("Error: {}", err) 583 | /// } 584 | /// ``` 585 | pub fn pidcwd(pid: pid_t) -> Result { 586 | fs::read_link(format!("/proc/{pid}/cwd")).map_err(|e| e.to_string()) 587 | } 588 | 589 | /// Gets path of current working directory for the current process. 590 | /// 591 | /// Just wraps rust's `env::current_dir()` function so not so useful. 592 | /// 593 | /// # Errors 594 | /// 595 | /// Returns an Err if the current working directory value is invalid. Possible cases: 596 | /// * Current directory does not exist. 597 | /// * There are insufficient permissions to access the current directory. 598 | /// 599 | /// # Examples 600 | /// 601 | /// ``` 602 | /// use libproc::libproc::proc_pid::cwdself; 603 | /// 604 | /// match cwdself() { 605 | /// Ok(cwd) => println!("The CWD of the current process is '{}'", cwd.display()), 606 | /// Err(err) => eprintln!("Error: {}", err) 607 | /// } 608 | /// ``` 609 | pub fn cwdself() -> Result { 610 | env::current_dir().map_err(|e| e.to_string()) 611 | } 612 | 613 | /// Determine if the current user ID of this process is root 614 | /// 615 | /// # Examples 616 | /// 617 | /// ``` 618 | /// use libproc::libproc::proc_pid::am_root; 619 | /// 620 | /// if am_root() { 621 | /// println!("With great power comes great responsibility"); 622 | /// } 623 | /// ``` 624 | #[cfg(target_os = "macos")] 625 | #[must_use] 626 | pub fn am_root() -> bool { 627 | // geteuid() is unstable still - wait for it or wrap this: 628 | // https://stackoverflow.com/questions/3214297/how-can-my-c-c-application-determine-if-the-root-user-is-executing-the-command 629 | unsafe { libc::getuid() == 0 } 630 | } 631 | 632 | /// Return true if the calling process is being run by the root user, false otherwise 633 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 634 | #[must_use] 635 | pub fn am_root() -> bool { 636 | // when this becomes stable in rust libc then we can remove this function or combine for mac and linux 637 | unsafe { libc::geteuid() == 0 } 638 | } 639 | 640 | // run tests with 'cargo test -- --nocapture' to see the test output 641 | #[cfg(test)] 642 | // Don't worry about wrapping in tests 643 | #[allow(clippy::cast_possible_wrap)] 644 | mod test { 645 | use std::env; 646 | use std::process; 647 | 648 | #[cfg(target_os = "macos")] 649 | use crate::libproc::bsd_info::BSDInfo; 650 | #[cfg(target_os = "macos")] 651 | use crate::libproc::file_info::ListFDs; 652 | #[cfg(target_os = "macos")] 653 | use crate::libproc::task_info::TaskAllInfo; 654 | 655 | use super::am_root; 656 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 657 | use super::pidcwd; 658 | use super::{cwdself, name, pidpath}; 659 | #[cfg(target_os = "macos")] 660 | use super::{libversion, listpidinfo, pidinfo, ListThreads}; 661 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 662 | use crate::libproc::helpers; 663 | #[cfg(target_os = "macos")] 664 | use crate::libproc::task_info::TaskInfo; 665 | #[cfg(target_os = "macos")] 666 | use crate::libproc::thread_info::ThreadInfo; 667 | #[cfg(target_os = "macos")] 668 | use crate::libproc::work_queue_info::WorkQueueInfo; 669 | 670 | #[cfg(target_os = "macos")] 671 | #[test] 672 | fn pidinfo_test() { 673 | let pid = process::id() as i32; 674 | 675 | match pidinfo::(pid, 0) { 676 | Ok(info) => assert_eq!(info.pbi_pid as i32, pid), 677 | Err(e) => panic!("Error retrieving BSDInfo: {}", e), 678 | } 679 | } 680 | 681 | #[cfg(target_os = "macos")] 682 | #[test] 683 | fn taskinfo_test() { 684 | let pid = process::id() as i32; 685 | 686 | match pidinfo::(pid, 0) { 687 | Ok(info) => assert!(info.pti_virtual_size > 0), 688 | Err(e) => panic!("Error retrieving TaskInfo: {}", e), 689 | } 690 | } 691 | 692 | #[cfg(target_os = "macos")] 693 | #[test] 694 | fn taskallinfo_test() { 695 | let pid = process::id() as i32; 696 | 697 | match pidinfo::(pid, 0) { 698 | Ok(info) => assert!(info.ptinfo.pti_virtual_size > 0), 699 | Err(e) => panic!("Error retrieving TaskAllInfo: {}", e), 700 | } 701 | } 702 | 703 | #[cfg(target_os = "macos")] 704 | #[test] 705 | fn threadinfo_test() { 706 | match pidinfo::(0, 0) { 707 | Ok(info) => assert!(!info.pth_name.is_empty()), 708 | Err(e) => panic!("Error retrieving ThreadInfo: {}", e), 709 | } 710 | } 711 | 712 | #[cfg(target_os = "macos")] 713 | #[test] 714 | fn workqueueinfo_test() { 715 | match pidinfo::(1, 0) { 716 | Ok(info) => assert!(info.pwq_nthreads > 0), 717 | Err(e) => panic!("{}: {}", "Error retrieving WorkQueueInfo", e), 718 | } 719 | } 720 | 721 | #[cfg(target_os = "macos")] 722 | #[test] 723 | #[allow(clippy::cast_sign_loss)] 724 | fn listpidinfo_test() { 725 | let pid = process::id() as i32; 726 | 727 | if let Ok(info) = pidinfo::(pid, 0) { 728 | if let Ok(threads) = listpidinfo::(pid, info.ptinfo.pti_threadnum as usize) 729 | { 730 | assert!(!threads.is_empty()); 731 | } 732 | if let Ok(fds) = listpidinfo::(pid, info.pbsd.pbi_nfiles as usize) { 733 | assert!(!fds.is_empty()); 734 | } 735 | } 736 | } 737 | 738 | #[test] 739 | #[cfg(target_os = "macos")] 740 | fn libversion_test() { 741 | libversion().expect("libversion() failed"); 742 | } 743 | 744 | #[test] 745 | fn name_test() { 746 | if am_root() 747 | || cfg!(any( 748 | target_os = "linux", 749 | target_os = "redox", 750 | target_os = "android" 751 | )) 752 | { 753 | assert!( 754 | &name(process::id() as i32) 755 | .expect("Could not get the process name") 756 | .starts_with("libproc"), 757 | "Incorrect process name" 758 | ); 759 | } else { 760 | println!("Cannot run 'name_test' on macos unless run as root"); 761 | } 762 | } 763 | 764 | #[test] 765 | // This checks that it cannot find the path of the process with pid -1 and returns correct error message 766 | fn pidpath_test_unknown_pid_test() { 767 | #[cfg(target_os = "macos")] 768 | let error_message = "No such process"; 769 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 770 | let error_message = "No such file or directory"; 771 | 772 | match pidpath(-1) { 773 | Ok(path) => panic!( 774 | "It found the path of process with ID = -1 (path = {}), that's not possible\n", 775 | path 776 | ), 777 | Err(message) => assert!(message.contains(error_message)), 778 | } 779 | } 780 | 781 | #[test] 782 | #[cfg(target_os = "macos")] 783 | // This checks that it cannot find the path of the process with pid 1 784 | fn pidpath_test() { 785 | assert_eq!("/sbin/launchd", pidpath(1).expect("pidpath() failed")); 786 | } 787 | 788 | // Pretty useless test as it uses the exact same code as the function - but I guess we 789 | // should check it can be called and returns correct value 790 | #[test] 791 | fn cwd_self_test() { 792 | assert_eq!( 793 | env::current_dir().expect("Could not get current directory"), 794 | cwdself().expect("cwdself() failed") 795 | ); 796 | } 797 | 798 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 799 | #[test] 800 | fn pidcwd_of_self_test() { 801 | assert_eq!( 802 | env::current_dir().expect("Could not get current directory"), 803 | pidcwd(process::id() as i32).expect("pidcwd() failed") 804 | ); 805 | } 806 | 807 | #[test] 808 | fn am_root_test() { 809 | if am_root() { 810 | println!("You are root"); 811 | } else { 812 | println!("You are not root"); 813 | } 814 | } 815 | 816 | #[test] 817 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 818 | fn procfile_field_test() { 819 | if am_root() { 820 | assert!(helpers::procfile_field("/proc/1/status", "invalid").is_err()); 821 | } 822 | } 823 | } 824 | -------------------------------------------------------------------------------- /src/libproc/sys/linux.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, io, path}; 2 | 3 | use crate::processes::ProcFilter; 4 | 5 | const FIRST_FIELD: isize = 2; 6 | 7 | enum ProcStatField { 8 | // Commented out fields are skipped during parsing 9 | // or not used in this module. Numbers are the 10 | // offset from Status (2) 11 | // PID = 0, 12 | // Cmd = 1, 13 | // Status = 2, 14 | Ppid = 3 - FIRST_FIELD, 15 | Pgrp = 4 - FIRST_FIELD, 16 | // SID = 5, 17 | TtyNr = 6 - FIRST_FIELD, 18 | // rest ignored 19 | } 20 | 21 | /// Parse out a specific field from the stat file beloning to a path starting 22 | /// with /proc/pid Expects the indicated (0-based) field to be parsable as a 23 | /// u32 integer, and field must be > 2. I/O errors are ignored, with the 24 | /// assumption that the process has gone away. 25 | fn proc_stat_field(proc_path: &path::Path, field: ProcStatField) -> Option { 26 | use io::BufRead; 27 | 28 | fs::File::open(proc_path.join("stat")) 29 | .and_then(|f| { 30 | // there should only be a single line, but best avoid reading more 31 | let mut buffer = io::BufReader::new(f); 32 | let mut line = String::new(); 33 | buffer.read_line(&mut line).map(|_| line) 34 | }) 35 | .map_or(None, |line| { 36 | line.rfind(')').and_then(|pos| { 37 | // Skip past the PID and command; the command is wrapped in (..) 38 | // and the closing parenthesis is the only such character in the 39 | // line if scanned from the end. 40 | line[pos + 2..] 41 | .split_ascii_whitespace() 42 | .nth(field as usize) 43 | .and_then(|v| v.parse().ok()) 44 | }) 45 | }) 46 | } 47 | 48 | /// Get the owner UID of a given path, as an option. Errors are ignored, it is 49 | /// assumed the process went away. 50 | fn file_owner_uid(path: &path::Path) -> Option { 51 | use std::os::unix::fs::MetadataExt; 52 | fs::metadata(path).map(|md| md.uid()).ok() 53 | } 54 | 55 | /// Reads process information from /proc/pid/{,stat} to enumerate PIDs matching the filter 56 | pub fn listpids(proc_types: ProcFilter) -> io::Result> { 57 | let mut pids = Vec::::new(); 58 | 59 | let proc_dir = fs::read_dir("/proc")?; 60 | 61 | for entry in proc_dir { 62 | let path = entry?.path(); 63 | let filename = path.file_name(); 64 | if let Some(name) = filename { 65 | if let Some(n) = name.to_str() { 66 | if let Ok(pid) = n.parse::() { 67 | let matches = match proc_types { 68 | ProcFilter::All => true, 69 | ProcFilter::ByProgramGroup { pgrpid } => { 70 | proc_stat_field(&path, ProcStatField::Pgrp) == Some(pgrpid) 71 | } 72 | ProcFilter::ByTTY { tty } => { 73 | proc_stat_field(&path, ProcStatField::TtyNr) == Some(tty) 74 | } 75 | ProcFilter::ByUID { uid } => file_owner_uid(&path) == Some(uid), 76 | ProcFilter::ByRealUID { ruid } => { 77 | file_owner_uid(&path.join("stat")) == Some(ruid) 78 | } 79 | ProcFilter::ByParentProcess { ppid } => { 80 | proc_stat_field(&path, ProcStatField::Ppid) == Some(ppid) 81 | } 82 | }; 83 | if matches { 84 | pids.push(pid); 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | Ok(pids) 92 | } 93 | 94 | #[cfg(test)] 95 | #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] 96 | mod test { 97 | use std::collections::{HashMap, HashSet}; 98 | use std::io::{Error, Write}; 99 | 100 | use super::*; 101 | 102 | #[test] 103 | fn test_proc_stat_field() -> Result<(), Error> { 104 | let tempdir = tempfile::tempdir()?; 105 | let path = tempdir.path(); 106 | let mut test_file = fs::File::create(path.join("stat"))?; 107 | // PPID: 17, PGRP 23, Session 11 (ignored), TTY 4201, TGPID 7 (ignored) 108 | writeln!( 109 | test_file, 110 | "42 (libproc-rs-mock-process) T 17 23 11 4201 7 ..." 111 | )?; 112 | 113 | assert_eq!(proc_stat_field(path, ProcStatField::Ppid), Some(17)); 114 | assert_eq!(proc_stat_field(path, ProcStatField::Pgrp), Some(23)); 115 | assert_eq!(proc_stat_field(path, ProcStatField::TtyNr), Some(4201)); 116 | 117 | Ok(()) 118 | } 119 | 120 | #[test] 121 | fn test_proc_stat_field_errors() -> Result<(), Error> { 122 | let tempdir = tempfile::tempdir()?; 123 | let path = tempdir.path(); 124 | let mut test_file = fs::File::create(path.join("stat"))?; 125 | // PPID: 17, PGRP 23, Session 11 (ignored), TTY 4201, TGPID 7 (ignored) 126 | writeln!(test_file, "garbage in\nerrors out")?; 127 | 128 | assert_eq!(proc_stat_field(path, ProcStatField::Ppid), None); 129 | assert_eq!( 130 | proc_stat_field(&path.join("nonesuch"), ProcStatField::Ppid), 131 | None 132 | ); 133 | 134 | Ok(()) 135 | } 136 | 137 | // Compare the (filtered) PID lists with what the procfs library sees. This 138 | // won't be a 1:1 match as processes come and go, but it shouldn't deviate 139 | // hugely either. Each test is retried multiple times to avoid random 140 | // failures. 141 | 142 | const PROCESS_DIFF_TOLERANCE: usize = 5; 143 | const MAX_RETRIES: usize = 5; 144 | 145 | #[test] 146 | fn test_listpids_all() -> Result<(), procfs::ProcError> { 147 | for _ in 0..MAX_RETRIES { 148 | let mut procfs_pids: HashSet<_> = procfs::process::all_processes()? 149 | .filter_map(|proc| proc.ok().map(|proc| proc.pid)) 150 | .collect(); 151 | let mut new_count = 0; 152 | let pids = listpids(ProcFilter::All).unwrap_or_default(); 153 | for pid in pids { 154 | if !procfs_pids.remove(&(pid as i32)) { 155 | new_count += 1; 156 | } 157 | } 158 | let gone_count = procfs_pids.len(); 159 | if new_count < PROCESS_DIFF_TOLERANCE && gone_count < PROCESS_DIFF_TOLERANCE { 160 | return Ok(()); 161 | } 162 | } 163 | panic!("Test failed"); 164 | } 165 | 166 | #[test] 167 | fn test_listpids_pgid() -> Result<(), procfs::ProcError> { 168 | for _ in 0..MAX_RETRIES { 169 | let mut procfs_pgrps: HashMap<_, HashSet<_>> = HashMap::new(); 170 | for proc in (procfs::process::all_processes()?).flatten() { 171 | if let Ok(stat) = proc.stat() { 172 | procfs_pgrps 173 | .entry(stat.pgrp) 174 | .and_modify(|pids| { 175 | pids.insert(stat.pid); 176 | }) 177 | .or_insert_with(|| vec![stat.pid].into_iter().collect()); 178 | } 179 | } 180 | let mut not_matched = 0; 181 | for (pgrp, procfs_pids) in &mut procfs_pgrps { 182 | if procfs_pids.len() <= 1 { 183 | continue; 184 | } 185 | let pids = listpids(ProcFilter::ByProgramGroup { 186 | pgrpid: *pgrp as u32, 187 | }) 188 | .unwrap_or_default(); 189 | for pid in pids { 190 | if !procfs_pids.remove(&(pid as i32)) { 191 | not_matched += 1; 192 | break; 193 | } 194 | } 195 | if !procfs_pids.is_empty() { 196 | not_matched += 1; 197 | } 198 | } 199 | if not_matched <= PROCESS_DIFF_TOLERANCE { 200 | return Ok(()); 201 | } 202 | } 203 | panic!("Test failed"); 204 | } 205 | 206 | #[test] 207 | fn test_listpids_tty() -> Result<(), procfs::ProcError> { 208 | for _ in 0..MAX_RETRIES { 209 | let mut procfs_ttys: HashMap<_, HashSet<_>> = HashMap::new(); 210 | for proc in (procfs::process::all_processes()?).flatten() { 211 | if let Ok(stat) = proc.stat() { 212 | procfs_ttys 213 | .entry(stat.tty_nr) 214 | .and_modify(|pids| { 215 | pids.insert(stat.pid); 216 | }) 217 | .or_insert_with(|| vec![stat.pid].into_iter().collect()); 218 | } 219 | } 220 | let mut not_matched = 0; 221 | for (tty_nr, procfs_pids) in &mut procfs_ttys { 222 | if procfs_pids.len() <= 1 { 223 | continue; 224 | } 225 | let pids = listpids(ProcFilter::ByTTY { 226 | tty: *tty_nr as u32, 227 | }) 228 | .unwrap_or_default(); 229 | for pid in pids { 230 | if !procfs_pids.remove(&(pid as i32)) { 231 | not_matched += 1; 232 | break; 233 | } 234 | } 235 | if !procfs_pids.is_empty() { 236 | not_matched += 1; 237 | } 238 | } 239 | if not_matched <= PROCESS_DIFF_TOLERANCE { 240 | return Ok(()); 241 | } 242 | } 243 | panic!("Test failed"); 244 | } 245 | 246 | #[test] 247 | fn test_listpids_uid() -> Result<(), procfs::ProcError> { 248 | for _ in 0..MAX_RETRIES { 249 | let mut procfs_uids: HashMap<_, HashSet<_>> = HashMap::new(); 250 | for proc in (procfs::process::all_processes()?).flatten() { 251 | if let Ok(status) = proc.status() { 252 | procfs_uids 253 | .entry(status.euid) 254 | .and_modify(|pids| { 255 | pids.insert(status.pid); 256 | }) 257 | .or_insert_with(|| vec![status.pid].into_iter().collect()); 258 | } 259 | } 260 | let mut not_matched = 0; 261 | for (uid, procfs_pids) in &mut procfs_uids { 262 | if procfs_pids.len() <= 1 { 263 | continue; 264 | } 265 | let pids = listpids(ProcFilter::ByUID { uid: *uid }).unwrap_or_default(); 266 | for pid in pids { 267 | if !procfs_pids.remove(&(pid as i32)) { 268 | not_matched += 1; 269 | break; 270 | } 271 | } 272 | if !procfs_pids.is_empty() { 273 | not_matched += 1; 274 | } 275 | } 276 | if not_matched <= PROCESS_DIFF_TOLERANCE { 277 | return Ok(()); 278 | } 279 | } 280 | panic!("Test failed"); 281 | } 282 | 283 | #[test] 284 | fn test_listpids_real_uid() -> Result<(), procfs::ProcError> { 285 | for _ in 0..MAX_RETRIES { 286 | let mut procfs_ruids: HashMap<_, HashSet<_>> = HashMap::new(); 287 | for proc in (procfs::process::all_processes()?).flatten() { 288 | if let Ok(status) = proc.status() { 289 | procfs_ruids 290 | .entry(status.ruid) 291 | .and_modify(|pids| { 292 | pids.insert(status.pid); 293 | }) 294 | .or_insert_with(|| vec![status.pid].into_iter().collect()); 295 | } 296 | } 297 | let mut not_matched = 0; 298 | for (ruid, procfs_pids) in &mut procfs_ruids { 299 | if procfs_pids.len() <= 1 { 300 | continue; 301 | } 302 | let pids = listpids(ProcFilter::ByRealUID { ruid: *ruid }).unwrap_or_default(); 303 | for pid in pids { 304 | if !procfs_pids.remove(&(pid as i32)) { 305 | not_matched += 1; 306 | break; 307 | } 308 | } 309 | if !procfs_pids.is_empty() { 310 | not_matched += 1; 311 | } 312 | } 313 | if not_matched <= PROCESS_DIFF_TOLERANCE { 314 | return Ok(()); 315 | } 316 | } 317 | panic!("Test failed"); 318 | } 319 | 320 | #[test] 321 | fn test_listpids_parent_pid() -> Result<(), procfs::ProcError> { 322 | for _ in 0..MAX_RETRIES { 323 | let mut procfs_ppids: HashMap<_, HashSet<_>> = HashMap::new(); 324 | for proc in (procfs::process::all_processes()?).flatten() { 325 | if let Ok(stat) = proc.stat() { 326 | procfs_ppids 327 | .entry(stat.ppid) 328 | .and_modify(|pids| { 329 | pids.insert(stat.pid); 330 | }) 331 | .or_insert_with(|| vec![stat.pid].into_iter().collect()); 332 | } 333 | } 334 | let mut not_matched = 0; 335 | for (ppid, procfs_pids) in &mut procfs_ppids { 336 | if procfs_pids.len() <= 1 { 337 | continue; 338 | } 339 | let pids = listpids(ProcFilter::ByParentProcess { ppid: *ppid as u32 }) 340 | .unwrap_or_default(); 341 | for pid in pids { 342 | if !procfs_pids.remove(&(pid as i32)) { 343 | not_matched += 1; 344 | break; 345 | } 346 | } 347 | if !procfs_pids.is_empty() { 348 | not_matched += 1; 349 | } 350 | } 351 | if not_matched <= PROCESS_DIFF_TOLERANCE { 352 | return Ok(()); 353 | } 354 | } 355 | panic!("Test failed"); 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/libproc/sys/macos.rs: -------------------------------------------------------------------------------- 1 | use std::os::unix::ffi::OsStrExt; 2 | use std::{ffi, io, mem, path, ptr}; 3 | 4 | use libc::{c_char, c_int, c_void}; 5 | 6 | use crate::osx_libproc_bindings; 7 | use crate::processes::ProcFilter; 8 | 9 | impl ProcFilter { 10 | pub(crate) fn typeinfo(self) -> u32 { 11 | match self { 12 | ProcFilter::All => 0, // The Darwin kernel ignores the value, it doesn't matter what we pass in 13 | ProcFilter::ByProgramGroup { pgrpid } => pgrpid, 14 | ProcFilter::ByTTY { tty } => tty, 15 | ProcFilter::ByUID { uid } => uid, 16 | ProcFilter::ByRealUID { ruid } => ruid, 17 | ProcFilter::ByParentProcess { ppid } => ppid, 18 | } 19 | } 20 | } 21 | 22 | impl From for u32 { 23 | fn from(proc_type: ProcFilter) -> Self { 24 | match proc_type { 25 | ProcFilter::All => osx_libproc_bindings::PROC_ALL_PIDS, 26 | ProcFilter::ByProgramGroup { .. } => osx_libproc_bindings::PROC_PGRP_ONLY, 27 | ProcFilter::ByTTY { .. } => osx_libproc_bindings::PROC_TTY_ONLY, 28 | ProcFilter::ByUID { .. } => osx_libproc_bindings::PROC_UID_ONLY, 29 | ProcFilter::ByRealUID { .. } => osx_libproc_bindings::PROC_RUID_ONLY, 30 | ProcFilter::ByParentProcess { .. } => osx_libproc_bindings::PROC_PPID_ONLY, 31 | } 32 | } 33 | } 34 | 35 | // similar to list_pids_ret() below, there are two cases when 0 is returned, one when there are 36 | // no pids, and the other when there is an error 37 | // when `errno` is set to indicate an error in the input type, the return value is 0 38 | fn check_listpid_ret(ret: c_int) -> io::Result> { 39 | if ret < 0 || (ret == 0 && io::Error::last_os_error().raw_os_error().unwrap_or(0) != 0) { 40 | return Err(io::Error::last_os_error()); 41 | } 42 | 43 | // `ret` cannot be negative here - so no possible loss of sign 44 | #[allow(clippy::cast_sign_loss)] 45 | let capacity = ret as usize / mem::size_of::(); 46 | Ok(Vec::with_capacity(capacity)) 47 | } 48 | 49 | // Common code for handling the special case of listpids return, where 0 is a valid return 50 | // but is also used in the error case - so we need to look at errno to distringish between a valid 51 | // 0 return and an error return 52 | // when `errno` is set to indicate an error in the input type, the return value is 0 53 | fn list_pids_ret(ret: c_int, mut pids: Vec) -> io::Result> { 54 | match ret { 55 | value 56 | if value < 0 57 | || (ret == 0 && io::Error::last_os_error().raw_os_error().unwrap_or(0) != 0) => 58 | { 59 | Err(io::Error::last_os_error()) 60 | } 61 | _ => { 62 | // `ret` cannot be negative here - so no possible loss of sign 63 | #[allow(clippy::cast_sign_loss)] 64 | let items_count = ret as usize / mem::size_of::(); 65 | unsafe { 66 | pids.set_len(items_count); 67 | } 68 | Ok(pids) 69 | } 70 | } 71 | } 72 | 73 | pub(crate) fn listpids(proc_type: ProcFilter) -> io::Result> { 74 | let buffer_size = unsafe { 75 | osx_libproc_bindings::proc_listpids( 76 | proc_type.into(), 77 | proc_type.typeinfo(), 78 | ptr::null_mut(), 79 | 0, 80 | ) 81 | }; 82 | let mut pids = check_listpid_ret(buffer_size)?; 83 | let buffer_ptr = pids.as_mut_ptr().cast::(); 84 | 85 | let ret = unsafe { 86 | osx_libproc_bindings::proc_listpids( 87 | proc_type.into(), 88 | proc_type.typeinfo(), 89 | buffer_ptr, 90 | buffer_size, 91 | ) 92 | }; 93 | 94 | list_pids_ret(ret, pids) 95 | } 96 | 97 | pub(crate) fn listpidspath( 98 | proc_type: ProcFilter, 99 | path: &path::Path, 100 | is_volume: bool, 101 | exclude_event_only: bool, 102 | ) -> io::Result> { 103 | let path_bytes = path.as_os_str().as_bytes(); 104 | let c_path = ffi::CString::new(path_bytes) 105 | .map_err(|_| io::Error::new(io::ErrorKind::Other, "CString::new failed"))?; 106 | let mut pathflags: u32 = 0; 107 | if is_volume { 108 | pathflags |= osx_libproc_bindings::PROC_LISTPIDSPATH_PATH_IS_VOLUME; 109 | } 110 | if exclude_event_only { 111 | pathflags |= osx_libproc_bindings::PROC_LISTPIDSPATH_EXCLUDE_EVTONLY; 112 | } 113 | 114 | let buffer_size = unsafe { 115 | osx_libproc_bindings::proc_listpidspath( 116 | proc_type.into(), 117 | proc_type.typeinfo(), 118 | c_path.as_ptr().cast::(), 119 | pathflags, 120 | ptr::null_mut(), 121 | 0, 122 | ) 123 | }; 124 | let mut pids = check_listpid_ret(buffer_size)?; 125 | let buffer_ptr = pids.as_mut_ptr().cast::(); 126 | 127 | let ret = unsafe { 128 | osx_libproc_bindings::proc_listpidspath( 129 | proc_type.into(), 130 | proc_type.typeinfo(), 131 | c_path.as_ptr().cast::(), 132 | 0, 133 | buffer_ptr, 134 | buffer_size, 135 | ) 136 | }; 137 | 138 | list_pids_ret(ret, pids) 139 | } 140 | 141 | #[cfg(test)] 142 | mod test { 143 | use std::collections::{HashMap, HashSet}; 144 | 145 | use super::*; 146 | 147 | use crate::libproc::{bsd_info, proc_pid}; 148 | 149 | // Don't worry about > i32::MAX number of processes 150 | #[allow(clippy::cast_possible_wrap)] 151 | fn get_all_pid_bsdinfo() -> io::Result> { 152 | let pids = listpids(ProcFilter::All)?; 153 | Ok(pids 154 | .iter() 155 | .filter_map(|pid| proc_pid::pidinfo::(*pid as i32, 0).ok()) 156 | .collect()) 157 | } 158 | 159 | #[test] 160 | fn test_listpids() -> io::Result<()> { 161 | let pid = std::process::id(); 162 | let pids = listpids(ProcFilter::All)?; 163 | assert!(!pids.is_empty()); 164 | assert!(pids.contains(&pid)); 165 | Ok(()) 166 | } 167 | 168 | // Compare the (filtered) PID lists with what manual filtering with BSDInfo 169 | // data. This won't be a 1:1 match as processes come and go, but it 170 | // shouldn't deviate hugely either. Each test is retried multiple times to 171 | // avoid random failures. 172 | 173 | const PROCESS_DIFF_TOLERANCE: usize = 15; 174 | 175 | #[test] 176 | fn test_listpids_pgid() { 177 | let mut bsdinfo_pgrps: HashMap<_, HashSet<_>> = HashMap::new(); 178 | for info in get_all_pid_bsdinfo().expect("Could not get all pids info") { 179 | if info.pbi_pgid == info.pbi_pid { 180 | continue; 181 | } 182 | bsdinfo_pgrps 183 | .entry(info.pbi_pgid) 184 | .and_modify(|pids| { 185 | pids.insert(info.pbi_pid); 186 | }) 187 | .or_insert_with(|| vec![info.pbi_pid].into_iter().collect()); 188 | } 189 | let mut not_matched = 0; 190 | for (pgrp, bsdinfo_pids) in &mut bsdinfo_pgrps { 191 | if bsdinfo_pids.len() <= 1 { 192 | continue; 193 | } 194 | let pids = 195 | listpids(ProcFilter::ByProgramGroup { pgrpid: *pgrp }).expect("Could not listpids"); 196 | for pid in pids { 197 | if !bsdinfo_pids.remove(&pid) { 198 | not_matched += 1; 199 | break; 200 | } 201 | } 202 | if !bsdinfo_pids.is_empty() { 203 | not_matched += 1; 204 | } 205 | } 206 | assert!(not_matched <= PROCESS_DIFF_TOLERANCE); 207 | } 208 | 209 | const NODEV: u32 = u32::MAX; 210 | 211 | #[test] 212 | fn test_listpids_tty() { 213 | let mut bsdinfo_ttys: HashMap<_, HashSet<_>> = HashMap::new(); 214 | for info in get_all_pid_bsdinfo().expect("Could not get all pids info") { 215 | if info.e_tdev == NODEV || info.e_tpgid == info.pbi_pid { 216 | continue; 217 | } 218 | bsdinfo_ttys 219 | .entry(info.e_tdev) 220 | .and_modify(|pids| { 221 | pids.insert(info.pbi_pid); 222 | }) 223 | .or_insert_with(|| vec![info.pbi_pid].into_iter().collect()); 224 | } 225 | let mut not_matched = 0; 226 | for (tty_nr, bsdinfo_pids) in &mut bsdinfo_ttys { 227 | if bsdinfo_pids.len() <= 1 { 228 | continue; 229 | } 230 | let pids = listpids(ProcFilter::ByTTY { tty: *tty_nr }).expect("Could not listpids"); 231 | for pid in pids { 232 | if !bsdinfo_pids.remove(&pid) { 233 | not_matched += 1; 234 | break; 235 | } 236 | } 237 | if !bsdinfo_pids.is_empty() { 238 | not_matched += 1; 239 | } 240 | } 241 | assert!(not_matched <= PROCESS_DIFF_TOLERANCE); 242 | } 243 | 244 | #[test] 245 | fn test_listpids_uid() { 246 | let mut bsdinfo_uids: HashMap<_, HashSet<_>> = HashMap::new(); 247 | for info in get_all_pid_bsdinfo().expect("Could not get all pids info") { 248 | bsdinfo_uids 249 | .entry(info.pbi_uid) 250 | .and_modify(|pids| { 251 | pids.insert(info.pbi_pid); 252 | }) 253 | .or_insert_with(|| vec![info.pbi_pid].into_iter().collect()); 254 | } 255 | let mut not_matched = 0; 256 | for (uid, bsdinfo_pids) in &mut bsdinfo_uids { 257 | if bsdinfo_pids.len() <= 1 { 258 | continue; 259 | } 260 | let pids = listpids(ProcFilter::ByUID { uid: *uid }).expect("Could not listpids"); 261 | for pid in pids { 262 | if !bsdinfo_pids.remove(&pid) { 263 | not_matched += 1; 264 | break; 265 | } 266 | } 267 | if !bsdinfo_pids.is_empty() { 268 | not_matched += 1; 269 | } 270 | } 271 | assert!(not_matched <= PROCESS_DIFF_TOLERANCE); 272 | } 273 | 274 | #[test] 275 | fn test_listpids_real_uid() { 276 | let mut bsdinfo_ruids: HashMap<_, HashSet<_>> = HashMap::new(); 277 | for info in get_all_pid_bsdinfo().expect("Could not get all pids info") { 278 | bsdinfo_ruids 279 | .entry(info.pbi_ruid) 280 | .and_modify(|pids| { 281 | pids.insert(info.pbi_pid); 282 | }) 283 | .or_insert_with(|| vec![info.pbi_pid].into_iter().collect()); 284 | } 285 | let mut not_matched = 0; 286 | for (ruid, bsdinfo_pids) in &mut bsdinfo_ruids { 287 | if bsdinfo_pids.len() <= 1 { 288 | continue; 289 | } 290 | let pids = listpids(ProcFilter::ByRealUID { ruid: *ruid }).expect("Could not listpids"); 291 | for pid in pids { 292 | if !bsdinfo_pids.remove(&pid) { 293 | not_matched += 1; 294 | println!("pid {pid} not matched for ruid {ruid}"); 295 | break; 296 | } 297 | } 298 | // PROC_ALL_PIDS and PROC_RUID_ONLY are regulargy not agreeing, with PROC_ALL_PIDS 299 | // listing more than PROC_RUID_ONLY for the same ruid. Testing if bsdinfo_pids is 300 | // empty is futile here. 301 | } 302 | assert!(not_matched <= PROCESS_DIFF_TOLERANCE); 303 | } 304 | 305 | #[test] 306 | fn test_listpids_parent_pid() { 307 | let mut bsdinfo_ppids: HashMap<_, HashSet<_>> = HashMap::new(); 308 | for info in get_all_pid_bsdinfo().expect("Could not get all pids info") { 309 | bsdinfo_ppids 310 | .entry(info.pbi_ppid) 311 | .and_modify(|pids| { 312 | pids.insert(info.pbi_pid); 313 | }) 314 | .or_insert_with(|| vec![info.pbi_pid].into_iter().collect()); 315 | } 316 | let mut not_matched = 0; 317 | for (ppid, bsdinfo_pids) in &mut bsdinfo_ppids { 318 | let pids = listpids(ProcFilter::ByParentProcess { ppid: *ppid }) 319 | .expect("Could not listpids by parent process"); 320 | for pid in pids { 321 | if !bsdinfo_pids.remove(&pid) { 322 | not_matched += 1; 323 | break; 324 | } 325 | } 326 | // PROC_ALL_PIDS is consistently producing processes that are 327 | // not listed by PROC_PPID_ONLY, so we can't make assertions 328 | // about having matched all child processes. There is no 329 | // signal that I can see on why this is. 330 | } 331 | assert!(not_matched <= PROCESS_DIFF_TOLERANCE); 332 | } 333 | 334 | #[test] 335 | fn test_listpids_invalid_parent_pid() { 336 | let pids = listpids(ProcFilter::ByParentProcess { ppid: u32::MAX }) 337 | .expect("Error requesting children of inexistant process"); 338 | assert!(pids.is_empty()); 339 | } 340 | 341 | // No point in writing test cases for all ProcFilter members, as the Darwin 342 | // implementation of proc_listpidspath is essentially a wrapper acound 343 | // proc_listpids with calls to proc_pidinfo to gather path information. 344 | // Tests here would simply repeat that work, and so in essence *test the 345 | // Darwin libproc library* and not our wrapping of that library. 346 | 347 | #[test] 348 | fn test_listpidspath() { 349 | let root = std::path::Path::new("/"); 350 | let pids: Vec = 351 | listpidspath(ProcFilter::All, root, true, false).expect("Failed to load PIDs for path"); 352 | assert!(!pids.is_empty()); 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/libproc/sys/mod.rs: -------------------------------------------------------------------------------- 1 | // OS-specific implementations of process-related functions 2 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 3 | mod linux; 4 | #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] 5 | pub(crate) use self::linux::*; 6 | 7 | #[cfg(target_os = "macos")] 8 | mod macos; 9 | #[cfg(target_os = "macos")] 10 | pub(crate) use self::macos::*; 11 | -------------------------------------------------------------------------------- /src/libproc/task_info.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "macos")] 2 | use crate::libproc::bsd_info::BSDInfo; 3 | use crate::libproc::proc_pid::{PIDInfo, PidInfoFlavor}; 4 | #[cfg(target_os = "macos")] 5 | pub use crate::osx_libproc_bindings::proc_taskinfo as TaskInfo; 6 | 7 | #[cfg(target_os = "macos")] 8 | impl PIDInfo for TaskInfo { 9 | fn flavor() -> PidInfoFlavor { 10 | PidInfoFlavor::TaskInfo 11 | } 12 | } 13 | 14 | #[cfg(target_os = "macos")] 15 | /// Struct for info on all Tasks 16 | #[repr(C)] 17 | pub struct TaskAllInfo { 18 | /// `BSDInfo` 19 | pub pbsd: BSDInfo, 20 | /// `TaskInfo` 21 | pub ptinfo: TaskInfo, 22 | } 23 | 24 | #[cfg(target_os = "macos")] 25 | impl PIDInfo for TaskAllInfo { 26 | fn flavor() -> PidInfoFlavor { 27 | PidInfoFlavor::TaskAllInfo 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/libproc/thread_info.rs: -------------------------------------------------------------------------------- 1 | use crate::libproc::proc_pid::{PIDInfo, PidInfoFlavor}; 2 | #[cfg(target_os = "macos")] 3 | pub use crate::osx_libproc_bindings::proc_threadinfo as ThreadInfo; 4 | 5 | #[cfg(target_os = "macos")] 6 | impl PIDInfo for ThreadInfo { 7 | fn flavor() -> PidInfoFlavor { 8 | PidInfoFlavor::ThreadInfo 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/libproc/work_queue_info.rs: -------------------------------------------------------------------------------- 1 | use crate::libproc::proc_pid::{PIDInfo, PidInfoFlavor}; 2 | 3 | /// Structure for work queue items 4 | #[derive(Default)] 5 | pub struct WorkQueueInfo { 6 | /// total number of workqueue threads 7 | pub pwq_nthreads: u32, 8 | /// total number of running workqueue threads 9 | pub pwq_runthreads: u32, 10 | /// total number of blocked workqueue threads 11 | pub pwq_blockedthreads: u32, 12 | /// reserved for future use 13 | pub reserved: [u32; 1], 14 | } 15 | 16 | impl PIDInfo for WorkQueueInfo { 17 | fn flavor() -> PidInfoFlavor { 18 | PidInfoFlavor::WorkQueueInfo 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/processes.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | #[cfg(target_os = "macos")] 3 | use std::path::Path; 4 | 5 | use crate::libproc::sys::listpids; 6 | #[cfg(target_os = "macos")] 7 | use crate::libproc::sys::listpidspath; 8 | 9 | /// `ProcFilter` is used to filter process ids. 10 | /// See [`pids_by_type`] and `pids_by_type_and_path` (macOS only) for details. 11 | #[derive(Copy, Clone)] 12 | pub enum ProcFilter { 13 | /// All processes 14 | All, 15 | /// Filter by program group id 16 | ByProgramGroup { 17 | /// List PIDs that are members of this process group 18 | pgrpid: u32, 19 | }, 20 | /// Filter by TTY 21 | ByTTY { 22 | /// List PIDs attached to the specific TTY 23 | tty: u32, 24 | }, 25 | /// Filter by (effective) user ID 26 | ByUID { 27 | /// List PIDs of processes with the permissions of this specific user. 28 | uid: u32, 29 | }, 30 | /// Filter by real user ID 31 | ByRealUID { 32 | /// List PIDs of processes started by this specific user. 33 | ruid: u32, 34 | }, 35 | /// Filter by parent process ID 36 | ByParentProcess { 37 | /// List PIDs of processes that are children of this specific process. 38 | ppid: u32, 39 | }, 40 | } 41 | 42 | /// Returns the PIDs of active processes that match the given [`ProcFilter`] filter. 43 | /// 44 | /// # Errors 45 | /// 46 | /// Will return an error if the pids matching the filter cannot be listed for some reason, as 47 | /// returned in `errno` by Darwin's libproc. 48 | /// 49 | /// # Examples 50 | /// 51 | /// Get the list of all running process IDs using [`pids_by_type`] and [`ProcFilter::All`]: 52 | /// 53 | /// ``` 54 | /// use std::io::Write; 55 | /// use libproc::processes; 56 | /// 57 | /// if let Ok(pids) = processes::pids_by_type(processes::ProcFilter::All) { 58 | /// println!("There are {} processes running on this system", pids.len()); 59 | /// } 60 | /// ``` 61 | /// 62 | /// Get a list of running process IDs that are children of the current process: 63 | /// 64 | /// ``` 65 | /// use std::io::Write; 66 | /// use std::process; 67 | /// use libproc::processes; 68 | /// 69 | /// let filter = processes::ProcFilter::ByParentProcess { ppid: process::id() }; 70 | /// if let Ok(pids) = processes::pids_by_type(filter) { 71 | /// println!("Found {} child processes of this process", pids.len()); 72 | /// } 73 | /// ``` 74 | pub fn pids_by_type(filter: ProcFilter) -> io::Result> { 75 | listpids(filter) 76 | } 77 | 78 | /// Returns the PIDs of active processes that reference an open file with the given path or volume. 79 | /// 80 | ///Filter for pids with or without files opened with the `O_EVTONLY` flag. 81 | /// 82 | /// Files opened with the `O_EVTONLY` flag will not prevent a volume from being 83 | /// unmounted. 84 | /// 85 | /// # Errors 86 | /// 87 | /// Will return an error if: 88 | /// * input `path` is invalid 89 | /// * the pids matching the filter cannot be listed for some reason, as 90 | /// returned in `errno` by Darwin's libproc. 91 | /// 92 | /// # Examples 93 | /// 94 | /// Get the list of all running process IDs that have a specific filename open: 95 | /// 96 | /// ``` 97 | /// use std::path::Path; 98 | /// use std::io::Write; 99 | /// use libproc::processes; 100 | /// 101 | /// let path = Path::new("/etc/hosts"); 102 | /// if let Ok(pids) = processes::pids_by_path(&path, false, false) { 103 | /// println!("Found {} processes accessing {}", pids.len(), path.display()); 104 | /// } 105 | /// ``` 106 | /// 107 | /// List all processes that have a file open on a specific volume; the path 108 | /// argument is used to get the filesystem device ID, and processes that have 109 | /// a file open on that same device ID match the filter: 110 | /// ``` 111 | /// use std::path::Path; 112 | /// use std::io::Write; 113 | /// use libproc::processes; 114 | /// 115 | /// let path = Path::new("/Volumes/MountedDrive"); 116 | /// if let Ok(pids) = processes::pids_by_path(&path, true, false) { 117 | /// println!("Found {} processes accessing files on {}", pids.len(), path.display()); 118 | /// } 119 | /// ``` 120 | #[cfg(target_os = "macos")] 121 | pub fn pids_by_path( 122 | path: &Path, 123 | is_volume: bool, 124 | exclude_event_only: bool, 125 | ) -> io::Result> { 126 | listpidspath(ProcFilter::All, path, is_volume, exclude_event_only) 127 | } 128 | 129 | /// Get a filtered list of PIDs of active processes that reference an open file with the given path or volume. 130 | /// 131 | /// Filter the list for pids with or without files opened with the `O_EVTONLY` flag. 132 | /// Use a [`ProcFilter`] member to specify how to filter the list of PIDs returned. 133 | /// 134 | /// Files opened with the `O_EVTONLY` flag will not prevent a volume from being unmounted. 135 | /// 136 | /// # Errors 137 | /// 138 | /// Will return an error if: 139 | /// * input `path` is invalid 140 | /// * the pids matching the filter cannot be listed for some reason, as 141 | /// returned in `errno` by Darwin's libproc. 142 | /// 143 | /// # Examples 144 | /// 145 | /// Get the list of process ids for child processes that have a specific filename open: 146 | /// 147 | /// ``` 148 | /// use std::path::Path; 149 | /// use std::process; 150 | /// use std::io::Write; 151 | /// use libproc::processes; 152 | /// 153 | /// let path = Path::new("/etc/hosts"); 154 | /// let filter = processes::ProcFilter::ByParentProcess { ppid: process::id() }; 155 | /// if let Ok(pids) = processes::pids_by_type_and_path(filter, &path, false, false) { 156 | /// println!("Found {} processes accessing {}", pids.len(), path.display()); 157 | /// } 158 | /// ``` 159 | /// 160 | /// List all processes within the current process group that have a file open on 161 | /// a specific volume; the path argument is used to get the filesystem device 162 | /// ID, and processes that have a file open on that same device ID match the 163 | /// filter: 164 | /// ``` 165 | /// use std::path::Path; 166 | /// use std::process; 167 | /// use std::io::Write; 168 | /// use libproc::processes; 169 | /// 170 | /// let path = Path::new("/Volumes/MountedDrive"); 171 | /// let filter = processes::ProcFilter::ByParentProcess { ppid: process::id() }; 172 | /// if let Ok(pids) = processes::pids_by_type_and_path(filter, &path, true, false) { 173 | /// println!("Found {} processes accessing files on {}", pids.len(), path.display()); 174 | /// } 175 | /// ``` 176 | #[cfg(target_os = "macos")] 177 | pub fn pids_by_type_and_path( 178 | filter: ProcFilter, 179 | path: &Path, 180 | is_volume: bool, 181 | exclude_event_only: bool, 182 | ) -> io::Result> { 183 | listpidspath(filter, path, is_volume, exclude_event_only) 184 | } 185 | --------------------------------------------------------------------------------