├── .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 | [
]()
5 |
6 | [andrewdavidmackenzie](https://github.com/andrewdavidmackenzie)
7 |
8 | [
]()
9 |
10 | [lambda-fairy](https://github.com/lambda-fairy)
11 |
12 | [
]()
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 | 
2 | [](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 |
--------------------------------------------------------------------------------