pax_global_header00006660000000000000000000000064150674726260014531gustar00rootroot0000000000000052 comment=067dd61372205880dda017c942fec0042421e45e GuillaumeGomez-sysinfo-067dd61/000077500000000000000000000000001506747262600165045ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/.cirrus.yml000066400000000000000000000112461506747262600206200ustar00rootroot00000000000000task: name: rust 1.88 on freebsd 13 freebsd_instance: image: freebsd-13-1-release-amd64 setup_script: - curl https://sh.rustup.rs -sSf --output rustup.sh - sh rustup.sh -y --profile=minimal --default-toolchain=1.88 - . $HOME/.cargo/env - rustup --version - rustup component add clippy test_script: - . $HOME/.cargo/env - cargo check - RUSTFLAGS=-Dwarnings cargo check --no-default-features - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features system - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,system - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features disk - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,disk - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features component - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,component - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features network - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,network - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features user - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,user - cargo clippy --features serde -- -D warnings - cargo clippy --features multithread -- -D warnings - cargo check --example simple - FREEBSD_CI=1 cargo test -- --test-threads=1 - FREEBSD_CI=1 cargo test --lib -- --ignored --test-threads=1 task: name: rust nightly on freebsd 13 freebsd_instance: image: freebsd-13-1-release-amd64 setup_script: - curl https://sh.rustup.rs -sSf --output rustup.sh - sh rustup.sh -y --profile=minimal --default-toolchain=nightly - . $HOME/.cargo/env - rustup --version - rustup component add clippy test_script: - . $HOME/.cargo/env - cargo check - RUSTFLAGS=-Dwarnings cargo check --no-default-features - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features system - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,system - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features disk - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,disk - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features component - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,component - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features network - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,network - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features user - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,user - cargo clippy --features serde -- -D warnings - cargo clippy --features multithread -- -D warnings - cargo check --example simple - FREEBSD_CI=1 cargo test -- --test-threads=1 - FREEBSD_CI=1 cargo test --lib -- --test-threads=1 task: name: rust 1.88 on mac m1 macos_instance: image: ghcr.io/cirruslabs/macos-monterey-base:latest setup_script: - brew update - brew install curl - curl https://sh.rustup.rs -sSf --output rustup.sh - sh rustup.sh -y --profile=minimal --default-toolchain=1.88 - source $HOME/.cargo/env - rustup --version - rustup component add clippy test_script: - source $HOME/.cargo/env - cargo check - RUSTFLAGS=-Dwarnings cargo check --no-default-features - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features system - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,system - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features disk - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,disk - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features component - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,component - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features network - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,network - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features user - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features debug,serde,user - RUSTFLAGS=-Dwarnings cargo check --no-default-features --features apple-sandbox - cargo clippy --features serde -- -D warnings - cargo clippy --features multithread -- -D warnings - cargo check --example simple - APPLE_CI=1 cargo test -- --test-threads=1 - APPLE_CI=1 cargo test --lib -- --ignored --test-threads=1 GuillaumeGomez-sysinfo-067dd61/.github/000077500000000000000000000000001506747262600200445ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/.github/FUNDING.yml000066400000000000000000000002121506747262600216540ustar00rootroot00000000000000# These are supported funding model platforms github: [GuillaumeGomez] patreon: GuillaumeGomez custom: ["https://paypal.me/imperioland"] GuillaumeGomez-sysinfo-067dd61/.github/ISSUE_TEMPLATE/000077500000000000000000000000001506747262600222275ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000005341506747262600247230ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- If you like my work, please consider sponsoring me! **Describe the bug** Please provide the system on which you're using `sysinfo` and also `sysinfo` version. **To Reproduce** Please provide the code showing the bug so it can be reproduced. GuillaumeGomez-sysinfo-067dd61/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000004521506747262600257550ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: feature request assignees: '' --- If you like my work, please consider sponsoring me! Please describe what you'd like to be added. For example: * Adding a new system support. * Getting new system information. GuillaumeGomez-sysinfo-067dd61/.github/workflows/000077500000000000000000000000001506747262600221015ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/.github/workflows/CI.yml000066400000000000000000000336211506747262600231240ustar00rootroot00000000000000on: pull_request: name: CI # If a new push on the PR is done, cancel the build concurrency: group: ${{ github.head_ref }} cancel-in-progress: true env: RUSTFLAGS: -Dwarnings CROSS_VERSION: git:167aa99f5c460bb88e9c8b31065f11a3325c9c73 CROSS_REPOSITORY: https://github.com/GuillaumeGomez/cross jobs: rustfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: rustfmt - run: cargo fmt -- --check - name: test that all source files are covered by rustfmt run: | for f in $(find src/ -name '*.rs'); do echo -e '\n\n\n\n\n' >> $f; done cargo fmt git diff --exit-code # expect all changes from above wiped away clippy: runs-on: ${{ matrix.os }} strategy: matrix: os: - ubuntu-latest - macos-latest - windows-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: toolchain: nightly components: clippy - run: cargo clippy --all-targets --features serde -- -D warnings - run: cargo clippy --all-targets --features multithread -- -D warnings check: name: Check ${{ matrix.toolchain }} / ${{ matrix.triple.target }} runs-on: ${{ matrix.triple.os }} strategy: fail-fast: false matrix: triple: - { os: 'ubuntu-latest', target: 'x86_64-unknown-linux-gnu', cross: false } - { os: 'ubuntu-latest', target: 'i686-unknown-linux-gnu', cross: true } - { os: 'macos-latest', target: 'x86_64-apple-darwin', cross: false } - { os: 'windows-latest', target: 'x86_64-pc-windows-msvc', cross: false } ## iOS - { os: 'macos-latest', target: 'aarch64-apple-ios', cross: true } - { os: 'macos-latest', target: 'x86_64-apple-ios', cross: true } ## ARM64 - { os: 'ubuntu-latest', target: 'aarch64-unknown-linux-gnu', cross: true } - { os: 'ubuntu-latest', target: 'aarch64-unknown-linux-musl', cross: true } ## ARMv7 - { os: 'ubuntu-latest', target: 'armv7-unknown-linux-gnueabihf', cross: true } - { os: 'ubuntu-latest', target: 'armv7-unknown-linux-musleabihf', cross: true } ## ARMv6 - { os: 'ubuntu-latest', target: 'arm-unknown-linux-gnueabihf', cross: true } - { os: 'ubuntu-latest', target: 'arm-unknown-linux-musleabihf', cross: true } # Android - { os: 'ubuntu-latest', target: 'aarch64-linux-android', cross: true } - { os: 'ubuntu-latest', target: 'armv7-linux-androideabi', cross: true } - { os: 'ubuntu-latest', target: 'x86_64-linux-android', cross: true } - { os: 'ubuntu-latest', target: 'i686-linux-android', cross: true } toolchain: - "1.88.0" # minimum supported rust version - stable - nightly steps: - uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.toolchain }} - name: Install cross-target run: rustup target add ${{ matrix.triple.target }} - name: Check uses: ClementTsang/cargo-action@v0.0.7 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check debug feature uses: ClementTsang/cargo-action@v0.0.7 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --features=debug use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check debug feature uses: ClementTsang/cargo-action@v0.0.7 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features=debug use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check serde feature uses: ClementTsang/cargo-action@v0.0.7 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --features=serde use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check serde feature uses: ClementTsang/cargo-action@v0.0.7 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features=serde use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check debug/serde feature (system) uses: ClementTsang/cargo-action@v0.0.7 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features=debug,serde,system use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check debug/serde feature (disk) uses: ClementTsang/cargo-action@v0.0.7 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features=debug,serde,disk use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check debug/serde feature (component) uses: ClementTsang/cargo-action@v0.0.7 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features=debug,serde,component use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check debug/serde feature (network) uses: ClementTsang/cargo-action@v0.0.7 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features=debug,serde,network use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check debug/serde feature (user) uses: ClementTsang/cargo-action@v0.0.7 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features=debug,serde,user use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check without multithreading uses: ClementTsang/cargo-action@v0.0.7 with: command: check args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check features (system) uses: ClementTsang/cargo-action@v0.0.7 with: command: check args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features system use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check features (disk) uses: ClementTsang/cargo-action@v0.0.7 with: command: check args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features disk use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check features (component) uses: ClementTsang/cargo-action@v0.0.7 with: command: check args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features component use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check features (network) uses: ClementTsang/cargo-action@v0.0.7 with: command: check args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features network use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check features (user) uses: ClementTsang/cargo-action@v0.0.7 with: command: check args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features user use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check example uses: ClementTsang/cargo-action@v0.0.7 with: command: check args: --target=${{ matrix.triple.target }} --example simple use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check (Apple app store restrictions) if: matrix.triple.os == 'macos-latest' uses: ClementTsang/cargo-action@v0.0.7 with: command: check args: --features apple-sandbox --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check without multithreading (Apple app store restrictions) if: matrix.triple.os == 'macos-latest' uses: ClementTsang/cargo-action@v0.0.7 with: command: check args: --features apple-sandbox --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} - name: Check features (system with Apple app store restrictions) uses: ClementTsang/cargo-action@v0.0.7 with: command: check args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features --features system,apple-sandbox use-cross: ${{ matrix.triple.cross }} cross-version: ${{ env.CROSS_VERSION }} repository-url: ${{ env.CROSS_REPOSITORY }} tests: needs: [check] name: Test ${{ matrix.os }} (rust ${{matrix.toolchain}}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - ubuntu-latest - macos-latest - windows-latest toolchain: - "1.88.0" # minimum supported rust version - stable - nightly steps: - uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.toolchain }} - name: Check docs run: cargo rustdoc -- -Z unstable-options --generate-link-to-definition if: matrix.toolchain == 'nightly' - name: Check docs with serde run: cargo rustdoc --features serde -- -Z unstable-options --generate-link-to-definition if: matrix.toolchain == 'nightly' - name: Execute tests (not mac) run: cargo test if: matrix.os != 'macos-latest' env: RUST_BACKTRACE: full - name: Execute tests (mac) run: cargo test -- --test-threads 1 if: matrix.os == 'macos-latest' env: RUST_BACKTRACE: full APPLE_CI: 1 # FIXME: remove this once CI mac tests are fixed - name: Execute tests (not mac, no features) run: cargo test --no-default-features if: matrix.os != 'macos-latest' env: RUST_BACKTRACE: full - name: Execute tests (mac, no features) run: cargo test --no-default-features -- --test-threads 1 if: matrix.os == 'macos-latest' env: RUST_BACKTRACE: full APPLE_CI: 1 # FIXME: remove this once CI mac tests are fixed - name: Run CPU test run: cargo test --lib -- --ignored --test-threads 1 env: RUST_BACKTRACE: full - name: Check serde feature run: | cargo check --features serde cargo test --features serde --doc env: RUST_BACKTRACE: full c_interface: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: toolchain: stable - run: make unknown-targets: runs-on: ubuntu-latest strategy: matrix: toolchain: - "1.88.0" # minimum supported rust version - stable steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.toolchain }} components: clippy - run: cargo clippy --features unknown-ci -- -D warnings - run: cargo check --features unknown-ci - run: cargo test --features unknown-ci - run: cargo install wasm-pack if: matrix.toolchain == 'stable' - run: cd test-unknown && wasm-pack build --target web if: matrix.toolchain == 'stable' GuillaumeGomez-sysinfo-067dd61/.gitignore000066400000000000000000000015711506747262600205000ustar00rootroot00000000000000 # Created by https://www.toptal.com/developers/gitignore/api/osx,rust ### OSX ### *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Rust ### # Generated by Cargo # will have compiled files and executables /target/ examples/target test-unknown/target test-unknown/pkg # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk rusty-tags.vi tags **.o simple .idea/ GuillaumeGomez-sysinfo-067dd61/ADDING_NEW_PLATFORMS.md000066400000000000000000000002201506747262600221060ustar00rootroot00000000000000To get the `target_os` etc: ``` rustc --print cfg ``` In `src/lib.rs` add the matching based on the `cfg` data. Create `src/`. GuillaumeGomez-sysinfo-067dd61/CHANGELOG.md000066400000000000000000000654301506747262600203250ustar00rootroot00000000000000# 0.37.2 * Improve documentation of `System::refresh_cpu_all` and of `System::refresh_cpu_specifics`. * Android: Fix `System::uptime`. * Linux: Correctly handled modified/removed `Process::exe` paths. # 0.37.1 * Fix `serde` serialization on `Process::name`. * Linux: Fix `get_cpu_frequency` on `loongarch64`. * Windows: Correctly handle invalid UTF-8 string in `Motherboard`. # 0.37.0 * Update minimum supported Rust version to `1.88` (for 2024 edition and `if let chain` feature). * Added `Component::id` API. * Linux: Greatly improve partial processes retrieval. * Linux: Simplify internal components retrieval code. # 0.36.1 * Linux: Improve processor CPU usage computation. # 0.36.0 * Add new `Motherboard` type. * Add new `Product` type. * Linux/macOS: Fix CPU usage update. * Linux: Improve `Component::label` generated `String`. * macOS: Make `Components` implement `Send` and `Sync` traits. # 0.35.2 * Linux: Fix retrieval of processor frequency on loongarch64. * Linux/Android: Improve `Process::wait` by checking if the process is still alive before waiting. * Windows: Fix `Process::wait`. # 0.35.1 * Linux: Fix processor retrieval if some information is missing. * Windows: Increase range of `windows` crate up to `< 0.63`. # 0.35.0 * Update MSRV to 1.75. * Add `System::open_files_limit`. * Add `Process::kill_and_wait` and `Process::kill_with_and_wait` methods. * `Process::open_files*` methods return `usize`. * `set_open_files_limit` argument type is `usize`. * Code example was improved. * macOS: Fix M4 CPU frequency calculation. * Linux: Correctly handle when new CPUs are added. * Linux: Ignore `autofs` disks. * Linux (raspberry pi): Retrieve more thermal zones. * Android: Update documentation to mention that newer Android version don't allow to retrieve CPU information. * Windows: Improve `Process::wait` code. * Windows: Fix `Groups::refresh`. # 0.34.2 * FreeBSD: Fix segfault when refreshing processes. # 0.34.1 * macOS: Remove forgotten debug print. # 0.34.0 * `multithread` feature is disabled by default. * Add new `Process` methods: `accumulated_cpu_time`, `exists`, `open_files` and `open_files_limit`. * `Process::wait` method now returns `Option`. * Add new `System` methods: `distribution_id_like` and `kernel_long_version`. * Turn `System::physical_core_count` method into an associated function. * Make `System::refresh_all` and `System::refresh_specifics` methods remove dead processes. * Add new `ProcessRefreshKind` refresh: `tasks`. * Fix `Group` docs. * Implement `Deserialize` trait on `DiskKind`, `MacAddr`, `IpNetwork`, `ProcessStatus`, `Signal` and `ThreadKind`. * Implement `FromStr` trait on `IpNetwork` and `MacAddr`. * Implement `Hash` trait on `DiskKind`, `MacAddr`, `ProcessStatus` and `Signal`. * macOS: Removed processes are now correctly handled when refreshing processes. * macOS: Strengthen dist list refresh. * macOS: Fix internal `cfg` handling which triggered compilation failures in some specific cases. * Linux: max cgroups memory is more reliant in case of wrong input. * Linux: Fix processes tasks update. * Linux: Improve disks retrieval. * Windows: Fix antivirus considering programs using `sysinfo` as viruses by replacing uses of `NtQueryInformationProcess` with other "mainstream" Windows API. * Windows: Improve CPU usage computation efficiency. * FreeBSD: Improve `process_procs` when there is only one process to refresh. # 0.33.1 * Linux: Fix components retrieval. * Linux: Filter out more virtual file systems for `Disk` API. * Linux/Android: Improve `System::long_os_version()`. * Apple: Add missing CPU brands for iPhone and iPads. * macOS: Improve system name retrieval in `System::long_os_version()`. * Linux/macOS: Avoid trailing whitespace and consecutive whitespace in `System::long_os_version()`. * Windows: Fix `User::groups`. * Improve documentation for `System::name`, `System::kernel_version`, `System::os_version`, `System::long_os_version` and `System::distribution_id`. # 0.33.0 * Linux: Add more ARM vendor IDs. * Linux: Improve CPU computation when refreshing a single process CPU usage. * Windows: Fix CPU frequency not being refreshed. * Windows: Improved components retrieval. * Windows: Fix inconsistent `boot_time` information. * Windows: Fix `User::groups`. * macOS: Fix network data information being limited to 32 bits. * macOS: Add newer macOS version for `System::long_os_version`. * unix: Add support for network sub-interfaces. * Use `std::env::consts::ARCH` as fallback for `System::cpu_arch`. * Add disk I/O support provided through `Disk::usage`. * Add `NetworkData::mtu`. * Add `DiskRefreshKind` to have finer-grained disks refreshes. * `Component::temperature` and `Component::max` now returns `Option`. * `Users::refresh_list`, `Groups::refresh_list`, `Components::refresh_list`, `Networks::refresh_list` and `Disks::refresh_list` methods were renamed `refresh`. * `*RefreshKind::new` methods were renamed `nothing`. * Improve documentation for `DiskUsage::read_bytes`. * Improve documentation for `Process::kill`. # 0.32.1 * Fix compilation error due to `libc` update. # 0.32.0 * Add new `Disk::is_read_only` API. * Add new `remove_dead_processes` argument to `System::refresh_processes` and `System::refresh_processes_specifics`. * macOS: Fix memory leak in disk refresh. # 0.31.4 * macOS: Force memory cleanup in disk list retrieval. # 0.31.3 * Raspberry Pi: Fix temperature retrieval. # 0.31.2 * Remove `bstr` dependency (needed for rustc development). # 0.31.1 * Downgrade version of `memchr` (needed for rustc development). # 0.31.0 * Split crate in features to only enable what you need. * Remove `System::refresh_process`, `System::refresh_process_specifics` and `System::refresh_pids` methods. * Add new argument of type `ProcessesToUpdate` to `System::refresh_processes` and `System::refresh_processes_specifics` methods. * Add new `NetworkData::ip_networks` method. * Add new `System::refresh_cpu_list` method. * Global CPU now only contains CPU usage. * Rename `TermalSensorType` to `ThermalSensorType`. * Process names is now an `OsString`. * Remove `System::global_cpu_info`. * Add `System::global_cpu_usage`. * macOS: Fix invalid CPU computation when single processes are refreshed one after the other. * Windows: Fix virtual memory computation. * Windows: Fix WoW64 parent process refresh. * Linux: Retrieve RSS (Resident Set Size) memory for cgroups. # 0.30.13 * macOS: Fix segfault when calling `Components::refresh_list` multiple times. * Windows: Fix CPU arch retrieval. # 0.30.12 * FreeBSD: Fix network interfaces retrieval (one was always missing). # 0.30.11 * macOS: Fix some invalid utf8 conversions. # 0.30.10 * Linux: Fix components not being listed anymore. # 0.30.9 * Linux/Windows: Performance improvements. * Linux/macOS/FreeBSD: Parent process ID is updated if changed as expected. # 0.30.8 * Linux: Fix missing parallelization. * Linux: Add `cargo` feature flag `linux-tmpfs` to list `tmpfs` mounts. * macOS: Fix CPU usage returning `NaN`. * `Components::refresh` is now parallelized. # 0.30.7 * Linux: Fix cgroup memory computation. * FreeBSD: Fix documentation about disk usage. # 0.30.6 * macOS: Fix missing update of process run time. * Add new `Groups` API. * Improve documentation. # 0.30.5 * Windows: Correctly retrieve processes name on 32 bits platforms. * Windows: Fix swap memory computation. # 0.30.4 * Windows: Fix misaligned read. # 0.30.3 * Improve dependency stack by updating the `windows` dependency. # 0.30.2 * Add `ThreadKind` enum. * Add `Process::thread_kind` method. # 0.30.1 * Linux: Fix invalid memory information retrieval (virtual and resident set size were reversed). # 0.30.0 * Split `System` into subtypes: `Components`, `Disks`, `Networks` and `Users`. * `brand`, `vendor_id` and `frequency` information is not set anymore on the global CPU. * Unix: Fix endless loop in user groups retrieval. * Unix/Windows: Fix infinite loop when retrieving various information because of bad usage of `Vec::reserve`. * Unix: Fix invalid usage of NULL pointer when retrieving user group name. * Linux: Fix CPU name retrieval. * Linux: Remove cgroup usage from memory computation. * Linux: Add `linux-netdevs` feature to allow to retrieve network devices. * Linux: Improve system memory information retrieval (using `statm` file instead of `stat`). * Linux: Tasks are listed in processes. * macOS: Correctly retrieve process root directory. * Windows: Add warning that `System::load_average` is not working in documentation. * Windows: Fix invalid use of NULL pointer when retrieving users groups. * Windows: Correctly retrieve process root directory. * Create new `System::cgroup_limits` method. * Remove `System::refresh_system` method. * `Disk::file_system` and `Disk::name` now return an `Option<&OsStr>`. * Implement `Display` trait on `DiskKind`. * Move from `winapi` to `windows` crate. * Add `System::cpu_arch`. * Add `System::refresh_pids` and `System::refresh_pids_specifics`. * `System::boot_time`, `System::cpu_arch`, `System::distribution_id`, `System::host_name`, `System::kernel_version`, `System::load_average`, `System::long_os_version`, `System::name`, `System::os_version` and `System::uptime` are static methods. * `ProcessRefreshKind` has a lot more of possibilities for better control over updates. * Add new `UpdateKind` enum. * Add new `MemoryRefreshKind` struct. * Add new `System::refresh_memory_specifics` method. * `Process::exe`, `Process::cwd` and `Process::root` return an `Option<&Path>`. * `Process::tasks` method is available on all platforms. * `Process::tasks` method returns a `HashSet`. * Move `System::IS_SUPPORTED`, `System::SUPPORTED_SIGNALS` and `System::MINIMUM_CPU_UPDATE_INTERVAL` constants out of `System` directly at the crate top-level. * Rename `IS_SUPPORTED` into `IS_SUPPORTED_SYSTEM`. * Fix `serde` serialization. * Add `System::refresh_cpu_frequency` and `System::refresh_cpu_all`. * Fix `sysinfo.h` and C wrapper. * Add a migration guide. # 0.29.11 * macOS: Fix bug when a user group doesn't have a name. # 0.29.10 * Linux: Correctly handle max memory value for cgroups. # 0.29.9 * Linux: Fix memory usage retrieval for cgroups. # 0.29.8 * Linux: Fix overflow bug. # 0.29.7 * macOS: Fix CPU frequency retrieval for M1 and M2. * Linux: Add support for cgroups v1/v2 for memory. * Windows: Fix processes name encoding issues. # 0.29.6 * Update minimum rust version to 1.63. * Windows: Fix memory corruption when listing processes. * Windows: Fix name inconsistency between `refresh_processes` and `refresh_process`. * `Cargo.lock` is now included to prevent minimum rust version disruptions. # 0.29.5 * Windows: Remove some undefined behaviour when listing processes. * : Use `--generate-link-to-definition` option to have better source code pages. # 0.29.4 * Windows: Improve code to retrieve network interfaces. * Improve serde documentation example. * Fix some clippy lints. # 0.29.3 * Fix some documentation examples. # 0.29.2 * : Generate documentation for all supported platforms. # 0.29.1 * Update `libc` version to 0.2.144. * Linux/FreeBSD/macOS: Fix retrieval of users groups in multi-threaded context. # 0.29.0 * Add `ProcessExt::effective_user_id` and `ProcessExt::effective_group_id`. * Rename `DiskType` into `DiskKind`. * Rename `DiskExt::type_` into `DiskExt::kind`. * macOS: Correctly handle `ProcessStatus` and remove public `ThreadStatus` field. * Windows 11: Fix CPU core usage. # 0.28.4 * macOS: Improve CPU computation. * Strengthen a process test (needed for debian). # 0.28.3 * FreeBSD/Windows: Add missing frequency for global CPU. * macOS: Fix used memory computation. * macOS: Improve available memory computation. * Windows: Fix potential panic when getting process data. # 0.28.2 * Linux: Improve CPU usage computation. # 0.28.1 * macOS: Fix overflow when computing CPU usage. # 0.28.0 * Linux: Fix name and CPU usage for processes tasks. * unix: Keep all users, even "not real" accounts. * Windows: Use SID for Users ID. * Fix C API. * Disable default cdylib compilation. * Add `serde` feature to enable serialization. * Linux: Handle `Idle` state in `ProcessStatus`. * Linux: Add brand and name of ARM CPUs. # 0.27.8 * macOS: Fix overflow when computing CPU usage. # 0.27.7 * macOS: Fix process CPU usage computation * Linux: Improve ARM CPU `brand` and `name` information. * Windows: Fix resource leak. * Documentation improvements. # 0.27.6 * Make `MacAddr` public. # 0.27.5 * Linux: Improve compatibility with upcoming `libc` changes for musl targets. # 0.27.4 * Create `SystemExt::MINIMUM_CPU_UPDATE_INTERVAL` constant. * Fix consecutive processes updates CPU usage computation. # 0.27.3 * macOS: Fix free/available memory computation. * Fix processes_by_name* lifetimes # 0.27.2 * Linux: Fix consecutive process CPU usage updates. * Linux: Ignore NFS disks. # 0.27.1 * Unix systems: Fix network address segfault issue. # 0.27.0 * Add `NetworkExt::mac_address` method and `MacAddr` type. * Linux: Fix truncated environment retrieval. * Implement `TryFrom` and `FromStr` for `Gid` and `Uid`. * Implement `TryFrom` for `Pid`. * Fix documentation of `System::new` about CPU list not loaded by default. # 0.26.9 * (backport) Linux: Improve compatibility with upcoming `libc` changes for musl targets. # 0.26.8 * Add `ProcessExt::session_id` method. * Linux: Ignore NFS disks. # 0.26.7 * Apple: Greatly improve disk retrieval (I recommend reading the pull request first comment for more information here: ). * Remove build script. # 0.26.6 * Add `Process::wait`. * Add "Good practice" entry into the crate level documentation and in the README. * Linux: More precise used memory computation. # 0.26.5 * Windows: Fix disk information retrieval. * Linux: Improve `Process` document. * Linux: Fix a compilation error if the `apple-sandbox` feature is enabled. * Internal code improvements. # 0.26.4 * Add `SystemExt::distribution_id` method. * Update `ntapi` version to `0.4`. * Update minimum supported Rust version (MSRV) to `1.59` for `ntapi` 0.4. # 0.26.3 * Update minimum supported Rust version (MSRV) to `1.56` to follow `once_cell` minor update. # 0.26.2 * Linux: Fix process information retrieval. * Linux: Get more components temperature. * Linux: Fix disk name retrieval (which in turn fixed disk type retrieval). # 0.26.1 * macOS M1: Fix segmentation fault crash. # 0.26.0 * Switch memory unit from kilobytes to bytes. * Windows: Fix Windows version display on Windows 11. # 0.25.3 * Add macOS M1 CI checks. * macOS M1: Add temperature support. * macOS: Fix leak in disk retrieval. # 0.25.2 * Windows: Fix `Process::exe` information retrieval. * All supported platforms: Correctly handle a PID owner change (#809). # 0.25.1 * Linux: Fix potential problem on `ProcessExt::exe` in case `/proc//exe` cannot be read. * Add `SystemExt::sort_disks_by`. # 0.25.0 * Linux: CPU frequency is now retrieved on-demand as expected when `CpuRefreshKind::frequency` is `true`. * `System::refresh_cpu` behaviour changed: it only computes CPU usage and doesn't retrieve CPU frequency. # 0.24.7 * Windows: Fix boot time computation. * macOS: Fix available memory computation. * Some documentation fixes. # 0.24.6 * macOS: Don't compute CPU usage when elapsed time is 0. * macOS: Fix memory leak when retrieving disks. * C interface: Fix `char` cast when platform is using unsigned `char`s. # 0.24.5 * Implement `Hash` trait on `Uid` and `Gid` types. * Remove dependency `once_cell` for targets other than `linux`, `android` and `windows`. # 0.24.4 * Windows: Fix `System::refresh_process` when required higher privileges. # 0.24.3 * macOS: Fix `System::refresh_processes` badly handling updates. * FreeBSD: Improve performance of `System::refresh_processes`. # 0.24.2 * Windows: Fix CPU usage computation. * Windows: Enable extra feature on `winapi`. * macOS: Fix executable path retrieval. # 0.24.1 * Use `saturating_*` function for mathematical operations to prevent overflows/underflows. # 0.24.0 * Rename `Processor` into `Cpu` and `ProcessorExt` into `CpuExt`. * Retrieve information about a process' owner. * Add `SystemExt::get_user_by_id`. * Add `ProcessExt::user_id`. * Add `ProcessExt::group_id`. * Add `user`-related methods to `ProcessRefreshKind`. * Linux: Improve performance when creating new `Process` by improving retrieval of user ID and group ID. # 0.23.14 * Linux: Fix processes' virtual memory computation. # 0.23.13 * macOS/FreeBSD: Fix `System::refresh_process` and `System::refresh_process_specifics` returned value. * Linux: Small performance improvement when updating process list. # 0.23.12 * Linux: Improve `System::refresh_cpu` performance. * Fix clippy lints. # 0.23.11 * Add FreeBSD to the "supported OS" list * Remove useless benchmark results # 0.23.10 * Improve documentation of `SystemExt::refresh_cpu`. # 0.23.9 * macOS: Fix disk retrieval # 0.23.8 * Windows: Fix underflow for `Process` run_time computation # 0.23.7 * macOS: Ignore non-root drive partitions # 0.23.6 * Windows: Fix process name retrieval * Windows: Unify internal process creation methods * FreeBSD: Simplify code for process update # 0.23.5 * Windows: Fix a bug which prevent all disks to be listed. # 0.23.4 * Linux (raspberry): Fix physical core count. # 0.23.3 * Impl `From` for Pid inner type. * Code cleanup. # 0.23.2 * Fix unsafe "correctness". * Correctly handle `MaybeUninit::assume_init`. # 0.23.1 * Implement `Into` trait on `Pid` * Add `#[repr(transparent)]` on `Pid` * Clean up `refresh_process` and `refresh_processes`: only `refresh_processes` removes non-existing processes. # 0.23.0 * Linux: Fix process uptime. * Rename `process_by_name` into `processes_by_name`. * Rename `process_by_name_exact` into `processes_by_name_exact`. * Change returned type of `process_by_name` and of `process_by_name_exact` into an iterator. * Improved `Signal` documentation. * Turned `Pid` type alias into a newtype. # 0.22.5 * Linux: Improve documentation on how processes queries are handled. * FreeBSD: Fix type error for 32-bit (on i386, armv6, armv7, powerpc). * Improve Pid type documentation. * Add `SystemExt::process_by_exact_name` method. * Add `SUPPORTED_SIGNALS` constant on `SystemExt`. * Fix common type aliases. * Implement `Display` for `Signal`. # 0.22.4 * Windows: Correctly handle COM initialization/deinitialization. * Linux: Fix panic when changing the limit of open files. # 0.22.3 * FreeBSD: Take ZFS ARC value into account when computing used system memory. * Add some missing `#[must_use]`. # 0.22.2 * FreeBSD: Improve memory information retrieval. # 0.22.1 * Remove forgotten debug. # 0.22.0 * Add FreeBSD support. * Create `SystemExt::refresh_processes_specifics` and `SystemExt::refresh_process_specifics` methods. * Update `ProcessExt::kill` API and add `ProcessExt::kill_with`. * Add `ProcessExt::run_time`. # 0.21.2 * Unsupported targets: Fix build. * Linux: Exclude rootfs disk type as well. * Windows: Performance improvement by lazily creating queries. # 0.21.1 * Linux: Process CPU usage cannot go above maximum value (number of CPUs * 100) anymore. * Linux: Improve processors update. * Linux: Improve processes CPU usage computation speed. # 0.21.0 * Linux: Fix processes CPU computation (if `System::refresh_cpu` wasn't used). * Fix build for unsupported targets. * Make `ProcessStatus` enum unique for all platforms. * Unify documentation over all platforms. # 0.20.5 * Linux: Prevented overflow in disk size computation (bug in `davfs2`). * Fixed clippy lints # 0.20.4 * Update libc version, allowing to remove a lot of FFI bindings. # 0.20.3 * Windows: Reworked process information retrieval * Windows: Fixed issue on `c_void` size. * Improved documentation of `ProcessExt::environ`. # 0.20.2 * Windows: Added support for getting process' current working directory * Windows: Added support for getting process' environment variables * Removed more FFI bindings and replaced them with libc's. # 0.20.1 * macOS: Added better support for sandboxing. * macOS: Added support for getting process current working directory. * Added more explanations in crate level code example. * Updated rayon version to 1.5.1. # 0.20.0 * macOS: Improved code readability. * Windows: Prevented the `taskkill.exe` console window from appearing when using `kill`. * Fixed benchmarks compilation issue. * Upgraded minimum supported Rust version to 1.54. * Removed doc-comment dependency. * Merged README and crate documentation. # 0.19.2 * Windows: Fixed swap memory information computation. # 0.19.1 * Windows: Got swap memory information. * Linux: Fixed memory information gathering (bad parsing of `/proc/meminfo`). # 0.19.0 * Renamed functions/methods to follow [Rust API guidelines on naming](https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter). * Linux: Set processes' executable path from command line if not found. * Linux: Added extra information about `ProcessExt::name()`. * macOS: Removed unneeded (re)import of CoreFoundation library at compile-time. * Reworked `DiskType` enum: there is no more `Removable` variant, it's now set into the `Disk` struct. `DiskExt::is_removable` was added. * Linux: Added support for removable disks. * Linux: Ensured there's a value in `global_processor` frequency. * Fixed tests to make them a bit less strict (which was problematic when run on VMs). * Linux: Fixed CPU usage subtraction overflow. # 0.18.2 * macOS: Brand and vendor ID information were reversed. * macOS: On Apple M1 processors, the vendor ID is empty, so instead we return "Apple". * Added tests to ensure that the processors are always set after `System::new()`. # 0.18.1 * Added `SystemExt::IS_SUPPORTED` constant to allow to easily query if a system is supported or not. * Used `SystemExt::IS_SUPPORTED` to fix tests on non-supported platforms and simplify others. # 0.18.0 * Improved documentation to make it more clear how to use the different information. * Turned the `Signal` enum into a full rust one by removing the `#[repr(C)]` attribute on it. Each platform now implements its own conversion. * Removed `Signal::Stklft` which wasn't used on any supported system. * Linux: Added support for paravirtualized disks. # 0.17.5 * Improved network code: network interfaces were handled a bit differently depending on the platform, it is now unified. # 0.17.4 * Linux: fixed invalid network interface cleanup when an interface was removed from the system in `refresh_networks_list`. * Added freebsd to CI runs. * Added `cargo test` command for freebsd on CI. * freebsd: Fixed build. # 0.17.3 * Removed manual FFI bindings in both Apple and Windows targets. * Fixed C-interface compilation. * Added information on how to add new platform. # 0.17.2 * Linux: fixed `System::refresh_process` return value. # 0.17.1 * Windows: fixed process CPU usage computation. * Linux: improved CPU usage values on first query by returning 0: it now waits the second cycle before computing it to avoid abherent values. * Linux: fixed process name retrieval by using `stat` information instead. * Apple: only list local users. # 0.17.0 * Linux: fixed OS version retrieval by adding a fallback to `/etc/lsb-release`. * iOS: fixed warnings. * Renamed `ProcessStatus::to_string` method to `as_str`. * macOS: fixed CPU usage computation. # 0.16.5 * Windows: Removed trailing NUL bytes in hostname. * Added user ID and group ID. # 0.16.4 * macOS: Removed trailing NUL bytes in various values returned by the `sysctl` calls. # 0.16.3 * Updated minimum libc version to 0.2.86. # 0.16.2 * Fixed network values computation: replaced the simple arithmetic with `saturating_sub` and `saturating_add`. * Converted values read in `/proc/meminfo` from KiB to KB (because contrary to what is said in the manual, they are in KiB, not in KB). * macOS: Rewrote `get_disks` function to remove the Objective-C dependency. * Added `SystemExt::get_long_os_version`. * Linux: Fixed sequences for disks. * Linux: Allowed `/run/media` as a mount path. * Windows: Fixed disk size computation. * Linux: Fixed virtual memory size computation. # 0.16.1 * Added support for Android. * Added flag to remove APIs prohibited in Apple store. # 0.16.0 * Windows: show removable drives on Windows. * Switched to Rust 2018 edition. * Split `SystemExt::get_version` into `SystemExt::get_kernel_version` and `SystemExt::get_os_version`. * Windows: added support for `get_kernel_version` and `get_os_version`. * Changed return type of `SystemExt::get_physical_core_count` from `usize` to `Option`. * Added `SystemExt::get_physical_core_numbers`. # 0.15.9 * iOS: Fixed build. * Fixed cross-compilation. # 0.15.8 * Apple: fixed Objective-C library imports. # 0.15.7 * Added `SystemExt::get_host_name`. # 0.15.6 * Upgraded `cfg-if` dependency version to `1.0`. # 0.15.5 * Added `SystemExt::get_name` and `SystemExt::get_version`. * Added `multithread` feature, making the `rayon` dependency optional. # 0.15.4 * Apple: gig source code cleanup. * Apple: improved disk handling. * Removed manual FFI code and used libc's instead. # 0.15.3 * Prevented CPU value to be NaN. # 0.15.2 * macOS: fixed disk space computation. # 0.15.1 * Improved documentation. * Extended example. # 0.15.0 * Added `SystemExt::get_available_memory`. # 0.14.15 * Linux: improved task source code. # 0.14.14 * macOS: renamed "CPU" into "CPU Die". * macOS: added "CPU proximity" information. # 0.14.13 * Linux: improved process name retrieval. # 0.14.12 * Linux: fixed infinite recursion when gathering disk information. # 0.14.11 * Added iOS support. # 0.14.10 * Simplified `DiskType` handling by removing `From` implementation. * Linux: fixed SSD/HDD detection. # 0.14.9 * Linux: fixed CPU usage computation. * Windows: fixed load average constants. # 0.14.8 * Linux: fixed network information retrieval by replacing `usize` with `u64` because it was too small on 32 bits systems. * Linux: get each core frequency. # 0.14.7 * Raspberry Pi: fixed temperature retrieval. # 0.14.6 * Linux: fixed infinite recursion when getting disk. # 0.14.5 * Strengthened cfg checks: use "linux" and "android" instead of "unix". # 0.14.4 * Linux: fixed memory usage computation. # 0.14.3 * Linux: fixed memory usage computation. # 0.14.2 * Windows: fixed CPU usage computation overflow. * macOS: fixed CPU usage computation overflow. * Windows: retrieved command line. # 0.14.1 * Removed empty disks. # 0.14.0 * Converted KiB to KB. # 0.13.4 * Code improvements. # 0.13.3 * Linux: fixed some issues on disks retrieval. * Linux: fixed out-of-bound access in `boot_time`. * Added benchmark on `Disk::refresh`. GuillaumeGomez-sysinfo-067dd61/Cargo.toml000066400000000000000000000113001506747262600204270ustar00rootroot00000000000000[package] name = "sysinfo" version = "0.37.2" authors = ["Guillaume Gomez "] description = "Library to get system information such as processes, CPUs, disks, components and networks" repository = "https://github.com/GuillaumeGomez/sysinfo" license = "MIT" readme = "README.md" rust-version = "1.88" exclude = ["/test-unknown"] keywords = ["system-information", "disk", "process", "network", "cpu"] edition = "2024" [lib] name = "sysinfo" [features] default = ["component", "disk", "network", "system", "user"] component = [ "windows/Win32_Foundation", "windows/Win32_Security", "windows/Win32_System_Com", "windows/Win32_System_Ole", "windows/Win32_System_Rpc", "windows/Win32_System_Variant", "windows/Win32_System_Wmi", "objc2-core-foundation/CFArray", "objc2-core-foundation/CFBase", "objc2-core-foundation/CFDictionary", "objc2-core-foundation/CFNumber", "objc2-core-foundation/CFString", "objc2-io-kit", "objc2-io-kit/hidsystem", ] disk = [ "windows/Win32_Foundation", "windows/Win32_Storage_FileSystem", "windows/Win32_Security", # For `windows::Win32::Storage::FileSystem::CreateFileW`. "windows/Win32_System_IO", "windows/Win32_System_Ioctl", "windows/Win32_System_SystemServices", "windows/Win32_System_WindowsProgramming", "objc2-core-foundation/CFArray", "objc2-core-foundation/CFBase", "objc2-core-foundation/CFDictionary", "objc2-core-foundation/CFError", "objc2-core-foundation/CFNumber", "objc2-core-foundation/CFString", "objc2-core-foundation/CFURL", "objc2-io-kit", ] system = [ "windows/Win32_Foundation", "windows/Win32_System_Diagnostics_ToolHelp", "windows/Wdk_System_SystemInformation", "windows/Wdk_System_SystemServices", "windows/Wdk_System_Threading", "windows/Win32_Security_Authorization", "windows/Win32_System_Diagnostics_Debug", "windows/Win32_System_Kernel", "windows/Win32_System_Memory", "windows/Win32_System_Performance", "windows/Win32_System_Power", "windows/Win32_System_ProcessStatus", "windows/Win32_System_Registry", "windows/Win32_System_RemoteDesktop", "windows/Win32_System_SystemInformation", "windows/Win32_System_SystemServices", "windows/Win32_System_Threading", "windows/Win32_UI_Shell", "dep:ntapi", "dep:memchr", "objc2-core-foundation/CFBase", "objc2-core-foundation/CFData", "objc2-core-foundation/CFDictionary", "objc2-core-foundation/CFString", "objc2-io-kit", ] network = [ "windows/Win32_Foundation", "windows/Win32_NetworkManagement_IpHelper", "windows/Win32_NetworkManagement_Ndis", "windows/Win32_Networking_WinSock", ] user = [ "windows/Win32_Foundation", "windows/Win32_NetworkManagement_NetManagement", "windows/Win32_Security", "windows/Win32_Security_Authentication_Identity", "windows/Win32_Security_Authorization", ] apple-sandbox = [] apple-app-store = ["apple-sandbox"] c-interface = ["default"] multithread = ["dep:rayon"] linux-netdevs = [] linux-tmpfs = [] debug = ["libc/extra_traits"] # This feature is used on CI to emulate unknown/unsupported target. unknown-ci = [] [package.metadata.docs.rs] features = ["serde"] # Setting this default target to prevent `freebsd` to be the default one. default-target = "x86_64-unknown-linux-gnu" targets = [ "i686-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "i686-pc-windows-msvc", "x86_64-unknown-freebsd", ] cargo-args = ["-Zbuild-std"] rustdoc-args = ["--generate-link-to-definition"] [dependencies] memchr = { version = "2.5", optional = true } rayon = { version = "^1.8", optional = true } serde = { version = "^1.0.190", optional = true, features = ["derive"] } [target.'cfg(windows)'.dependencies] ntapi = { version = "0.4", optional = true } # Support a range of versions in order to avoid duplication of this crate. Make sure to test all # versions when bumping to a new release, and only increase the minimum when absolutely necessary. windows = { version = ">=0.59, <0.62", optional = true } [target.'cfg(not(any(target_os = "unknown", target_arch = "wasm32")))'.dependencies] libc = "^0.2.173" [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] objc2-core-foundation = { version = "0.3.1", optional = true, default-features = false, features = [ "std", ] } objc2-io-kit = { version = "0.3.1", optional = true, default-features = false, features = [ "std", "libc", ] } [dev-dependencies] serde_json = "1.0" # Used in documentation tests. bstr = "1.9.0" tempfile = "3.9" itertools = "0.14.0" [[example]] name = "simple" path = "examples/simple.rs" required-features = ["default"] doc-scrape-examples = true GuillaumeGomez-sysinfo-067dd61/LICENSE000066400000000000000000000020731506747262600175130ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Guillaume Gomez Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. GuillaumeGomez-sysinfo-067dd61/Makefile000066400000000000000000000015111506747262600201420ustar00rootroot00000000000000# # Sysinfo # # Copyright (c) 2017 Guillaume Gomez # # # Please note that this Makefile only generates the c example. # IDIR = ./src CC = gcc CFLAGS = -I$(IDIR) ODIR = examples/ LDIR = ./target/debug/ LDIR-RELEASE = ./target/release/ LIBS = -lsysinfo -lpthread _DEPS = sysinfo.h DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) _OBJ = simple.o OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) simple: $(OBJ) @echo "Compiling in debug mode" cargo rustc --features=c-interface --crate-type cdylib gcc -o $@ $^ $(CFLAGS) -L$(LDIR) $(LIBS) release: $(OBJ) @echo "Compiling in release mode" cargo rustc --features=c-interface --release --crate-type cdylib gcc -o simple $^ $(CFLAGS) -L$(LDIR-RELEASE) $(LIBS) $(ODIR)/%.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) .PHONY: simple clean: @echo "Cleaning mess" rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~ GuillaumeGomez-sysinfo-067dd61/README.md000066400000000000000000000171301506747262600177650ustar00rootroot00000000000000# sysinfo [![][img_crates]][crates] [![][img_doc]][doc] `sysinfo` is a crate used to get a system's information. ## Supported OSes It currently supports the following OSes (alphabetically sorted): * Android * FreeBSD * iOS * Linux * macOS * Raspberry Pi * Windows You can still use `sysinfo` on non-supported OSes, it'll simply do nothing and always return empty values. You can check in your program directly if an OS is supported by checking the [`IS_SUPPORTED_SYSTEM`] constant. The minimum-supported version of `rustc` is **1.88**. ## Usage If you want to migrate from an older version, don't hesitate to take a look at the [CHANGELOG](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md) and at the [migration guide](https://github.com/GuillaumeGomez/sysinfo/blob/master/migration_guide.md). ⚠️ Before any attempt to read the different structs' information, you need to update them to get up-to-date information because for most of them, it works on diff between the current value and the old one. Which is why, it's much better to keep the same instance of [`System`] around instead of recreating it multiple times. You have an example into the `examples` folder. You can run it with `cargo run --example simple`. Otherwise, here is a little code sample: ```rust use sysinfo::{ Components, Disks, Networks, System, }; // Please note that we use "new_all" to ensure that all lists of // CPUs and processes are filled! let mut sys = System::new_all(); // First we update all information of our `System` struct. sys.refresh_all(); println!("=> system:"); // RAM and swap information: println!("total memory: {} bytes", sys.total_memory()); println!("used memory : {} bytes", sys.used_memory()); println!("total swap : {} bytes", sys.total_swap()); println!("used swap : {} bytes", sys.used_swap()); // Display system information: println!("System name: {:?}", System::name()); println!("System kernel version: {:?}", System::kernel_version()); println!("System OS version: {:?}", System::os_version()); println!("System host name: {:?}", System::host_name()); // Number of CPUs: println!("NB CPUs: {}", sys.cpus().len()); // Display processes ID, name and disk usage: for (pid, process) in sys.processes() { println!("[{pid}] {:?} {:?}", process.name(), process.disk_usage()); } // We display all disks' information: println!("=> disks:"); let disks = Disks::new_with_refreshed_list(); for disk in &disks { println!("{disk:?}"); } // Network interfaces name, total data received and total data transmitted: let networks = Networks::new_with_refreshed_list(); println!("=> networks:"); for (interface_name, data) in &networks { println!( "{interface_name}: {} B (down) / {} B (up)", data.total_received(), data.total_transmitted(), ); // If you want the amount of data received/transmitted since last call // to `Networks::refresh`, use `received`/`transmitted`. } // Components temperature: let components = Components::new_with_refreshed_list(); println!("=> components:"); for component in &components { println!("{component:?}"); } ``` Please remember that to have some up-to-date information, you need to call the equivalent `refresh` method. For example, for the CPU usage: ```rust,no_run use sysinfo::System; let mut sys = System::new(); loop { sys.refresh_cpu_usage(); // Refreshing CPU usage. for cpu in sys.cpus() { print!("{}% ", cpu.cpu_usage()); } // Sleeping to let time for the system to run for long // enough to have useful information. std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); } ``` By default, `sysinfo` uses multiple threads. However, this can increase the memory usage on some platforms (macOS for example). The behavior can be disabled by setting `default-features = false` in `Cargo.toml` (which disables the `multithread` cargo feature). ### Good practice / Performance tips Most of the time, you don't want all information provided by `sysinfo` but just a subset of it. In this case, it's recommended to use `refresh_specifics(...)` methods with only what you need to have much better performance. Another issue frequently encountered: unless you know what you're doing, it's almost all the time better to instantiate the `System` struct once and use this one instance through your program. The reason is because a lot of information needs a previous measure to be computed (the CPU usage for example). Another example why it's much better: in case you want to list all running processes, `sysinfo` needs to allocate all memory for the `Process` struct list, which takes quite some time on the first run. If your program needs to use a lot of file descriptors, you'd better use: ```rust,no_run sysinfo::set_open_files_limit(0); ``` as `sysinfo` keeps a number of file descriptors open to have better performance on some targets when refreshing processes. ### Running on Raspberry Pi It'll be difficult to build on Raspberry Pi. A good way-around is to cross-build, then send the executable to your Raspberry Pi. First install the arm toolchain, for example on Ubuntu: ```bash > sudo apt-get install gcc-multilib-arm-linux-gnueabihf ``` Then configure cargo to use the corresponding toolchain: ```bash cat << EOF > ~/.cargo/config [target.armv7-unknown-linux-gnueabihf] linker = "arm-linux-gnueabihf-gcc" EOF ``` Finally, cross compile: ```bash rustup target add armv7-unknown-linux-gnueabihf cargo build --target=armv7-unknown-linux-gnueabihf ``` ### Linux on Docker & Windows Subsystem for Linux (WSL) Virtual Linux systems, such as those run through Docker and Windows Subsystem for Linux (WSL), do not receive host hardware information via `/sys/class/hwmon` or `/sys/class/thermal`. As such, querying for components may return no results (or unexpected results) when using this library on virtual systems. ### Use in binaries running inside the macOS or iOS Sandbox/stores Apple has restrictions as to which APIs can be linked into binaries that are distributed through the app store. By default, `sysinfo` is not compatible with these restrictions. You can use the `apple-app-store` feature flag to disable the Apple prohibited features. This also enables the `apple-sandbox` feature. In the case of applications using the sandbox outside of the app store, the `apple-sandbox` feature can be used alone to avoid causing policy violations at runtime. ### How it works I wrote a blog post you can find [here][sysinfo-blog] which explains how `sysinfo` extracts information on the different systems. [sysinfo-blog]: https://blog.guillaume-gomez.fr/articles/2021-09-06+sysinfo%3A+how+to+extract+systems%27+information ### Running tests Because we're looking at system information, some tests have a better chance to succeed when there is a limited number of parallel running tests. To ensure they all pass, use: ```bash cargo test -- --test-threads=1 ``` ### C interface It's possible to use this crate directly from C. Take a look at the `Makefile` and at the `examples/simple.c` file. To build the C example, just run: ```bash > make > ./simple # If needed: > LD_LIBRARY_PATH=target/debug/ ./simple ``` ### Benchmarks You can run the benchmarks locally with rust **nightly** by doing: ```bash > cargo bench ``` ## Donations If you appreciate my work and want to support me, you can do it with [github sponsors](https://github.com/sponsors/GuillaumeGomez) or with [patreon](https://www.patreon.com/GuillaumeGomez). [img_crates]: https://img.shields.io/crates/v/sysinfo.svg [img_doc]: https://img.shields.io/badge/rust-documentation-blue.svg [crates]: https://crates.io/crates/sysinfo [doc]: https://docs.rs/sysinfo/ GuillaumeGomez-sysinfo-067dd61/benches/000077500000000000000000000000001506747262600201135ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/benches/basic.rs000066400000000000000000000055321506747262600215470ustar00rootroot00000000000000#![feature(test)] extern crate test; #[cfg(feature = "system")] #[bench] fn bench_new(b: &mut test::Bencher) { b.iter(|| { sysinfo::System::new(); }); } #[cfg(feature = "system")] #[bench] fn bench_new_all(b: &mut test::Bencher) { b.iter(|| { sysinfo::System::new_all(); }); } #[cfg(feature = "system")] #[bench] fn bench_refresh_all(b: &mut test::Bencher) { let mut s = sysinfo::System::new_all(); b.iter(move || { s.refresh_all(); }); } #[cfg(feature = "system")] #[bench] fn bench_refresh_processes(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); s.refresh_processes(sysinfo::ProcessesToUpdate::All, true); // to load the whole processes list a first time. b.iter(move || { s.refresh_processes(sysinfo::ProcessesToUpdate::All, true); }); } #[cfg(feature = "system")] #[bench] fn bench_first_refresh_processes(b: &mut test::Bencher) { b.iter(move || { let mut s = sysinfo::System::new(); s.refresh_processes(sysinfo::ProcessesToUpdate::All, true); }); } #[cfg(feature = "system")] #[bench] fn bench_refresh_process(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); s.refresh_all(); // to be sure it'll exist for at least as long as we run let pid = sysinfo::get_current_pid().expect("failed to get current pid"); b.iter(move || { s.refresh_processes(sysinfo::ProcessesToUpdate::Some(&[pid]), true); }); } #[bench] fn bench_refresh_disk(b: &mut test::Bencher) { let mut disks = sysinfo::Disks::new_with_refreshed_list(); let disks = disks.list_mut(); let disk = &mut disks[0]; b.iter(move || { disk.refresh(); }); } #[bench] fn bench_refresh_disks(b: &mut test::Bencher) { let mut disks = sysinfo::Disks::new_with_refreshed_list(); b.iter(move || { disks.refresh(true); }); } #[cfg(feature = "network")] #[bench] fn bench_refresh_networks(b: &mut test::Bencher) { let mut n = sysinfo::Networks::new_with_refreshed_list(); b.iter(move || { n.refresh(true); }); } #[cfg(feature = "system")] #[bench] fn bench_refresh_memory(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); b.iter(move || { s.refresh_memory(); }); } #[cfg(feature = "system")] #[bench] fn bench_refresh_cpu_usage(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); s.refresh_cpu_usage(); b.iter(move || { s.refresh_cpu_usage(); }); } #[cfg(feature = "component")] #[bench] fn bench_refresh_components(b: &mut test::Bencher) { let mut c = sysinfo::Components::new_with_refreshed_list(); b.iter(move || { c.refresh(false); }); } #[bench] fn bench_refresh_users_list(b: &mut test::Bencher) { let mut users = sysinfo::Users::new_with_refreshed_list(); b.iter(move || { users.refresh(); }); } GuillaumeGomez-sysinfo-067dd61/examples/000077500000000000000000000000001506747262600203225ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/examples/simple.c000066400000000000000000000070231506747262600217610ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #include #include #include #include #include #include "sysinfo.h" void print_process(CProcess process) { RString exe = sysinfo_process_executable_path(process); printf("process[%d]: parent: %d,\n" " cpu_usage: %f,\n" " memory: %ld,\n" " virtual memory: %ld,\n" " executable path: '%s'\n", sysinfo_process_pid(process), sysinfo_process_parent_pid(process), sysinfo_process_cpu_usage(process), sysinfo_process_memory(process), sysinfo_process_virtual_memory(process), exe); sysinfo_rstring_free(exe); } void check_tasks(CSystem system) { #ifdef __linux__ bool task_loop(pid_t pid, CProcess process, void *data) { (void)data; printf(" "); print_process(process); return true; } void *sleeping_func(void *data) { sleep(3); return data; } pthread_t thread; pthread_create(&thread, NULL, sleeping_func, NULL); sysinfo_refresh_processes(system); CProcess process = sysinfo_process_by_pid(system, getpid()); printf("\n== Task(s) for current process: ==\n"); print_process(process); printf("Got %ld task(s)\n", sysinfo_process_tasks(process, task_loop, NULL)); #else (void)system; #endif } bool process_loop(pid_t pid, CProcess process, void *data) { unsigned int *i = data; print_process(process); *i += 1; return *i < 10; } int main() { CSystem system = sysinfo_init(); CNetworks networks = sysinfo_networks_init(); sysinfo_refresh_all(system); sysinfo_networks_refresh(networks); printf("os name: %s\n", sysinfo_system_name(system)); printf("os version: %s\n", sysinfo_system_version(system)); printf("kernel version: %s\n", sysinfo_system_kernel_version(system)); printf("long os version: %s\n", sysinfo_system_long_version(system)); printf("host name: %s\n", sysinfo_system_host_name(system)); printf("cpu vendor id: %s\n", sysinfo_cpu_vendor_id(system)); printf("cpu brand: %s\n", sysinfo_cpu_brand(system)); printf("cpu frequency: %ld\n", sysinfo_cpu_frequency(system)); printf("cpu cores: %d\n", sysinfo_cpu_physical_cores(system)); printf("total memory: %ld\n", sysinfo_total_memory(system)); printf("free memory: %ld\n", sysinfo_free_memory(system)); printf("used memory: %ld\n", sysinfo_used_memory(system)); printf("total swap: %ld\n", sysinfo_total_swap(system)); printf("free swap: %ld\n", sysinfo_free_swap(system)); printf("used swap: %ld\n", sysinfo_used_swap(system)); printf("networks received: %ld\n", sysinfo_networks_received(networks)); printf("networks transmitted: %ld\n", sysinfo_networks_transmitted(networks)); unsigned int len = 0, i = 0; float *procs = NULL; sysinfo_cpus_usage(system, &len, &procs); while (i < len) { printf("CPU #%d usage: %f%%\n", i, procs[i]); i += 1; } free(procs); // processes part i = 0; printf("For a total of %ld processes.\n", sysinfo_processes(system, process_loop, &i)); check_tasks(system); // we can now free the CSystem and the CNetworks objects. sysinfo_destroy(system); sysinfo_networks_destroy(networks); return 0; } GuillaumeGomez-sysinfo-067dd61/examples/simple.rs000066400000000000000000000314761506747262600221740ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![crate_type = "bin"] #![allow(unused_must_use, non_upper_case_globals)] #![allow(clippy::manual_range_contains)] use std::io::{self, BufRead, Write}; use std::str::FromStr; use sysinfo::{Components, Disks, Groups, Networks, Pid, SUPPORTED_SIGNALS, System, Users}; fn print_help() { println!( "\ == Help menu == help : shows this menu quit : exits the program = Refresh commands = refresh : reloads all processes information refresh [pid] : reloads corresponding process information refresh_components : reloads components information refresh_cpu : reloads CPU information refresh_disks : reloads disks information refresh_networks : reloads networks information refresh_users : reloads users information = Process commands = all : displays all process name and pid kill [pid] [signal]: sends [signal] to the process with this [pid]. To get the [signal] number, use the `signals` command. show [pid | name] : shows information of the given process corresponding to [pid | name] signals : shows the available signals pid : displays this example's PID = CPU commands = brand : displays CPU brand cpus : displays CPUs state frequency : displays CPU frequency vendor_id : displays CPU vendor id = Users and groups commands = groups : displays all groups users : displays all users and their groups = System commands = boot_time : displays system boot time load_avg : displays system load average system : displays system information (such as name, version and hostname) uptime : displays system uptime = Extra components commands = disks : displays disks' information memory : displays memory state network : displays network' information temperature : displays components' temperature" ); } fn interpret_input( input: &str, sys: &mut System, networks: &mut Networks, disks: &mut Disks, components: &mut Components, users: &mut Users, ) -> bool { match input.trim() { "help" => print_help(), "refresh_disks" => { println!("Refreshing disk list..."); disks.refresh(true); println!("Done."); } "refresh_users" => { println!("Refreshing user list..."); users.refresh(); println!("Done."); } "refresh_networks" => { println!("Refreshing network list..."); networks.refresh(true); println!("Done."); } "refresh_components" => { println!("Refreshing component list..."); components.refresh(true); println!("Done."); } "refresh_cpu" => { println!("Refreshing CPUs..."); sys.refresh_cpu_all(); println!("Done."); } "signals" => { for (nb, sig) in SUPPORTED_SIGNALS.iter().enumerate() { println!("{:2}:{sig:?}", nb + 1); } } "cpus" => { // Note: you should refresh a few times before using this, so that usage statistics // can be ascertained println!( "number of physical cores: {}", System::physical_core_count() .map(|c| c.to_string()) .unwrap_or_else(|| "Unknown".to_owned()), ); println!("total CPU usage: {}%", sys.global_cpu_usage(),); for cpu in sys.cpus() { println!("{cpu:?}"); } } "memory" => { println!("total memory: {: >10} KB", sys.total_memory() / 1_000); println!( "available memory: {: >10} KB", sys.available_memory() / 1_000 ); println!("used memory: {: >10} KB", sys.used_memory() / 1_000); println!("total swap: {: >10} KB", sys.total_swap() / 1_000); println!("used swap: {: >10} KB", sys.used_swap() / 1_000); } "quit" | "exit" => return true, "all" => { for (pid, proc_) in sys.processes() { println!( "{}:{} status={:?}", pid, proc_.name().to_string_lossy(), proc_.status() ); } } "frequency" => { for cpu in sys.cpus() { println!("[{}] {} MHz", cpu.name(), cpu.frequency(),); } } "vendor_id" => { println!("vendor ID: {}", sys.cpus()[0].vendor_id()); } "brand" => { println!("brand: {}", sys.cpus()[0].brand()); } "load_avg" => { let load_avg = System::load_average(); println!("one minute : {}%", load_avg.one); println!("five minutes : {}%", load_avg.five); println!("fifteen minutes: {}%", load_avg.fifteen); } e if e.starts_with("show ") => { let tmp: Vec<&str> = e.split(' ').filter(|s| !s.is_empty()).collect(); if tmp.len() != 2 { println!("show command takes a pid or a name in parameter!"); println!("example: show 1254"); } else if let Ok(pid) = Pid::from_str(tmp[1]) { match sys.process(pid) { Some(p) => { println!("{:?}", *p); println!( "Files open/limit: {:?}/{:?}", p.open_files(), p.open_files_limit(), ); } None => { println!("pid \"{pid:?}\" not found"); } } } else { let proc_name = tmp[1]; for proc_ in sys.processes_by_name(proc_name.as_ref()) { println!("==== {} ====", proc_.name().to_string_lossy()); println!("{proc_:?}"); } } } "temperature" => { for component in components.iter() { println!("{component:?}"); } } "network" => { for (interface_name, data) in networks.iter() { println!( "\ {interface_name}: ether {} input data (new / total): {} / {} B output data (new / total): {} / {} B", data.mac_address(), data.received(), data.total_received(), data.transmitted(), data.total_transmitted(), ); } } "show" => { println!("'show' command expects a pid number or a process name"); } e if e.starts_with("kill ") => { let tmp: Vec<&str> = e .split(' ') .map(|s| s.trim()) .filter(|s| !s.is_empty()) .collect(); if tmp.len() != 3 { println!("kill command takes the pid and a signal number in parameter!"); println!("example: kill 1254 9"); } else { let Ok(pid) = Pid::from_str(tmp[1]) else { eprintln!("Expected a number for the PID, found {:?}", tmp[1]); return false; }; let Ok(signal) = usize::from_str(tmp[2]) else { eprintln!("Expected a number for the signal, found {:?}", tmp[2]); return false; }; let Some(signal) = SUPPORTED_SIGNALS.get(signal) else { eprintln!( "No signal matching {signal}. Use the `signals` command to get the \ list of signals.", ); return false; }; match sys.process(pid) { Some(p) => { if let Some(res) = p.kill_with(*signal) { println!("kill: {res}"); } else { eprintln!("kill: signal not supported on this platform"); } } None => { eprintln!("pid not found"); } } } } "disks" => { for disk in disks { println!("{disk:?}"); } } "users" => { for user in users { println!("{:?} => {:?}", user.name(), user.groups(),); } } "groups" => { for group in Groups::new_with_refreshed_list().list() { println!("{group:?}"); } } "boot_time" => { println!("{} seconds", System::boot_time()); } "uptime" => { let up = System::uptime(); let mut uptime = up; let days = uptime / 86400; uptime -= days * 86400; let hours = uptime / 3600; uptime -= hours * 3600; let minutes = uptime / 60; println!("{days} days {hours} hours {minutes} minutes ({up} seconds in total)",); } x if x.starts_with("refresh") => { if x == "refresh" { println!("Getting processes' information..."); sys.refresh_all(); println!("Done."); } else if x.starts_with("refresh ") { println!("Getting process' information..."); if let Some(pid) = x .split(' ') .filter_map(|pid| pid.parse().ok()) .take(1) .next() { if sys.refresh_processes(sysinfo::ProcessesToUpdate::Some(&[pid]), true) != 0 { println!("Process `{pid}` updated successfully"); } else { println!("Process `{pid}` couldn't be updated..."); } } else { println!("Invalid [pid] received..."); } } else { println!( "\"{x}\": Unknown command. Enter 'help' if you want to get the commands' \ list.", ); } } "pid" => { println!( "PID: {}", sysinfo::get_current_pid().expect("failed to get PID") ); } "system" => { println!( "System name: {}\n\ System kernel version: {}\n\ System OS version: {}\n\ System OS (long) version: {}\n\ System host name: {}\n\ System kernel: {}", System::name().unwrap_or_else(|| "".to_owned()), System::kernel_version().unwrap_or_else(|| "".to_owned()), System::os_version().unwrap_or_else(|| "".to_owned()), System::long_os_version().unwrap_or_else(|| "".to_owned()), System::host_name().unwrap_or_else(|| "".to_owned()), System::kernel_long_version(), ); } e => { println!( "\"{e}\": Unknown command. Enter 'help' if you want to get the commands' \ list.", ); } } false } fn main() { println!("Getting system information..."); let mut system = System::new_all(); let mut networks = Networks::new_with_refreshed_list(); let mut disks = Disks::new_with_refreshed_list(); let mut components = Components::new_with_refreshed_list(); let mut users = Users::new_with_refreshed_list(); println!("Done."); let t_stin = io::stdin(); let mut stin = t_stin.lock(); let mut done = false; println!("To get the commands' list, enter 'help'."); while !done { let mut input = String::new(); write!(&mut io::stdout(), "> "); io::stdout().flush(); stin.read_line(&mut input); if input.is_empty() { // The string is empty, meaning there is no '\n', meaning // that the user used CTRL+D so we can just quit! println!("\nLeaving, bye!"); break; } if (&input as &str).ends_with('\n') { input.pop(); } done = interpret_input( input.as_ref(), &mut system, &mut networks, &mut disks, &mut components, &mut users, ); } } GuillaumeGomez-sysinfo-067dd61/funding.json000066400000000000000000000045521506747262600210370ustar00rootroot00000000000000{ "version": "v1.0.0", "entity": { "type": "individual", "role": "owner", "name": "Guillaume Gomez", "email": "guillaume1.gomez@gmail.com", "phone": "", "description": "Contributor on multiple open-source projects in the Rust ecosystem, including the Rust compiler itself.", "webpageUrl": { "url": "https://github.com/GuillaumeGomez/sysinfo" } }, "projects": [ { "guid": "sysinfo-crate", "name": "sysinfo", "description": "Cross-platform library to fetch system information.", "webpageUrl": { "url": "https://github.com/GuillaumeGomez/sysinfo" }, "repositoryUrl": { "url": "https://github.com/GuillaumeGomez/sysinfo" }, "licenses": ["spdx:MIT"], "tags": ["rust", "system", "CPU", "process"] } ], "funding": { "channels": [ { "guid": "github-sponsors", "type": "payment-provider", "address": "https://github.com/sponsors/GuillaumeGomez", "description": "Sponsor me through Github." }, { "guid": "paypal-sponsors", "type": "payment-provider", "address": "https://www.paypal.com/paypalme/imperioland", "description": "Sponsor me through Paypal." } ], "plans": [ { "guid": "developer-time", "status": "active", "name": "Developer compensation", "description": "This will cover the cost of one developer working part-time on the projects.", "amount": 0, "currency": "USD", "frequency": "monthly", "channels": ["github-sponsors", "paypal-sponsors"] }, { "guid": "support-plan", "status": "active", "name": "Support plan", "description": "Pay anything you wish/can to show your support for the projects.", "amount": 0, "currency": "USD", "frequency": "one-time", "channels": ["github-sponsors", "paypal-sponsors"] } ], "history": [] } } GuillaumeGomez-sysinfo-067dd61/md_doc/000077500000000000000000000000001506747262600177315ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/md_doc/is_supported.md000066400000000000000000000004251506747262600227740ustar00rootroot00000000000000Returns `true` if this OS is supported. Please refer to the [crate-level documentation](index.html) to get the list of supported OSes. ``` if sysinfo::IS_SUPPORTED_SYSTEM { println!("This OS is supported!"); } else { println!("This OS isn't supported (yet?)."); } ``` GuillaumeGomez-sysinfo-067dd61/md_doc/minimum_cpu_update_interval.md000066400000000000000000000004731506747262600260470ustar00rootroot00000000000000This is the minimum interval time used internally by `sysinfo` to refresh the CPU time. ⚠️ This value differs from one OS to another. Why is this constant even needed? If refreshed too often, the CPU usage of processes will be `0` whereas on Linux it'll always be the maximum value (`number of CPUs * 100`). GuillaumeGomez-sysinfo-067dd61/md_doc/pid.md000066400000000000000000000011531506747262600210270ustar00rootroot00000000000000Process ID. Can be used as an integer type by simple casting. For example: ``` use sysinfo::Pid; // 0's type will be different depending on the platform! let p = Pid::from(0); // For something more "general": let p = Pid::from_u32(0); let i: u32 = p.as_u32(); ``` On glibc systems this is a glibc [`pid_t`](https://www.gnu.org/software/libc/manual/html_node/Process-Identification.html). On Windows systems this is a [`usize` and represents a windows process identifier](https://learn.microsoft.com/en-us/windows/win32/procthread/process-handles-and-identifiers). On unsupported systems, this is also a `usize`. GuillaumeGomez-sysinfo-067dd61/md_doc/serde.md000066400000000000000000000004701506747262600213560ustar00rootroot00000000000000 With the `serde` feature enabled, you can then serialize `sysinfo` types. Let's see an example with `serde_json`: ``` use sysinfo::System; let mut sys = System::new_all(); // First we update all information of our `System` struct. sys.refresh_all(); println!("{}", serde_json::to_string(&sys).unwrap()); ``` GuillaumeGomez-sysinfo-067dd61/md_doc/sid.md000066400000000000000000000000521506747262600210270ustar00rootroot00000000000000Opaque type encapsulating a Windows SID. GuillaumeGomez-sysinfo-067dd61/md_doc/supported_signals.md000066400000000000000000000003421506747262600240170ustar00rootroot00000000000000Returns the list of the supported signals on this system (used by [`Process::kill_with`][crate::Process::kill_with]). ``` use sysinfo::{System, SUPPORTED_SIGNALS}; println!("supported signals: {:?}", SUPPORTED_SIGNALS); ``` GuillaumeGomez-sysinfo-067dd61/migration_guide.md000066400000000000000000000167051506747262600222050ustar00rootroot00000000000000# Migration guide ## 0.34 to 0.35 ### Major changes `Process::open_files*` methods now return a `usize`. The `set_open_files_limit` function argument type is now `usize`. ### New APIs `System` has a new `open_files_limit` method. `Process` has two new methods: `kill_and_wait` and `kill_with_and_wait`. ## 0.33 to 0.34 ### Major changes The `multithread` feature is now disabled by default. It is to reduce the issues linked to the `rayon` crate usage. The `System::physical_core_count` method was turned into an associated function. No need to have a `System` instance to get this information anymore. The `System::refresh_all` and `System::refresh_specifics` methods now remove dead processes. If you don't want the dead processes to be removed, you will need to use `System::refresh_processes_specifics` directly instead. ### New APIs The `Process` type has new methods: `accumulated_cpu_time`, `exists`, `open_files` and `open_files_limit`. The `Process::wait` method now returns `Option` instead of `ExitStatus`. The `System` type has new methods: `distribution_id_like` and `kernel_long_version`. The `ProcessRefreshKind` type has a new "refresh kind": `tasks`. ## 0.32 to 0.33 ### Major changes `Users::refresh_list`, `Groups::refresh_list`, `Components::refresh_list`, `Networks::refresh_list` and `Disks::refresh_list` methods were renamed into `refresh`. All of them except for `Users::refresh` and `Groups::refresh` expect a boolean to tell whether or not `sysinfo` should keep removed items. `Component::temperature` and `Component::max` now returns `Option` instead of returning `f32::NaN` in case the information isn't available. `*RefreshKind::new` methods were renamed `nothing` to better the match the `*RefreshKind::everything` method. ### New APIs `Disks` now has a new `refresh_specifics` method expecting a `DiskRefreshKind` argument to allow you finer-grained refreshes. The `NetworkData` type now has a new `mtu` method to retrieve the Maximum Transfer Unit. ## 0.31 to 0.32 ### Major changes `System::refresh_process` and `System::refresh_process_specifics` methods now take an extra `remove_dead_processes` argument. When set to `true`, dead processes will be removed. ## 0.30 to 0.31 With this update, the minimum supported Rust version goes up to 1.74. ### Major changes `System::refresh_process`, `System::refresh_process_specifics` and `System::refresh_pids` methods were removed. The `System::refresh_processes` and `System::refresh_processes_specifics` methods take a new argument of type `ProcessesToUpdate`. The equivalent of `System::refresh_process`, `System::refresh_process_specifics` and `System::refresh_pids` looks like this: ```rust use sysinfo::{ProcessesToUpdate, System}; let pid = 1337; let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::Some(&[pid.into()])); ``` The equivalent of `System::refresh_processes` and `System::refresh_processes_specifics` looks like this: ```rust use sysinfo::{ProcessesToUpdate, System}; let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::All); ``` #### Global CPU usage `System::global_cpu_info` was replaced with `System::global_cpu_usage` which returns an `f32` representing the global CPU usage and no other information. #### Features You can now enable/disable parts of `sysinfo` API through its cargo features to have smaller build (size and time). If you're only interested by network information, then you'll import `sysinfo` like this: ```toml sysinfo = { version = "0.31", default-features = false, features = ["network"] } ``` #### Renaming The `TermalSensorType` type was renamed into `ThermalSensorType`. ## 0.29 to 0.30 With this update, the minimum supported Rust version goes up to 1.69. ### Major changes There are two major changes in this update. The first one was that all the traits were removed. It means that now, if you want to use methods on `System`, you don't need to import `SystemExt` anymore. So before you had: ```rust use sysinfo::{System, SystemExt}; // `SystemExt` is needed for both `new` and `refresh_processes`. let mut s = System::new(); s.refresh_processes(); ``` And now you have: ```rust use sysinfo::System; // No need for `SystemExt` anymore! let s = System::new(); s.refresh_processes(); ``` The second major change was that the `System` type has been split into smaller types: * `Components` * `Disks` * `Networks` * `Users` The `System` type itself still handles CPU, memory and processes. ### Finer control over what is refreshed The `*RefreshKind` types now have many more options allowing you to control exactly what is retrieved. In particular, the `ProcessRefreshKind` now allows you to refresh specifically: * `cmd` * `cpu` * `disk_usage` * `environ` * `exe` * `memory` * `root` * `user` In some cases, like `user`, you might want this information to be retrieved only if it hasn't been already. For them, a new `UpdateKind` enum was added. It contains three variants: * `Never` * `Always` * `OnlyIfNotSet` Like that, you get yet another extra level of control over what's updated and when. ### Constants in `System` have been moved to crate level `System::IS_SUPPORTED` is now `sysinfo::IS_SUPPORTED_SYSTEM`. `System::SUPPORTED_SIGNALS` is now `sysinfo::SUPPORTED_SIGNALS`. `System::MINIMUM_CPU_UPDATE_INTERVAL` is now `sysinfo::MINIMUM_CPU_UPDATE_INTERVAL`. ### `System` changes `System::refresh_pids` and `System::refresh_pids_specifics` methods have been added. They allow you to be able to refresh multiple PIDs while being able to have support for `sysinfo` multi-threading context (and much better performance in any case even if disabled). Some methods are now static methods: * `boot_time` * `cpu_arch` * `distribution_id` * `host_name` * `kernel_version` * `load_average` * `long_os_version` * `name` * `os_version` * `uptime` Meaning you can call them without having an instance of `System`: ```rust println!("host name: {}", System::host_name()); ``` A new `System::refresh_memory_specifics` method and a new `MemoryRefreshKind` type were added, allowing you to control whether you want both RAM and SWAP memories to be updated or only one of the two. This change was needed because getting SWAP information on Windows is very slow. `System::tasks` method is now available on all OSes even if it only returns something on Linux. Its return type is now a `Option>` instead of `HashMap`. The tasks are listed in `processes`. ### `Disk` changes `Disk::name` and `Disk::file_system` now returns `&OsStr`. ### cgroups handling Before, `sysinfo` was handling cgroups internally and the users had no control over it. Now there is a `System::cgroup_limits` method which allows you to query this information if you need it. ### `Process` changes `Process::cwd`, `Process::exe` and `Process::root` now return an `Option<&Path>`. ### Removal of `sort_by` methods If you want to sort `Disks`, `Users` or `Components`, you can do it by calling `sort` (or equivalents) on the value returned by the `list_mut` methods. ### New `linux-netdevs` feature By default, `sysinfo` excludes network devices because they can make the retrieval hangs indefinitely. If you still want to get network devices knowing this risk, you can enable this feature. ### `Cpu` changes Information like `Cpu::brand`, `Cpu::vendor_id` or `Cpu::frequency` are not set on the "global" CPU. ## CHANGELOG If you want the full list of changes, take a look at the [CHANGELOG](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md). GuillaumeGomez-sysinfo-067dd61/src/000077500000000000000000000000001506747262600172735ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/c_interface.rs000066400000000000000000000551551506747262600221160ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Disks, Motherboard, Networks, Pid, Process, ProcessesToUpdate, Product, System}; use libc::{self, c_char, c_float, c_uint, c_void, size_t}; use std::borrow::BorrowMut; use std::ffi::CString; /// on windows, libc has not include pid_t. #[cfg(target_os = "windows")] pub type PID = usize; /// other platforms, use libc::pid_t #[cfg(not(target_os = "windows"))] pub type PID = libc::pid_t; /// Equivalent of [`System`][crate::System] struct. pub type CSystem = *mut c_void; /// Equivalent of [`Process`][crate::Process] struct. pub type CProcess = *const c_void; /// C string returned from `CString::into_raw`. pub type RString = *const c_char; /// Callback used by [`processes`][crate::System#method.processes]. pub type ProcessLoop = extern "C" fn(pid: PID, process: CProcess, data: *mut c_void) -> bool; /// Callback used by [`tasks`][crate::Process#method.tasks]. pub type ProcessPidLoop = extern "C" fn(pid: PID, data: *mut c_void) -> bool; /// Equivalent of [`Networks`][crate::Networks] struct. pub type CNetworks = *mut c_void; /// Equivalent of [`Disks`][crate::Disks] struct. pub type CDisks = *mut c_void; /// Equivalent of [`System::new()`][crate::System#method.new]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_init() -> CSystem { let system = Box::new(System::new()); Box::into_raw(system) as CSystem } /// Equivalent of `System::drop()`. Important in C to cleanup memory. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_destroy(system: CSystem) { assert!(!system.is_null()); unsafe { drop(Box::from_raw(system as *mut System)); } } /// Equivalent of [`System::refresh_memory()`][crate::System#method.refresh_memory]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_refresh_memory(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_memory(); } let _ = Box::into_raw(system); } } /// Equivalent of [`System::refresh_cpu_usage()`][crate::System#method.refresh_cpu_usage]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_refresh_cpu(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_cpu_usage(); } let _ = Box::into_raw(system); } } /// Equivalent of [`System::refresh_all()`][crate::System#method.refresh_all]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_refresh_all(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_all(); } let _ = Box::into_raw(system); } } /// Equivalent of [`System::refresh_processes(ProcessesToUpdate::All)`]. /// /// [`System::refresh_processes(ProcessesToUpdate::All)`]: crate::System#method.refresh_processes #[unsafe(no_mangle)] pub extern "C" fn sysinfo_refresh_processes(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_processes(ProcessesToUpdate::All, true); } let _ = Box::into_raw(system); } } /// Equivalent of [`System::refresh_processes(ProcessesToUpdate::Some(pid))`]. /// /// [`System::refresh_processes(ProcessesToUpdate::Some(pid))`]: crate::System#method.refresh_processes #[unsafe(no_mangle)] pub extern "C" fn sysinfo_refresh_process(system: CSystem, pid: PID) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_processes(ProcessesToUpdate::Some(&[Pid::from_u32(pid as _)]), true); } let _ = Box::into_raw(system); } } /// Equivalent of [`Disks::new()`][crate::Disks#method.new]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_disks_init() -> CDisks { let disks = Box::new(Disks::new()); Box::into_raw(disks) as CDisks } /// Equivalent of `Disks::drop()`. Important in C to cleanup memory. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_disks_destroy(disks: CDisks) { assert!(!disks.is_null()); unsafe { drop(Box::from_raw(disks as *mut Disks)); } } /// Equivalent of [`Disks::refresh()`][crate::Disks#method.refresh]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_disks_refresh(disks: CDisks) { assert!(!disks.is_null()); unsafe { let mut disks: Box = Box::from_raw(disks as *mut Disks); { let disks: &mut Disks = disks.borrow_mut(); disks.refresh(true); } let _ = Box::into_raw(disks); } } /// Equivalent of [`System::total_memory()`][crate::System#method.total_memory]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_total_memory(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.total_memory() as size_t; let _ = Box::into_raw(system); ret } } /// Equivalent of [`System::free_memory()`][crate::System#method.free_memory]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_free_memory(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.free_memory() as size_t; let _ = Box::into_raw(system); ret } } /// Equivalent of [`System::used_memory()`][crate::System#method.used_memory]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_used_memory(system: CSystem) -> size_t { assert!(!system.is_null()); let system: Box = unsafe { Box::from_raw(system as *mut System) }; let ret = system.used_memory() as size_t; let _ = Box::into_raw(system); ret } /// Equivalent of [`System::total_swap()`][crate::System#method.total_swap]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_total_swap(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.total_swap() as size_t; let _ = Box::into_raw(system); ret } } /// Equivalent of [`System::free_swap()`][crate::System#method.free_swap]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_free_swap(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.free_swap() as size_t; let _ = Box::into_raw(system); ret } } /// Equivalent of [`System::used_swap()`][crate::System#method.used_swap]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_used_swap(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.used_swap() as size_t; let _ = Box::into_raw(system); ret } } /// Equivalent of [`Networks::new()`][crate::Networks#method.new]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_networks_init() -> CNetworks { let networks = Box::new(Networks::new()); Box::into_raw(networks) as CNetworks } /// Equivalent of `Networks::drop()`. Important in C to cleanup memory. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_networks_destroy(networks: CNetworks) { assert!(!networks.is_null()); unsafe { drop(Box::from_raw(networks as *mut Networks)); } } /// Equivalent of [`Networks::refresh()`][crate::Networks#method.refresh]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_networks_refresh(networks: CNetworks) { assert!(!networks.is_null()); unsafe { let mut networks: Box = Box::from_raw(networks as *mut Networks); { let networks: &mut Networks = networks.borrow_mut(); networks.refresh(true); } let _ = Box::into_raw(networks); } } /// Equivalent of /// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.received() as size_t)`. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_networks_received(networks: CNetworks) -> size_t { assert!(!networks.is_null()); unsafe { let networks: Box = Box::from_raw(networks as *mut Networks); let ret = networks.iter().fold(0, |acc: size_t, (_, data)| { acc.saturating_add(data.received() as size_t) }); let _ = Box::into_raw(networks); ret } } /// Equivalent of /// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.transmitted() as size_t)`. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_networks_transmitted(networks: CNetworks) -> size_t { assert!(!networks.is_null()); unsafe { let networks: Box = Box::from_raw(networks as *mut Networks); let ret = networks.iter().fold(0, |acc: size_t, (_, data)| { acc.saturating_add(data.transmitted() as size_t) }); let _ = Box::into_raw(networks); ret } } /// Equivalent of [`System::cpus_usage()`][crate::System#method.cpus_usage]. /// /// * `length` will contain the number of CPU usage added into `procs`. /// * `procs` will be allocated if it's null and will contain of CPU usage. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_cpus_usage( system: CSystem, length: *mut c_uint, procs: *mut *mut c_float, ) { assert!(!system.is_null()); if procs.is_null() || length.is_null() { return; } unsafe { let system: Box = Box::from_raw(system as *mut System); { let cpus = system.cpus(); if (*procs).is_null() { (*procs) = libc::malloc(::std::mem::size_of::() * cpus.len()) as *mut c_float; } for (pos, cpu) in cpus.iter().skip(1).enumerate() { (*(*procs).offset(pos as isize)) = cpu.cpu_usage(); } *length = cpus.len() as c_uint - 1; } let _ = Box::into_raw(system); } } /// Equivalent of [`System::processes()`][crate::System#method.processes]. Returns an /// array ended by a null pointer. Must be freed. /// /// # ⚠️ WARNING ⚠️ /// /// While having this method returned processes, you should *never* call any refresh method! #[unsafe(no_mangle)] pub extern "C" fn sysinfo_processes( system: CSystem, fn_pointer: Option, data: *mut c_void, ) -> size_t { assert!(!system.is_null()); if let Some(fn_pointer) = fn_pointer { unsafe { let system: Box = Box::from_raw(system as *mut System); let len = { let entries = system.processes(); for (pid, process) in entries { if !fn_pointer(pid.0 as _, process as *const Process as CProcess, data) { break; } } entries.len() as size_t }; let _ = Box::into_raw(system); len } } else { 0 } } /// Equivalent of [`System::process()`][crate::System#method.process]. /// /// # ⚠️ WARNING ⚠️ /// /// While having this method returned process, you should *never* call any /// refresh method! #[unsafe(no_mangle)] pub extern "C" fn sysinfo_process_by_pid(system: CSystem, pid: PID) -> CProcess { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = if let Some(process) = system.process(Pid(pid as _)) { process as *const Process as CProcess } else { std::ptr::null() }; let _ = Box::into_raw(system); ret } } /// Equivalent of iterating over [`Process::tasks()`][crate::Process#method.tasks]. /// /// # ⚠️ WARNING ⚠️ /// /// While having this method processes, you should *never* call any refresh method! #[unsafe(no_mangle)] pub extern "C" fn sysinfo_process_tasks( process: CProcess, fn_pointer: Option, data: *mut c_void, ) -> size_t { assert!(!process.is_null()); if let Some(fn_pointer) = fn_pointer { unsafe { let process = process as *const Process; if let Some(tasks) = (*process).tasks() { for pid in tasks { if !fn_pointer(pid.0 as _, data) { break; } } tasks.len() as size_t } else { 0 } } } else { 0 } } /// Equivalent of [`Process::pid()`][crate::Process#method.pid]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_process_pid(process: CProcess) -> PID { assert!(!process.is_null()); let process = process as *const Process; unsafe { (*process).pid().0 as _ } } /// Equivalent of [`Process::parent()`][crate::Process#method.parent]. /// /// In case there is no known parent, it returns `0`. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_process_parent_pid(process: CProcess) -> PID { assert!(!process.is_null()); let process = process as *const Process; unsafe { (*process).parent().unwrap_or(Pid(0)).0 as _ } } /// Equivalent of [`Process::cpu_usage()`][crate::Process#method.cpu_usage]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_process_cpu_usage(process: CProcess) -> c_float { assert!(!process.is_null()); let process = process as *const Process; unsafe { (*process).cpu_usage() } } /// Equivalent of [`Process::memory()`][crate::Process#method.memory]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_process_memory(process: CProcess) -> size_t { assert!(!process.is_null()); let process = process as *const Process; unsafe { (*process).memory() as usize } } /// Equivalent of [`Process::virtual_memory()`][crate::Process#method.virtual_memory]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_process_virtual_memory(process: CProcess) -> size_t { assert!(!process.is_null()); let process = process as *const Process; unsafe { (*process).virtual_memory() as usize } } /// Equivalent of [`Process::exe()`][crate::Process#method.exe]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_process_executable_path(process: CProcess) -> RString { assert!(!process.is_null()); let process = process as *const Process; unsafe { if let Some(p) = (*process).exe().and_then(|exe| exe.to_str()) { if let Ok(c) = CString::new(p) { return c.into_raw() as _; } } std::ptr::null() } } /// Equivalent of [`Process::root()`][crate::Process#method.root]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_process_root_directory(process: CProcess) -> RString { assert!(!process.is_null()); let process = process as *const Process; unsafe { if let Some(p) = (*process).root().and_then(|root| root.to_str()) { if let Ok(c) = CString::new(p) { return c.into_raw() as _; } } std::ptr::null() } } /// Equivalent of [`Process::cwd()`][crate::Process#method.cwd]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_process_current_directory(process: CProcess) -> RString { assert!(!process.is_null()); let process = process as *const Process; unsafe { if let Some(p) = (*process).cwd().and_then(|cwd| cwd.to_str()) { if let Ok(c) = CString::new(p) { return c.into_raw() as _; } } std::ptr::null() } } /// Frees a C string created with `CString::into_raw()`. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_rstring_free(s: RString) { if !s.is_null() { unsafe { let _ = CString::from_raw(s as usize as *mut _); } } } /// Equivalent of [`cpu::vendor_id()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_cpu_vendor_id(system: CSystem) -> RString { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let c_string = if let Some(c) = system .cpus() .first() .and_then(|cpu| CString::new(cpu.vendor_id()).ok()) { c.into_raw() as RString } else { std::ptr::null() }; let _ = Box::into_raw(system); c_string } } /// Equivalent of [`cpu::brand()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_cpu_brand(system: CSystem) -> RString { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let c_string = if let Some(c) = system .cpus() .first() .and_then(|cpu| CString::new(cpu.brand()).ok()) { c.into_raw() as RString } else { std::ptr::null() }; let _ = Box::into_raw(system); c_string } } /// Equivalent of [`cpu::frequency()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_cpu_frequency(system: CSystem) -> u64 { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let freq = system .cpus() .first() .map(|cpu| cpu.frequency()) .unwrap_or(0); let _ = Box::into_raw(system); freq } } /// Equivalent of [`System::name()`][crate::System#method.name]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_system_name() -> RString { if let Some(c) = System::name().and_then(|p| CString::new(p).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`System::version()`][crate::System#method.version]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_system_version() -> RString { if let Some(c) = System::os_version().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`System::kernel_version()`][crate::System#method.kernel_version]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_system_kernel_version() -> RString { if let Some(c) = System::kernel_version().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`System::host_name()`][crate::System#method.host_name]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_system_host_name() -> RString { if let Some(c) = System::host_name().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`System::long_os_version()`][crate::System#method.long_os_version]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_system_long_version() -> RString { if let Some(c) = System::long_os_version().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`system::physical_core_count()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_cpu_physical_cores() -> u32 { System::physical_core_count().unwrap_or(0) as u32 } /// Equivalent of [`Motherboard::asset_tag()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_motherboard_asset_tag() -> RString { if let Some(c) = Motherboard::new() .and_then(|m| m.asset_tag()) .and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Motherboard::name()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_motherboard_name() -> RString { if let Some(c) = Motherboard::new() .and_then(|m| m.name()) .and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Motherboard::vendor_name()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_motherboard_vendor() -> RString { if let Some(c) = Motherboard::new() .and_then(|m| m.vendor_name()) .and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Motherboard::version()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_motherboard_version() -> RString { if let Some(c) = Motherboard::new() .and_then(|m| m.version()) .and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Motherboard::serial_number()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_motherboard_serial_number() -> RString { if let Some(c) = Motherboard::new() .and_then(|m| m.serial_number()) .and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Product::family()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_product_family() -> RString { if let Some(c) = Product::family().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Product::name()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_product_name() -> RString { if let Some(c) = Product::name().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Product::serial_number()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_product_serial_number() -> RString { if let Some(c) = Product::serial_number().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Product::stock_keeping_unit()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_product_stock_keeping_unit() -> RString { if let Some(c) = Product::stock_keeping_unit().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Product::uuid()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_product_uuid() -> RString { if let Some(c) = Product::uuid().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Product::version()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_product_version() -> RString { if let Some(c) = Product::version().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } /// Equivalent of [`Product::vendor_name()`]. #[unsafe(no_mangle)] pub extern "C" fn sysinfo_product_vendor_name() -> RString { if let Some(c) = Product::vendor_name().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() } } GuillaumeGomez-sysinfo-067dd61/src/common/000077500000000000000000000000001506747262600205635ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/common/component.rs000066400000000000000000000223701506747262600231370ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ComponentInner, ComponentsInner}; /// Interacting with components. /// /// ```no_run /// use sysinfo::Components; /// /// let components = Components::new_with_refreshed_list(); /// for component in &components { /// println!("{component:?}"); /// } /// ``` pub struct Components { pub(crate) inner: ComponentsInner, } impl Default for Components { fn default() -> Self { Self::new() } } impl From for Vec { fn from(components: Components) -> Self { components.inner.into_vec() } } impl From> for Components { fn from(components: Vec) -> Self { Self { inner: ComponentsInner::from_vec(components), } } } impl std::ops::Deref for Components { type Target = [Component]; fn deref(&self) -> &Self::Target { self.list() } } impl std::ops::DerefMut for Components { fn deref_mut(&mut self) -> &mut Self::Target { self.list_mut() } } impl<'a> IntoIterator for &'a Components { type Item = &'a Component; type IntoIter = std::slice::Iter<'a, Component>; fn into_iter(self) -> Self::IntoIter { self.list().iter() } } impl<'a> IntoIterator for &'a mut Components { type Item = &'a mut Component; type IntoIter = std::slice::IterMut<'a, Component>; fn into_iter(self) -> Self::IntoIter { self.list_mut().iter_mut() } } impl Components { /// Creates a new empty [`Components`][crate::Components] type. /// /// If you want it to be filled directly, take a look at /// [`Components::new_with_refreshed_list`]. /// /// ```no_run /// use sysinfo::Components; /// /// let mut components = Components::new(); /// components.refresh(false); /// for component in &components { /// println!("{component:?}"); /// } /// ``` pub fn new() -> Self { Self { inner: ComponentsInner::new(), } } /// Creates a new [`Components`][crate::Components] type with the components list /// loaded. /// /// ```no_run /// use sysinfo::Components; /// /// let mut components = Components::new_with_refreshed_list(); /// for component in components.list() { /// println!("{component:?}"); /// } /// ``` pub fn new_with_refreshed_list() -> Self { let mut components = Self::new(); components.refresh(true); components } /// Returns the components list. /// /// ```no_run /// use sysinfo::Components; /// /// let components = Components::new_with_refreshed_list(); /// for component in components.list() { /// println!("{component:?}"); /// } /// ``` pub fn list(&self) -> &[Component] { self.inner.list() } /// Returns the components list. /// /// ```no_run /// use sysinfo::Components; /// /// let mut components = Components::new_with_refreshed_list(); /// for component in components.list_mut() { /// component.refresh(); /// println!("{component:?}"); /// } /// ``` pub fn list_mut(&mut self) -> &mut [Component] { self.inner.list_mut() } /// Refreshes the components list. /// /// ```no_run /// use sysinfo::Components; /// /// let mut components = Components::new_with_refreshed_list(); /// // We wait some time...? /// components.refresh(false); /// ``` pub fn refresh(&mut self, remove_not_listed_components: bool) { self.inner.refresh(); if remove_not_listed_components { // Remove interfaces which are gone. self.inner.components.retain_mut(|c| { if !c.inner.updated { return false; } c.inner.updated = false; true }); } } } /// Getting a component temperature information. /// /// ```no_run /// use sysinfo::Components; /// /// let components = Components::new_with_refreshed_list(); /// for component in &components { /// if let Some(temperature) = component.temperature() { /// println!("{} {temperature}°C", component.label()); /// } else { /// println!("{} (unknown temperature)", component.label()); /// } /// } /// ``` pub struct Component { pub(crate) inner: ComponentInner, } impl Component { /// Returns the temperature of the component (in celsius degree). /// /// ## Linux /// /// Returns `f32::NAN` if it failed to retrieve it. /// /// ```no_run /// use sysinfo::Components; /// /// let components = Components::new_with_refreshed_list(); /// for component in &components { /// if let Some(temperature) = component.temperature() { /// println!("{temperature}°C"); /// } /// } /// ``` pub fn temperature(&self) -> Option { self.inner.temperature() } /// Returns the maximum temperature of the component (in celsius degree). /// /// Note: if `temperature` is higher than the current `max`, /// `max` value will be updated on refresh. /// /// ## Linux /// /// May be computed by `sysinfo` from kernel. /// Returns `f32::NAN` if it failed to retrieve it. /// /// ```no_run /// use sysinfo::Components; /// /// let components = Components::new_with_refreshed_list(); /// for component in &components { /// if let Some(max) = component.max() { /// println!("{max}°C"); /// } /// } /// ``` pub fn max(&self) -> Option { self.inner.max() } /// Returns the highest temperature before the component halts (in celsius degree). /// /// ## Linux /// /// Critical threshold defined by chip or kernel. /// /// ```no_run /// use sysinfo::Components; /// /// let components = Components::new_with_refreshed_list(); /// for component in &components { /// if let Some(critical) = component.critical() { /// println!("{critical}°C"); /// } /// } /// ``` pub fn critical(&self) -> Option { self.inner.critical() } /// Returns the label of the component. /// /// ## Linux /// /// Since components information is retrieved thanks to `hwmon`, /// the labels are generated as follows. /// Note: it may change and it was inspired by `sensors` own formatting. /// /// | name | label | device_model | id_sensor | Computed label by `sysinfo` | /// |---------|--------|------------|----------|----------------------| /// | ✓ | ✓ | ✓ | ✓ | `"{name} {label} {device_model}"` | /// | ✓ | ✓ | ✗ | ✓ | `"{name} {label}"` | /// | ✓ | ✗ | ✓ | ✓ | `"{name} {device_model}"` | /// | ✓ | ✗ | ✗ | ✓ | `"{name} temp{id}"` | /// /// ```no_run /// use sysinfo::Components; /// /// let components = Components::new_with_refreshed_list(); /// for component in &components { /// println!("{}", component.label()); /// } /// ``` pub fn label(&self) -> &str { self.inner.label() } /// Returns the identifier of the component. /// /// Note: The identifier should be reasonably unique but is provided by the kernel. /// It could change if the hardware changes or after a reboot. /// /// | OS | Computed ID by `sysinfo` | Example | /// |----|--------------------------|----------| /// | Linux/hwmon | hwmon file concatenated with the temp index. | ` hwmon0_1` if the temperature data comes from the `hwmon0/temp1_input` file. | /// | Linux/thermal | thermal file name | `thermal_zone0` | /// | FreeBSD | `cpu_` concatenated with the core index. | `cpu_1` for the first core. | /// | macOS/arm | Serial ID reported by the HID driver. | | /// | macOS/x86 | Technical ID sent to the OS (see below) | `TXCX` | /// | Windows | `Computer` (same as the label) | `Computer` | /// | appstore | Components are not available | None | /// | unknown | Components are not available | None | /// /// For macOS on X86 the following identifiers are possible: /// - `TXCX` or `TXCx` for PECI CPU (depending on if run on iMac or MacBook) /// - `TC0P` for CPU Proximity /// - `TG0P` for GPU /// - `TB0T` for Battery /// /// ```no_run /// use sysinfo::Components; /// /// let components = Components::new_with_refreshed_list(); /// for component in &components { /// if let Some(id) = component.id() { /// println!("{id}"); /// } /// } /// ``` pub fn id(&self) -> Option<&str> { self.inner.id() } /// Refreshes component. /// /// ```no_run /// use sysinfo::Components; /// /// let mut components = Components::new_with_refreshed_list(); /// for component in components.iter_mut() { /// component.refresh(); /// } /// ``` pub fn refresh(&mut self) { self.inner.refresh() } } #[cfg(test)] mod tests { use crate::*; #[test] fn test_components_mac_m1() { let mut components = Components::new(); components.refresh(false); components.refresh(false); } } GuillaumeGomez-sysinfo-067dd61/src/common/disk.rs000066400000000000000000000311601506747262600220640ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::ffi::OsStr; use std::fmt; use std::path::Path; use crate::DiskUsage; use crate::common::impl_get_set::impl_get_set; /// Struct containing a disk information. /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("{:?}: {:?}", disk.name(), disk.kind()); /// } /// ``` pub struct Disk { pub(crate) inner: crate::DiskInner, } impl Disk { /// Returns the kind of disk. /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("[{:?}] {:?}", disk.name(), disk.kind()); /// } /// ``` pub fn kind(&self) -> DiskKind { self.inner.kind() } /// Returns the disk name. /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("{:?}", disk.name()); /// } /// ``` pub fn name(&self) -> &OsStr { self.inner.name() } /// Returns the file system used on this disk (so for example: `EXT4`, `NTFS`, etc...). /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("[{:?}] {:?}", disk.name(), disk.file_system()); /// } /// ``` pub fn file_system(&self) -> &OsStr { self.inner.file_system() } /// Returns the mount point of the disk (`/` for example). /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("[{:?}] {:?}", disk.name(), disk.mount_point()); /// } /// ``` pub fn mount_point(&self) -> &Path { self.inner.mount_point() } /// Returns the total disk size, in bytes. /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("[{:?}] {}B", disk.name(), disk.total_space()); /// } /// ``` pub fn total_space(&self) -> u64 { self.inner.total_space() } /// Returns the available disk size, in bytes. /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("[{:?}] {}B", disk.name(), disk.available_space()); /// } /// ``` pub fn available_space(&self) -> u64 { self.inner.available_space() } /// Returns `true` if the disk is removable. /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("[{:?}] {}", disk.name(), disk.is_removable()); /// } /// ``` pub fn is_removable(&self) -> bool { self.inner.is_removable() } /// Returns `true` if the disk is read-only. /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("[{:?}] is read-only: {}", disk.name(), disk.is_read_only()); /// } /// ``` pub fn is_read_only(&self) -> bool { self.inner.is_read_only() } /// Updates the disk' information with everything loaded. /// /// Equivalent to [Disk::refresh_specifics]\([DiskRefreshKind::everything]\()). /// /// ```no_run /// use sysinfo::Disks; /// /// let mut disks = Disks::new_with_refreshed_list(); /// for disk in disks.list_mut() { /// disk.refresh(); /// } /// ``` pub fn refresh(&mut self) -> bool { self.refresh_specifics(DiskRefreshKind::everything()) } /// Updates the disk's information corresponding to the given [`DiskRefreshKind`]. /// /// ```no_run /// use sysinfo::{Disks, DiskRefreshKind}; /// /// let mut disks = Disks::new_with_refreshed_list(); /// for disk in disks.list_mut() { /// disk.refresh_specifics(DiskRefreshKind::nothing()); /// } /// ``` pub fn refresh_specifics(&mut self, refreshes: DiskRefreshKind) -> bool { self.inner.refresh_specifics(refreshes) } /// Returns number of bytes read and written by the disk /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("[{:?}] disk usage: {:?}", disk.name(), disk.usage()); /// } /// ``` pub fn usage(&self) -> DiskUsage { self.inner.usage() } } /// Disks interface. /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("{disk:?}"); /// } /// ``` /// /// ⚠️ Note that tmpfs mounts are excluded by default under Linux. /// To display tmpfs mount points, the `linux-tmpfs` feature must be enabled. /// /// ⚠️ Note that network devices are excluded by default under Linux. /// To display mount points using the CIFS and NFS protocols, the `linux-netdevs` /// feature must be enabled. Note, however, that sysinfo may hang under certain /// circumstances. For example, if a CIFS or NFS share has been mounted with /// the _hard_ option, but the connection has an error, such as the share server has stopped. pub struct Disks { inner: crate::DisksInner, } impl Default for Disks { fn default() -> Self { Self::new() } } impl From for Vec { fn from(disks: Disks) -> Vec { disks.inner.into_vec() } } impl From> for Disks { fn from(disks: Vec) -> Self { Self { inner: crate::DisksInner::from_vec(disks), } } } impl<'a> IntoIterator for &'a Disks { type Item = &'a Disk; type IntoIter = std::slice::Iter<'a, Disk>; fn into_iter(self) -> Self::IntoIter { self.list().iter() } } impl<'a> IntoIterator for &'a mut Disks { type Item = &'a mut Disk; type IntoIter = std::slice::IterMut<'a, Disk>; fn into_iter(self) -> Self::IntoIter { self.list_mut().iter_mut() } } impl Disks { /// Creates a new empty [`Disks`][crate::Disks] type. /// /// If you want it to be filled directly, take a look at [`Disks::new_with_refreshed_list`]. /// /// ```no_run /// use sysinfo::Disks; /// /// let mut disks = Disks::new(); /// disks.refresh(false); /// for disk in disks.list() { /// println!("{disk:?}"); /// } /// ``` pub fn new() -> Self { Self { inner: crate::DisksInner::new(), } } /// Creates a new [`Disks`][crate::Disks] type with the disk list loaded. /// /// Equivalent to [Disks::new_with_refreshed_list_specifics]\([DiskRefreshKind::everything]\()). /// /// ```no_run /// use sysinfo::Disks; /// /// let mut disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("{disk:?}"); /// } /// ``` pub fn new_with_refreshed_list() -> Self { Self::new_with_refreshed_list_specifics(DiskRefreshKind::everything()) } /// Creates a new [`Disks`][crate::Disks] type with the disk list loaded /// and refreshed according to the given [`DiskRefreshKind`]. /// /// ```no_run /// use sysinfo::{Disks, DiskRefreshKind}; /// /// let mut disks = Disks::new_with_refreshed_list_specifics(DiskRefreshKind::nothing()); /// for disk in disks.list() { /// println!("{disk:?}"); /// } /// ``` pub fn new_with_refreshed_list_specifics(refreshes: DiskRefreshKind) -> Self { let mut disks = Self::new(); disks.refresh_specifics(false, refreshes); disks } /// Returns the disks list. /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("{disk:?}"); /// } /// ``` pub fn list(&self) -> &[Disk] { self.inner.list() } /// Returns the disks list. /// /// ```no_run /// use sysinfo::Disks; /// /// let mut disks = Disks::new_with_refreshed_list(); /// for disk in disks.list_mut() { /// disk.refresh(); /// println!("{disk:?}"); /// } /// ``` pub fn list_mut(&mut self) -> &mut [Disk] { self.inner.list_mut() } /// Refreshes the listed disks' information. /// /// Equivalent to [Disks::refresh_specifics]\([DiskRefreshKind::everything]\()). pub fn refresh(&mut self, remove_not_listed_disks: bool) { self.inner .refresh_specifics(remove_not_listed_disks, DiskRefreshKind::everything()); } /// Refreshes the disks' information according to the given [`DiskRefreshKind`]. /// /// ```no_run /// use sysinfo::Disks; /// /// let mut disks = Disks::new_with_refreshed_list(); /// // We wait some time...? /// disks.refresh(true); /// ``` pub fn refresh_specifics(&mut self, remove_not_listed_disks: bool, refreshes: DiskRefreshKind) { self.inner .refresh_specifics(remove_not_listed_disks, refreshes); } } impl std::ops::Deref for Disks { type Target = [Disk]; fn deref(&self) -> &Self::Target { self.list() } } impl std::ops::DerefMut for Disks { fn deref_mut(&mut self) -> &mut Self::Target { self.list_mut() } } /// Enum containing the different supported kinds of disks. /// /// This type is returned by [`Disk::kind`](`crate::Disk::kind`). /// /// ```no_run /// use sysinfo::Disks; /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("{:?}: {:?}", disk.name(), disk.kind()); /// } /// ``` #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub enum DiskKind { /// HDD type. HDD, /// SSD type. SSD, /// Unknown type. Unknown(isize), } impl fmt::Display for DiskKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { DiskKind::HDD => "HDD", DiskKind::SSD => "SSD", _ => "Unknown", }) } } /// Used to determine what you want to refresh specifically on the [`Disk`] type. /// /// * `kind` is about refreshing the [`Disk::kind`] information. /// * `storage` is about refreshing the [`Disk::available_space`] and [`Disk::total_space`] information. /// * `io_usage` is about refreshing the [`Disk::usage`] information. /// /// ```no_run /// use sysinfo::{Disks, DiskRefreshKind}; /// /// let mut disks = Disks::new_with_refreshed_list_specifics(DiskRefreshKind::everything()); /// /// for disk in disks.list() { /// assert!(disk.total_space() != 0); /// } /// ``` #[derive(Clone, Copy, Debug, Default)] pub struct DiskRefreshKind { kind: bool, storage: bool, io_usage: bool, } impl DiskRefreshKind { /// Creates a new `DiskRefreshKind` with every refresh set to false. /// /// ``` /// use sysinfo::DiskRefreshKind; /// /// let r = DiskRefreshKind::nothing(); /// /// assert_eq!(r.kind(), false); /// assert_eq!(r.storage(), false); /// assert_eq!(r.io_usage(), false); /// ``` pub fn nothing() -> Self { Self::default() } /// Creates a new `DiskRefreshKind` with every refresh set to true. /// /// ``` /// use sysinfo::DiskRefreshKind; /// /// let r = DiskRefreshKind::everything(); /// /// assert_eq!(r.kind(), true); /// assert_eq!(r.storage(), true); /// assert_eq!(r.io_usage(), true); /// ``` pub fn everything() -> Self { Self { kind: true, storage: true, io_usage: true, } } impl_get_set!(DiskRefreshKind, kind, with_kind, without_kind); impl_get_set!(DiskRefreshKind, storage, with_storage, without_storage); impl_get_set!(DiskRefreshKind, io_usage, with_io_usage, without_io_usage); } #[cfg(test)] mod tests { /// This first doctest ensure that we can create a new `Disks`. /// /// ``` /// let x = sysinfo::Disks::new(); /// ``` /// /// This second doctest ensures that `Disks` doesn't implement `Clone`. /// /// ```compile_fail /// let x = sysinfo::Disks::new(); /// x.clone(); /// ``` #[test] fn check_if_disks_is_send() { fn is_send(_: &T) {} let disks = crate::Disks::new(); is_send(&disks); } } GuillaumeGomez-sysinfo-067dd61/src/common/impl_get_set.rs000066400000000000000000000122771506747262600236150ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. macro_rules! impl_get_set { ($ty_name:ident, $name:ident, $with:ident, $without:ident $(, $extra_doc:literal)? $(,)?) => { #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.")] $(#[doc = concat!(" ", $extra_doc, " ")])? #[doc = concat!(" ``` use sysinfo::", stringify!($ty_name), "; let r = ", stringify!($ty_name), "::nothing(); let r = r.with_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "(), true); let r = r.without_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "(), false); ```")] pub fn $name(&self) -> bool { self.$name } #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `true`. ``` use sysinfo::", stringify!($ty_name), "; let r = ", stringify!($ty_name), "::nothing(); let r = r.with_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "(), true); ```")] #[must_use] pub fn $with(mut self) -> Self { self.$name = true; self } #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `false`. ``` use sysinfo::", stringify!($ty_name), "; let r = ", stringify!($ty_name), "::everything(); assert_eq!(r.", stringify!($name), "(), true); let r = r.without_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "(), false); ```")] #[must_use] pub fn $without(mut self) -> Self { self.$name = false; self } }; // To handle `UpdateKind`. ($ty_name:ident, $name:ident, $with:ident, $without:ident, UpdateKind $(, $extra_doc:literal)? $(,)?) => { #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.")] $(#[doc = concat!(" ", $extra_doc, " ")])? #[doc = concat!(" ``` use sysinfo::{", stringify!($ty_name), ", UpdateKind}; let r = ", stringify!($ty_name), "::nothing(); assert_eq!(r.", stringify!($name), "(), UpdateKind::Never); let r = r.with_", stringify!($name), "(UpdateKind::OnlyIfNotSet); assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet); let r = r.without_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "(), UpdateKind::Never); ```")] pub fn $name(&self) -> UpdateKind { self.$name } #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind. ``` use sysinfo::{", stringify!($ty_name), ", UpdateKind}; let r = ", stringify!($ty_name), "::nothing(); assert_eq!(r.", stringify!($name), "(), UpdateKind::Never); let r = r.with_", stringify!($name), "(UpdateKind::OnlyIfNotSet); assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet); ```")] #[must_use] pub fn $with(mut self, kind: UpdateKind) -> Self { self.$name = kind; self } #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `UpdateKind::Never`. ``` use sysinfo::{", stringify!($ty_name), ", UpdateKind}; let r = ", stringify!($ty_name), "::everything(); assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet); let r = r.without_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "(), UpdateKind::Never); ```")] #[must_use] pub fn $without(mut self) -> Self { self.$name = UpdateKind::Never; self } }; // To handle `*RefreshKind`. ($ty_name:ident, $name:ident, $with:ident, $without:ident, $typ:ty $(,)?) => { #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind. ``` use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "}; let r = ", stringify!($ty_name), "::nothing(); assert_eq!(r.", stringify!($name), "().is_some(), false); let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything()); assert_eq!(r.", stringify!($name), "().is_some(), true); let r = r.without_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "().is_some(), false); ```")] pub fn $name(&self) -> Option<$typ> { self.$name } #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `Some(...)`. ``` use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "}; let r = ", stringify!($ty_name), "::nothing(); assert_eq!(r.", stringify!($name), "().is_some(), false); let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything()); assert_eq!(r.", stringify!($name), "().is_some(), true); ```")] #[must_use] pub fn $with(mut self, kind: $typ) -> Self { self.$name = Some(kind); self } #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `None`. ``` use sysinfo::", stringify!($ty_name), "; let r = ", stringify!($ty_name), "::everything(); assert_eq!(r.", stringify!($name), "().is_some(), true); let r = r.without_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "().is_some(), false); ```")] #[must_use] pub fn $without(mut self) -> Self { self.$name = None; self } }; } pub(crate) use impl_get_set; GuillaumeGomez-sysinfo-067dd61/src/common/mod.rs000066400000000000000000000102361506747262600217120ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "component")] pub(crate) mod component; #[cfg(feature = "disk")] pub(crate) mod disk; #[cfg(any(feature = "system", feature = "disk"))] pub(crate) mod impl_get_set; #[cfg(feature = "network")] pub(crate) mod network; #[cfg(feature = "system")] pub(crate) mod system; #[cfg(feature = "user")] pub(crate) mod user; /// Type containing read and written bytes. /// /// It is returned by [`Process::disk_usage`][crate::Process::disk_usage] and [`Disk::usage`][crate::Disk::usage]. /// #[cfg_attr(not(all(feature = "system", feature = "disk")), doc = "```ignore")] /// ```no_run /// use sysinfo::{Disks, System}; /// /// let s = System::new_all(); /// for (pid, process) in s.processes() { /// let disk_usage = process.disk_usage(); /// println!("[{}] read bytes : new/total => {}/{} B", /// pid, /// disk_usage.read_bytes, /// disk_usage.total_read_bytes, /// ); /// println!("[{}] written bytes: new/total => {}/{} B", /// pid, /// disk_usage.written_bytes, /// disk_usage.total_written_bytes, /// ); /// } /// /// let disks = Disks::new_with_refreshed_list(); /// for disk in disks.list() { /// println!("[{:?}] disk usage: {:?}", disk.name(), disk.usage()); /// } /// ``` #[cfg(any(feature = "disk", feature = "system"))] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd)] pub struct DiskUsage { /// Total number of written bytes. pub total_written_bytes: u64, /// Number of written bytes since the last refresh. pub written_bytes: u64, /// Total number of read bytes. pub total_read_bytes: u64, /// Number of read bytes since the last refresh. pub read_bytes: u64, } macro_rules! xid { ($(#[$outer:meta])+ $name:ident, $type:ty $(, $trait:ty)?) => { #[cfg(any(feature = "system", feature = "user"))] $(#[$outer])+ #[repr(transparent)] #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct $name(pub(crate) $type); #[cfg(any(feature = "system", feature = "user"))] impl std::ops::Deref for $name { type Target = $type; fn deref(&self) -> &Self::Target { &self.0 } } $( #[cfg(any(feature = "system", feature = "user"))] impl TryFrom for $name { type Error = <$type as TryFrom>::Error; fn try_from(t: usize) -> Result>::Error> { Ok(Self(<$type>::try_from(t)?)) } } #[cfg(any(feature = "system", feature = "user"))] impl $trait for $name { type Err = <$type as $trait>::Err; fn from_str(t: &str) -> Result::Err> { Ok(Self(<$type>::from_str(t)?)) } } )? }; } macro_rules! uid { ($type:ty$(, $trait:ty)?) => { xid!( /// A user id wrapping a platform specific type. Uid, $type $(, $trait)? ); }; } macro_rules! gid { ($type:ty) => { xid!( /// A group id wrapping a platform specific type. #[derive(Copy)] Gid, $type, std::str::FromStr ); }; } cfg_if! { if #[cfg(all( not(feature = "unknown-ci"), any( target_os = "freebsd", target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios", ) ))] { uid!(libc::uid_t, std::str::FromStr); gid!(libc::gid_t); } else if #[cfg(windows)] { uid!(crate::windows::Sid); gid!(u32); // Manual implementation outside of the macro... #[cfg(any(feature = "system", feature = "user"))] impl std::str::FromStr for Uid { type Err = ::Err; fn from_str(t: &str) -> Result { Ok(Self(t.parse()?)) } } } else { uid!(u32, std::str::FromStr); gid!(u32); } } GuillaumeGomez-sysinfo-067dd61/src/common/network.rs000066400000000000000000000503111506747262600226220ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::HashMap; use std::fmt; use std::net::{AddrParseError, IpAddr}; use std::num::ParseIntError; use std::str::FromStr; use crate::{NetworkDataInner, NetworksInner}; /// Interacting with network interfaces. /// /// ```no_run /// use sysinfo::Networks; /// /// let networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("[{interface_name}]: {network:?}"); /// } /// ``` pub struct Networks { pub(crate) inner: NetworksInner, } impl<'a> IntoIterator for &'a Networks { type Item = (&'a String, &'a NetworkData); type IntoIter = std::collections::hash_map::Iter<'a, String, NetworkData>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl Default for Networks { fn default() -> Self { Networks::new() } } impl Networks { /// Creates a new empty [`Networks`][crate::Networks] type. /// /// If you want it to be filled directly, take a look at [`Networks::new_with_refreshed_list`]. /// /// ```no_run /// use sysinfo::Networks; /// /// let mut networks = Networks::new(); /// networks.refresh(true); /// for (interface_name, network) in &networks { /// println!("[{interface_name}]: {network:?}"); /// } /// ``` pub fn new() -> Self { Self { inner: NetworksInner::new(), } } /// Creates a new [`Networks`][crate::Networks] type with the network interfaces /// list loaded. /// /// ```no_run /// use sysinfo::Networks; /// /// let networks = Networks::new_with_refreshed_list(); /// for network in &networks { /// println!("{network:?}"); /// } /// ``` pub fn new_with_refreshed_list() -> Self { let mut networks = Self::new(); networks.refresh(false); networks } /// Returns the network interfaces map. /// /// ```no_run /// use sysinfo::Networks; /// /// let networks = Networks::new_with_refreshed_list(); /// for network in networks.list() { /// println!("{network:?}"); /// } /// ``` pub fn list(&self) -> &HashMap { self.inner.list() } /// Refreshes the network interfaces. /// /// ```no_run /// use sysinfo::Networks; /// /// let mut networks = Networks::new_with_refreshed_list(); /// // Wait some time...? Then refresh the data of each network. /// networks.refresh(true); /// ``` pub fn refresh(&mut self, remove_not_listed_interfaces: bool) { self.inner.refresh(remove_not_listed_interfaces) } } impl std::ops::Deref for Networks { type Target = HashMap; fn deref(&self) -> &Self::Target { self.list() } } /// Getting volume of received and transmitted data. /// /// ```no_run /// use sysinfo::Networks; /// /// let networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("[{interface_name}] {network:?}"); /// } /// ``` pub struct NetworkData { pub(crate) inner: NetworkDataInner, } impl NetworkData { /// Returns the number of received bytes since the last refresh. /// /// If you want the total number of bytes received, take a look at the /// [`total_received`](NetworkData::total_received) method. /// /// ```no_run /// use sysinfo::Networks; /// use std::{thread, time}; /// /// let mut networks = Networks::new_with_refreshed_list(); /// // Waiting a bit to get data from network... /// thread::sleep(time::Duration::from_millis(10)); /// // Refreshing again to generate diff. /// networks.refresh(true); /// /// for (interface_name, network) in &networks { /// println!("in: {} B", network.received()); /// } /// ``` pub fn received(&self) -> u64 { self.inner.received() } /// Returns the total number of received bytes. /// /// If you want the amount of received bytes since the last refresh, take a look at the /// [`received`](NetworkData::received) method. /// /// ```no_run /// use sysinfo::Networks; /// /// let networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("in: {} B", network.total_received()); /// } /// ``` pub fn total_received(&self) -> u64 { self.inner.total_received() } /// Returns the number of transmitted bytes since the last refresh. /// /// If you want the total number of bytes transmitted, take a look at the /// [`total_transmitted`](NetworkData::total_transmitted) method. /// /// ```no_run /// use sysinfo::Networks; /// use std::{thread, time}; /// /// let mut networks = Networks::new_with_refreshed_list(); /// // Waiting a bit to get data from network... /// thread::sleep(time::Duration::from_millis(10)); /// // Refreshing again to generate diff. /// networks.refresh(true); /// /// for (interface_name, network) in &networks { /// println!("out: {} B", network.transmitted()); /// } /// ``` pub fn transmitted(&self) -> u64 { self.inner.transmitted() } /// Returns the total number of transmitted bytes. /// /// If you want the amount of transmitted bytes since the last refresh, take a look at the /// [`transmitted`](NetworkData::transmitted) method. /// /// ```no_run /// use sysinfo::Networks; /// /// let networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("out: {} B", network.total_transmitted()); /// } /// ``` pub fn total_transmitted(&self) -> u64 { self.inner.total_transmitted() } /// Returns the number of incoming packets since the last refresh. /// /// If you want the total number of packets received, take a look at the /// [`total_packets_received`](NetworkData::total_packets_received) method. /// /// ```no_run /// use sysinfo::Networks; /// use std::{thread, time}; /// /// let mut networks = Networks::new_with_refreshed_list(); /// // Waiting a bit to get data from network... /// thread::sleep(time::Duration::from_millis(10)); /// // Refreshing again to generate diff. /// networks.refresh(true); /// /// for (interface_name, network) in &networks { /// println!("in: {}", network.packets_received()); /// } /// ``` pub fn packets_received(&self) -> u64 { self.inner.packets_received() } /// Returns the total number of incoming packets. /// /// If you want the amount of received packets since the last refresh, take a look at the /// [`packets_received`](NetworkData::packets_received) method. /// /// ```no_run /// use sysinfo::Networks; /// /// let networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("in: {}", network.total_packets_received()); /// } /// ``` pub fn total_packets_received(&self) -> u64 { self.inner.total_packets_received() } /// Returns the number of outcoming packets since the last refresh. /// /// If you want the total number of packets transmitted, take a look at the /// [`total_packets_transmitted`](NetworkData::total_packets_transmitted) method. /// /// ```no_run /// use sysinfo::Networks; /// use std::{thread, time}; /// /// let mut networks = Networks::new_with_refreshed_list(); /// // Waiting a bit to get data from network... /// thread::sleep(time::Duration::from_millis(10)); /// // Refreshing again to generate diff. /// networks.refresh(true); /// /// for (interface_name, network) in &networks { /// println!("out: {}", network.packets_transmitted()); /// } /// ``` pub fn packets_transmitted(&self) -> u64 { self.inner.packets_transmitted() } /// Returns the total number of outcoming packets. /// /// If you want the amount of transmitted packets since the last refresh, take a look at the /// [`packets_transmitted`](NetworkData::packets_transmitted) method. /// /// ```no_run /// use sysinfo::Networks; /// /// let networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("out: {}", network.total_packets_transmitted()); /// } /// ``` pub fn total_packets_transmitted(&self) -> u64 { self.inner.total_packets_transmitted() } /// Returns the number of incoming errors since the last refresh. /// /// If you want the total number of errors on received packets, take a look at the /// [`total_errors_on_received`](NetworkData::total_errors_on_received) method. /// /// ```no_run /// use sysinfo::Networks; /// use std::{thread, time}; /// /// let mut networks = Networks::new_with_refreshed_list(); /// // Waiting a bit to get data from network... /// thread::sleep(time::Duration::from_millis(10)); /// // Refreshing again to generate diff. /// networks.refresh(true); /// /// for (interface_name, network) in &networks { /// println!("in: {}", network.errors_on_received()); /// } /// ``` pub fn errors_on_received(&self) -> u64 { self.inner.errors_on_received() } /// Returns the total number of incoming errors. /// /// If you want the amount of errors on received packets since the last refresh, take a look at /// the [`errors_on_received`](NetworkData::errors_on_received) method. /// /// ```no_run /// use sysinfo::Networks; /// /// let networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("in: {}", network.total_errors_on_received()); /// } /// ``` pub fn total_errors_on_received(&self) -> u64 { self.inner.total_errors_on_received() } /// Returns the number of outcoming errors since the last refresh. /// /// If you want the total number of errors on transmitted packets, take a look at the /// [`total_errors_on_transmitted`](NetworkData::total_errors_on_transmitted) method. /// /// ```no_run /// use sysinfo::Networks; /// use std::{thread, time}; /// /// let mut networks = Networks::new_with_refreshed_list(); /// // Waiting a bit to get data from network... /// thread::sleep(time::Duration::from_millis(10)); /// // Refreshing again to generate diff. /// networks.refresh(true); /// /// for (interface_name, network) in &networks { /// println!("out: {}", network.errors_on_transmitted()); /// } /// ``` pub fn errors_on_transmitted(&self) -> u64 { self.inner.errors_on_transmitted() } /// Returns the total number of outcoming errors. /// /// If you want the amount of errors on transmitted packets since the last refresh, take a look at /// the [`errors_on_transmitted`](NetworkData::errors_on_transmitted) method. /// /// ```no_run /// use sysinfo::Networks; /// /// let networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("out: {}", network.total_errors_on_transmitted()); /// } /// ``` pub fn total_errors_on_transmitted(&self) -> u64 { self.inner.total_errors_on_transmitted() } /// Returns the MAC address associated to current interface. /// /// ```no_run /// use sysinfo::Networks; /// /// let mut networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("MAC address: {}", network.mac_address()); /// } /// ``` pub fn mac_address(&self) -> MacAddr { self.inner.mac_address() } /// Returns the Ip Networks associated to current interface. /// /// ```no_run /// use sysinfo::Networks; /// /// let mut networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("Ip Networks: {:?}", network.ip_networks()); /// } /// ``` pub fn ip_networks(&self) -> &[IpNetwork] { self.inner.ip_networks() } /// Returns the Maximum Transfer Unit (MTU) of the interface. /// /// ```no_run /// use sysinfo::Networks; /// /// let mut networks = Networks::new_with_refreshed_list(); /// for (interface_name, network) in &networks { /// println!("mtu: {}", network.mtu()); /// } /// ``` pub fn mtu(&self) -> u64 { self.inner.mtu() } } /// MAC address for network interface. /// /// It is returned by [`NetworkData::mac_address`][crate::NetworkData::mac_address]. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub struct MacAddr(pub [u8; 6]); impl MacAddr { /// A `MacAddr` with all bytes set to `0`. pub const UNSPECIFIED: Self = MacAddr([0; 6]); /// Checks if this `MacAddr` has all bytes equal to `0`. pub fn is_unspecified(&self) -> bool { self == &MacAddr::UNSPECIFIED } } impl fmt::Display for MacAddr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let data = &self.0; write!( f, "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", data[0], data[1], data[2], data[3], data[4], data[5], ) } } /// Error type returned from `MacAddr::from_str` implementation. #[derive(Debug, Clone, PartialEq, Eq)] pub enum MacAddrFromStrError { /// A number is not in hexadecimal format. IntError(ParseIntError), /// Input is not of format `{02X}:{02X}:{02X}:{02X}:{02X}:{02X}`. InvalidAddrFormat, } impl FromStr for MacAddr { type Err = MacAddrFromStrError; fn from_str(s: &str) -> Result { let mut parts = s .split(':') .map(|s| u8::from_str_radix(s, 16).map_err(MacAddrFromStrError::IntError)); let Some(data0) = parts.next() else { return Err(MacAddrFromStrError::InvalidAddrFormat); }; let Some(data1) = parts.next() else { return Err(MacAddrFromStrError::InvalidAddrFormat); }; let Some(data2) = parts.next() else { return Err(MacAddrFromStrError::InvalidAddrFormat); }; let Some(data3) = parts.next() else { return Err(MacAddrFromStrError::InvalidAddrFormat); }; let Some(data4) = parts.next() else { return Err(MacAddrFromStrError::InvalidAddrFormat); }; let Some(data5) = parts.next() else { return Err(MacAddrFromStrError::InvalidAddrFormat); }; if parts.next().is_some() { return Err(MacAddrFromStrError::InvalidAddrFormat); } Ok(MacAddr([data0?, data1?, data2?, data3?, data4?, data5?])) } } /// IP networks address for network interface. /// /// It is returned by [`NetworkData::ip_networks`][crate::NetworkData::ip_networks]. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub struct IpNetwork { /// The IP of the network interface. pub addr: IpAddr, /// The netmask, prefix of the IP address. pub prefix: u8, } impl fmt::Display for IpNetwork { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}/{}", self.addr, self.prefix) } } /// Error type returned from `MacAddr::from_str` implementation. #[derive(Debug, Clone, PartialEq, Eq)] pub enum IpNetworkFromStrError { /// Prefix is not an integer. PrefixError(ParseIntError), /// Failed to parse IP address. AddrParseError(AddrParseError), /// Input is not of format `[IP address]/[number]`. InvalidAddrFormat, } impl FromStr for IpNetwork { type Err = IpNetworkFromStrError; #[allow(clippy::from_str_radix_10)] fn from_str(s: &str) -> Result { let mut parts = s.split('/'); let Some(addr) = parts.next() else { return Err(IpNetworkFromStrError::InvalidAddrFormat); }; let Some(prefix) = parts.next() else { return Err(IpNetworkFromStrError::InvalidAddrFormat); }; if parts.next().is_some() { return Err(IpNetworkFromStrError::InvalidAddrFormat); } Ok(IpNetwork { addr: IpAddr::from_str(addr).map_err(IpNetworkFromStrError::AddrParseError)?, prefix: u8::from_str_radix(prefix, 10).map_err(IpNetworkFromStrError::PrefixError)?, }) } } #[cfg(test)] mod tests { use crate::*; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::str::FromStr; // Ensure that the `Display` and `Debug` traits are implemented on the `MacAddr` struct #[test] fn check_display_impl_mac_address() { println!( "{} {:?}", MacAddr([0x1, 0x2, 0x3, 0x4, 0x5, 0x6]), MacAddr([0xa, 0xb, 0xc, 0xd, 0xe, 0xf]) ); } #[test] fn check_mac_address_is_unspecified_true() { assert!(MacAddr::UNSPECIFIED.is_unspecified()); assert!(MacAddr([0; 6]).is_unspecified()); } #[test] fn check_mac_address_is_unspecified_false() { assert!(!MacAddr([1, 2, 3, 4, 5, 6]).is_unspecified()); } #[test] fn check_mac_address_conversions() { let mac = MacAddr([0xa, 0xb, 0xc, 0xd, 0xe, 0xf]); let mac_s = mac.to_string(); assert_eq!("0a:0b:0c:0d:0e:0f", mac_s); assert_eq!(Ok(mac), MacAddr::from_str(&mac_s)); assert_eq!( MacAddr::from_str("0a:0b:0c:0d:0e:0f:01"), Err(MacAddrFromStrError::InvalidAddrFormat) ); assert_eq!( MacAddr::from_str("0a:0b:0c:0d:0e"), Err(MacAddrFromStrError::InvalidAddrFormat) ); } // Ensure that the `Display` and `Debug` traits are implemented on the `IpNetwork` struct #[test] fn check_display_impl_ip_network_ipv4() { println!( "{} {:?}", IpNetwork { addr: IpAddr::from(Ipv4Addr::new(1, 2, 3, 4)), prefix: 3 }, IpNetwork { addr: IpAddr::from(Ipv4Addr::new(255, 255, 255, 0)), prefix: 21 } ); } #[test] fn check_display_impl_ip_network_ipv6() { println!( "{} {:?}", IpNetwork { addr: IpAddr::from(Ipv6Addr::new(0xffff, 0xaabb, 00, 0, 0, 0x000c, 11, 21)), prefix: 127 }, IpNetwork { addr: IpAddr::from(Ipv6Addr::new(0xffcc, 0, 0, 0xffcc, 0, 0xffff, 0, 0xccaa)), prefix: 120 } ) } #[test] fn check_ip_networks() { if !IS_SUPPORTED_SYSTEM { return; } let networks = Networks::new_with_refreshed_list(); if networks.iter().any(|(_, n)| !n.ip_networks().is_empty()) { return; } panic!("Networks should have at least one IP network "); } #[test] fn check_ip_network_conversions() { let addr = IpNetwork { addr: IpAddr::from(Ipv6Addr::new(0xff, 0xa, 0x8, 0x12, 0x7, 0xc, 0xa, 0xb)), prefix: 12, }; let addr_s = addr.to_string(); assert_eq!("ff:a:8:12:7:c:a:b/12", addr_s); assert_eq!(Ok(addr), IpNetwork::from_str(&addr_s)); let addr = IpNetwork { addr: IpAddr::from(Ipv4Addr::new(255, 255, 255, 0)), prefix: 21, }; let addr_s = addr.to_string(); assert_eq!("255.255.255.0/21", addr_s); assert_eq!(Ok(addr), IpNetwork::from_str(&addr_s)); assert_eq!( IpNetwork::from_str("ff:a:8:12:7:c:a:b"), Err(IpNetworkFromStrError::InvalidAddrFormat) ); assert_eq!( IpNetwork::from_str("ff:a:8:12:7:c:a:b/12/12"), Err(IpNetworkFromStrError::InvalidAddrFormat) ); match IpNetwork::from_str("0a:0b:0c:0d:0e/12") { Err(IpNetworkFromStrError::AddrParseError(_)) => {} x => panic!("expected `IpNetworkFromStrError::AddrParseError`, found {x:?}"), } } } GuillaumeGomez-sysinfo-067dd61/src/common/system.rs000066400000000000000000003023151506747262600224610ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::{HashMap, HashSet}; use std::ffi::{OsStr, OsString}; use std::fmt; use std::path::Path; use std::process::ExitStatus; use std::str::FromStr; use crate::common::DiskUsage; use crate::common::impl_get_set::impl_get_set; use crate::{CpuInner, Gid, MotherboardInner, ProcessInner, ProductInner, SystemInner, Uid}; /// Type containing system's information such as processes, memory and CPU. /// /// ⚠️ On newer Android versions, there are restrictions on which system information /// a non-system application has access to. So CPU information might not be available. /// /// ``` /// use sysinfo::System; /// /// if sysinfo::IS_SUPPORTED_SYSTEM { /// println!("System: {:?}", System::new_all()); /// } else { /// println!("This OS isn't supported (yet?)."); /// } /// ``` pub struct System { pub(crate) inner: SystemInner, } impl Default for System { fn default() -> System { System::new() } } impl System { /// Creates a new [`System`] instance with nothing loaded. /// /// Use one of the refresh methods (like [`refresh_all`]) to update its internal information. /// /// [`System`]: crate::System /// [`refresh_all`]: #method.refresh_all /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new(); /// ``` pub fn new() -> Self { Self::new_with_specifics(RefreshKind::nothing()) } /// Creates a new [`System`] instance with everything loaded. /// /// It is an equivalent of [`System::new_with_specifics`]`(`[`RefreshKind::everything`]`())`. /// /// [`System`]: crate::System /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// ``` pub fn new_all() -> Self { Self::new_with_specifics(RefreshKind::everything()) } /// Creates a new [`System`] instance and refresh the data corresponding to the /// given [`RefreshKind`]. /// /// [`System`]: crate::System /// /// ``` /// use sysinfo::{ProcessRefreshKind, RefreshKind, System}; /// /// // We want to only refresh processes. /// let mut system = System::new_with_specifics( /// RefreshKind::nothing().with_processes(ProcessRefreshKind::everything()), /// ); /// /// # if sysinfo::IS_SUPPORTED_SYSTEM && !cfg!(feature = "apple-sandbox") { /// assert!(!system.processes().is_empty()); /// # } /// ``` pub fn new_with_specifics(refreshes: RefreshKind) -> Self { let mut s = Self { inner: SystemInner::new(), }; s.refresh_specifics(refreshes); s } /// Refreshes according to the given [`RefreshKind`]. It calls the corresponding /// "refresh_" methods. /// /// It will remove dead processes if [`RefreshKind::processes`] returns `Some`. /// If you want to keep dead processes, use [`System::refresh_processes_specifics`] /// directly. /// /// ``` /// use sysinfo::{ProcessRefreshKind, RefreshKind, System}; /// /// let mut s = System::new_all(); /// /// // Let's just update processes: /// s.refresh_specifics( /// RefreshKind::nothing().with_processes(ProcessRefreshKind::everything()), /// ); /// ``` pub fn refresh_specifics(&mut self, refreshes: RefreshKind) { if let Some(kind) = refreshes.memory() { self.refresh_memory_specifics(kind); } if let Some(kind) = refreshes.cpu() { self.refresh_cpu_specifics(kind); } if let Some(kind) = refreshes.processes() { self.refresh_processes_specifics(ProcessesToUpdate::All, true, kind); } } /// Refreshes all system and processes information. /// /// It is the same as calling `system.refresh_specifics(RefreshKind::everything())`. /// /// Don't forget to take a look at [`ProcessRefreshKind::everything`] method to see what it /// will update for processes more in details. /// /// It will remove dead processes. If you want to keep dead processes, use /// [`System::refresh_processes_specifics`] directly. /// /// ```no_run /// use sysinfo::System; /// /// let mut s = System::new(); /// s.refresh_all(); /// ``` pub fn refresh_all(&mut self) { self.refresh_specifics(RefreshKind::everything()); } /// Refreshes RAM and SWAP usage. /// /// It is the same as calling `system.refresh_memory_specifics(MemoryRefreshKind::everything())`. /// /// If you don't want to refresh both, take a look at [`System::refresh_memory_specifics`]. /// /// ```no_run /// use sysinfo::System; /// /// let mut s = System::new(); /// s.refresh_memory(); /// ``` pub fn refresh_memory(&mut self) { self.refresh_memory_specifics(MemoryRefreshKind::everything()) } /// Refreshes system memory specific information. /// /// ```no_run /// use sysinfo::{MemoryRefreshKind, System}; /// /// let mut s = System::new(); /// s.refresh_memory_specifics(MemoryRefreshKind::nothing().with_ram()); /// ``` pub fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) { self.inner.refresh_memory_specifics(refresh_kind) } /// Refreshes CPUs usage. /// /// ⚠️ Please note that the result will very likely be inaccurate at the first call. /// You need to call this method at least twice (with a bit of time between each call, like /// 200 ms, take a look at [`MINIMUM_CPU_UPDATE_INTERVAL`] for more information) /// to get accurate value as it uses previous results to compute the next value. /// /// Calling this method is the same as calling /// `system.refresh_cpu_specifics(CpuRefreshKind::nothing().with_cpu_usage())`. /// /// ```no_run /// use sysinfo::System; /// /// let mut s = System::new_all(); /// // Wait a bit because CPU usage is based on diff. /// std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); /// // Refresh CPUs again. /// s.refresh_cpu_usage(); /// ``` /// /// [`MINIMUM_CPU_UPDATE_INTERVAL`]: crate::MINIMUM_CPU_UPDATE_INTERVAL pub fn refresh_cpu_usage(&mut self) { self.refresh_cpu_specifics(CpuRefreshKind::nothing().with_cpu_usage()) } /// Refreshes CPUs frequency information. /// /// Calling this method is the same as calling /// `system.refresh_cpu_specifics(CpuRefreshKind::nothing().with_frequency())`. /// /// ```no_run /// use sysinfo::System; /// /// let mut s = System::new_all(); /// s.refresh_cpu_frequency(); /// ``` pub fn refresh_cpu_frequency(&mut self) { self.refresh_cpu_specifics(CpuRefreshKind::nothing().with_frequency()) } /// Refreshes the list of CPU. /// /// Normally, this should almost never be needed as it's pretty rare for a computer /// to add a CPU while running, but it's possible on some computers which shutdown /// CPU if the load is low enough. /// /// The `refresh_kind` argument tells what information you want to be retrieved /// for each CPU. /// /// ```no_run /// use sysinfo::{CpuRefreshKind, System}; /// /// let mut s = System::new_all(); /// // We already have the list of CPU filled, but we want to recompute it /// // in case new CPUs were added. /// s.refresh_cpu_list(CpuRefreshKind::everything()); /// ``` pub fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) { self.inner.refresh_cpu_list(refresh_kind); } /// Refreshes all information related to CPUs information. It does not refresh the CPU list. /// If you want to refresh the CPU list, use [`System::refresh_cpu_list`] instead. /// /// If you only want the CPU usage, use [`System::refresh_cpu_usage`] instead. /// /// ⚠️ Please note that the result will be inaccurate at the first call. /// You need to call this method at least twice (with a bit of time between each call, like /// 200 ms, take a look at [`MINIMUM_CPU_UPDATE_INTERVAL`] for more information) /// to get accurate value as it uses previous results to compute the next value. /// /// Calling this method is the same as calling /// `system.refresh_cpu_specifics(CpuRefreshKind::everything())`. /// /// ```no_run /// use sysinfo::System; /// /// let mut s = System::new_all(); /// s.refresh_cpu_all(); /// ``` /// /// [`MINIMUM_CPU_UPDATE_INTERVAL`]: crate::MINIMUM_CPU_UPDATE_INTERVAL pub fn refresh_cpu_all(&mut self) { self.refresh_cpu_specifics(CpuRefreshKind::everything()) } /// Refreshes CPUs specific information. It does not refresh the CPU list. /// If you want to refresh the CPU list, use [`System::refresh_cpu_list`] instead. /// /// ```no_run /// use sysinfo::{System, CpuRefreshKind}; /// /// let mut s = System::new_all(); /// s.refresh_cpu_specifics(CpuRefreshKind::everything()); /// ``` pub fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { self.inner.refresh_cpu_specifics(refresh_kind) } /// Gets all processes and updates their information, along with all the tasks each process has. /// /// It does the same as: /// /// ```no_run /// # use sysinfo::{ProcessesToUpdate, ProcessRefreshKind, System, UpdateKind}; /// # let mut system = System::new(); /// system.refresh_processes_specifics( /// ProcessesToUpdate::All, /// true, /// ProcessRefreshKind::nothing() /// .with_memory() /// .with_cpu() /// .with_disk_usage() /// .with_exe(UpdateKind::OnlyIfNotSet) /// ); /// ``` /// /// ⚠️ `remove_dead_processes` works as follows: if an updated process is dead, then it is /// removed. So if you refresh pids 1, 2 and 3. If 2 and 7 are dead, only 2 will be removed /// since 7 is not part of the update. /// /// ⚠️ On Linux, `sysinfo` keeps the `stat` files open by default. You can change this behaviour /// by using [`set_open_files_limit`][crate::set_open_files_limit]. /// /// ⚠️ On Linux, if you dont need the tasks of each process, you can use /// `refresh_processes_specifics` with `ProcessRefreshKind::everything().without_tasks()`. /// Refreshing all processes and their tasks can be quite expensive. For more information /// see [`ProcessRefreshKind`]. /// /// Example: /// /// ```no_run /// use sysinfo::{ProcessesToUpdate, System}; /// /// let mut s = System::new_all(); /// s.refresh_processes(ProcessesToUpdate::All, true); /// ``` pub fn refresh_processes( &mut self, processes_to_update: ProcessesToUpdate<'_>, remove_dead_processes: bool, ) -> usize { self.refresh_processes_specifics( processes_to_update, remove_dead_processes, ProcessRefreshKind::nothing() .with_memory() .with_cpu() .with_disk_usage() .with_exe(UpdateKind::OnlyIfNotSet) .with_tasks(), ) } /// Gets all processes and updates the specified information. /// /// Returns the number of updated processes. /// /// ⚠️ `remove_dead_processes` works as follows: if an updated process is dead, then it is /// removed. So if you refresh pids 1, 2 and 3. If 2 and 7 are dead, only 2 will be removed /// since 7 is not part of the update. /// /// ⚠️ On Linux, `sysinfo` keeps the `stat` files open by default. You can change this behaviour /// by using [`set_open_files_limit`][crate::set_open_files_limit]. /// /// ```no_run /// use sysinfo::{ProcessesToUpdate, ProcessRefreshKind, System}; /// /// let mut s = System::new_all(); /// s.refresh_processes_specifics( /// ProcessesToUpdate::All, /// true, /// ProcessRefreshKind::everything(), /// ); /// ``` pub fn refresh_processes_specifics( &mut self, processes_to_update: ProcessesToUpdate<'_>, remove_dead_processes: bool, refresh_kind: ProcessRefreshKind, ) -> usize { fn update_and_remove(pid: &Pid, processes: &mut HashMap) { let updated = if let Some(proc) = processes.get_mut(pid) { proc.inner.switch_updated() } else { return; }; if !updated { processes.remove(pid); } } fn update(pid: &Pid, processes: &mut HashMap) { if let Some(proc) = processes.get_mut(pid) && !proc.inner.switch_updated() { proc.inner.set_nonexistent(); } } let nb_updated = self .inner .refresh_processes_specifics(processes_to_update, refresh_kind); let processes = self.inner.processes_mut(); match processes_to_update { ProcessesToUpdate::All => { if remove_dead_processes { processes.retain(|_, v| v.inner.switch_updated()); } else { for proc in processes.values_mut() { proc.inner.switch_updated(); } } } ProcessesToUpdate::Some(pids) => { let call = if remove_dead_processes { update_and_remove } else { update }; for pid in pids { call(pid, processes); } } } nb_updated } /// Returns the process list. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// for (pid, process) in s.processes() { /// println!("{} {:?}", pid, process.name()); /// } /// ``` pub fn processes(&self) -> &HashMap { self.inner.processes() } /// Returns the process corresponding to the given `pid` or `None` if no such process exists. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.name()); /// } /// ``` pub fn process(&self, pid: Pid) -> Option<&Process> { self.inner.process(pid) } /// Returns an iterator of process containing the given `name`. /// /// If you want only the processes with exactly the given `name`, take a look at /// [`System::processes_by_exact_name`]. /// /// **⚠️ Important ⚠️** /// /// On **Linux**, there are two things to know about processes' name: /// 1. It is limited to 15 characters. /// 2. It is not always the exe name. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// for process in s.processes_by_name("htop".as_ref()) { /// println!("{} {:?}", process.pid(), process.name()); /// } /// ``` pub fn processes_by_name<'a: 'b, 'b>( &'a self, name: &'b OsStr, ) -> impl Iterator + 'b { let finder = memchr::memmem::Finder::new(name.as_encoded_bytes()); self.processes() .values() .filter(move |val: &&Process| finder.find(val.name().as_encoded_bytes()).is_some()) } /// Returns an iterator of processes with exactly the given `name`. /// /// If you instead want the processes containing `name`, take a look at /// [`System::processes_by_name`]. /// /// **⚠️ Important ⚠️** /// /// On **Linux**, there are two things to know about processes' name: /// 1. It is limited to 15 characters. /// 2. It is not always the exe name. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// for process in s.processes_by_exact_name("htop".as_ref()) { /// println!("{} {:?}", process.pid(), process.name()); /// } /// ``` pub fn processes_by_exact_name<'a: 'b, 'b>( &'a self, name: &'b OsStr, ) -> impl Iterator + 'b { self.processes() .values() .filter(move |val: &&Process| val.name() == name) } /// Returns "global" CPUs usage (aka the addition of all the CPUs). /// /// To have up-to-date information, you need to call [`System::refresh_cpu_specifics`] or /// [`System::refresh_specifics`] with `cpu` enabled. /// /// ```no_run /// use sysinfo::{CpuRefreshKind, RefreshKind, System}; /// /// let mut s = System::new_with_specifics( /// RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()), /// ); /// // Wait a bit because CPU usage is based on diff. /// std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); /// // Refresh CPUs again to get actual value. /// s.refresh_cpu_usage(); /// println!("{}%", s.global_cpu_usage()); /// ``` pub fn global_cpu_usage(&self) -> f32 { self.inner.global_cpu_usage() } /// Returns the list of the CPUs. /// /// By default, the list of CPUs is empty until you call [`System::refresh_cpu_specifics`] or /// [`System::refresh_specifics`] with `cpu` enabled. /// /// ```no_run /// use sysinfo::{CpuRefreshKind, RefreshKind, System}; /// /// let mut s = System::new_with_specifics( /// RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()), /// ); /// // Wait a bit because CPU usage is based on diff. /// std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); /// // Refresh CPUs again to get actual value. /// s.refresh_cpu_usage(); /// for cpu in s.cpus() { /// println!("{}%", cpu.cpu_usage()); /// } /// ``` pub fn cpus(&self) -> &[Cpu] { self.inner.cpus() } /// Returns the RAM size in bytes. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// println!("{} bytes", s.total_memory()); /// ``` /// /// On Linux, if you want to see this information with the limit of your cgroup, take a look /// at [`cgroup_limits`](System::cgroup_limits). pub fn total_memory(&self) -> u64 { self.inner.total_memory() } /// Returns the amount of free RAM in bytes. /// /// Generally, "free" memory refers to unallocated memory whereas "available" memory refers to /// memory that is available for (re)use. /// /// Side note: Windows doesn't report "free" memory so this method returns the same value /// as [`available_memory`](System::available_memory). /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// println!("{} bytes", s.free_memory()); /// ``` pub fn free_memory(&self) -> u64 { self.inner.free_memory() } /// Returns the amount of available RAM in bytes. /// /// Generally, "free" memory refers to unallocated memory whereas "available" memory refers to /// memory that is available for (re)use. /// /// ⚠️ Windows and FreeBSD don't report "available" memory so [`System::free_memory`] /// returns the same value as this method. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// println!("{} bytes", s.available_memory()); /// ``` pub fn available_memory(&self) -> u64 { self.inner.available_memory() } /// Returns the amount of used RAM in bytes. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// println!("{} bytes", s.used_memory()); /// ``` pub fn used_memory(&self) -> u64 { self.inner.used_memory() } /// Returns the SWAP size in bytes. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// println!("{} bytes", s.total_swap()); /// ``` pub fn total_swap(&self) -> u64 { self.inner.total_swap() } /// Returns the amount of free SWAP in bytes. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// println!("{} bytes", s.free_swap()); /// ``` pub fn free_swap(&self) -> u64 { self.inner.free_swap() } /// Returns the amount of used SWAP in bytes. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// println!("{} bytes", s.used_swap()); /// ``` pub fn used_swap(&self) -> u64 { self.inner.used_swap() } /// Retrieves the limits for the current cgroup (if any), otherwise it returns `None`. /// /// This information is computed every time the method is called. /// /// ⚠️ You need to have run [`refresh_memory`](System::refresh_memory) at least once before /// calling this method. /// /// ⚠️ This method is only implemented for Linux. It always returns `None` for all other /// systems. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// println!("limits: {:?}", s.cgroup_limits()); /// ``` pub fn cgroup_limits(&self) -> Option { self.inner.cgroup_limits() } /// Returns system uptime (in seconds). /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("System running since {} seconds", System::uptime()); /// ``` pub fn uptime() -> u64 { SystemInner::uptime() } /// Returns the time (in seconds) when the system booted since UNIX epoch. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("System booted at {} seconds", System::boot_time()); /// ``` pub fn boot_time() -> u64 { SystemInner::boot_time() } /// Returns the system load average value. /// /// **Important**: this information is computed every time this function is called. /// /// ⚠️ This is currently not working on **Windows**. /// /// ```no_run /// use sysinfo::System; /// /// let load_avg = System::load_average(); /// println!( /// "one minute: {}%, five minutes: {}%, fifteen minutes: {}%", /// load_avg.one, /// load_avg.five, /// load_avg.fifteen, /// ); /// ``` pub fn load_average() -> LoadAvg { SystemInner::load_average() } /// Returns the system name. /// /// | example platform | value of `System::name()` | /// |---|---| /// | linux laptop | "Ubuntu" | /// | android phone | "Pixel 9 Pro" | /// | apple laptop | "Darwin" | /// | windows server | "Windows" | /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("OS: {:?}", System::name()); /// ``` pub fn name() -> Option { SystemInner::name() } /// Returns the system's kernel version. /// /// | example platform | value of `System::kernel_version()` | /// |---|---| /// | linux laptop | "6.8.0-48-generic" | /// | android phone | "6.1.84-android14-11" | /// | apple laptop | "24.1.0" | /// | windows server | "20348" | /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("kernel version: {:?}", System::kernel_version()); /// ``` pub fn kernel_version() -> Option { SystemInner::kernel_version() } /// Returns the system version (e.g. for macOS this will return 15.1 rather than the kernel /// version). /// /// | example platform | value of `System::os_version()` | /// |---|---| /// | linux laptop | "24.04" | /// | android phone | "15" | /// | apple laptop | "15.1.1" | /// | windows server | "10 (20348)" | /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("OS version: {:?}", System::os_version()); /// ``` pub fn os_version() -> Option { SystemInner::os_version() } /// Returns the system long os version. /// /// | example platform | value of `System::long_os_version()` | /// |---|---| /// | linux laptop | "Linux (Ubuntu 24.04)" | /// | android phone | "Android 15 on Pixel 9 Pro" | /// | apple laptop | "macOS 15.1.1 Sequoia" | /// | windows server | "Windows Server 2022 Datacenter" | /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("Long OS Version: {:?}", System::long_os_version()); /// ``` pub fn long_os_version() -> Option { SystemInner::long_os_version() } /// Returns the distribution id as defined by os-release, /// or [`std::env::consts::OS`]. /// /// See also /// - /// - /// /// | example platform | value of `System::distribution_id()` | /// |---|---| /// | linux laptop | "ubuntu" | /// | android phone | "android" | /// | apple laptop | "macos" | /// | windows server | "windows" | /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("Distribution ID: {:?}", System::distribution_id()); /// ``` pub fn distribution_id() -> String { SystemInner::distribution_id() } /// Returns the distribution ids of operating systems that are closely /// related to the local operating system in regards to packaging and /// programming interfaces, for example listing one or more OS identifiers /// the local OS is a derivative from. /// /// See also /// - /// /// | example platform | value of `System::distribution_id_like()` | /// |---|---| /// | android phone | [] | /// | archlinux laptop | [] | /// | centos server | ["rhel", "fedora"] | /// | ubuntu laptop | ["debian"] | /// | windows laptop | [] | /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("Distribution ID_LIKE: {:?}", System::distribution_id_like()); /// ``` pub fn distribution_id_like() -> Vec { SystemInner::distribution_id_like() } /// Provides kernel version following this string format: /// /// | Platform | Result | /// |-|-| /// | Windows | Windows OS Build 20348.2227 | /// | Linux | Linux 6.12.13-200.fc41.x86_64 | /// | Android | Android 612.13-200 | /// | MacOS | Darwin 21.6.0 | /// | FreeBSD | FreeBSD 199506 | /// /// If any of the information is not available, it will be replaced with "unknown". /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("Kernel long version: {}", System::kernel_long_version()); /// ``` /// /// [distribution_id]: System::distribution_id /// [kernel_version]: System::kernel_version pub fn kernel_long_version() -> String { let kernel_version = match System::kernel_version() { None => "unknown".to_string(), Some(s) => s, }; let kernel_name = SystemInner::kernel_name().unwrap_or("Unknown"); if cfg!(windows) { format!("{kernel_name} OS Build {kernel_version}") } else { format!("{kernel_name} {kernel_version}") } } /// Returns the system hostname based off DNS. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("Hostname: {:?}", System::host_name()); /// ``` pub fn host_name() -> Option { SystemInner::host_name() } /// Returns the CPU architecture (eg. x86, amd64, aarch64, ...). /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("CPU Architecture: {:?}", System::cpu_arch()); /// ``` pub fn cpu_arch() -> String { SystemInner::cpu_arch().unwrap_or_else(|| std::env::consts::ARCH.to_owned()) } /// Returns the number of physical cores on the CPU or `None` if it couldn't get it. /// /// In case there are multiple CPUs, it will combine the physical core count of all the CPUs. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new(); /// println!("{:?}", System::physical_core_count()); /// ``` pub fn physical_core_count() -> Option { SystemInner::physical_core_count() } /// Returns the (default) maximum number of open files for a process. /// /// Returns `None` if it failed retrieving the information or if the current system is not /// supported. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// println!("Max open files: {:?}", System::open_files_limit()); /// ``` pub fn open_files_limit() -> Option { SystemInner::open_files_limit() } } /// This type allows to retrieve motherboard-related information. /// /// ``` /// use sysinfo::Motherboard; /// /// if let Some(m) = Motherboard::new() { /// println!("{m:?}"); /// } /// ``` pub struct Motherboard { pub(crate) inner: MotherboardInner, } impl Motherboard { /// Creates a new instance of the `Motherboard` type. /// /// ``` /// use sysinfo::Motherboard; /// /// if let Some(m) = Motherboard::new() { /// println!("{m:?}"); /// } /// ``` pub fn new() -> Option { Some(Self { inner: MotherboardInner::new()?, }) } /// Returns the motherboard name. /// /// This corresponds to the model identifier assigned by the motherboard's /// manufacturer (e.g. "20BE0061MC"). /// This information isn't available on ARM-based macOS systems. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::Motherboard; /// /// if let Some(m) = Motherboard::new() { /// println!("Motherboard name: {:?}", m.name()); /// } /// ``` pub fn name(&self) -> Option { self.inner.name() } /// Returns the motherboard vendor name. /// /// This corresponds to the name of the motherboard's manufacturer (e.g. "LENOVO"). /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::Motherboard; /// /// if let Some(m) = Motherboard::new() { /// println!("Motherboard vendor: {:?}", m.vendor_name()); /// } /// ``` pub fn vendor_name(&self) -> Option { self.inner.vendor_name() } /// Returns the motherboard version. /// /// This corresponds to the version or model number assigned by the motherboard's /// manufacturer (e.g. "0B98401 Pro"). /// This information isn't available on ARM-based macOS systems. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::Motherboard; /// /// if let Some(m) = Motherboard::new() { /// println!("Motherboard version: {:?}", m.version()); /// } /// ``` pub fn version(&self) -> Option { self.inner.version() } /// Returns the motherboard serial number. /// /// This corresponds to the serial number assigned by the motherboard's /// manufacturer (e.g. "W1KS427111E"). /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::Motherboard; /// /// if let Some(m) = Motherboard::new() { /// println!("Motherboard serial number: {:?}", m.serial_number()); /// } /// ``` pub fn serial_number(&self) -> Option { self.inner.serial_number() } /// Returns the motherboard asset tag. /// /// This corresponds to the identifier assigned by the motherboard's /// manufacturer to track the physical device for inventory purposes. /// /// **Important**: this information is computed every time this function is called. /// /// ⚠️ Not supported on macOS/iOS. /// /// ```no_run /// use sysinfo::Motherboard; /// /// if let Some(m) = Motherboard::new() { /// println!("Motherboard asset tag: {:?}", m.asset_tag()); /// } /// ``` pub fn asset_tag(&self) -> Option { self.inner.asset_tag() } } /// This type allows to retrieve product-related information. /// /// ``` /// use sysinfo::Product; /// /// println!("{:?}", Product); /// ``` pub struct Product; impl Product { /// Returns the product name. /// /// This corresponds to the product name assigned by the hardware /// manufacturer (e.g. "20AN"). /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::Product; /// /// println!("Product name: {:?}", Product::name()); /// ``` pub fn name() -> Option { ProductInner::name() } /// Returns the product family identifier. /// /// This corresponds to the product family assigned by the hardware /// manufacturer (e.g. "T440p"). /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::Product; /// /// println!("Product family: {:?}", Product::family()); /// ``` pub fn family() -> Option { ProductInner::family() } /// Returns the product serial number. /// /// This corresponds to the serial identifier assigned by the hardware /// manufacturer (e.g. "W1KS427111E"). /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::Product; /// /// println!("Product serial: {:?}", Product::serial_number()); /// ``` pub fn serial_number() -> Option { ProductInner::serial_number() } /// Returns the product Stock Keeping Unit (SKU). /// /// This corresponds to the Stock Keeping Unit assigned by the hardware /// manufacturer (e.g. "LENOVO_MT_20AN") which identifies a specific /// model or configuration. /// /// **Important**: this information is computed every time this function is called. /// /// ⚠️ Not supported on macOS/iOS. /// /// ```no_run /// use sysinfo::Product; /// /// println!("Product sku: {:?}", Product::stock_keeping_unit()); /// ``` #[doc(alias = "sku")] pub fn stock_keeping_unit() -> Option { ProductInner::stock_keeping_unit() } /// Returns the product UUID. /// /// This corresponds to the unique identifier assigned by the hardware /// manufacturer (e.g. "407488fe-960a-43b5-a265-8fd0e9200b8f") which uniquely /// identifies the physical system. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::Product; /// /// println!("Product UUID: {:?}", Product::uuid()); /// ``` pub fn uuid() -> Option { ProductInner::uuid() } /// Returns the product version. /// /// This corresponds to the version assigned by the hardware /// manufacturer (e.g. "Lenovo ThinkPad T440p") which identifies /// the specific version or revision of the product. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::Product; /// /// println!("Product version: {:?}", Product::version()); /// ``` pub fn version() -> Option { ProductInner::version() } /// Returns the product vendor name. /// /// This corresponds to the vendor name assigned by the hardware /// manufacturer (e.g. "LENOVO"). /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::Product; /// /// println!("Vendor name: {:?}", Product::vendor_name()); /// ``` pub fn vendor_name() -> Option { ProductInner::vendor_name() } } /// A struct representing system load average value. /// /// It is returned by [`System::load_average`][crate::System::load_average]. /// /// ```no_run /// use sysinfo::System; /// /// let load_avg = System::load_average(); /// println!( /// "one minute: {}%, five minutes: {}%, fifteen minutes: {}%", /// load_avg.one, /// load_avg.five, /// load_avg.fifteen, /// ); /// ``` #[repr(C)] #[derive(Default, Debug, Clone)] pub struct LoadAvg { /// Average load within one minute. pub one: f64, /// Average load within five minutes. pub five: f64, /// Average load within fifteen minutes. pub fifteen: f64, } /// An enum representing signals on UNIX-like systems. /// /// On non-unix systems, this enum is mostly useless and is only there to keep coherency between /// the different OSes. /// /// If you want the list of the supported signals on the current system, use /// [`SUPPORTED_SIGNALS`][crate::SUPPORTED_SIGNALS]. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub enum Signal { /// Hangup detected on controlling terminal or death of controlling process. Hangup, /// Interrupt from keyboard. Interrupt, /// Quit from keyboard. Quit, /// Illegal instruction. Illegal, /// Trace/breakpoint trap. Trap, /// Abort signal from C abort function. Abort, /// IOT trap. A synonym for SIGABRT. IOT, /// Bus error (bad memory access). Bus, /// Floating point exception. FloatingPointException, /// Kill signal. Kill, /// User-defined signal 1. User1, /// Invalid memory reference. Segv, /// User-defined signal 2. User2, /// Broken pipe: write to pipe with no readers. Pipe, /// Timer signal from C alarm function. Alarm, /// Termination signal. Term, /// Child stopped or terminated. Child, /// Continue if stopped. Continue, /// Stop process. Stop, /// Stop typed at terminal. TSTP, /// Terminal input for background process. TTIN, /// Terminal output for background process. TTOU, /// Urgent condition on socket. Urgent, /// CPU time limit exceeded. XCPU, /// File size limit exceeded. XFSZ, /// Virtual alarm clock. VirtualAlarm, /// Profiling time expired. Profiling, /// Windows resize signal. Winch, /// I/O now possible. IO, /// Pollable event (Sys V). Synonym for IO Poll, /// Power failure (System V). /// /// Doesn't exist on apple systems so will be ignored. Power, /// Bad argument to routine (SVr4). Sys, } impl std::fmt::Display for Signal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match *self { Self::Hangup => "Hangup", Self::Interrupt => "Interrupt", Self::Quit => "Quit", Self::Illegal => "Illegal", Self::Trap => "Trap", Self::Abort => "Abort", Self::IOT => "IOT", Self::Bus => "Bus", Self::FloatingPointException => "FloatingPointException", Self::Kill => "Kill", Self::User1 => "User1", Self::Segv => "Segv", Self::User2 => "User2", Self::Pipe => "Pipe", Self::Alarm => "Alarm", Self::Term => "Term", Self::Child => "Child", Self::Continue => "Continue", Self::Stop => "Stop", Self::TSTP => "TSTP", Self::TTIN => "TTIN", Self::TTOU => "TTOU", Self::Urgent => "Urgent", Self::XCPU => "XCPU", Self::XFSZ => "XFSZ", Self::VirtualAlarm => "VirtualAlarm", Self::Profiling => "Profiling", Self::Winch => "Winch", Self::IO => "IO", Self::Poll => "Poll", Self::Power => "Power", Self::Sys => "Sys", }; f.write_str(s) } } /// Contains memory limits for the current process. #[derive(Default, Debug, Clone)] pub struct CGroupLimits { /// Total memory (in bytes) for the current cgroup. pub total_memory: u64, /// Free memory (in bytes) for the current cgroup. pub free_memory: u64, /// Free swap (in bytes) for the current cgroup. pub free_swap: u64, /// Resident Set Size (RSS) (in bytes) for the current cgroup. pub rss: u64, } /// Enum describing the different status of a process. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub enum ProcessStatus { /// ## Linux /// /// Idle kernel thread. /// /// ## macOs/FreeBSD /// /// Process being created by fork. /// /// ## Other OS /// /// Not available. Idle, /// Running. Run, /// ## Linux /// /// Sleeping in an interruptible waiting. /// /// ## macOS/FreeBSD /// /// Sleeping on an address. /// /// ## Other OS /// /// Not available. Sleep, /// ## Linux /// /// Stopped (on a signal) or (before Linux 2.6.33) trace stopped. /// /// ## macOS/FreeBSD /// /// Process debugging or suspension. /// /// ## Other OS /// /// Not available. Stop, /// ## Linux/FreeBSD/macOS /// /// Zombie process. Terminated but not reaped by its parent. /// /// ## Other OS /// /// Not available. Zombie, /// ## Linux /// /// Tracing stop (Linux 2.6.33 onward). Stopped by debugger during the tracing. /// /// ## Other OS /// /// Not available. Tracing, /// ## Linux /// /// Dead/uninterruptible sleep (usually IO). /// /// ## FreeBSD /// /// A process should never end up in this state. /// /// ## Other OS /// /// Not available. Dead, /// ## Linux /// /// Wakekill (Linux 2.6.33 to 3.13 only). /// /// ## Other OS /// /// Not available. Wakekill, /// ## Linux /// /// Waking (Linux 2.6.33 to 3.13 only). /// /// ## Other OS /// /// Not available. Waking, /// ## Linux /// /// Parked (Linux 3.9 to 3.13 only). /// /// ## macOS /// /// Halted at a clean point. /// /// ## Other OS /// /// Not available. Parked, /// ## FreeBSD /// /// Blocked on a lock. /// /// ## Other OS /// /// Not available. LockBlocked, /// ## Linux /// /// Waiting in uninterruptible disk sleep. /// /// ## Other OS /// /// Not available. UninterruptibleDiskSleep, /// Unknown. Unknown(u32), } /// Enum describing the different kind of threads. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub enum ThreadKind { /// Kernel thread. Kernel, /// User thread. Userland, } /// Enum describing possible [`Process::kill_and_wait`] errors. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub enum KillError { /// This signal doesn't exist on this platform. SignalDoesNotExist, /// The signal failed to be sent to the target process. FailedToSendSignal, } /// Struct containing information of a process. /// /// ## iOS /// /// This information cannot be retrieved on iOS due to sandboxing. /// /// ## Apple app store /// /// If you are building a macOS Apple app store, it won't be able /// to retrieve this information. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.name()); /// } /// ``` pub struct Process { pub(crate) inner: ProcessInner, } impl Process { /// Sends [`Signal::Kill`] to the process (which is the only signal supported on all supported /// platforms by this crate). /// /// Returns `true` if the signal was sent successfully. If you want to wait for this process /// to end, you can use [`Process::wait`] or directly [`Process::kill_and_wait`]. /// /// ⚠️ Even if this function returns `true`, it doesn't necessarily mean that the process will /// be killed. It just means that the signal was sent successfully. /// /// ⚠️ Please note that some processes might not be "killable", like if they run with higher /// levels than the current process for example. /// /// If you want to use another signal, take a look at [`Process::kill_with`]. /// /// To get the list of the supported signals on this system, use /// [`SUPPORTED_SIGNALS`][crate::SUPPORTED_SIGNALS]. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// process.kill(); /// } /// ``` pub fn kill(&self) -> bool { self.kill_with(Signal::Kill).unwrap_or(false) } /// Sends the given `signal` to the process. If the signal doesn't exist on this platform, /// it'll do nothing and will return `None`. Otherwise it'll return `Some(bool)`. The boolean /// value will depend on whether or not the signal was sent successfully. /// /// If you just want to kill the process, use [`Process::kill`] directly. If you want to wait /// for this process to end, you can use [`Process::wait`] or [`Process::kill_with_and_wait`]. /// /// ⚠️ Please note that some processes might not be "killable", like if they run with higher /// levels than the current process for example. /// /// To get the list of the supported signals on this system, use /// [`SUPPORTED_SIGNALS`][crate::SUPPORTED_SIGNALS]. /// /// ```no_run /// use sysinfo::{Pid, Signal, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// if process.kill_with(Signal::Kill).is_none() { /// println!("This signal isn't supported on this platform"); /// } /// } /// ``` pub fn kill_with(&self, signal: Signal) -> Option { self.inner.kill_with(signal) } /// Sends [`Signal::Kill`] to the process then waits for its termination. /// /// Internally, this method is calling [`Process::kill`] then [`Process::wait`]. /// /// ⚠️ Please note that some processes might not be "killable", like if they run with higher /// levels than the current process for example. In this case, this method could enter an /// infinite loop. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// if let Err(error) = process.kill_and_wait() { /// println!("`kill_and_wait` failed: {error:?}"); /// } /// } /// ``` pub fn kill_and_wait(&self) -> Result, KillError> { self.kill_with_and_wait(Signal::Kill) } /// Sends the given `signal` to the process.then waits for its termination. /// /// Internally, this method is calling [`Process::kill_with`] then [`Process::wait`]. /// /// ⚠️ Please note that some processes might not be "killable", like if they run with higher /// levels than the current process for example. In this case, this method could enter an /// infinite loop. /// /// To get the list of the supported signals on this system, use /// [`SUPPORTED_SIGNALS`][crate::SUPPORTED_SIGNALS]. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// if let Err(error) = process.kill_and_wait() { /// println!("`kill_and_wait` failed: {error:?}"); /// } /// } /// ``` pub fn kill_with_and_wait(&self, signal: Signal) -> Result, KillError> { match self.inner.kill_with(signal) { Some(sent) => { if !sent { return Err(KillError::FailedToSendSignal); } } None => return Err(KillError::SignalDoesNotExist), } Ok(self.inner.wait()) } /// Waits for process termination and returns its [`ExitStatus`] if it could be retrieved, /// returns `None` otherwise. It means that as long as the process is alive, this method will /// not return. /// /// ⚠️ On **macOS** and **FreeBSD**, if the process died and a new one took its PID, unless /// you refreshed, it will wait for the new process to end. /// /// On **Windows**, as long as we have a (internal) handle, we can always retrieve the exit /// status. /// /// On **Linux**/**Android**, we check that the start time of the PID we're waiting is the same /// as the current process'. If not it means the process died and a new one got its PID. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("Waiting for pid 1337"); /// let exit_status = process.wait(); /// println!("Pid 1337 exited with: {exit_status:?}"); /// } /// ``` pub fn wait(&self) -> Option { self.inner.wait() } /// Returns the name of the process. /// /// **⚠️ Important ⚠️** /// /// On **Linux**, there are two things to know about processes' name: /// 1. It is limited to 15 characters. /// 2. It is not always the exe name. /// /// If you are looking for a specific process, unless you know what you are /// doing, in most cases it's better to use [`Process::exe`] instead (which /// can be empty sometimes!). /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.name()); /// } /// ``` pub fn name(&self) -> &OsStr { self.inner.name() } /// Returns the command line. /// /// **⚠️ Important ⚠️** /// /// On **Windows**, you might need to use `administrator` privileges when running your program /// to have access to this information. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.cmd()); /// } /// ``` pub fn cmd(&self) -> &[OsString] { self.inner.cmd() } /// Returns the path to the process. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.exe()); /// } /// ``` /// /// ### Implementation notes /// /// On Linux, this method will return an empty path if there /// was an error trying to read `/proc//exe`. This can /// happen, for example, if the permission levels or UID namespaces /// between the caller and target processes are different. /// /// It is also the case that `cmd[0]` is _not_ usually a correct /// replacement for this. /// A process [may change its `cmd[0]` value](https://man7.org/linux/man-pages/man5/proc.5.html) /// freely, making this an untrustworthy source of information. pub fn exe(&self) -> Option<&Path> { self.inner.exe() } /// Returns the PID of the process. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}", process.pid()); /// } /// ``` pub fn pid(&self) -> Pid { self.inner.pid() } /// Returns the environment variables of the process. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.environ()); /// } /// ``` pub fn environ(&self) -> &[OsString] { self.inner.environ() } /// Returns the current working directory. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.cwd()); /// } /// ``` pub fn cwd(&self) -> Option<&Path> { self.inner.cwd() } /// Returns the path of the root directory. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.root()); /// } /// ``` pub fn root(&self) -> Option<&Path> { self.inner.root() } /// Returns the memory usage (in bytes). /// /// This method returns the [size of the resident set], that is, the amount of memory that the /// process allocated and which is currently mapped in physical RAM. It does not include memory /// that is swapped out, or, in some operating systems, that has been allocated but never used. /// /// Thus, it represents exactly the amount of physical RAM that the process is using at the /// present time, but it might not be a good indicator of the total memory that the process will /// be using over its lifetime. For that purpose, you can try and use /// [`virtual_memory`](Process::virtual_memory). /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{} bytes", process.memory()); /// } /// ``` /// /// [size of the resident set]: https://en.wikipedia.org/wiki/Resident_set_size pub fn memory(&self) -> u64 { self.inner.memory() } /// Returns the virtual memory usage (in bytes). /// /// This method returns the [size of virtual memory], that is, the amount of memory that the /// process can access, whether it is currently mapped in physical RAM or not. It includes /// physical RAM, allocated but not used regions, swapped-out regions, and even memory /// associated with [memory-mapped files](https://en.wikipedia.org/wiki/Memory-mapped_file). /// /// This value has limitations though. Depending on the operating system and type of process, /// this value might be a good indicator of the total memory that the process will be using over /// its lifetime. However, for example, in the version 14 of macOS this value is in the order of /// the hundreds of gigabytes for every process, and thus not very informative. Moreover, if a /// process maps into memory a very large file, this value will increase accordingly, even if /// the process is not actively using the memory. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{} bytes", process.virtual_memory()); /// } /// ``` /// /// [size of virtual memory]: https://en.wikipedia.org/wiki/Virtual_memory pub fn virtual_memory(&self) -> u64 { self.inner.virtual_memory() } /// Returns the parent PID. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.parent()); /// } /// ``` pub fn parent(&self) -> Option { self.inner.parent() } /// Returns the status of the process. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.status()); /// } /// ``` pub fn status(&self) -> ProcessStatus { self.inner.status() } /// Returns the time where the process was started (in seconds) from epoch. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("Started at {} seconds", process.start_time()); /// } /// ``` pub fn start_time(&self) -> u64 { self.inner.start_time() } /// Returns for how much time the process has been running (in seconds). /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("Running since {} seconds", process.run_time()); /// } /// ``` pub fn run_time(&self) -> u64 { self.inner.run_time() } /// Returns the total CPU usage (in %). Notice that it might be bigger than /// 100 if run on a multi-core machine. /// /// If you want a value between 0% and 100%, divide the returned value by /// the number of CPUs. /// /// ⚠️ To start to have accurate CPU usage, a process needs to be refreshed /// **twice** because CPU usage computation is based on time diff (process /// time on a given time period divided by total system time on the same /// time period). /// /// ⚠️ If you want accurate CPU usage number, better leave a bit of time /// between two calls of this method (take a look at /// [`MINIMUM_CPU_UPDATE_INTERVAL`][crate::MINIMUM_CPU_UPDATE_INTERVAL] for /// more information). /// /// ```no_run /// use sysinfo::{Pid, ProcessesToUpdate, ProcessRefreshKind, System}; /// /// let mut s = System::new_all(); /// // Wait a bit because CPU usage is based on diff. /// std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); /// // Refresh CPU usage to get actual value. /// s.refresh_processes_specifics( /// ProcessesToUpdate::All, /// true, /// ProcessRefreshKind::nothing().with_cpu() /// ); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}%", process.cpu_usage()); /// } /// ``` pub fn cpu_usage(&self) -> f32 { self.inner.cpu_usage() } /// Returns the total accumulated CPU usage (in CPU-milliseconds). Note /// that it might be bigger than the total clock run time of a process if /// run on a multi-core machine. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}", process.accumulated_cpu_time()); /// } /// ``` pub fn accumulated_cpu_time(&self) -> u64 { self.inner.accumulated_cpu_time() } /// Returns number of bytes read and written to disk. /// /// ⚠️ On Windows, this method actually returns **ALL** I/O read and /// written bytes. /// /// ⚠️ Files might be cached in memory by your OS, meaning that reading/writing them might not /// increase the `read_bytes`/`written_bytes` values. You can find more information about it /// in the `proc_pid_io` manual (`man proc_pid_io` on unix platforms). /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// let disk_usage = process.disk_usage(); /// println!("read bytes : new/total => {}/{}", /// disk_usage.read_bytes, /// disk_usage.total_read_bytes, /// ); /// println!("written bytes: new/total => {}/{}", /// disk_usage.written_bytes, /// disk_usage.total_written_bytes, /// ); /// } /// ``` pub fn disk_usage(&self) -> DiskUsage { self.inner.disk_usage() } /// Returns the ID of the owner user of this process or `None` if this /// information couldn't be retrieved. If you want to get the [`User`] from /// it, take a look at [`Users::get_user_by_id`]. /// /// [`User`]: crate::User /// [`Users::get_user_by_id`]: crate::Users::get_user_by_id /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("User id for process 1337: {:?}", process.user_id()); /// } /// ``` pub fn user_id(&self) -> Option<&Uid> { self.inner.user_id() } /// Returns the user ID of the effective owner of this process or `None` if /// this information couldn't be retrieved. If you want to get the [`User`] /// from it, take a look at [`Users::get_user_by_id`]. /// /// If you run something with `sudo`, the real user ID of the launched /// process will be the ID of the user you are logged in as but effective /// user ID will be `0` (i-e root). /// /// ⚠️ It always returns `None` on Windows. /// /// [`User`]: crate::User /// [`Users::get_user_by_id`]: crate::Users::get_user_by_id /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("User id for process 1337: {:?}", process.effective_user_id()); /// } /// ``` pub fn effective_user_id(&self) -> Option<&Uid> { self.inner.effective_user_id() } /// Returns the process group ID of the process. /// /// ⚠️ It always returns `None` on Windows. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("Group ID for process 1337: {:?}", process.group_id()); /// } /// ``` pub fn group_id(&self) -> Option { self.inner.group_id() } /// Returns the effective group ID of the process. /// /// If you run something with `sudo`, the real group ID of the launched /// process will be the primary group ID you are logged in as but effective /// group ID will be `0` (i-e root). /// /// ⚠️ It always returns `None` on Windows. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("User id for process 1337: {:?}", process.effective_group_id()); /// } /// ``` pub fn effective_group_id(&self) -> Option { self.inner.effective_group_id() } /// Returns the session ID for the current process or `None` if it couldn't /// be retrieved. /// /// ⚠️ This information is computed every time this method is called. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("Session ID for process 1337: {:?}", process.session_id()); /// } /// ``` pub fn session_id(&self) -> Option { self.inner.session_id() } /// Tasks run by this process. If there are none, returns `None`. /// /// ⚠️ This method always returns `None` on other platforms than Linux. /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// if let Some(tasks) = process.tasks() { /// println!("Listing tasks for process {:?}", process.pid()); /// for task_pid in tasks { /// if let Some(task) = s.process(*task_pid) { /// println!("Task {:?}: {:?}", task.pid(), task.name()); /// } /// } /// } /// } /// ``` pub fn tasks(&self) -> Option<&HashSet> { cfg_if! { if #[cfg(all( any(target_os = "linux", target_os = "android"), not(feature = "unknown-ci") ))] { self.inner.tasks.as_ref() } else { None } } } /// If the process is a thread, it'll return `Some` with the kind of thread it is. Returns /// `None` otherwise. /// /// ⚠️ This method always returns `None` on other platforms than Linux. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// /// for (_, process) in s.processes() { /// if let Some(thread_kind) = process.thread_kind() { /// println!("Process {:?} is a {thread_kind:?} thread", process.pid()); /// } /// } /// ``` pub fn thread_kind(&self) -> Option { cfg_if! { if #[cfg(all( any(target_os = "linux", target_os = "android"), not(feature = "unknown-ci") ))] { self.inner.thread_kind() } else { None } } } /// Returns `true` if the process doesn't exist anymore but was not yet removed from /// the processes list because the `remove_dead_processes` argument was set to `false` /// in methods like [`System::refresh_processes`]. /// /// ```no_run /// use sysinfo::{ProcessesToUpdate, System}; /// /// let mut s = System::new_all(); /// // We set the `remove_dead_processes` to `false`. /// s.refresh_processes(ProcessesToUpdate::All, false); /// /// for (_, process) in s.processes() { /// println!( /// "Process {:?} {}", /// process.pid(), /// if process.exists() { "exists" } else { "doesn't exist" }, /// ); /// } /// ``` pub fn exists(&self) -> bool { self.inner.exists() } /// Returns the number of open files in the current process. /// /// Returns `None` if it failed retrieving the information or if the current system is not /// supported. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// /// for (_, process) in s.processes() { /// println!( /// "Process {:?} {:?}", /// process.pid(), /// process.open_files(), /// ); /// } /// ``` pub fn open_files(&self) -> Option { self.inner.open_files() } /// Returns the maximum number of open files for the current process. /// /// Returns `None` if it failed retrieving the information or if the current system is not /// supported. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::System; /// /// let s = System::new_all(); /// /// for (_, process) in s.processes() { /// println!( /// "Process {:?} {:?}", /// process.pid(), /// process.open_files_limit(), /// ); /// } /// ``` pub fn open_files_limit(&self) -> Option { self.inner.open_files_limit() } } macro_rules! pid_decl { ($typ:ty) => { #[doc = include_str!("../../md_doc/pid.md")] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct Pid(pub(crate) $typ); impl From for Pid { fn from(v: usize) -> Self { Self(v as _) } } impl From for usize { fn from(v: Pid) -> Self { v.0 as _ } } impl FromStr for Pid { type Err = <$typ as FromStr>::Err; fn from_str(s: &str) -> Result { Ok(Self(<$typ>::from_str(s)?)) } } impl fmt::Display for Pid { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl Pid { /// Allows to convert [`Pid`][crate::Pid] into [`u32`]. /// /// ``` /// use sysinfo::Pid; /// /// let pid = Pid::from_u32(0); /// let value: u32 = pid.as_u32(); /// ``` pub fn as_u32(self) -> u32 { self.0 as _ } /// Allows to convert a [`u32`] into [`Pid`][crate::Pid]. /// /// ``` /// use sysinfo::Pid; /// /// let pid = Pid::from_u32(0); /// ``` pub fn from_u32(v: u32) -> Self { Self(v as _) } } }; } cfg_if! { if #[cfg(all( not(feature = "unknown-ci"), any( target_os = "freebsd", target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios", ) ))] { use libc::pid_t; pid_decl!(pid_t); } else { pid_decl!(usize); } } /// This enum allows you to specify when you want the related information to be updated. /// /// For example if you only want the [`Process::exe()`] information to be refreshed only if it's not /// already set: /// /// ```no_run /// use sysinfo::{ProcessesToUpdate, ProcessRefreshKind, System, UpdateKind}; /// /// let mut system = System::new(); /// system.refresh_processes_specifics( /// ProcessesToUpdate::All, /// true, /// ProcessRefreshKind::nothing().with_exe(UpdateKind::OnlyIfNotSet), /// ); /// ``` #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum UpdateKind { /// Never update the related information. #[default] Never, /// Always update the related information. Always, /// Only update the related information if it was not already set at least once. OnlyIfNotSet, } impl UpdateKind { /// If `self` is `OnlyIfNotSet`, `f` is called and its returned value is returned. #[allow(dead_code)] // Needed for unsupported targets. pub(crate) fn needs_update(self, f: impl Fn() -> bool) -> bool { match self { Self::Never => false, Self::Always => true, Self::OnlyIfNotSet => f(), } } } /// This enum allows you to specify if you want all processes to be updated or just /// some of them. /// /// Example: /// /// ```no_run /// use sysinfo::{ProcessesToUpdate, System, get_current_pid}; /// /// let mut system = System::new(); /// // To refresh all processes: /// system.refresh_processes(ProcessesToUpdate::All, true); /// /// // To refresh only the current one: /// system.refresh_processes( /// ProcessesToUpdate::Some(&[get_current_pid().unwrap()]), /// true, /// ); /// ``` #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ProcessesToUpdate<'a> { /// To refresh all processes. All, /// To refresh only the processes with the listed [`Pid`]. /// /// [`Pid`]: crate::Pid Some(&'a [Pid]), } /// Used to determine what you want to refresh specifically on the [`Process`] type. /// /// When all refresh are ruled out, a [`Process`] will still retrieve the following information: /// * Process ID ([`Pid`]) /// * Parent process ID (on Windows it never changes though) /// * Process name /// * Start time /// /// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that /// the information won't be retrieved if the information is accessible without needing /// extra computation. /// /// ⚠️ ** Linux Specific ** ⚠️ /// When using `ProcessRefreshKind::everything()`, in linux we will fetch all relevant /// information from `/proc//` as well as all the information from `/proc//task//` /// folders. This makes the refresh mechanism a lot slower depending on the number of tasks /// each process has. /// /// If you don't care about tasks information, use `ProcessRefreshKind::everything().without_tasks()` /// as much as possible. /// /// ``` /// use sysinfo::{ProcessesToUpdate, ProcessRefreshKind, System}; /// /// let mut system = System::new(); /// /// // We don't want to update the CPU information. /// system.refresh_processes_specifics( /// ProcessesToUpdate::All, /// true, /// ProcessRefreshKind::everything().without_cpu(), /// ); /// /// for (_, proc_) in system.processes() { /// // We use a `==` comparison on float only because we know it's set to 0 here. /// assert_eq!(proc_.cpu_usage(), 0.); /// } /// ``` /// /// [`Process`]: crate::Process #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ProcessRefreshKind { cpu: bool, disk_usage: bool, memory: bool, user: UpdateKind, cwd: UpdateKind, root: UpdateKind, environ: UpdateKind, cmd: UpdateKind, exe: UpdateKind, tasks: bool, } /// Creates a new `ProcessRefreshKind` with every refresh set to `false`, except for `tasks`. /// By default, we want to list all processes and tasks are considered processes on their own /// in linux so we still fetch them by default. However, the processes information are not /// refreshed. impl Default for ProcessRefreshKind { fn default() -> Self { Self { cpu: false, disk_usage: false, memory: false, user: UpdateKind::default(), cwd: UpdateKind::default(), root: UpdateKind::default(), environ: UpdateKind::default(), cmd: UpdateKind::default(), exe: UpdateKind::default(), tasks: true, // Process by default includes all tasks. } } } impl ProcessRefreshKind { /// Creates a new `ProcessRefreshKind` with every refresh set to `false`, except for `tasks`. /// By default, we want to list all processes and tasks are considered processes on their own /// in linux so we still fetch them by default. However, the processes information are not /// refreshed. /// ``` /// use sysinfo::{ProcessRefreshKind, UpdateKind}; /// /// let r = ProcessRefreshKind::nothing(); /// /// assert_eq!(r.cpu(), false); /// assert_eq!(r.user(), UpdateKind::Never); /// ``` pub fn nothing() -> Self { Self::default() } /// Creates a new `ProcessRefreshKind` with every refresh set to `true` or /// [`UpdateKind::OnlyIfNotSet`]. /// /// ``` /// use sysinfo::{ProcessRefreshKind, UpdateKind}; /// /// let r = ProcessRefreshKind::everything(); /// /// assert_eq!(r.cpu(), true); /// assert_eq!(r.user(), UpdateKind::OnlyIfNotSet); /// ``` pub fn everything() -> Self { Self { cpu: true, disk_usage: true, memory: true, user: UpdateKind::OnlyIfNotSet, cwd: UpdateKind::OnlyIfNotSet, root: UpdateKind::OnlyIfNotSet, environ: UpdateKind::OnlyIfNotSet, cmd: UpdateKind::OnlyIfNotSet, exe: UpdateKind::OnlyIfNotSet, tasks: true, } } impl_get_set!( ProcessRefreshKind, cpu, with_cpu, without_cpu, "\ It will retrieve both CPU usage and CPU accumulated time," ); impl_get_set!( ProcessRefreshKind, disk_usage, with_disk_usage, without_disk_usage ); impl_get_set!( ProcessRefreshKind, user, with_user, without_user, UpdateKind, "\ It will retrieve the following information: * user ID * user effective ID (if available on the platform) * user group ID (if available on the platform) * user effective ID (if available on the platform)" ); impl_get_set!(ProcessRefreshKind, memory, with_memory, without_memory); impl_get_set!(ProcessRefreshKind, cwd, with_cwd, without_cwd, UpdateKind); impl_get_set!( ProcessRefreshKind, root, with_root, without_root, UpdateKind ); impl_get_set!( ProcessRefreshKind, environ, with_environ, without_environ, UpdateKind ); impl_get_set!(ProcessRefreshKind, cmd, with_cmd, without_cmd, UpdateKind); impl_get_set!(ProcessRefreshKind, exe, with_exe, without_exe, UpdateKind); impl_get_set!(ProcessRefreshKind, tasks, with_tasks, without_tasks); } /// Used to determine what you want to refresh specifically on the [`Cpu`] type. /// /// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that /// the information won't be retrieved if the information is accessible without needing /// extra computation. /// /// ``` /// use sysinfo::{CpuRefreshKind, System}; /// /// let mut system = System::new(); /// /// // We don't want to update all the CPU information. /// system.refresh_cpu_specifics(CpuRefreshKind::everything().without_frequency()); /// /// for cpu in system.cpus() { /// assert_eq!(cpu.frequency(), 0); /// } /// ``` /// /// [`Cpu`]: crate::Cpu #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct CpuRefreshKind { cpu_usage: bool, frequency: bool, } impl CpuRefreshKind { /// Creates a new `CpuRefreshKind` with every refresh set to `false`. /// /// ``` /// use sysinfo::CpuRefreshKind; /// /// let r = CpuRefreshKind::nothing(); /// /// assert_eq!(r.frequency(), false); /// assert_eq!(r.cpu_usage(), false); /// ``` pub fn nothing() -> Self { Self::default() } /// Creates a new `CpuRefreshKind` with every refresh set to `true`. /// /// ``` /// use sysinfo::CpuRefreshKind; /// /// let r = CpuRefreshKind::everything(); /// /// assert_eq!(r.frequency(), true); /// assert_eq!(r.cpu_usage(), true); /// ``` pub fn everything() -> Self { Self { cpu_usage: true, frequency: true, } } impl_get_set!(CpuRefreshKind, cpu_usage, with_cpu_usage, without_cpu_usage); impl_get_set!(CpuRefreshKind, frequency, with_frequency, without_frequency); } /// Used to determine which memory you want to refresh specifically. /// /// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that /// the information won't be retrieved if the information is accessible without needing /// extra computation. /// /// ``` /// use sysinfo::{MemoryRefreshKind, System}; /// /// let mut system = System::new(); /// /// // We don't want to update all memories information. /// system.refresh_memory_specifics(MemoryRefreshKind::nothing().with_ram()); /// /// println!("total RAM: {}", system.total_memory()); /// println!("free RAM: {}", system.free_memory()); /// ``` #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct MemoryRefreshKind { ram: bool, swap: bool, } impl MemoryRefreshKind { /// Creates a new `MemoryRefreshKind` with every refresh set to `false`. /// /// ``` /// use sysinfo::MemoryRefreshKind; /// /// let r = MemoryRefreshKind::nothing(); /// /// assert_eq!(r.ram(), false); /// assert_eq!(r.swap(), false); /// ``` pub fn nothing() -> Self { Self::default() } /// Creates a new `MemoryRefreshKind` with every refresh set to `true`. /// /// ``` /// use sysinfo::MemoryRefreshKind; /// /// let r = MemoryRefreshKind::everything(); /// /// assert_eq!(r.ram(), true); /// assert_eq!(r.swap(), true); /// ``` pub fn everything() -> Self { Self { ram: true, swap: true, } } impl_get_set!(MemoryRefreshKind, ram, with_ram, without_ram); impl_get_set!(MemoryRefreshKind, swap, with_swap, without_swap); } /// Used to determine what you want to refresh specifically on the [`System`][crate::System] type. /// /// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that /// the information won't be retrieved if the information is accessible without needing /// extra computation. /// /// ``` /// use sysinfo::{RefreshKind, System}; /// /// // We want everything except memory. /// let mut system = System::new_with_specifics(RefreshKind::everything().without_memory()); /// /// assert_eq!(system.total_memory(), 0); /// # if sysinfo::IS_SUPPORTED_SYSTEM && !cfg!(feature = "apple-sandbox") { /// assert!(system.processes().len() > 0); /// # } /// ``` #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct RefreshKind { processes: Option, memory: Option, cpu: Option, } impl RefreshKind { /// Creates a new `RefreshKind` with every refresh set to `false`/`None`. /// /// ``` /// use sysinfo::RefreshKind; /// /// let r = RefreshKind::nothing(); /// /// assert_eq!(r.processes().is_some(), false); /// assert_eq!(r.memory().is_some(), false); /// assert_eq!(r.cpu().is_some(), false); /// ``` pub fn nothing() -> Self { Self::default() } /// Creates a new `RefreshKind` with every refresh set to `true`/`Some(...)`. /// /// ``` /// use sysinfo::RefreshKind; /// /// let r = RefreshKind::everything(); /// /// assert_eq!(r.processes().is_some(), true); /// assert_eq!(r.memory().is_some(), true); /// assert_eq!(r.cpu().is_some(), true); /// ``` pub fn everything() -> Self { Self { processes: Some(ProcessRefreshKind::everything()), memory: Some(MemoryRefreshKind::everything()), cpu: Some(CpuRefreshKind::everything()), } } impl_get_set!( RefreshKind, processes, with_processes, without_processes, ProcessRefreshKind ); impl_get_set!( RefreshKind, memory, with_memory, without_memory, MemoryRefreshKind ); impl_get_set!(RefreshKind, cpu, with_cpu, without_cpu, CpuRefreshKind); } /// Returns the pid for the current process. /// /// `Err` is returned in case the platform isn't supported. /// /// ```no_run /// use sysinfo::get_current_pid; /// /// match get_current_pid() { /// Ok(pid) => { /// println!("current pid: {}", pid); /// } /// Err(e) => { /// println!("failed to get current pid: {}", e); /// } /// } /// ``` #[allow(clippy::unnecessary_wraps)] pub fn get_current_pid() -> Result { cfg_if! { if #[cfg(feature = "unknown-ci")] { fn inner() -> Result { Err("Unknown platform (CI)") } } else if #[cfg(any( target_os = "freebsd", target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios", ))] { fn inner() -> Result { unsafe { Ok(Pid(libc::getpid())) } } } else if #[cfg(windows)] { fn inner() -> Result { use windows::Win32::System::Threading::GetCurrentProcessId; unsafe { Ok(Pid(GetCurrentProcessId() as _)) } } } else { fn inner() -> Result { Err("Unknown platform") } } } inner() } /// Contains all the methods of the [`Cpu`][crate::Cpu] struct. /// /// ```no_run /// use sysinfo::{System, RefreshKind, CpuRefreshKind}; /// /// let mut s = System::new_with_specifics( /// RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()), /// ); /// /// // Wait a bit because CPU usage is based on diff. /// std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); /// // Refresh CPUs again to get actual value. /// s.refresh_cpu_all(); /// /// for cpu in s.cpus() { /// println!("{}%", cpu.cpu_usage()); /// } /// ``` pub struct Cpu { pub(crate) inner: CpuInner, } impl Cpu { /// Returns this CPU's usage. /// /// Note: You'll need to refresh it at least twice (diff between the first and the second is /// how CPU usage is computed) at first if you want to have a non-zero value. /// /// ```no_run /// use sysinfo::{System, RefreshKind, CpuRefreshKind}; /// /// let mut s = System::new_with_specifics( /// RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()), /// ); /// /// // Wait a bit because CPU usage is based on diff. /// std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); /// // Refresh CPUs again to get actual value. /// s.refresh_cpu_all(); /// /// for cpu in s.cpus() { /// println!("{}%", cpu.cpu_usage()); /// } /// ``` pub fn cpu_usage(&self) -> f32 { self.inner.cpu_usage() } /// Returns this CPU's name. /// /// ```no_run /// use sysinfo::{System, RefreshKind, CpuRefreshKind}; /// /// let s = System::new_with_specifics( /// RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()), /// ); /// for cpu in s.cpus() { /// println!("{}", cpu.name()); /// } /// ``` pub fn name(&self) -> &str { self.inner.name() } /// Returns the CPU's vendor id. /// /// ```no_run /// use sysinfo::{System, RefreshKind, CpuRefreshKind}; /// /// let s = System::new_with_specifics( /// RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()), /// ); /// for cpu in s.cpus() { /// println!("{}", cpu.vendor_id()); /// } /// ``` pub fn vendor_id(&self) -> &str { self.inner.vendor_id() } /// Returns the CPU's brand. /// /// ```no_run /// use sysinfo::{System, RefreshKind, CpuRefreshKind}; /// /// let s = System::new_with_specifics( /// RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()), /// ); /// for cpu in s.cpus() { /// println!("{}", cpu.brand()); /// } /// ``` pub fn brand(&self) -> &str { self.inner.brand() } /// Returns the CPU's frequency. /// /// ```no_run /// use sysinfo::{System, RefreshKind, CpuRefreshKind}; /// /// let s = System::new_with_specifics( /// RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()), /// ); /// for cpu in s.cpus() { /// println!("{}", cpu.frequency()); /// } /// ``` pub fn frequency(&self) -> u64 { self.inner.frequency() } } #[cfg(test)] mod test { use crate::*; use std::str::FromStr; // In case `Process::updated` is misused, `System::refresh_processes` might remove them // so this test ensures that it doesn't happen. #[test] fn check_refresh_process_update() { if !IS_SUPPORTED_SYSTEM { return; } let mut s = System::new_all(); let total = s.processes().len() as isize; s.refresh_processes(ProcessesToUpdate::All, false); let new_total = s.processes().len() as isize; // There should be almost no difference in the processes count. assert!( (new_total - total).abs() <= 5, "{} <= 5", (new_total - total).abs() ); } #[test] fn check_cpu_arch() { assert!(!System::cpu_arch().is_empty()); } // Ensure that the CPUs frequency isn't retrieved until we ask for it. #[test] fn check_cpu_frequency() { if !IS_SUPPORTED_SYSTEM { return; } let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::All, false); for proc_ in s.cpus() { assert_eq!(proc_.frequency(), 0); } s.refresh_cpu_usage(); for proc_ in s.cpus() { assert_eq!(proc_.frequency(), 0); } // In a VM, it'll fail. if std::env::var("APPLE_CI").is_err() && std::env::var("FREEBSD_CI").is_err() { s.refresh_cpu_specifics(CpuRefreshKind::everything()); for proc_ in s.cpus() { assert_ne!(proc_.frequency(), 0); } } } #[test] fn check_process_memory_usage() { let mut s = System::new(); s.refresh_specifics(RefreshKind::everything()); if IS_SUPPORTED_SYSTEM { // No process should have 0 as memory usage. #[cfg(not(feature = "apple-sandbox"))] assert!(!s.processes().iter().all(|(_, proc_)| proc_.memory() == 0)); } else { // There should be no process, but if there is one, its memory usage should be 0. assert!(s.processes().iter().all(|(_, proc_)| proc_.memory() == 0)); } } #[test] fn check_system_implemented_traits() { fn check(_: T) {} check(System::new()); } #[test] fn check_memory_usage() { let mut s = System::new(); assert_eq!(s.total_memory(), 0); assert_eq!(s.free_memory(), 0); assert_eq!(s.available_memory(), 0); assert_eq!(s.used_memory(), 0); assert_eq!(s.total_swap(), 0); assert_eq!(s.free_swap(), 0); assert_eq!(s.used_swap(), 0); s.refresh_memory(); if IS_SUPPORTED_SYSTEM { assert!(s.total_memory() > 0); assert!(s.used_memory() > 0); if s.total_swap() > 0 { // I think it's pretty safe to assume that there is still some swap left... assert!(s.free_swap() > 0); } } else { assert_eq!(s.total_memory(), 0); assert_eq!(s.used_memory(), 0); assert_eq!(s.total_swap(), 0); assert_eq!(s.free_swap(), 0); } } #[cfg(target_os = "linux")] #[test] fn check_processes_cpu_usage() { if !IS_SUPPORTED_SYSTEM { return; } let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::All, false); // All CPU usage will start at zero until the second refresh assert!( s.processes() .iter() .all(|(_, proc_)| proc_.cpu_usage() == 0.0) ); // Wait a bit to update CPU usage values std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL); s.refresh_processes(ProcessesToUpdate::All, true); assert!( s.processes() .iter() .all(|(_, proc_)| proc_.cpu_usage() >= 0.0 && proc_.cpu_usage() <= (s.cpus().len() as f32) * 100.0) ); assert!( s.processes() .iter() .any(|(_, proc_)| proc_.cpu_usage() > 0.0) ); } #[test] fn check_cpu_usage() { if !IS_SUPPORTED_SYSTEM { return; } let mut s = System::new(); for _ in 0..10 { s.refresh_cpu_usage(); // Wait a bit to update CPU usage values std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL); if s.cpus().iter().any(|c| c.cpu_usage() > 0.0) { // All good! return; } } panic!("CPU usage is always zero..."); } #[test] fn check_system_info() { // We don't want to test on unsupported systems. if IS_SUPPORTED_SYSTEM { assert!( !System::name() .expect("Failed to get system name") .is_empty() ); assert!( !System::kernel_version() .expect("Failed to get kernel version") .is_empty() ); assert!( !System::os_version() .expect("Failed to get os version") .is_empty() ); assert!( !System::long_os_version() .expect("Failed to get long OS version") .is_empty() ); } assert!(!System::distribution_id().is_empty()); } #[test] fn check_host_name() { // We don't want to test on unsupported systems. if IS_SUPPORTED_SYSTEM { assert!(System::host_name().is_some()); } } #[test] fn check_refresh_process_return_value() { // We don't want to test on unsupported systems. if IS_SUPPORTED_SYSTEM { let _pid = get_current_pid().expect("Failed to get current PID"); #[cfg(not(feature = "apple-sandbox"))] { let mut s = System::new(); // First check what happens in case the process isn't already in our process list. assert_eq!( s.refresh_processes(ProcessesToUpdate::Some(&[_pid]), true), 1 ); // Then check that it still returns 1 if the process is already in our process list. assert_eq!( s.refresh_processes(ProcessesToUpdate::Some(&[_pid]), true), 1 ); } } } #[test] fn check_cpus_number() { let mut s = System::new(); // This information isn't retrieved by default. assert!(s.cpus().is_empty()); if IS_SUPPORTED_SYSTEM { // The physical cores count is recomputed every time the function is called, so the // information must be relevant even with nothing initialized. let physical_cores_count = System::physical_core_count().expect("failed to get number of physical cores"); s.refresh_cpu_usage(); // The cpus shouldn't be empty anymore. assert!(!s.cpus().is_empty()); // In case we are running inside a VM, it's possible to not have a physical core, only // logical ones, which is why we don't test `physical_cores_count > 0`. let physical_cores_count2 = System::physical_core_count().expect("failed to get number of physical cores"); assert!(physical_cores_count2 <= s.cpus().len()); assert_eq!(physical_cores_count, physical_cores_count2); } else { assert_eq!(System::physical_core_count(), None); } assert!(System::physical_core_count().unwrap_or(0) <= s.cpus().len()); } // This test only exists to ensure that the `Display` and `Debug` traits are implemented on the // `ProcessStatus` enum on all targets. #[test] fn check_display_impl_process_status() { println!("{} {:?}", ProcessStatus::Parked, ProcessStatus::Idle); } #[test] #[allow(clippy::unnecessary_fallible_conversions)] fn check_pid_from_impls() { assert!(crate::Pid::try_from(0usize).is_ok()); // If it doesn't panic, it's fine. let _ = crate::Pid::from(0); assert!(crate::Pid::from_str("0").is_ok()); } #[test] #[allow(clippy::const_is_empty)] fn check_nb_supported_signals() { if IS_SUPPORTED_SYSTEM { assert!( !SUPPORTED_SIGNALS.is_empty(), "SUPPORTED_SIGNALS shouldn't be empty on supported systems!" ); } else { assert!( SUPPORTED_SIGNALS.is_empty(), "SUPPORTED_SIGNALS should be empty on not support systems!" ); } } } #[cfg(doctest)] mod doctest { // FIXME: Can be removed once negative trait bounds are supported. /// Check that `Process` doesn't implement `Clone`. /// /// First we check that the "basic" code works: /// /// ```no_run /// use sysinfo::{Process, System}; /// /// let mut s = System::new_all(); /// let p: &Process = s.processes().values().next().unwrap(); /// ``` /// /// And now we check if it fails when we try to clone it: /// /// ```compile_fail /// use sysinfo::{Process, System}; /// /// let mut s = System::new_all(); /// let p: &Process = s.processes().values().next().unwrap(); /// let p = (*p).clone(); /// ``` mod process_clone {} // FIXME: Can be removed once negative trait bounds are supported. /// Check that `System` doesn't implement `Clone`. /// /// First we check that the "basic" code works: /// /// ```no_run /// use sysinfo::{Process, System}; /// /// let s = System::new(); /// ``` /// /// And now we check if it fails when we try to clone it: /// /// ```compile_fail /// use sysinfo::{Process, System}; /// /// let s = System::new(); /// let s = s.clone(); /// ``` mod system_clone {} } GuillaumeGomez-sysinfo-067dd61/src/common/user.rs000066400000000000000000000311741506747262600221150ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::cmp::Ordering; use crate::{Gid, Uid, UserInner}; /// Type containing user information. /// /// It is returned by [`Users`][crate::Users]. /// /// ```no_run /// use sysinfo::Users; /// /// let users = Users::new_with_refreshed_list(); /// for user in users.list() { /// println!("{:?}", user); /// } /// ``` pub struct User { pub(crate) inner: UserInner, } impl PartialEq for User { fn eq(&self, other: &Self) -> bool { self.id() == other.id() && self.group_id() == other.group_id() && self.name() == other.name() } } impl Eq for User {} impl PartialOrd for User { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for User { fn cmp(&self, other: &Self) -> Ordering { self.name().cmp(other.name()) } } impl User { /// Returns the ID of the user. /// /// ```no_run /// use sysinfo::Users; /// /// let users = Users::new_with_refreshed_list(); /// for user in users.list() { /// println!("{:?}", *user.id()); /// } /// ``` pub fn id(&self) -> &Uid { self.inner.id() } /// Returns the group ID of the user. /// /// ⚠️ This information is not set on Windows. Windows doesn't have a `username` specific /// group assigned to the user. They do however have unique /// [Security Identifiers](https://docs.microsoft.com/en-us/windows/win32/secauthz/security-identifiers) /// made up of various [Components](https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-components). /// Pieces of the SID may be a candidate for this field, but it doesn't map well to a single /// group ID. /// /// ```no_run /// use sysinfo::Users; /// /// let users = Users::new_with_refreshed_list(); /// for user in users.list() { /// println!("{}", *user.group_id()); /// } /// ``` pub fn group_id(&self) -> Gid { self.inner.group_id() } /// Returns the name of the user. /// /// ```no_run /// use sysinfo::Users; /// /// let users = Users::new_with_refreshed_list(); /// for user in users.list() { /// println!("{}", user.name()); /// } /// ``` pub fn name(&self) -> &str { self.inner.name() } /// Returns the groups of the user. /// /// ⚠️ This is computed every time this method is called. /// /// ```no_run /// use sysinfo::Users; /// /// let users = Users::new_with_refreshed_list(); /// for user in users.list() { /// println!("{} is in {:?}", user.name(), user.groups()); /// } /// ``` pub fn groups(&self) -> Vec { self.inner.groups() } } #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub(crate) struct GroupInner { pub(crate) id: Gid, pub(crate) name: String, } /// Type containing group information. /// /// It is returned by [`User::groups`] or [`Groups::list`]. /// /// ```no_run /// use sysinfo::Users; /// /// let mut users = Users::new_with_refreshed_list(); /// /// for user in users.list() { /// println!( /// "user: (ID: {:?}, group ID: {:?}, name: {:?})", /// user.id(), /// user.group_id(), /// user.name(), /// ); /// for group in user.groups() { /// println!("group: (ID: {:?}, name: {:?})", group.id(), group.name()); /// } /// } /// ``` #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Group { pub(crate) inner: GroupInner, } impl Group { /// Returns the ID of the group. /// /// ⚠️ This information is not set on Windows. /// /// ```no_run /// use sysinfo::Users; /// /// let mut users = Users::new_with_refreshed_list(); /// /// for user in users.list() { /// for group in user.groups() { /// println!("{:?}", group.id()); /// } /// } /// ``` pub fn id(&self) -> &Gid { self.inner.id() } /// Returns the name of the group. /// /// ```no_run /// use sysinfo::Users; /// /// let mut users = Users::new_with_refreshed_list(); /// /// for user in users.list() { /// for group in user.groups() { /// println!("{}", group.name()); /// } /// } /// ``` pub fn name(&self) -> &str { self.inner.name() } } /// Interacting with users. /// /// ```no_run /// use sysinfo::Users; /// /// let mut users = Users::new(); /// for user in users.list() { /// println!("{} is in {} groups", user.name(), user.groups().len()); /// } /// ``` pub struct Users { users: Vec, } impl Default for Users { fn default() -> Self { Self::new() } } impl From for Vec { fn from(users: Users) -> Self { users.users } } impl From> for Users { fn from(users: Vec) -> Self { Self { users } } } impl std::ops::Deref for Users { type Target = [User]; fn deref(&self) -> &Self::Target { self.list() } } impl std::ops::DerefMut for Users { fn deref_mut(&mut self) -> &mut Self::Target { self.list_mut() } } impl<'a> IntoIterator for &'a Users { type Item = &'a User; type IntoIter = std::slice::Iter<'a, User>; fn into_iter(self) -> Self::IntoIter { self.list().iter() } } impl<'a> IntoIterator for &'a mut Users { type Item = &'a mut User; type IntoIter = std::slice::IterMut<'a, User>; fn into_iter(self) -> Self::IntoIter { self.list_mut().iter_mut() } } impl Users { /// Creates a new empty [`Users`][crate::Users] type. /// /// If you want it to be filled directly, take a look at [`Users::new_with_refreshed_list`]. /// /// ```no_run /// use sysinfo::Users; /// /// let mut users = Users::new(); /// users.refresh(); /// for user in users.list() { /// println!("{user:?}"); /// } /// ``` pub fn new() -> Self { Self { users: Vec::new() } } /// Creates a new [`Users`][crate::Users] type with the user list loaded. /// /// ```no_run /// use sysinfo::Users; /// /// let mut users = Users::new_with_refreshed_list(); /// for user in users.list() { /// println!("{user:?}"); /// } /// ``` pub fn new_with_refreshed_list() -> Self { let mut users = Self::new(); users.refresh(); users } /// Returns the users list. /// /// ```no_run /// use sysinfo::Users; /// /// let users = Users::new_with_refreshed_list(); /// for user in users.list() { /// println!("{user:?}"); /// } /// ``` pub fn list(&self) -> &[User] { &self.users } /// Returns the users list. /// /// ```no_run /// use sysinfo::Users; /// /// let mut users = Users::new_with_refreshed_list(); /// users.list_mut().sort_by(|user1, user2| { /// user1.name().partial_cmp(user2.name()).unwrap() /// }); /// ``` pub fn list_mut(&mut self) -> &mut [User] { &mut self.users } /// The user list will be emptied then completely recomputed. /// /// ```no_run /// use sysinfo::Users; /// /// let mut users = Users::new(); /// users.refresh(); /// ``` pub fn refresh(&mut self) { crate::sys::get_users(&mut self.users); } /// Returns the [`User`] matching the given `user_id`. /// /// **Important**: The user list must be filled before using this method, otherwise it will /// always return `None` (through the `refresh_*` methods). /// /// It is a shorthand for: /// /// ```ignore /// # use sysinfo::Users; /// let users = Users::new_with_refreshed_list(); /// users.list().find(|user| user.id() == user_id); /// ``` /// /// Full example: /// #[cfg_attr(feature = "system", doc = "```no_run")] #[cfg_attr(not(feature = "system"), doc = "```ignore")] /// use sysinfo::{Pid, System, Users}; /// /// let mut s = System::new_all(); /// let users = Users::new_with_refreshed_list(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// if let Some(user_id) = process.user_id() { /// println!("User for process 1337: {:?}", users.get_user_by_id(user_id)); /// } /// } /// ``` pub fn get_user_by_id(&self, user_id: &Uid) -> Option<&User> { self.users.iter().find(|user| user.id() == user_id) } } /// Interacting with groups. /// /// ```no_run /// use sysinfo::Groups; /// /// let mut groups = Groups::new(); /// for group in groups.list() { /// println!("{}", group.name()); /// } /// ``` pub struct Groups { groups: Vec, } impl Default for Groups { fn default() -> Self { Self::new() } } impl From for Vec { fn from(groups: Groups) -> Self { groups.groups } } impl From> for Groups { fn from(groups: Vec) -> Self { Self { groups } } } impl std::ops::Deref for Groups { type Target = [Group]; fn deref(&self) -> &Self::Target { self.list() } } impl std::ops::DerefMut for Groups { fn deref_mut(&mut self) -> &mut Self::Target { self.list_mut() } } impl<'a> IntoIterator for &'a Groups { type Item = &'a Group; type IntoIter = std::slice::Iter<'a, Group>; fn into_iter(self) -> Self::IntoIter { self.list().iter() } } impl<'a> IntoIterator for &'a mut Groups { type Item = &'a mut Group; type IntoIter = std::slice::IterMut<'a, Group>; fn into_iter(self) -> Self::IntoIter { self.list_mut().iter_mut() } } impl Groups { /// Creates a new empty [`Groups`][crate::Groups] type. /// /// If you want it to be filled directly, take a look at [`Groups::new_with_refreshed_list`]. /// /// ```no_run /// use sysinfo::Groups; /// /// let mut groups = Groups::new(); /// groups.refresh(); /// for group in groups.list() { /// println!("{group:?}"); /// } /// ``` pub fn new() -> Self { Self { groups: Vec::new() } } /// Creates a new [`Groups`][crate::Groups] type with the group list loaded. /// /// ```no_run /// use sysinfo::Groups; /// /// let mut groups = Groups::new_with_refreshed_list(); /// for group in groups.list() { /// println!("{group:?}"); /// } /// ``` pub fn new_with_refreshed_list() -> Self { let mut groups = Self::new(); groups.refresh(); groups } /// Returns the groups list. /// /// ```no_run /// use sysinfo::Groups; /// /// let groups = Groups::new_with_refreshed_list(); /// for group in groups.list() { /// println!("{group:?}"); /// } /// ``` pub fn list(&self) -> &[Group] { &self.groups } /// Returns the groups list. /// /// ```no_run /// use sysinfo::Groups; /// /// let mut groups = Groups::new_with_refreshed_list(); /// groups.list_mut().sort_by(|user1, user2| { /// user1.name().partial_cmp(user2.name()).unwrap() /// }); /// ``` pub fn list_mut(&mut self) -> &mut [Group] { &mut self.groups } /// The group list will be emptied then completely recomputed. /// /// ```no_run /// use sysinfo::Groups; /// /// let mut groups = Groups::new(); /// groups.refresh(); /// ``` pub fn refresh(&mut self) { crate::sys::get_groups(&mut self.groups); } } #[cfg(test)] mod tests { use crate::*; #[test] fn check_list() { let mut users = Users::new(); assert!(users.list().is_empty()); users.refresh(); assert!(users.list().len() >= MIN_USERS); } // This test exists to ensure that the `TryFrom` and `FromStr` traits are implemented // on `Uid`, `Gid` and `Pid`. #[allow(clippy::unnecessary_fallible_conversions)] #[test] fn check_uid_gid_from_impls() { use std::convert::TryFrom; use std::str::FromStr; #[cfg(not(windows))] { assert!(crate::Uid::try_from(0usize).is_ok()); assert!(crate::Uid::from_str("0").is_ok()); } #[cfg(windows)] { assert!(crate::Uid::from_str("S-1-5-18").is_ok()); // SECURITY_LOCAL_SYSTEM_RID assert!(crate::Uid::from_str("0").is_err()); } assert!(crate::Gid::try_from(0usize).is_ok()); assert!(crate::Gid::from_str("0").is_ok()); } #[test] fn check_groups() { if !crate::IS_SUPPORTED_SYSTEM { return; } assert!(!Groups::new_with_refreshed_list().is_empty()); } } GuillaumeGomez-sysinfo-067dd61/src/debug.rs000066400000000000000000000150771506747262600207410ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "system")] impl std::fmt::Debug for crate::Cpu { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Cpu") .field("name", &self.name()) .field("CPU usage", &self.cpu_usage()) .field("frequency", &self.frequency()) .field("vendor ID", &self.vendor_id()) .field("brand", &self.brand()) .finish() } } #[cfg(feature = "system")] impl std::fmt::Debug for crate::System { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("System") .field("global CPU usage", &self.global_cpu_usage()) .field("load average", &Self::load_average()) .field("total memory", &self.total_memory()) .field("free memory", &self.free_memory()) .field("total swap", &self.total_swap()) .field("free swap", &self.free_swap()) .field("nb CPUs", &self.cpus().len()) .field("nb processes", &self.processes().len()) .finish() } } #[cfg(feature = "system")] impl std::fmt::Debug for crate::Motherboard { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Motherboard") .field("name", &self.name()) .field("vendor_name", &self.vendor_name()) .field("version", &self.version()) .field("serial_number", &self.serial_number()) .field("asset_tag", &self.asset_tag()) .finish() } } #[cfg(feature = "system")] impl std::fmt::Debug for crate::Product { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Product") .field("name", &Self::name()) .field("family", &Self::family()) .field("serial_number", &Self::serial_number()) .field("stock_keeping_unit", &Self::stock_keeping_unit()) .field("uuid", &Self::uuid()) .field("version", &Self::version()) .field("vendor_name", &Self::vendor_name()) .finish() } } #[cfg(feature = "system")] impl std::fmt::Debug for crate::Process { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Process") .field("pid", &self.pid()) .field("parent", &self.parent()) .field("name", &self.name()) .field("environ", &self.environ()) .field("command", &self.cmd()) .field("executable path", &self.exe()) .field("current working directory", &self.cwd()) .field("memory usage", &self.memory()) .field("virtual memory usage", &self.virtual_memory()) .field("CPU usage", &self.cpu_usage()) .field("accumulated CPU time", &self.accumulated_cpu_time()) .field("status", &self.status()) .field("root", &self.root()) .field("disk_usage", &self.disk_usage()) .field("user_id", &self.user_id()) .field("effective_user_id", &self.effective_user_id()) .finish() } } #[cfg(feature = "disk")] impl std::fmt::Debug for crate::Disk { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( fmt, "Disk({:?})[FS: {:?}][Type: {:?}][removable: {}][I/O: {:?}] mounted on {:?}: {}/{} B", self.name(), self.file_system(), self.kind(), if self.is_removable() { "yes" } else { "no" }, self.usage(), self.mount_point(), self.available_space(), self.total_space(), ) } } #[cfg(feature = "disk")] impl std::fmt::Debug for crate::Disks { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.iter()).finish() } } #[cfg(feature = "component")] impl std::fmt::Debug for crate::Components { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.iter()).finish() } } #[cfg(feature = "component")] impl std::fmt::Debug for crate::Component { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} ", self.label())?; if let Some(temperature) = self.temperature() { write!(f, "temperature: {temperature}°C (")?; } else { f.write_str("temperature: unknown (")?; } if let Some(max) = self.max() { write!(f, "max: {max}°C / ")?; } else { f.write_str("max: unknown / ")?; } if let Some(critical) = self.critical() { write!(f, "critical: {critical}°C)") } else { f.write_str("critical: unknown)") } } } #[cfg(feature = "network")] impl std::fmt::Debug for crate::Networks { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.iter()).finish() } } #[cfg(feature = "network")] impl std::fmt::Debug for crate::NetworkData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("NetworkData") .field("income", &self.received()) .field("total income", &self.total_received()) .field("outcome", &self.transmitted()) .field("total outcome", &self.total_transmitted()) .field("packets income", &self.packets_received()) .field("total packets income", &self.total_packets_received()) .field("packets outcome", &self.packets_transmitted()) .field("total packets outcome", &self.total_packets_transmitted()) .field("errors income", &self.errors_on_received()) .field("total errors income", &self.total_errors_on_received()) .field("errors outcome", &self.errors_on_transmitted()) .field("total errors outcome", &self.total_errors_on_transmitted()) .field("maximum transfer unit", &self.mtu()) .finish() } } #[cfg(feature = "user")] impl std::fmt::Debug for crate::Users { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.iter()).finish() } } #[cfg(feature = "user")] impl std::fmt::Debug for crate::User { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("User") .field("uid", &self.id()) .field("gid", &self.group_id()) .field("name", &self.name()) .finish() } } GuillaumeGomez-sysinfo-067dd61/src/lib.rs000066400000000000000000000311351506747262600204120ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![cfg_attr( all(feature = "system", feature = "disk", feature = "component", feature = "system"), doc = include_str!("../README.md") )] #![cfg_attr( not(all( feature = "system", feature = "disk", feature = "component", feature = "system" )), doc = "For crate-level documentation, all features need to be enabled." )] #![cfg_attr(feature = "serde", doc = include_str!("../md_doc/serde.md"))] #![allow(unknown_lints)] #![deny(missing_docs)] #![deny(rustdoc::broken_intra_doc_links)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::non_send_fields_in_send_ty)] #![allow(renamed_and_removed_lints)] #![allow(clippy::assertions_on_constants)] #[macro_use] mod macros; cfg_if! { if #[cfg(feature = "unknown-ci")] { // This is used in CI to check that the build for unknown targets is compiling fine. mod unknown; use crate::unknown as sys; #[cfg(test)] pub(crate) const MIN_USERS: usize = 0; } else if #[cfg(any( target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android", target_os = "freebsd"))] { mod unix; use crate::unix::sys as sys; #[cfg(feature = "network")] mod network; #[cfg(feature = "network")] use crate::unix::network_helper; #[cfg(test)] pub(crate) const MIN_USERS: usize = 1; } else if #[cfg(windows)] { mod windows; use crate::windows as sys; #[cfg(feature = "network")] mod network; #[cfg(feature = "network")] use crate::windows::network_helper; #[cfg(test)] pub(crate) const MIN_USERS: usize = 1; } else { mod unknown; use crate::unknown as sys; #[cfg(test)] pub(crate) const MIN_USERS: usize = 0; } } #[cfg(feature = "component")] pub use crate::common::component::{Component, Components}; #[cfg(feature = "disk")] pub use crate::common::disk::{Disk, DiskKind, DiskRefreshKind, Disks}; #[cfg(feature = "network")] pub use crate::common::network::{ IpNetwork, IpNetworkFromStrError, MacAddr, MacAddrFromStrError, NetworkData, Networks, }; #[cfg(feature = "system")] pub use crate::common::system::{ CGroupLimits, Cpu, CpuRefreshKind, KillError, LoadAvg, MemoryRefreshKind, Motherboard, Pid, Process, ProcessRefreshKind, ProcessStatus, ProcessesToUpdate, Product, RefreshKind, Signal, System, ThreadKind, UpdateKind, get_current_pid, }; #[cfg(feature = "user")] pub use crate::common::user::{Group, Groups, User, Users}; #[cfg(any(feature = "user", feature = "system"))] pub use crate::common::{Gid, Uid}; #[cfg(feature = "system")] pub use crate::sys::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS}; #[cfg(any(feature = "system", feature = "disk"))] pub use crate::common::DiskUsage; #[cfg(feature = "user")] pub(crate) use crate::common::user::GroupInner; #[cfg(feature = "user")] pub(crate) use crate::sys::UserInner; #[cfg(feature = "component")] pub(crate) use crate::sys::{ComponentInner, ComponentsInner}; #[cfg(feature = "system")] pub(crate) use crate::sys::{CpuInner, MotherboardInner, ProcessInner, ProductInner, SystemInner}; #[cfg(feature = "disk")] pub(crate) use crate::sys::{DiskInner, DisksInner}; #[cfg(feature = "network")] pub(crate) use crate::sys::{NetworkDataInner, NetworksInner}; pub use crate::sys::IS_SUPPORTED_SYSTEM; #[cfg(feature = "c-interface")] pub use crate::c_interface::*; #[cfg(feature = "c-interface")] mod c_interface; mod common; mod debug; #[cfg(feature = "serde")] mod serde; pub(crate) mod utils; // Make formattable by rustfmt. #[cfg(any())] mod network; #[cfg(any())] mod unix; #[cfg(any())] mod unknown; #[cfg(any())] mod windows; /// This function is only used on Linux targets, when the `system` feature is enabled. In other /// cases, it does nothing and returns `false`. /// /// On Linux, to improve performance, we keep a `/proc` file open for each process we index with /// a maximum number of files open equivalent to half of the system limit. /// /// The problem is that some users might need all the available file descriptors so we need to /// allow them to change this limit. /// /// Note that if you set a limit bigger than the system limit, the system limit will be set. /// /// Returns `true` if the new value has been set. /// #[cfg_attr(feature = "system", doc = "```no_run")] #[cfg_attr(not(feature = "system"), doc = "```ignore")] /// use sysinfo::{System, set_open_files_limit}; /// /// // We call the function before any call to the processes update. /// if !set_open_files_limit(10) { /// // It'll always return false on non-linux targets. /// eprintln!("failed to update the open files limit..."); /// } /// let s = System::new_all(); /// ``` pub fn set_open_files_limit(mut _new_limit: usize) -> bool { cfg_if! { if #[cfg(all(feature = "system", not(feature = "unknown-ci"), any(target_os = "linux", target_os = "android")))] { use crate::sys::system::remaining_files; use std::sync::atomic::Ordering; let max = sys::system::get_max_nb_fds(); if _new_limit > max { _new_limit = max; } // If files are already open, to be sure that the number won't be bigger when those // files are closed, we subtract the current number of opened files to the new // limit. remaining_files().fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| { let _new_limit = _new_limit as isize; let diff = (max as isize).saturating_sub(remaining); Some(_new_limit.saturating_sub(diff)) }).unwrap(); true } else { false } } } #[cfg(doctest)] mod doctest { macro_rules! compile_fail_import { ($mod_name:ident => $($imports:ident),+ $(,)?) => { $(#[doc = concat!(r"```compile_fail use sysinfo::", stringify!($imports), r"; ``` ")])+ mod $mod_name {} }; } #[cfg(not(feature = "system"))] compile_fail_import!( no_system_feature => get_current_pid, CGroupLimits, Cpu, CpuRefreshKind, DiskUsage, KillError, LoadAvg, MemoryRefreshKind, Motherboard, Pid, Process, ProcessesToUpdate, ProcessRefreshKind, ProcessStatus, Product, RefreshKind, Signal, System, ThreadKind, UpdateKind, ); #[cfg(not(feature = "disk"))] compile_fail_import!( no_disk_feature => Disk, Disks, DiskKind, ); #[cfg(not(feature = "component"))] compile_fail_import!( no_component_feature => Component, Components, ); #[cfg(not(feature = "network"))] compile_fail_import!( no_network_feature => IpNetwork, MacAddr, NetworkData, Networks, ); #[cfg(not(feature = "user"))] compile_fail_import!( no_user_feature => Group, Groups, User, Users, ); } #[cfg(test)] mod test { use crate::*; #[cfg(feature = "unknown-ci")] #[test] fn check_unknown_ci_feature() { assert!(!IS_SUPPORTED_SYSTEM); } // If this test doesn't compile, it means the current OS doesn't implement them correctly. #[test] fn check_macro_types() { fn check_is_supported(_: bool) {} check_is_supported(IS_SUPPORTED_SYSTEM); } // If this test doesn't compile, it means the current OS doesn't implement them correctly. #[cfg(feature = "system")] #[test] fn check_macro_types2() { fn check_supported_signals(_: &'static [Signal]) {} fn check_minimum_cpu_update_interval(_: std::time::Duration) {} check_supported_signals(SUPPORTED_SIGNALS); check_minimum_cpu_update_interval(MINIMUM_CPU_UPDATE_INTERVAL); } #[cfg(feature = "user")] #[test] fn check_uid_gid() { let mut users = Users::new(); assert!(users.list().is_empty()); users.refresh(); let user_list = users.list(); assert!(user_list.len() >= MIN_USERS); if IS_SUPPORTED_SYSTEM { #[cfg(not(target_os = "windows"))] { let user = user_list .iter() .find(|u| u.name() == "root") .expect("no root user"); assert_eq!(**user.id(), 0); assert_eq!(*user.group_id(), 0); if let Some(user) = users.iter().find(|u| *u.group_id() > 0) { assert!(**user.id() > 0); assert!(*user.group_id() > 0); } assert!(user_list.iter().filter(|u| **u.id() > 0).count() > 0); } #[cfg(feature = "system")] { // And now check that our `get_user_by_id` method works. let s = System::new_with_specifics(RefreshKind::nothing().with_processes( ProcessRefreshKind::nothing().with_user(UpdateKind::Always), )); assert!( s.processes() .iter() .filter_map(|(_, p)| p.user_id()) .any(|uid| users.get_user_by_id(uid).is_some()) ); } } } #[cfg(all(feature = "system", feature = "user"))] #[test] fn check_all_process_uids_resolvable() { // On linux, some user IDs don't have an associated user (no idea why though). // If `getent` doesn't find them, we can assume it's a dark secret from the linux land. if IS_SUPPORTED_SYSTEM && cfg!(not(target_os = "linux")) { let s = System::new_with_specifics( RefreshKind::nothing() .with_processes(ProcessRefreshKind::nothing().with_user(UpdateKind::Always)), ); let users = Users::new_with_refreshed_list(); // For every process where we can get a user ID, we should also be able // to find that user ID in the global user list for process in s.processes().values() { if let Some(uid) = process.user_id() { assert!(users.get_user_by_id(uid).is_some(), "No UID {uid:?} found"); } } } } #[test] fn ensure_is_supported_is_set_correctly() { if MIN_USERS > 0 { assert!(IS_SUPPORTED_SYSTEM); } else { assert!(!IS_SUPPORTED_SYSTEM); } } // If it doesn't compile, it means types don't implement expected traits. #[cfg(any( feature = "system", feature = "disk", feature = "component", feature = "user", feature = "network" ))] #[test] fn test_send_and_sync() { #[allow(dead_code)] trait HasSendAndSync: Send + Sync {} // Structs impl HasSendAndSync for CGroupLimits {} impl HasSendAndSync for Component {} impl HasSendAndSync for Components {} impl HasSendAndSync for Cpu {} impl HasSendAndSync for CpuRefreshKind {} impl HasSendAndSync for Disk {} impl HasSendAndSync for Disks {} impl HasSendAndSync for DiskRefreshKind {} impl HasSendAndSync for DiskUsage {} impl HasSendAndSync for Gid {} impl HasSendAndSync for Group {} impl HasSendAndSync for Groups {} impl HasSendAndSync for IpNetwork {} impl HasSendAndSync for LoadAvg {} impl HasSendAndSync for MacAddr {} impl HasSendAndSync for MemoryRefreshKind {} impl HasSendAndSync for NetworkData {} impl HasSendAndSync for Networks {} impl HasSendAndSync for Pid {} impl HasSendAndSync for Process {} impl HasSendAndSync for ProcessRefreshKind {} impl HasSendAndSync for Product {} impl HasSendAndSync for RefreshKind {} impl HasSendAndSync for System {} impl HasSendAndSync for Uid {} impl HasSendAndSync for User {} impl HasSendAndSync for Users {} // Enums impl HasSendAndSync for DiskKind {} impl HasSendAndSync for IpNetworkFromStrError {} impl HasSendAndSync for KillError {} impl HasSendAndSync for MacAddrFromStrError {} impl HasSendAndSync for ProcessStatus {} impl HasSendAndSync for ProcessesToUpdate<'_> {} impl HasSendAndSync for Signal {} impl HasSendAndSync for ThreadKind {} impl HasSendAndSync for UpdateKind {} } } GuillaumeGomez-sysinfo-067dd61/src/macros.rs000066400000000000000000000203031506747262600211230ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "debug")] #[doc(hidden)] #[allow(unused)] macro_rules! sysinfo_debug { ($($x:tt)*) => {{ eprintln!($($x)*); }} } #[cfg(not(feature = "debug"))] #[doc(hidden)] #[allow(unused)] macro_rules! sysinfo_debug { ($($x:tt)*) => {{}}; } #[cfg(feature = "system")] macro_rules! declare_signals { ($kind:ty, _ => None,) => ( use crate::Signal; pub(crate) const fn supported_signals() -> &'static [Signal] { &[] } ); ($kind:ty, $(Signal::$signal:ident => $map:expr,)+ _ => None,) => ( use crate::Signal; pub(crate) const fn supported_signals() -> &'static [Signal] { &[$(Signal::$signal,)*] } #[inline] pub(crate) fn convert_signal(s: Signal) -> Option<$kind> { match s { $(Signal::$signal => Some($map),)* _ => None, } } ); ($kind:ty, $(Signal::$signal:ident => $map:expr,)+) => ( use crate::Signal; pub(crate) const fn supported_signals() -> &'static [Signal] { &[$(Signal::$signal,)*] } #[inline] pub(crate) fn convert_signal(s: Signal) -> Option<$kind> { match s { $(Signal::$signal => Some($map),)* } } ) } #[cfg(all(unix, not(feature = "unknown-ci")))] #[allow(unused_macros)] macro_rules! retry_eintr { (set_to_0 => $($t:tt)+) => {{ #[allow(unused_unsafe)] let errno = unsafe { crate::unix::libc_errno() }; if !errno.is_null() { #[allow(unused_unsafe)] unsafe { *errno = 0; } } retry_eintr!($($t)+) }}; ($errno_value:ident => $($t:tt)+) => {{ loop { let ret = $($t)+; if ret < 0 { let tmp = std::io::Error::last_os_error(); if tmp.kind() == std::io::ErrorKind::Interrupted { continue; } $errno_value = tmp.raw_os_error().unwrap_or(0); } break ret; } }}; ($($t:tt)+) => {{ loop { let ret = $($t)+; if ret < 0 && std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted { continue; } break ret; } }}; } //FIXME: Remove this code if https://github.com/rust-lang/cfg-if/pull/78 is ever merged. macro_rules! cfg_if { // match if/else chains with a final `else` ( $( if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* } ) else+ else { $( $e_tokens:tt )* } ) => { cfg_if! { @__items () ; $( (( $i_meta ) ( $( $i_tokens )* )) , )+ (() ( $( $e_tokens )* )) , } }; // Allow to multiple conditions in a same call. ( $( if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* } ) else+ else { $( $e_tokens:tt )* } if $($extra_conditions:tt)+ ) => { cfg_if! { @__items () ; $( (( $i_meta ) ( $( $i_tokens )* )) , )+ (() ( $( $e_tokens )* )) , } cfg_if! { if $($extra_conditions)+ } }; // match if/else chains lacking a final `else` ( if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* } $( else if #[cfg( $e_meta:meta )] { $( $e_tokens:tt )* } )* ) => { cfg_if! { @__items () ; (( $i_meta ) ( $( $i_tokens )* )) , $( (( $e_meta ) ( $( $e_tokens )* )) , )* } }; // Allow to multiple conditions in a same call. ( if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* } $( else if #[cfg( $e_meta:meta )] { $( $e_tokens:tt )* } )* if $($extra_conditions:tt)+ ) => { cfg_if! { @__items () ; (( $i_meta ) ( $( $i_tokens )* )) , $( (( $e_meta ) ( $( $e_tokens )* )) , )* } cfg_if! { if $($extra_conditions)+ } }; // Internal and recursive macro to emit all the items // // Collects all the previous cfgs in a list at the beginning, so they can be // negated. After the semicolon is all the remaining items. (@__items ( $( $_:meta , )* ) ; ) => {}; ( @__items ( $( $no:meta , )* ) ; (( $( $yes:meta )? ) ( $( $tokens:tt )* )) , $( $rest:tt , )* ) => { // Emit all items within one block, applying an appropriate #[cfg]. The // #[cfg] will require all `$yes` matchers specified and must also negate // all previous matchers. #[cfg(all( $( $yes , )? not(any( $( $no ),* )) ))] cfg_if! { @__identity $( $tokens )* } // Recurse to emit all other items in `$rest`, and when we do so add all // our `$yes` matchers to the list of `$no` matchers as future emissions // will have to negate everything we just matched as well. cfg_if! { @__items ( $( $no , )* $( $yes , )? ) ; $( $rest , )* } }; // Internal macro to make __apply work out right for different match types, // because of how macros match/expand stuff. (@__identity $( $tokens:tt )* ) => { $( $tokens )* }; } #[cfg(test)] #[allow(unexpected_cfgs)] mod tests { cfg_if! { if #[cfg(test)] { use core::option::Option as Option2; fn works1() -> Option2 { Some(1) } } else { fn works1() -> Option { None } } } cfg_if! { if #[cfg(foo)] { fn works2() -> bool { false } } else if #[cfg(test)] { fn works2() -> bool { true } } else { fn works2() -> bool { false } } } cfg_if! { if #[cfg(foo)] { fn works3() -> bool { false } } else { fn works3() -> bool { true } } } cfg_if! { if #[cfg(test)] { use core::option::Option as Option3; fn works4() -> Option3 { Some(1) } } } cfg_if! { if #[cfg(foo)] { fn works5() -> bool { false } } else if #[cfg(test)] { fn works5() -> bool { true } } } cfg_if! { if #[cfg(foo)] { fn works6() -> bool { false } } else if #[cfg(test)] { fn works6() -> bool { true } } if #[cfg(test)] { fn works7() -> bool { true } } else { fn works7() -> bool { false } } } cfg_if! { if #[cfg(test)] { fn works8() -> bool { true } } else if #[cfg(foo)] { fn works8() -> bool { false } } if #[cfg(foo)] { fn works9() -> bool { false } } else if #[cfg(test)] { fn works9() -> bool { true } } } #[test] fn it_works() { assert!(works1().is_some()); assert!(works2()); assert!(works3()); assert!(works4().is_some()); assert!(works5()); assert!(works6()); assert!(works7()); assert!(works8()); assert!(works9()); } #[test] #[allow(clippy::assertions_on_constants)] fn test_usage_within_a_function() { cfg_if! {if #[cfg(debug_assertions)] { // we want to put more than one thing here to make sure that they // all get configured properly. assert!(cfg!(debug_assertions)); assert_eq!(4, 2+2); } else { assert!(works1().is_some()); assert_eq!(10, 5+5); }} } #[allow(dead_code)] trait Trait { fn blah(&self); } #[allow(dead_code)] struct Struct; impl Trait for Struct { cfg_if! { if #[cfg(feature = "blah")] { fn blah(&self) { unimplemented!(); } } else { fn blah(&self) { unimplemented!(); } } } } } GuillaumeGomez-sysinfo-067dd61/src/network.rs000066400000000000000000000020371506747262600213340ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::HashMap; use crate::NetworkData; use crate::network_helper::{get_interface_address, get_interface_ip_networks}; /// Interface addresses are OS-independent pub(crate) fn refresh_networks_addresses(interfaces: &mut HashMap) { let interface_networks = unsafe { get_interface_ip_networks() }; for (interface_name, ip_networks) in interface_networks { if let Some(interface) = interfaces.get_mut(&interface_name) { interface.inner.ip_networks = ip_networks.into_iter().collect::>(); } } match unsafe { get_interface_address() } { Ok(ifa_iterator) => { for (name, ifa) in ifa_iterator { if let Some(interface) = interfaces.get_mut(&name) { interface.inner.mac_addr = ifa; } } } Err(_e) => { sysinfo_debug!("refresh_networks_addresses failed: {:?}", _e); } } } GuillaumeGomez-sysinfo-067dd61/src/serde.rs000066400000000000000000000433521506747262600207520ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(any( feature = "component", feature = "disk", feature = "network", feature = "system", feature = "user" ))] use serde::{Serialize, Serializer, ser::SerializeStruct}; #[cfg(feature = "disk")] impl Serialize for crate::Disk { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `7` corresponds to the (maximum) number of fields. let mut state = serializer.serialize_struct("Disk", 7)?; state.serialize_field("DiskKind", &self.kind())?; if let Some(s) = self.name().to_str() { state.serialize_field("name", s)?; } state.serialize_field("file_system", &self.file_system())?; state.serialize_field("mount_point", &self.mount_point())?; state.serialize_field("total_space", &self.total_space())?; state.serialize_field("available_space", &self.available_space())?; state.serialize_field("is_removable", &self.is_removable())?; state.end() } } #[cfg(feature = "disk")] impl Serialize for crate::Disks { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_seq(self.iter()) } } #[cfg(feature = "disk")] impl Serialize for crate::DiskKind { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let (index, variant, maybe_value) = match *self { Self::HDD => (0, "HDD", None), Self::SSD => (1, "SSD", None), Self::Unknown(ref s) => (2, "Unknown", Some(s)), }; if let Some(ref value) = maybe_value { serializer.serialize_newtype_variant("DiskKind", index, variant, value) } else { serializer.serialize_unit_variant("DiskKind", index, variant) } } } #[cfg(feature = "system")] impl Serialize for crate::Pid { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_newtype_struct("Pid", &self.to_string()) } } #[cfg(feature = "system")] impl Serialize for crate::Process { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `19` corresponds to the (maximum) number of fields. let mut state = serializer.serialize_struct("Process", 19)?; state.serialize_field("name", &self.name().to_string_lossy())?; state.serialize_field("cmd", &self.cmd())?; state.serialize_field("exe", &self.exe())?; state.serialize_field("pid", &self.pid().as_u32())?; state.serialize_field("environ", &self.environ())?; state.serialize_field("cwd", &self.cwd())?; state.serialize_field("root", &self.root())?; state.serialize_field("memory", &self.memory())?; state.serialize_field("virtual_memory", &self.virtual_memory())?; state.serialize_field("parent", &self.parent())?; state.serialize_field("status", &self.status())?; state.serialize_field("start_time", &self.start_time())?; state.serialize_field("run_time", &self.run_time())?; state.serialize_field("cpu_usage", &self.cpu_usage())?; state.serialize_field("accumulated_cpu_time", &self.accumulated_cpu_time())?; state.serialize_field("disk_usage", &self.disk_usage())?; state.serialize_field("user_id", &self.user_id())?; state.serialize_field("group_id", &self.group_id())?; state.serialize_field("session_id", &self.session_id())?; state.end() } } #[cfg(feature = "system")] impl Serialize for crate::Cpu { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `5` corresponds to the number of fields. let mut state = serializer.serialize_struct("Cpu", 5)?; state.serialize_field("cpu_usage", &self.cpu_usage())?; state.serialize_field("name", &self.name())?; state.serialize_field("vendor_id", &self.vendor_id())?; state.serialize_field("brand", &self.brand())?; state.serialize_field("frequency", &self.frequency())?; state.end() } } #[cfg(feature = "system")] impl serde::Serialize for crate::System { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { // `19` corresponds to the number of fields. let mut state = serializer.serialize_struct("System", 19)?; state.serialize_field("global_cpu_usage", &self.global_cpu_usage())?; state.serialize_field("cpus", &self.cpus())?; state.serialize_field("physical_core_count", &Self::physical_core_count())?; state.serialize_field("total_memory", &self.total_memory())?; state.serialize_field("free_memory", &self.free_memory())?; state.serialize_field("available_memory", &self.available_memory())?; state.serialize_field("used_memory", &self.used_memory())?; state.serialize_field("total_swap", &self.total_swap())?; state.serialize_field("free_swap", &self.free_swap())?; state.serialize_field("used_swap", &self.used_swap())?; state.serialize_field("uptime", &Self::uptime())?; state.serialize_field("boot_time", &Self::boot_time())?; state.serialize_field("load_average", &Self::load_average())?; state.serialize_field("name", &Self::name())?; state.serialize_field("kernel_version", &Self::kernel_version())?; state.serialize_field("os_version", &Self::os_version())?; state.serialize_field("long_os_version", &Self::long_os_version())?; state.serialize_field("distribution_id", &Self::distribution_id())?; state.serialize_field("host_name", &Self::host_name())?; state.end() } } #[cfg(feature = "system")] impl serde::Serialize for crate::Motherboard { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { // `5` corresponds to the number of fields. let mut state = serializer.serialize_struct("Motherboard", 5)?; state.serialize_field("name", &self.name())?; state.serialize_field("vendor_name", &self.vendor_name())?; state.serialize_field("version", &self.version())?; state.serialize_field("serial_number", &self.serial_number())?; state.serialize_field("asset_tag", &self.asset_tag())?; state.end() } } #[cfg(feature = "system")] impl serde::Serialize for crate::Product { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { // `7` corresponds to the number of "fields". let mut state = serializer.serialize_struct("Product", 5)?; state.serialize_field("name", &Self::name())?; state.serialize_field("family", &Self::family())?; state.serialize_field("serial_number", &Self::serial_number())?; state.serialize_field("stock_keeping_unit", &Self::stock_keeping_unit())?; state.serialize_field("uuid", &Self::uuid())?; state.serialize_field("version", &Self::version())?; state.serialize_field("vendor_name", &Self::vendor_name())?; state.end() } } #[cfg(feature = "system")] impl Serialize for crate::CGroupLimits { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `3` corresponds to the number of fields. let mut state = serializer.serialize_struct("CGroupLimits", 3)?; state.serialize_field("total_memory", &self.total_memory)?; state.serialize_field("free_memory", &self.free_memory)?; state.serialize_field("free_swap", &self.free_swap)?; state.end() } } #[cfg(feature = "system")] impl Serialize for crate::ThreadKind { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let (index, variant) = match *self { Self::Kernel => (0, "Kernel"), Self::Userland => (1, "Userland"), }; serializer.serialize_unit_variant("ThreadKind", index, variant) } } #[cfg(feature = "system")] impl Serialize for crate::Signal { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let (index, variant) = match *self { Self::Hangup => (0, "Hangup"), Self::Interrupt => (1, "Interrupt"), Self::Quit => (2, "Quit"), Self::Illegal => (3, "Illegal"), Self::Trap => (4, "Trap"), Self::Abort => (5, "Abort"), Self::IOT => (6, "IOT"), Self::Bus => (7, "Bus"), Self::FloatingPointException => (8, "FloatingPointException"), Self::Kill => (9, "Kill"), Self::User1 => (10, "User1"), Self::Segv => (11, "Segv"), Self::User2 => (12, "User2"), Self::Pipe => (13, "Pipe"), Self::Alarm => (14, "Alarm"), Self::Term => (15, "Term"), Self::Child => (16, "Child"), Self::Continue => (17, "Continue"), Self::Stop => (18, "Stop"), Self::TSTP => (19, "TSTP"), Self::TTIN => (20, "TTIN"), Self::TTOU => (21, "TTOU"), Self::Urgent => (22, "Urgent"), Self::XCPU => (23, "XCPU"), Self::XFSZ => (24, "XFSZ"), Self::VirtualAlarm => (25, "VirtualAlarm"), Self::Profiling => (26, "Profiling"), Self::Winch => (27, "Winch"), Self::IO => (28, "IO"), Self::Poll => (29, "Poll"), Self::Power => (30, "Power"), Self::Sys => (31, "Sys"), }; serializer.serialize_unit_variant("Signal", index, variant) } } #[cfg(feature = "system")] impl Serialize for crate::LoadAvg { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `3` corresponds to the number of fields. let mut state = serializer.serialize_struct("LoadAvg", 3)?; state.serialize_field("one", &self.one)?; state.serialize_field("five", &self.five)?; state.serialize_field("fifteen", &self.fifteen)?; state.end() } } #[cfg(feature = "system")] impl Serialize for crate::ProcessStatus { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let (index, variant, maybe_value) = match *self { Self::Idle => (0, "Idle", None), Self::Run => (1, "Run", None), Self::Sleep => (2, "Sleep", None), Self::Stop => (3, "Stop", None), Self::Zombie => (4, "Zombie", None), Self::Tracing => (5, "Tracing", None), Self::Dead => (6, "Dead", None), Self::Wakekill => (7, "Wakekill", None), Self::Waking => (8, "Waking", None), Self::Parked => (9, "Parked", None), Self::LockBlocked => (10, "LockBlocked", None), Self::UninterruptibleDiskSleep => (11, "UninterruptibleDiskSleep", None), Self::Unknown(n) => (12, "Unknown", Some(n)), }; if let Some(ref value) = maybe_value { serializer.serialize_newtype_variant("ProcessStatus", index, variant, value) } else { serializer.serialize_unit_variant("ProcessStatus", index, variant) } } } #[cfg(feature = "system")] impl Serialize for crate::DiskUsage { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `4` corresponds to the number of fields. let mut state = serializer.serialize_struct("DiskUsage", 4)?; state.serialize_field("total_written_bytes", &self.total_written_bytes)?; state.serialize_field("written_bytes", &self.written_bytes)?; state.serialize_field("total_read_bytes", &self.total_read_bytes)?; state.serialize_field("read_bytes", &self.read_bytes)?; state.end() } } #[cfg(feature = "component")] impl Serialize for crate::Components { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_seq(self.iter()) } } #[cfg(feature = "component")] impl Serialize for crate::Component { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `4` corresponds to the number of fields. let mut state = serializer.serialize_struct("Component", 4)?; state.serialize_field("temperature", &self.temperature())?; state.serialize_field("max", &self.max())?; state.serialize_field("critical", &self.critical())?; state.serialize_field("label", &self.label())?; state.end() } } #[cfg(feature = "network")] impl Serialize for crate::Networks { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_seq(self.iter()) } } #[cfg(feature = "network")] impl Serialize for crate::NetworkData { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `14` corresponds to the number of fields. let mut state = serializer.serialize_struct("NetworkData", 14)?; state.serialize_field("received", &self.received())?; state.serialize_field("total_received", &self.total_received())?; state.serialize_field("transmitted", &self.transmitted())?; state.serialize_field("total_transmitted", &self.total_transmitted())?; state.serialize_field("packets_received", &self.packets_received())?; state.serialize_field("total_packets_received", &self.total_packets_received())?; state.serialize_field("packets_transmitted", &self.packets_transmitted())?; state.serialize_field( "total_packets_transmitted", &self.total_packets_transmitted(), )?; state.serialize_field("errors_on_received", &self.errors_on_received())?; state.serialize_field("total_errors_on_received", &self.total_errors_on_received())?; state.serialize_field("errors_on_transmitted", &self.errors_on_transmitted())?; state.serialize_field( "total_errors_on_transmitted", &self.total_errors_on_transmitted(), )?; state.serialize_field("mac_address", &self.mac_address())?; state.serialize_field("ip_networks", &self.ip_networks())?; state.serialize_field("mtu", &self.mtu())?; state.end() } } #[cfg(feature = "network")] impl Serialize for crate::MacAddr { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_newtype_struct("MacAddr", &self.0) } } #[cfg(feature = "network")] impl Serialize for crate::IpNetwork { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("IpNetwork", 2)?; state.serialize_field("addr", &self.addr)?; state.serialize_field("prefix", &self.prefix)?; state.end() } } #[cfg(feature = "user")] impl Serialize for crate::Users { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_seq(self.iter()) } } #[cfg(feature = "user")] impl Serialize for crate::User { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `4` corresponds to the number of fields. let mut state = serializer.serialize_struct("User", 4)?; state.serialize_field("id", &self.id())?; state.serialize_field("group_id", &self.group_id())?; state.serialize_field("name", &self.name())?; state.serialize_field("groups", &self.groups())?; state.end() } } #[cfg(feature = "user")] impl Serialize for crate::Group { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `2` corresponds to the number of fields. let mut state = serializer.serialize_struct("Group", 2)?; state.serialize_field("id", &self.id())?; state.serialize_field("name", &self.name())?; state.end() } } #[cfg(any(feature = "user", feature = "system"))] impl Serialize for crate::Gid { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_newtype_struct("Gid", &self.to_string()) } } #[cfg(any(feature = "user", feature = "system"))] impl Serialize for crate::Uid { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_newtype_struct("Uid", &self.to_string()) } } #[cfg(test)] mod tests { #[test] fn test_serde_process_name() { if !crate::IS_SUPPORTED_SYSTEM { return; } let mut s = crate::System::new(); s.refresh_processes_specifics( crate::ProcessesToUpdate::All, false, crate::ProcessRefreshKind::nothing(), ); if s.processes().is_empty() { panic!("no processes?"); } for p in s.processes().values() { let values = match serde_json::to_value(p) { Ok(serde_json::Value::Object(values)) => values, other => panic!("expected object, found `{other:?}`"), }; match values.get("name") { Some(serde_json::Value::String(_)) => {} value => panic!("expected a string, found `{value:?}`"), } } } } GuillaumeGomez-sysinfo-067dd61/src/sysinfo.h000066400000000000000000000062521506747262600211430ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #pragma once #include #include #include typedef void* CSystem; typedef const void* CProcess; typedef const char* RString; typedef void* CNetworks; typedef void* CDisks; #ifdef WIN32 typedef size_t PID; #else typedef pid_t PID; #endif CSystem sysinfo_init(void); void sysinfo_destroy(CSystem system); CNetworks sysinfo_networks_init(void); void sysinfo_networks_destroy(CNetworks networks); void sysinfo_refresh_memory(CSystem system); void sysinfo_refresh_cpu(CSystem system); void sysinfo_refresh_all(CSystem system); void sysinfo_refresh_processes(CSystem system); void sysinfo_refresh_process(CSystem system, PID pid); CDisks sysinfo_disks_init(void); void sysinfo_disks_destroy(CDisks disks); void sysinfo_disks_refresh(CDisks disks); size_t sysinfo_total_memory(CSystem system); size_t sysinfo_free_memory(CSystem system); size_t sysinfo_used_memory(CSystem system); size_t sysinfo_total_swap(CSystem system); size_t sysinfo_free_swap(CSystem system); size_t sysinfo_used_swap(CSystem system); void sysinfo_cpus_usage(CSystem system, unsigned int *length, float **cpus); size_t sysinfo_processes(CSystem system, bool (*fn_pointer)(PID, CProcess, void*), void *data); size_t sysinfo_process_tasks(CProcess process, bool (*fn_pointer)(PID, void*), void *data); CProcess sysinfo_process_by_pid(CSystem system, PID pid); PID sysinfo_process_pid(CProcess process); PID sysinfo_process_parent_pid(CProcess process); float sysinfo_process_cpu_usage(CProcess process); size_t sysinfo_process_memory(CProcess process); size_t sysinfo_process_virtual_memory(CProcess process); RString sysinfo_process_executable_path(CProcess process); RString sysinfo_process_root_directory(CProcess process); RString sysinfo_process_current_directory(CProcess process); void sysinfo_networks_refresh(CNetworks networks); size_t sysinfo_networks_received(CNetworks networks); size_t sysinfo_networks_transmitted(CNetworks networks); RString sysinfo_cpu_vendor_id(CSystem system); RString sysinfo_cpu_brand(CSystem system); uint64_t sysinfo_cpu_frequency(CSystem system); RString sysinfo_system_name(); RString sysinfo_system_kernel_version(); RString sysinfo_system_version(); RString sysinfo_system_host_name(); RString sysinfo_system_long_version(); uint32_t sysinfo_cpu_physical_cores(); RString sysinfo_motherboard_asset_tag(); RString sysinfo_motherboard_name(); RString sysinfo_motherboard_vendor_name(); RString sysinfo_motherboard_version(); RString sysinfo_motherboard_serial_number(); RString sysinfo_product_family(); RString sysinfo_product_name(); RString sysinfo_product_serial_number(); RString sysinfo_product_stock_keeping_unit(); RString sysinfo_product_uuid(); RString sysinfo_product_version(); RString sysinfo_product_vendor_name(); void sysinfo_rstring_free(RString str); GuillaumeGomez-sysinfo-067dd61/src/unix/000077500000000000000000000000001506747262600202565ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/unix/apple/000077500000000000000000000000001506747262600213575ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/unix/apple/app_store/000077500000000000000000000000001506747262600233535ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/unix/apple/app_store/component.rs000066400000000000000000000023031506747262600257210ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::Component; pub(crate) struct ComponentInner { pub(crate) updated: bool, } impl ComponentInner { pub(crate) fn temperature(&self) -> Option { None } pub(crate) fn max(&self) -> Option { None } pub(crate) fn critical(&self) -> Option { None } pub(crate) fn label(&self) -> &str { "" } pub(crate) fn id(&self) -> Option<&str> { None } pub(crate) fn refresh(&mut self) {} } pub(crate) struct ComponentsInner { pub(crate) components: Vec, } impl ComponentsInner { pub(crate) fn new() -> Self { Self { components: Vec::new(), } } pub(crate) fn from_vec(components: Vec) -> Self { Self { components } } pub(crate) fn into_vec(self) -> Vec { self.components } pub(crate) fn list(&self) -> &[Component] { &self.components } pub(crate) fn list_mut(&mut self) -> &mut [Component] { &mut self.components } pub(crate) fn refresh(&mut self) { // Doesn't do anything. } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/app_store/mod.rs000066400000000000000000000002561506747262600245030ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "component")] pub mod component; #[cfg(feature = "system")] pub mod process; GuillaumeGomez-sysinfo-067dd61/src/unix/apple/app_store/process.rs000066400000000000000000000040771506747262600254070ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::ffi::{OsStr, OsString}; use std::path::Path; use std::process::ExitStatus; use crate::{DiskUsage, Gid, Pid, ProcessStatus, Signal, Uid}; pub(crate) struct ProcessInner; impl ProcessInner { pub(crate) fn kill_with(&self, _signal: Signal) -> Option { None } pub(crate) fn name(&self) -> &OsStr { OsStr::new("") } pub(crate) fn cmd(&self) -> &[OsString] { &[] } pub(crate) fn exe(&self) -> Option<&Path> { None } pub(crate) fn pid(&self) -> Pid { Pid(0) } pub(crate) fn environ(&self) -> &[OsString] { &[] } pub(crate) fn cwd(&self) -> Option<&Path> { None } pub(crate) fn root(&self) -> Option<&Path> { None } pub(crate) fn memory(&self) -> u64 { 0 } pub(crate) fn virtual_memory(&self) -> u64 { 0 } pub(crate) fn parent(&self) -> Option { None } pub(crate) fn status(&self) -> ProcessStatus { ProcessStatus::Unknown(0) } pub(crate) fn start_time(&self) -> u64 { 0 } pub(crate) fn run_time(&self) -> u64 { 0 } pub(crate) fn cpu_usage(&self) -> f32 { 0.0 } pub(crate) fn accumulated_cpu_time(&self) -> u64 { 0 } pub(crate) fn disk_usage(&self) -> DiskUsage { DiskUsage::default() } pub(crate) fn user_id(&self) -> Option<&Uid> { None } pub(crate) fn effective_user_id(&self) -> Option<&Uid> { None } pub(crate) fn group_id(&self) -> Option { None } pub(crate) fn effective_group_id(&self) -> Option { None } pub(crate) fn wait(&self) -> Option { None } pub(crate) fn session_id(&self) -> Option { None } pub(crate) fn switch_updated(&mut self) -> bool { false } pub(crate) fn set_nonexistent(&mut self) {} pub(crate) fn exists(&self) -> bool { false } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/component.rs000066400000000000000000000002011506747262600237200ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub(crate) use crate::sys::inner::component::*; GuillaumeGomez-sysinfo-067dd61/src/unix/apple/cpu.rs000066400000000000000000000357331506747262600225270ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::{get_sys_value, get_sys_value_by_name}; use crate::{Cpu, CpuRefreshKind}; #[allow(deprecated)] use libc::mach_task_self; use libc::{c_char, c_void, host_processor_info, mach_port_t}; use std::mem; use std::ops::Deref; use std::sync::Arc; use std::time::Instant; pub(crate) struct CpusWrapper { pub(crate) global_cpu: CpuUsage, pub(crate) cpus: Vec, pub(crate) got_cpu_frequency: bool, /// This field is needed to prevent updating when not enough time passed since last update. last_update: Option, } impl CpusWrapper { pub(crate) fn new() -> Self { Self { global_cpu: CpuUsage::new(), cpus: Vec::new(), got_cpu_frequency: false, last_update: None, } } pub(crate) fn refresh(&mut self, refresh_kind: CpuRefreshKind, port: mach_port_t) { let need_cpu_usage_update = self .last_update .is_some_and(|last_update| last_update.elapsed() >= crate::MINIMUM_CPU_UPDATE_INTERVAL); let cpus = &mut self.cpus; if cpus.is_empty() { init_cpus(port, cpus, &mut self.global_cpu, refresh_kind); self.last_update = Some(Instant::now()); self.got_cpu_frequency = refresh_kind.frequency(); return; } if refresh_kind.frequency() && !self.got_cpu_frequency { let frequency = unsafe { get_cpu_frequency(cpus.first().map_or("", |c| c.brand())) }; for proc_ in cpus.iter_mut() { proc_.inner.set_frequency(frequency); } self.got_cpu_frequency = true; } if refresh_kind.cpu_usage() && need_cpu_usage_update { self.last_update = Some(Instant::now()); update_cpu_usage(port, &mut self.global_cpu, |proc_data, cpu_info| { let mut percentage = 0f32; let mut offset = 0; for proc_ in cpus.iter_mut() { let cpu_usage = compute_usage_of_cpu(proc_, cpu_info, offset); proc_.inner.update(cpu_usage, Arc::clone(&proc_data)); percentage += proc_.inner.cpu_usage(); offset += libc::CPU_STATE_MAX as isize; } (percentage, cpus.len()) }); } } } pub(crate) struct UnsafePtr(*mut T); unsafe impl Send for UnsafePtr {} unsafe impl Sync for UnsafePtr {} impl Deref for UnsafePtr { type Target = *mut T; fn deref(&self) -> &*mut T { &self.0 } } pub(crate) struct CpuData { pub cpu_info: UnsafePtr, pub num_cpu_info: u32, } impl CpuData { pub fn new(cpu_info: *mut i32, num_cpu_info: u32) -> CpuData { CpuData { cpu_info: UnsafePtr(cpu_info), num_cpu_info, } } } impl Drop for CpuData { fn drop(&mut self) { if !self.cpu_info.0.is_null() { let prev_cpu_info_size = std::mem::size_of::() as u32 * self.num_cpu_info; unsafe { libc::vm_deallocate( #[allow(deprecated)] mach_task_self(), self.cpu_info.0 as _, prev_cpu_info_size as _, ); } self.cpu_info.0 = std::ptr::null_mut(); } } } pub(crate) struct CpuUsage { percent: f32, data: Arc, // Cannot be frequency for each CPU apparently so we store it in the CPU usage... frequency: u64, } impl CpuUsage { pub(crate) fn new() -> Self { Self { percent: 0., data: Arc::new(CpuData::new(std::ptr::null_mut(), 0)), frequency: 0, } } pub(crate) fn percent(&self) -> f32 { self.percent } pub(crate) fn set_cpu_usage(&mut self, value: f32) { self.percent = value; } } pub(crate) struct CpuInner { name: String, vendor_id: String, brand: String, usage: CpuUsage, } impl CpuInner { pub(crate) fn new( name: String, cpu_data: Arc, frequency: u64, vendor_id: String, brand: String, ) -> Self { Self { name, usage: CpuUsage { percent: 0., data: cpu_data, frequency, }, vendor_id, brand, } } pub(crate) fn set_cpu_usage(&mut self, cpu_usage: f32) { self.usage.set_cpu_usage(cpu_usage); } pub(crate) fn update(&mut self, cpu_usage: f32, cpu_data: Arc) { self.usage.percent = cpu_usage; self.usage.data = cpu_data; } pub(crate) fn data(&self) -> Arc { Arc::clone(&self.usage.data) } pub(crate) fn set_frequency(&mut self, frequency: u64) { self.usage.frequency = frequency; } pub(crate) fn cpu_usage(&self) -> f32 { self.usage.percent() } pub(crate) fn name(&self) -> &str { &self.name } pub(crate) fn frequency(&self) -> u64 { self.usage.frequency } pub(crate) fn vendor_id(&self) -> &str { &self.vendor_id } pub(crate) fn brand(&self) -> &str { &self.brand } } pub(crate) unsafe fn get_cpu_frequency(#[allow(unused_variables)] brand: &str) -> u64 { let mut speed: u64 = 0; let mut len = std::mem::size_of::(); if unsafe { libc::sysctlbyname( "hw.cpufrequency".as_ptr() as *const _, &mut speed as *mut _ as _, &mut len, std::ptr::null_mut(), 0, ) } == 0 { return speed / 1_000_000; } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] { 0 } #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] unsafe { crate::sys::inner::cpu::get_cpu_frequency(brand) } } pub(crate) fn physical_core_count() -> Option { let mut physical_core_count = 0; unsafe { if get_sys_value_by_name( b"hw.physicalcpu\0", &mut mem::size_of::(), &mut physical_core_count as *mut usize as *mut c_void, ) { Some(physical_core_count) } else { None } } } #[inline] fn get_in_use(cpu_info: *mut i32, offset: isize) -> i64 { unsafe { let user = *cpu_info.offset(offset + libc::CPU_STATE_USER as isize) as i64; let system = *cpu_info.offset(offset + libc::CPU_STATE_SYSTEM as isize) as i64; let nice = *cpu_info.offset(offset + libc::CPU_STATE_NICE as isize) as i64; user.saturating_add(system).saturating_add(nice) } } #[inline] fn get_idle(cpu_info: *mut i32, offset: isize) -> i32 { unsafe { *cpu_info.offset(offset + libc::CPU_STATE_IDLE as isize) } } pub(crate) fn compute_usage_of_cpu(proc_: &Cpu, cpu_info: *mut i32, offset: isize) -> f32 { let old_cpu_info = proc_.inner.data().cpu_info.0; let in_use; let idle; // In case we are initializing cpus, there is no "old value" yet. if std::ptr::eq(old_cpu_info, cpu_info) { in_use = get_in_use(cpu_info, offset); idle = get_idle(cpu_info, offset); } else { let new_in_use = get_in_use(cpu_info, offset); let old_in_use = get_in_use(old_cpu_info, offset); let new_idle = get_idle(cpu_info, offset); let old_idle = get_idle(old_cpu_info, offset); in_use = new_in_use.saturating_sub(old_in_use); idle = new_idle.saturating_sub(old_idle) as _; } let total = in_use.saturating_add(idle as _); let usage = (in_use as f32 / total as f32) * 100.; if usage.is_nan() { // If divided by zero, avoid returning a NaN 0. } else { usage } } pub(crate) fn update_cpu_usage, *mut i32) -> (f32, usize)>( port: libc::mach_port_t, global_cpu: &mut CpuUsage, f: F, ) { let mut num_cpu_u = 0u32; let mut cpu_info: *mut i32 = std::ptr::null_mut(); let mut num_cpu_info = 0u32; let mut total_cpu_usage = 0f32; unsafe { if host_processor_info( port, libc::PROCESSOR_CPU_LOAD_INFO, &mut num_cpu_u as *mut u32, &mut cpu_info as *mut *mut i32, &mut num_cpu_info as *mut u32, ) == libc::KERN_SUCCESS { let (total_percentage, len) = f(Arc::new(CpuData::new(cpu_info, num_cpu_info)), cpu_info); total_cpu_usage = total_percentage / len as f32; } global_cpu.set_cpu_usage(total_cpu_usage); } } pub(crate) fn init_cpus( port: libc::mach_port_t, cpus: &mut Vec, global_cpu: &mut CpuUsage, refresh_kind: CpuRefreshKind, ) { let mut num_cpu = 0; let mut mib = [libc::CTL_HW as _, libc::HW_NCPU as _]; let (vendor_id, brand) = get_vendor_id_and_brand(); let frequency = if refresh_kind.frequency() { unsafe { get_cpu_frequency(&brand) } } else { global_cpu.frequency }; unsafe { if !get_sys_value( mem::size_of::(), &mut num_cpu as *mut _ as *mut _, &mut mib, ) { num_cpu = 1; } } update_cpu_usage(port, global_cpu, |proc_data, cpu_info| { let mut percentage = 0f32; let mut offset = 0; for i in 0..num_cpu { let mut cpu = Cpu { inner: CpuInner::new( format!("{}", i + 1), Arc::clone(&proc_data), frequency, vendor_id.clone(), brand.clone(), ), }; if refresh_kind.cpu_usage() { let cpu_usage = compute_usage_of_cpu(&cpu, cpu_info, offset); cpu.inner.set_cpu_usage(cpu_usage); percentage += cpu.cpu_usage(); } cpus.push(cpu); offset += libc::CPU_STATE_MAX as isize; } (percentage, cpus.len()) }); } pub(crate) fn get_sysctl_str(s: &[u8]) -> String { let mut len = 0; unsafe { libc::sysctlbyname( s.as_ptr() as *const c_char, std::ptr::null_mut(), &mut len, std::ptr::null_mut(), 0, ); if len < 1 { return String::new(); } let mut buf = Vec::with_capacity(len); libc::sysctlbyname( s.as_ptr() as *const c_char, buf.as_mut_ptr() as _, &mut len, std::ptr::null_mut(), 0, ); if len > 0 { buf.set_len(len); while buf.last() == Some(&b'\0') { buf.pop(); } String::from_utf8(buf).unwrap_or_else(|_| String::new()) } else { String::new() } } } pub(crate) fn get_vendor_id_and_brand() -> (String, String) { // On apple M1, `sysctl machdep.cpu.vendor` returns "", so fallback to "Apple" if the result // is empty. let mut vendor = get_sysctl_str(b"machdep.cpu.vendor\0"); if vendor.is_empty() { vendor = "Apple".to_string(); } let brand = get_sysctl_str(b"machdep.cpu.brand_string\0"); if !brand.is_empty() { return (vendor, brand); } let full_brand = get_sysctl_str(b"hw.machine\0"); // This is a fallback when the `sysctl` to get the CPU brand returns an empty string. let mut iter = full_brand.split(','); let brand = match (iter.next().unwrap_or(""), iter.next()) { ("iPhone1", Some("1" | "2")) => "S5L8900", ("iPhone2", Some("1")) => "S5L8920", ("iPhone3", Some("1" | "2" | "3")) => "A4", ("iPhone4", Some("1" | "2")) => "A5", ("iPhone5", Some("1" | "2")) => "A6", ("iPhone5", Some("3" | "4")) => "A6", ("iPhone6", Some("1" | "2")) => "A7", ("iPhone7", Some("1" | "2")) => "A8", ("iPhone8", Some("1" | "2" | "4")) => "A9", ("iPhone9", Some("1" | "2" | "3" | "4")) => "A10 Fusion", ("iPhone10", Some("1" | "2" | "3" | "4" | "5" | "6")) => "A11 Bionic", ("iPhone11", Some("1" | "2" | "4" | "6" | "8")) => "A12 Bionic", ("iPhone12", Some("1" | "3" | "5" | "8")) => "A13 Bionic", ("iPhone13", Some("1" | "2" | "3" | "4")) => "A14 Bionic", ("iPhone14", Some("1" | "2" | "3" | "4" | "5" | "6" | "7" | "8")) => "A15 Bionic", ("iPhone15", Some("2" | "3" | "4" | "5")) => "A16 Bionic", ("iPhone16", Some("1" | "2")) => "A17 Pro", ("iPhone17", Some("1" | "2")) => "A18 Pro", ("iPhone17", Some("3" | "4")) => "A18", ("iPad1", Some("1" | "2")) => "A4", ("iPad2", Some("1" | "2" | "3" | "4" | "5" | "6" | "7")) => "A5", ("iPad3", Some("1" | "2" | "3")) => "A5X", ("iPad3", Some("4" | "5" | "6")) => "A6X", ("iPad4", Some("1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9")) => "A7", ("iPad5", Some("1" | "2")) => "A8", ("iPad5", Some("3" | "4")) => "A8X", ("iPad6", Some("3" | "4" | "7" | "8")) => "A9X", ("iPad6", Some("11" | "12")) => "A9", ("iPad7", Some("1" | "2" | "3" | "4")) => "A10X Fusion", ("iPad7", Some("5" | "6" | "11" | "12")) => "A10 Fusion", ("iPad8", Some("1" | "2" | "3" | "4" | "5" | "6" | "7" | "8")) => "A12X Bionic", ("iPad8", Some("9" | "10" | "11" | "12")) => "A12Z Bionic", ("iPad11", Some("1" | "2" | "3" | "4" | "6" | "7")) => "A12 Bionic", ("iPad12", Some("1" | "2")) => "A13 Bionic", ("iPad13", Some("1" | "2" | "18" | "19")) => "A14 Bionic", ("iPad13", Some("4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "16" | "17")) => "M1", ("iPad14", Some("1" | "2")) => "A15 Bionic", ("iPad14", Some("3" | "4" | "5" | "6" | "10" | "11")) => "M2", ("iPad14", Some("8" | "9")) => "M1", ("iPad16", Some("3" | "4" | "5" | "6")) => "M3", _ => "unknown", }; (vendor, brand.to_string()) } #[cfg(test)] mod test { use crate::*; use std::process::Command; #[test] fn check_vendor_and_brand() { let child = Command::new("sysctl") .arg("-a") .output() .expect("Failed to start command..."); assert!(child.status.success()); let stdout = String::from_utf8(child.stdout).expect("Not valid UTF8"); let sys = System::new_with_specifics( crate::RefreshKind::nothing().with_cpu(CpuRefreshKind::nothing().with_cpu_usage()), ); let cpus = sys.cpus(); assert!(!cpus.is_empty(), "no CPU found"); if let Some(line) = stdout.lines().find(|l| l.contains("machdep.cpu.vendor")) { let sysctl_value = line.split(':').nth(1).unwrap(); assert_eq!(cpus[0].vendor_id(), sysctl_value.trim()); } if let Some(line) = stdout .lines() .find(|l| l.contains("machdep.cpu.brand_string")) { let sysctl_value = line.split(':').nth(1).unwrap(); assert_eq!(cpus[0].brand(), sysctl_value.trim()); } } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/disk.rs000066400000000000000000000451141506747262600226640ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Disk, DiskKind, DiskRefreshKind}; use crate::{DiskUsage, sys::ffi}; use objc2_core_foundation::{ CFArray, CFBoolean, CFDictionary, CFNumber, CFRetained, CFString, CFURL, kCFAllocatorDefault, kCFTypeArrayCallBacks, kCFURLVolumeAvailableCapacityForImportantUsageKey, kCFURLVolumeAvailableCapacityKey, kCFURLVolumeIsBrowsableKey, kCFURLVolumeIsEjectableKey, kCFURLVolumeIsInternalKey, kCFURLVolumeIsLocalKey, kCFURLVolumeIsRemovableKey, kCFURLVolumeNameKey, kCFURLVolumeTotalCapacityKey, kCFURLVolumeUUIDStringKey, }; use libc::c_void; use std::ffi::{CStr, OsStr, OsString}; use std::os::unix::ffi::{OsStrExt, OsStringExt}; use std::path::{Path, PathBuf}; use std::ptr; pub(crate) struct DiskInner { pub(crate) type_: DiskKind, pub(crate) name: OsString, #[cfg(target_os = "macos")] bsd_name: Option>, pub(crate) file_system: OsString, pub(crate) mount_point: PathBuf, volume_url: CFRetained, pub(crate) total_space: u64, pub(crate) available_space: u64, pub(crate) is_removable: bool, pub(crate) is_read_only: bool, pub(crate) old_written_bytes: u64, pub(crate) old_read_bytes: u64, pub(crate) written_bytes: u64, pub(crate) read_bytes: u64, updated: bool, uuid: OsString, } // Needed because `volume_url` type (`CFURL` contains an `*const UnsafeCell`). `UnsafeCell` is // `!Sync` and `*const` is both `!Sync` and `!Send`. However, this type is not `Clone` and is not // `Copy`, so it's "safe" to make it `Send`. unsafe impl Send for DiskInner {} impl DiskInner { pub(crate) fn kind(&self) -> DiskKind { self.type_ } pub(crate) fn name(&self) -> &OsStr { &self.name } pub(crate) fn file_system(&self) -> &OsStr { &self.file_system } pub(crate) fn mount_point(&self) -> &Path { &self.mount_point } pub(crate) fn total_space(&self) -> u64 { self.total_space } pub(crate) fn available_space(&self) -> u64 { self.available_space } pub(crate) fn is_removable(&self) -> bool { self.is_removable } pub(crate) fn is_read_only(&self) -> bool { self.is_read_only } pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool { self.refresh_kind(refresh_kind); self.refresh_io(refresh_kind); if refresh_kind.storage() { unsafe { if let Some(requested_properties) = build_requested_properties(&[ kCFURLVolumeTotalCapacityKey, kCFURLVolumeAvailableCapacityKey, kCFURLVolumeAvailableCapacityForImportantUsageKey, ]) { match get_disk_properties(&self.volume_url, &requested_properties) { Some(disk_props) => { match get_int_value(&disk_props, kCFURLVolumeTotalCapacityKey) { Some(total_space) => self.total_space = total_space, None => { sysinfo_debug!("Failed to get disk total space"); } } match get_available_volume_space(&disk_props) { Some(available_space) => self.available_space = available_space, None => { sysinfo_debug!("Failed to get disk available space"); } } } None => { sysinfo_debug!("Failed to get disk properties"); } } } else { sysinfo_debug!("failed to create volume key list, skipping refresh"); } } } true } pub(crate) fn usage(&self) -> DiskUsage { DiskUsage { read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, } } fn refresh_kind(&mut self, refresh_kind: DiskRefreshKind) { if refresh_kind.kind() && self.type_ == DiskKind::Unknown(-1) { #[cfg(target_os = "macos")] { match self .bsd_name .as_ref() .and_then(|name| crate::sys::inner::disk::get_disk_type(name)) { Some(type_) => self.type_ = type_, None => { sysinfo_debug!("Failed to retrieve `DiskKind`"); } } } #[cfg(not(target_os = "macos"))] { self.type_ = DiskKind::SSD; } } } #[cfg(target_os = "macos")] fn refresh_io(&mut self, refresh_kind: DiskRefreshKind) { if refresh_kind.io_usage() { match self .bsd_name .as_ref() .and_then(|name| crate::sys::inner::disk::get_disk_io(name)) { Some((read_bytes, written_bytes)) => { self.old_read_bytes = self.read_bytes; self.old_written_bytes = self.written_bytes; self.read_bytes = read_bytes; self.written_bytes = written_bytes; } None => { sysinfo_debug!("Failed to update disk i/o stats"); } } } } #[cfg(not(target_os = "macos"))] fn refresh_io(&mut self, _refresh_kind: DiskRefreshKind) {} } impl crate::DisksInner { pub(crate) fn new() -> Self { Self { disks: Vec::with_capacity(2), } } pub(crate) fn refresh_specifics( &mut self, remove_not_listed_disks: bool, refresh_kind: DiskRefreshKind, ) { unsafe { // SAFETY: We don't keep any Objective-C objects around because we // don't make any direct Objective-C calls in this code. with_autorelease(|| { get_list(&mut self.disks, refresh_kind); }) } if remove_not_listed_disks { self.disks.retain_mut(|disk| { if !disk.inner.updated { return false; } disk.inner.updated = false; true }); } else { for c in self.disks.iter_mut() { c.inner.updated = false; } } } pub(crate) fn list(&self) -> &[Disk] { &self.disks } pub(crate) fn list_mut(&mut self) -> &mut [Disk] { &mut self.disks } } unsafe fn get_list(container: &mut Vec, refresh_kind: DiskRefreshKind) { unsafe { let raw_disks = { let count = libc::getfsstat(ptr::null_mut(), 0, libc::MNT_NOWAIT); if count < 1 { return; } let bufsize = count * std::mem::size_of::() as libc::c_int; let mut disks = Vec::with_capacity(count as _); let count = libc::getfsstat(disks.as_mut_ptr(), bufsize, libc::MNT_NOWAIT); if count < 1 { return; } disks.set_len(count as usize); disks }; // Currently we query maximum 10 properties. let mut properties = Vec::with_capacity(10); // "mandatory" information properties.push(kCFURLVolumeNameKey); properties.push(kCFURLVolumeIsBrowsableKey); properties.push(kCFURLVolumeIsLocalKey); properties.push(kCFURLVolumeUUIDStringKey); // is_removable properties.push(kCFURLVolumeIsEjectableKey); properties.push(kCFURLVolumeIsRemovableKey); properties.push(kCFURLVolumeIsInternalKey); if refresh_kind.storage() { properties.push(kCFURLVolumeTotalCapacityKey); properties.push(kCFURLVolumeAvailableCapacityForImportantUsageKey); properties.push(kCFURLVolumeAvailableCapacityKey); } // Create a list of properties about the disk that we want to fetch. let requested_properties = match build_requested_properties(&properties) { Some(properties) => properties, None => { sysinfo_debug!("failed to create volume key list"); return; } }; for c_disk in raw_disks { let volume_url = match CFURL::from_file_system_representation( kCFAllocatorDefault, c_disk.f_mntonname.as_ptr() as *const _, c_disk.f_mntonname.len() as _, false as _, ) { Some(url) => url, None => { sysinfo_debug!("getfsstat returned incompatible paths"); continue; } }; let prop_dict = match get_disk_properties(&volume_url, &requested_properties) { Some(props) => props, None => continue, }; // Future note: There is a difference between `kCFURLVolumeIsBrowsableKey` and the // `kCFURLEnumeratorSkipInvisibles` option of `CFURLEnumeratorOptions`. Specifically, // the first one considers the writable `Data`(`/System/Volumes/Data`) partition to be // browsable, while it is classified as "invisible" by CoreFoundation's volume enumerator. let browsable = get_bool_value(&prop_dict, kCFURLVolumeIsBrowsableKey).unwrap_or_default(); // Do not return invisible "disks". Most of the time, these are APFS snapshots, hidden // system volumes, etc. Browsable is defined to be visible in the system's UI like Finder, // disk utility, system information, etc. // // To avoid seemingly duplicating many disks and creating an inaccurate view of the system's // resources, these are skipped entirely. if !browsable { continue; } let local_only = get_bool_value(&prop_dict, kCFURLVolumeIsLocalKey).unwrap_or(true); // Skip any drive that is not locally attached to the system. // // This includes items like SMB mounts, and matches the other platform's behavior. if !local_only { continue; } let mount_point = PathBuf::from(OsStr::from_bytes( CStr::from_ptr(c_disk.f_mntonname.as_ptr()).to_bytes(), )); let uuid = OsStr::from_bytes(CStr::from_ptr(c_disk.f_mntonname.as_ptr()).to_bytes()) .to_os_string(); let disk = container.iter_mut().find(|d| d.inner.uuid == uuid); if let Some(disk) = new_disk( disk, mount_point, volume_url, c_disk, &prop_dict, refresh_kind, uuid, ) { container.push(disk); } } } } unsafe fn build_requested_properties( properties: &[Option<&CFString>], ) -> Option> { unsafe { CFArray::new( None, properties.as_ptr() as *mut *const c_void, properties.len() as _, &kCFTypeArrayCallBacks, ) } } fn get_disk_properties( volume_url: &CFURL, requested_properties: &CFArray, ) -> Option> { unsafe { volume_url.resource_properties_for_keys(Some(requested_properties), ptr::null_mut()) } } fn get_available_volume_space(disk_props: &CFDictionary) -> Option { // We prefer `AvailableCapacityForImportantUsage` over `AvailableCapacity` because // it takes more of the system's properties into account, like the trash, system-managed caches, // etc. It generally also returns higher values too, because of the above, so it's a more // accurate representation of what the system _could_ still use. unsafe { get_int_value( disk_props, kCFURLVolumeAvailableCapacityForImportantUsageKey, ) .filter(|bytes| *bytes != 0) .or_else(|| get_int_value(disk_props, kCFURLVolumeAvailableCapacityKey)) } } unsafe fn get_dict_value Option>( dict: &CFDictionary, key: Option<&CFString>, callback: F, ) -> Option { let mut value = std::ptr::null(); let key: *const CFString = key.map(|key| key as *const CFString).unwrap_or(ptr::null()); if unsafe { dict.value_if_present(key.cast(), &mut value) } { callback(value) } else { None } } pub(super) unsafe fn get_str_value(dict: &CFDictionary, key: Option<&CFString>) -> Option { unsafe { get_dict_value(dict, key, |v| { let v = &*v.cast::(); Some(v.to_string()) }) } } unsafe fn get_bool_value(dict: &CFDictionary, key: Option<&CFString>) -> Option { unsafe { get_dict_value(dict, key, |v| { let v = &*v.cast::(); Some(v.as_bool()) }) } } pub(super) unsafe fn get_int_value(dict: &CFDictionary, key: Option<&CFString>) -> Option { unsafe { get_dict_value(dict, key, |v| { let v = &*v.cast::(); Some(v.as_i64()? as u64) }) } } unsafe fn new_disk( disk: Option<&mut Disk>, mount_point: PathBuf, volume_url: CFRetained, c_disk: libc::statfs, disk_props: &CFDictionary, refresh_kind: DiskRefreshKind, uuid: OsString, ) -> Option { let (total_space, available_space) = if refresh_kind.storage() { ( unsafe { get_int_value(disk_props, kCFURLVolumeTotalCapacityKey) }, get_available_volume_space(disk_props), ) } else { (None, None) }; // We update the existing disk here to prevent having another call to get `storage` info. if let Some(disk) = disk { let disk = &mut disk.inner; if let Some(total_space) = total_space { disk.total_space = total_space; } if let Some(available_space) = available_space { disk.available_space = available_space; } disk.refresh_io(refresh_kind); disk.refresh_kind(refresh_kind); disk.updated = true; return None; } // Note: Since we requested these properties from the system, we don't expect // these property retrievals to fail. let name = unsafe { get_str_value(disk_props, kCFURLVolumeNameKey) }.map(OsString::from)?; let file_system = { let len = c_disk .f_fstypename .iter() .position(|&b| b == 0) .unwrap_or(c_disk.f_fstypename.len()); OsString::from_vec( c_disk.f_fstypename[..len] .iter() .map(|c| *c as u8) .collect(), ) }; #[cfg(target_os = "macos")] let bsd_name = get_bsd_name(&c_disk); // IOKit is not available on any but the most recent (16+) iOS and iPadOS versions. // Due to this, we can't query the medium type and disk i/o stats. All iOS devices use flash-based storage // so we just assume the disk type is an SSD and set disk i/o stats to 0 until Rust has a way to conditionally link to // IOKit in more recent deployment versions. let ejectable = unsafe { get_bool_value(disk_props, kCFURLVolumeIsEjectableKey) }.unwrap_or(false); let removable = unsafe { get_bool_value(disk_props, kCFURLVolumeIsRemovableKey) }.unwrap_or(false); let is_removable = if ejectable || removable { true } else { // If neither `ejectable` or `removable` return `true`, fallback to checking // if the disk is attached to the internal system. let internal = unsafe { get_bool_value(disk_props, kCFURLVolumeIsInternalKey) }.unwrap_or_default(); !internal }; let is_read_only = (c_disk.f_flags & libc::MNT_RDONLY as u32) != 0; let mut disk = DiskInner { type_: DiskKind::Unknown(-1), name, #[cfg(target_os = "macos")] bsd_name, file_system, mount_point, volume_url, total_space: total_space.unwrap_or(0), available_space: available_space.unwrap_or(0), is_removable, is_read_only, read_bytes: 0, written_bytes: 0, old_read_bytes: 0, old_written_bytes: 0, updated: true, uuid, }; disk.refresh_kind(refresh_kind); disk.refresh_io(refresh_kind); Some(Disk { inner: disk }) } /// Calls the provided closure in the context of a new autorelease pool that is drained /// before returning. /// /// ## SAFETY: /// You must not return an Objective-C object that is autoreleased from this function since it /// will be freed before usable. unsafe fn with_autorelease T>(call: F) -> T { // NB: This struct exists to help prevent memory leaking if `call` were to panic. // Otherwise, the call to `objc_autoreleasePoolPop` would never be made as the stack unwinds. // `Drop` destructors for existing types on the stack are run during unwinding, so we can // ensure the autorelease pool is drained by using a RAII pattern here. struct DrainPool { ctx: *mut c_void, } impl Drop for DrainPool { fn drop(&mut self) { // SAFETY: We have not manipulated `pool_ctx` since it was received from a corresponding // pool push call. unsafe { ffi::objc_autoreleasePoolPop(self.ctx) } } } // SAFETY: Creating a new pool is safe in any context. They can be arbitrarily nested // as long as pool objects are not used in deeper layers, but we only have one and don't // allow it to leave this scope. let _pool_ctx = DrainPool { ctx: unsafe { ffi::objc_autoreleasePoolPush() }, }; call() // Pool is drained here before returning } #[cfg(target_os = "macos")] fn get_bsd_name(disk: &libc::statfs) -> Option> { // Removes `/dev/` from the value. unsafe { CStr::from_ptr(disk.f_mntfromname.as_ptr()) .to_bytes_with_nul() .strip_prefix(b"/dev/") .map(|slice| slice.to_vec()) .or_else(|| { sysinfo_debug!("unknown disk mount path format"); None }) } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/ffi.rs000066400000000000000000000004401506747262600224670ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "disk")] #[link(name = "objc", kind = "dylib")] unsafe extern "C" { pub fn objc_autoreleasePoolPop(pool: *mut libc::c_void); pub fn objc_autoreleasePoolPush() -> *mut libc::c_void; } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/ios.rs000066400000000000000000000003531506747262600225200ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod ffi {} #[cfg(feature = "component")] pub use crate::sys::app_store::component; #[cfg(feature = "system")] pub use crate::sys::app_store::process; GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/000077500000000000000000000000001506747262600224615ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/component/000077500000000000000000000000001506747262600244635ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/component/arm.rs000066400000000000000000000141041506747262600256100ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use objc2_core_foundation::{ CFArray, CFDictionary, CFNumber, CFRetained, CFString, kCFAllocatorDefault, }; use objc2_io_kit::{IOHIDEventSystemClient, IOHIDServiceClient}; use crate::Component; use crate::sys::inner::ffi::{ HID_DEVICE_PROPERTY_PRIMARY_USAGE, HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE, HID_DEVICE_PROPERTY_PRODUCT, IOHIDEventFieldBase, IOHIDEventGetFloatValue, IOHIDEventSystemClientCreate, IOHIDEventSystemClientSetMatching, IOHIDServiceClientCopyEvent, kHIDPage_AppleVendor, kHIDUsage_AppleVendor_TemperatureSensor, kIOHIDEventTypeTemperature, kIOHIDSerialNumberKey, }; pub(crate) struct ComponentsInner { pub(crate) components: Vec, client: Option>, } // SAFETY: `ComponentsInner::client` is never updated in a `&self` context, so it's safe to // make the type `Sync` and `Send`. unsafe impl Send for ComponentsInner {} unsafe impl Sync for ComponentsInner {} impl ComponentsInner { pub(crate) fn new() -> Self { Self { components: vec![], client: None, } } pub(crate) fn from_vec(components: Vec) -> Self { Self { components, client: None, } } pub(crate) fn into_vec(self) -> Vec { self.components } pub(crate) fn list(&self) -> &[Component] { &self.components } pub(crate) fn list_mut(&mut self) -> &mut [Component] { &mut self.components } #[allow(unreachable_code)] pub(crate) fn refresh(&mut self) { let keys = [ &*CFString::from_static_str(HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE), &*CFString::from_static_str(HID_DEVICE_PROPERTY_PRIMARY_USAGE), ]; let nums = [ &*CFNumber::new_i32(kHIDPage_AppleVendor), &*CFNumber::new_i32(kHIDUsage_AppleVendor_TemperatureSensor), ]; let matches = CFDictionary::from_slices(&keys, &nums); let matches = matches.as_opaque(); unsafe { if self.client.is_none() { let client = match IOHIDEventSystemClientCreate(kCFAllocatorDefault) { // SAFETY: `IOHIDEventSystemClientCreate` is a "create" // function, so the client has +1 retain count. Some(c) => CFRetained::from_raw(c), None => return, }; self.client = Some(client); } let client = self.client.as_ref().unwrap(); let _ = IOHIDEventSystemClientSetMatching(client, matches); let services = match client.services() { Some(s) => s, None => return, }; // SAFETY: Return type documented to be CFArray of IOHIDServiceClient. let services = CFRetained::cast_unchecked::>(services); let key = CFString::from_static_str(HID_DEVICE_PROPERTY_PRODUCT); let serial_key = CFString::from_static_str(kIOHIDSerialNumberKey); for service in services { let Some(name) = service.property(&key) else { continue; }; let serial = service .property(&serial_key) .and_then(|value| value.downcast::().ok()) .as_deref() .map(CFString::to_string); let name = name.downcast::().unwrap(); let name_str = name.to_string(); if let Some(c) = self .components .iter_mut() .find(|c| c.inner.label == name_str) { c.refresh(); c.inner.updated = true; continue; } let mut component = ComponentInner::new(serial, name_str, None, None, service); component.refresh(); self.components.push(Component { inner: component }); } } } } pub(crate) struct ComponentInner { id: Option, service: CFRetained, temperature: Option, label: String, max: f32, critical: Option, pub(crate) updated: bool, } // SAFETY: `ComponentsInner::service` is never updated, so it's safe to make the type `Sync` // and `Send`. unsafe impl Send for ComponentInner {} unsafe impl Sync for ComponentInner {} impl ComponentInner { pub(crate) fn new( id: Option, label: String, max: Option, critical: Option, service: CFRetained, ) -> Self { Self { id, service, label, max: max.unwrap_or(0.), critical, temperature: None, updated: true, } } pub(crate) fn temperature(&self) -> Option { self.temperature } pub(crate) fn max(&self) -> Option { Some(self.max) } pub(crate) fn critical(&self) -> Option { self.critical } pub(crate) fn label(&self) -> &str { &self.label } pub(crate) fn id(&self) -> Option<&str> { self.id.as_deref() } pub(crate) fn refresh(&mut self) { unsafe { let Some(event) = IOHIDServiceClientCopyEvent(&self.service, kIOHIDEventTypeTemperature, 0, 0) else { self.temperature = None; return; }; // SAFETY: `IOHIDServiceClientCopyEvent` is a "copy" function, so // the event has +1 retain count. let event = CFRetained::from_raw(event); let temperature = IOHIDEventGetFloatValue(&event, IOHIDEventFieldBase(kIOHIDEventTypeTemperature)) as _; self.temperature = Some(temperature); if temperature > self.max { self.max = temperature; } } } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/component/mod.rs000066400000000000000000000005501506747262600256100ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub(crate) mod x86; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub(crate) use self::x86::*; #[cfg(target_arch = "aarch64")] pub(crate) mod arm; #[cfg(target_arch = "aarch64")] pub(crate) use self::arm::*; GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/component/x86.rs000066400000000000000000000262261506747262600254660ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::Component; use crate::sys::macos::{ffi, utils::IOReleaser}; use libc::{c_char, c_int, c_void}; use objc2_core_foundation::{CFDictionary, CFRetained}; use objc2_io_kit::{ IOConnectCallStructMethod, IOIteratorNext, IOServiceClose, IOServiceGetMatchingServices, IOServiceMatching, IOServiceOpen, io_connect_t, io_iterator_t, kIOMasterPortDefault, kIOReturnSuccess, }; use std::mem; const COMPONENTS_TEMPERATURE_IDS: &[(&str, &str, &[i8])] = &[ ( "PECI CPU", "TCXC", &['T' as i8, 'C' as i8, 'X' as i8, 'C' as i8], ), // PECI CPU "TCXC" ( "PECI CPU", "TCXc", &['T' as i8, 'C' as i8, 'X' as i8, 'c' as i8], ), // PECI CPU "TCXc" ( "CPU Proximity", "TC0P", &['T' as i8, 'C' as i8, '0' as i8, 'P' as i8], ), // CPU Proximity (heat spreader) "TC0P" ("GPU", "TG0P", &['T' as i8, 'G' as i8, '0' as i8, 'P' as i8]), // GPU "TG0P" ( "Battery", "TB0T", &['T' as i8, 'B' as i8, '0' as i8, 'T' as i8], ), // Battery "TB0T" ]; pub(crate) struct ComponentFFI { input_structure: ffi::KeyData_t, val: ffi::Val_t, /// It is the `System::connection`. We need it to not require an extra argument /// in `ComponentInner::refresh`. connection: io_connect_t, } impl ComponentFFI { fn new(key: &[i8], connection: io_connect_t) -> Option { unsafe { get_key_size(connection, key) .ok() .map(|(input_structure, val)| ComponentFFI { input_structure, val, connection, }) } } fn temperature(&self) -> Option { get_temperature_inner(self.connection, &self.input_structure, &self.val) } } // Used to get CPU information, not supported on iOS, or inside the default macOS sandbox. pub(crate) struct ComponentsInner { pub(crate) components: Vec, connection: Option, } impl ComponentsInner { pub(crate) fn new() -> Self { Self { components: Vec::with_capacity(2), connection: IoService::new_connection(), } } pub(crate) fn from_vec(components: Vec) -> Self { Self { components, connection: IoService::new_connection(), } } pub(crate) fn into_vec(self) -> Vec { self.components } pub(crate) fn list(&self) -> &[Component] { &self.components } pub(crate) fn list_mut(&mut self) -> &mut [Component] { &mut self.components } pub(crate) fn refresh(&mut self) { let Some(ref connection) = self.connection else { sysinfo_debug!("No connection to IoService, skipping components refresh"); return; }; let connection = connection.inner(); // getting CPU critical temperature let critical_temp = get_temperature(connection, &['T' as i8, 'C' as i8, '0' as i8, 'D' as i8, 0]); for (label, id, v) in COMPONENTS_TEMPERATURE_IDS.iter() { if let Some(c) = self.components.iter_mut().find(|c| c.inner.id == *id) { c.refresh(); c.inner.updated = true; } else if let Some(c) = ComponentInner::new( (*id).to_owned(), (*label).to_owned(), None, critical_temp, v, connection, ) { self.components.push(Component { inner: c }); } } } } pub(crate) struct ComponentInner { id: String, temperature: Option, max: f32, critical: Option, label: String, ffi_part: ComponentFFI, pub(crate) updated: bool, } impl ComponentInner { /// Creates a new `ComponentInner` with the given information. pub(crate) fn new( id: String, label: String, max: Option, critical: Option, key: &[i8], connection: io_connect_t, ) -> Option { let ffi_part = ComponentFFI::new(key, connection)?; ffi_part.temperature().map(|temperature| Self { id, temperature: Some(temperature), label, max: max.unwrap_or(temperature), critical, ffi_part, updated: true, }) } pub(crate) fn temperature(&self) -> Option { self.temperature } pub(crate) fn max(&self) -> Option { Some(self.max) } pub(crate) fn critical(&self) -> Option { self.critical } pub(crate) fn label(&self) -> &str { &self.label } pub(crate) fn id(&self) -> Option<&str> { Some(&self.id) } pub(crate) fn refresh(&mut self) { self.temperature = self.ffi_part.temperature(); if let Some(temperature) = self.temperature { if temperature > self.max { self.max = temperature; } } } } unsafe fn perform_call( conn: io_connect_t, index: c_int, input_structure: *const ffi::KeyData_t, output_structure: *mut ffi::KeyData_t, ) -> i32 { let mut structure_output_size = mem::size_of::(); unsafe { IOConnectCallStructMethod( conn, index as u32, input_structure.cast(), mem::size_of::(), output_structure.cast(), &mut structure_output_size, ) } } // Adapted from https://github.com/lavoiesl/osx-cpu-temp/blob/master/smc.c#L28 #[inline] fn strtoul(s: &[i8]) -> u32 { unsafe { ((*s.get_unchecked(0) as u32) << (3u32 << 3)) + ((*s.get_unchecked(1) as u32) << (2u32 << 3)) + ((*s.get_unchecked(2) as u32) << (1u32 << 3)) + (*s.get_unchecked(3) as u32) } } #[inline] unsafe fn ultostr(s: *mut c_char, val: u32) { unsafe { *s.offset(0) = ((val >> 24) % 128) as i8; *s.offset(1) = ((val >> 16) % 128) as i8; *s.offset(2) = ((val >> 8) % 128) as i8; *s.offset(3) = (val % 128) as i8; *s.offset(4) = 0; } } unsafe fn get_key_size(con: io_connect_t, key: &[i8]) -> Result<(ffi::KeyData_t, ffi::Val_t), i32> { unsafe { let mut input_structure: ffi::KeyData_t = mem::zeroed::(); let mut output_structure: ffi::KeyData_t = mem::zeroed::(); let mut val: ffi::Val_t = mem::zeroed::(); input_structure.key = strtoul(key); input_structure.data8 = ffi::SMC_CMD_READ_KEYINFO; let result = perform_call( con, ffi::KERNEL_INDEX_SMC, &input_structure, &mut output_structure, ); if result != kIOReturnSuccess { return Err(result); } val.data_size = output_structure.key_info.data_size; ultostr( val.data_type.as_mut_ptr(), output_structure.key_info.data_type, ); input_structure.key_info.data_size = val.data_size; input_structure.data8 = ffi::SMC_CMD_READ_BYTES; Ok((input_structure, val)) } } unsafe fn read_key( con: io_connect_t, input_structure: &ffi::KeyData_t, mut val: ffi::Val_t, ) -> Result { unsafe { let mut output_structure: ffi::KeyData_t = mem::zeroed::(); #[allow(non_upper_case_globals)] match perform_call( con, ffi::KERNEL_INDEX_SMC, input_structure, &mut output_structure, ) { kIOReturnSuccess => { libc::memcpy( val.bytes.as_mut_ptr() as *mut c_void, output_structure.bytes.as_mut_ptr() as *mut c_void, mem::size_of::<[u8; 32]>(), ); Ok(val) } result => Err(result), } } } fn get_temperature_inner( con: io_connect_t, input_structure: &ffi::KeyData_t, original_val: &ffi::Val_t, ) -> Option { unsafe { if let Ok(val) = read_key(con, input_structure, (*original_val).clone()) { if val.data_size > 0 && libc::strcmp(val.data_type.as_ptr(), c"sp78".as_ptr() as *const i8) == 0 { // convert sp78 value to temperature let x = (i32::from(val.bytes[0]) << 6) + (i32::from(val.bytes[1]) >> 2); return Some(x as f32 / 64f32); } } } None } fn get_temperature(con: io_connect_t, key: &[i8]) -> Option { unsafe { let (input_structure, val) = get_key_size(con, key).ok()?; get_temperature_inner(con, &input_structure, &val) } } pub(crate) struct IoService(io_connect_t); impl IoService { fn new(obj: io_connect_t) -> Option { if obj == 0 { None } else { Some(Self(obj)) } } pub(crate) fn inner(&self) -> io_connect_t { self.0 } // code from https://github.com/Chris911/iStats // Not supported on iOS, or in the default macOS pub(crate) fn new_connection() -> Option { let mut iterator: io_iterator_t = 0; unsafe { let Some(matching) = IOServiceMatching(c"AppleSMC".as_ptr() as *const i8) else { sysinfo_debug!("IOServiceMatching call failed, `AppleSMC` not found"); return None; }; let matching = CFRetained::::from(&matching); let result = IOServiceGetMatchingServices(kIOMasterPortDefault, Some(matching), &mut iterator); if result != kIOReturnSuccess { sysinfo_debug!("Error: IOServiceGetMatchingServices() = {}", result); return None; } let iterator = match IOReleaser::new(iterator) { Some(i) => i, None => { sysinfo_debug!( "Error: IOServiceGetMatchingServices() succeeded but returned invalid descriptor" ); return None; } }; let device = match IOReleaser::new(IOIteratorNext(iterator.inner())) { Some(d) => d, None => { sysinfo_debug!("Error: no SMC found"); return None; } }; let mut conn = 0; let result = IOServiceOpen( device.inner(), #[allow(deprecated)] libc::mach_task_self(), 0, &mut conn, ); if result != kIOReturnSuccess { sysinfo_debug!("Error: IOServiceOpen() = {}", result); return None; } let conn = IoService::new(conn); if conn.is_none() { sysinfo_debug!( "Error: IOServiceOpen() succeeded but returned invalid descriptor..." ); } conn } } } impl Drop for IoService { fn drop(&mut self) { unsafe { IOServiceClose(self.0) }; } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/cpu.rs000066400000000000000000000074031506747262600236220ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "apple-sandbox")] pub(crate) unsafe fn get_cpu_frequency(_brand: &str) -> u64 { 0 } #[cfg(not(feature = "apple-sandbox"))] pub(crate) unsafe fn get_cpu_frequency(brand: &str) -> u64 { use crate::sys::macos::utils::IOReleaser; use objc2_core_foundation::{ CFData, CFDictionary, CFRange, CFRetained, CFString, kCFAllocatorDefault, }; use objc2_io_kit::{ IOIteratorNext, IORegistryEntryCreateCFProperty, IORegistryEntryGetName, IOServiceGetMatchingServices, IOServiceMatching, io_iterator_t, kIOMasterPortDefault, kIOReturnSuccess, }; unsafe { let Some(matching) = IOServiceMatching(c"AppleARMIODevice".as_ptr().cast()) else { sysinfo_debug!("IOServiceMatching call failed, `AppleARMIODevice` not found"); return 0; }; let matching = CFRetained::::from(&matching); // Starting from mac M1, the above call returns nothing for the CPU frequency // so we try to get it from another source. This code comes from // . let mut iterator: io_iterator_t = 0; let result = IOServiceGetMatchingServices(kIOMasterPortDefault, Some(matching), &mut iterator); if result != kIOReturnSuccess { sysinfo_debug!("Error: IOServiceGetMatchingServices() = {}", result); return 0; } let iterator = match IOReleaser::new(iterator) { Some(i) => i, None => { sysinfo_debug!( "Error: IOServiceGetMatchingServices() succeeded but returned invalid descriptor" ); return 0; } }; let mut name = [0; 128]; let entry = loop { let entry = match IOReleaser::new(IOIteratorNext(iterator.inner())) { Some(d) => d, None => { sysinfo_debug!("`pmgr` entry was not found in AppleARMIODevice service"); return 0; } }; let status = IORegistryEntryGetName(entry.inner(), &mut name); if status != libc::KERN_SUCCESS { continue; } else if libc::strcmp(name.as_ptr(), c"pmgr".as_ptr() as *const _) == 0 { break entry; } }; let node_name = CFString::from_static_str("voltage-states5-sram"); let core_ref = match IORegistryEntryCreateCFProperty( entry.inner(), Some(&node_name), kCFAllocatorDefault, 0, ) { Some(c) => c, None => { sysinfo_debug!("`voltage-states5-sram` property not found"); return 0; } }; let Ok(core_ref) = core_ref.downcast::() else { sysinfo_debug!("`voltage-states5-sram` property was not CFData"); return 0; }; let core_length = core_ref.length(); if core_length < 8 { sysinfo_debug!("expected `voltage-states5-sram` buffer to have at least size 8"); return 0; } let mut max: u64 = 0; core_ref.bytes( CFRange { location: core_length - 8, length: 4, }, &mut max as *mut _ as *mut _, ); // Check taken from https://github.com/vladkens/macmon/commit/9e05a6f6e9aee01c4cd6e01e0639ac23f5820f18. // Not sure if there is a better way to differentiate this. if brand.contains("M1") || brand.contains("M2") | brand.contains("M3") { max / 1_000_000 } else { max / 1_000 } } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/disk.rs000066400000000000000000000113051506747262600237610ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::DiskKind; use crate::sys::{ disk::{get_int_value, get_str_value}, macos::{ffi, utils::IOReleaser}, }; use objc2_core_foundation::{CFDictionary, CFRetained, CFString, kCFAllocatorDefault}; use objc2_io_kit::{ IOBSDNameMatching, IOIteratorNext, IOObjectConformsTo, IORegistryEntryCreateCFProperty, IORegistryEntryGetParentEntry, IOServiceGetMatchingServices, io_iterator_t, io_registry_entry_t, kIOMasterPortDefault, kIOServicePlane, }; fn iterate_service_tree(bsd_name: &[u8], key: &CFString, eval: F) -> Option where F: Fn(io_registry_entry_t, &CFDictionary) -> Option, { let matching = unsafe { IOBSDNameMatching(kIOMasterPortDefault, 0, bsd_name.as_ptr().cast()) }?; let matching = CFRetained::::from(&matching); let mut service_iterator: io_iterator_t = 0; if unsafe { IOServiceGetMatchingServices(kIOMasterPortDefault, Some(matching), &mut service_iterator) } != libc::KERN_SUCCESS { return None; } // Safety: We checked for success, so there is always a valid iterator, even if its empty. let service_iterator = unsafe { IOReleaser::new_unchecked(service_iterator) }; let mut parent_entry: io_registry_entry_t = 0; while let Some(mut current_service_entry) = IOReleaser::new(unsafe { IOIteratorNext(service_iterator.inner()) }) { // Note: This loop is required in a non-obvious way. Due to device properties existing as a tree // in IOKit, we may need an arbitrary number of calls to `IORegistryEntryCreateCFProperty` in order to find // the values we are looking for. The function may return nothing if we aren't deep enough into the registry // tree, so we need to continue going from child->parent node until its found. loop { if unsafe { IORegistryEntryGetParentEntry( current_service_entry.inner(), kIOServicePlane.as_ptr().cast_mut().cast(), &mut parent_entry, ) } != libc::KERN_SUCCESS { break; } current_service_entry = match IOReleaser::new(parent_entry) { Some(service) => service, // There were no more parents left None => break, }; let properties_result = unsafe { IORegistryEntryCreateCFProperty( current_service_entry.inner(), Some(key), kCFAllocatorDefault, 0, ) }; if let Some(properties) = properties_result && let Ok(properties) = properties.downcast::() && let Some(result) = eval(parent_entry, &properties) { return Some(result); } } } None } pub(crate) fn get_disk_type(bsd_name: &[u8]) -> Option { let characteristics_string = CFString::from_static_str(ffi::kIOPropertyDeviceCharacteristicsKey); iterate_service_tree(bsd_name, &characteristics_string, |_, properties| { let medium = unsafe { super::disk::get_str_value( properties, Some(&CFString::from_static_str(ffi::kIOPropertyMediumTypeKey)), ) }?; match medium.as_str() { _ if medium == ffi::kIOPropertyMediumTypeSolidStateKey => Some(DiskKind::SSD), _ if medium == ffi::kIOPropertyMediumTypeRotationalKey => Some(DiskKind::HDD), _ => Some(DiskKind::Unknown(-1)), } }) } /// Returns a tuple consisting of the total number of bytes read and written by the specified disk pub(crate) fn get_disk_io(bsd_name: &[u8]) -> Option<(u64, u64)> { let stat_string = CFString::from_static_str(ffi::kIOBlockStorageDriverStatisticsKey); iterate_service_tree(bsd_name, &stat_string, |parent_entry, properties| { if !unsafe { IOObjectConformsTo(parent_entry, c"IOBlockStorageDriver".as_ptr() as *mut _) } { return None; } unsafe { let read_bytes = super::disk::get_int_value( properties, Some(&CFString::from_static_str( ffi::kIOBlockStorageDriverStatisticsBytesReadKey, )), )?; let written_bytes = super::disk::get_int_value( properties, Some(&CFString::from_static_str( ffi::kIOBlockStorageDriverStatisticsBytesWrittenKey, )), )?; Some((read_bytes, written_bytes)) } }) } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/ffi.rs000066400000000000000000000120361506747262600235750ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. // Note: IOKit is only available on macOS up until very recent iOS versions: https://developer.apple.com/documentation/iokit cfg_if! { // TODO(madsmtm): Expose this in `objc2-io-kit`. if #[cfg(feature = "disk")] { #[allow(non_upper_case_globals)] pub const kIOPropertyDeviceCharacteristicsKey: &str = "Device Characteristics"; #[allow(non_upper_case_globals)] pub const kIOPropertyMediumTypeKey: &str = "Medium Type"; #[allow(non_upper_case_globals)] pub const kIOPropertyMediumTypeSolidStateKey: &str = "Solid State"; #[allow(non_upper_case_globals)] pub const kIOPropertyMediumTypeRotationalKey: &str = "Rotational"; #[allow(non_upper_case_globals)] pub const kIOBlockStorageDriverStatisticsKey: &str = "Statistics"; #[allow(non_upper_case_globals)] pub const kIOBlockStorageDriverStatisticsBytesReadKey: &str = "Bytes (Read)"; #[allow(non_upper_case_globals)] pub const kIOBlockStorageDriverStatisticsBytesWrittenKey: &str = "Bytes (Write)"; } } #[cfg(all( not(feature = "apple-sandbox"), all( feature = "component", any(target_arch = "x86", target_arch = "x86_64") ), ))] mod keydata { #[cfg_attr(feature = "debug", derive(Eq, Hash, PartialEq))] #[derive(Clone)] #[repr(C)] pub struct Val_t { pub key: [i8; 5], pub data_size: u32, pub data_type: [i8; 5], // UInt32Char_t pub bytes: [i8; 32], // SMCBytes_t } #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] #[repr(C)] pub struct KeyData_vers_t { pub major: u8, pub minor: u8, pub build: u8, pub reserved: [u8; 1], pub release: u16, } #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] #[repr(C)] pub struct KeyData_pLimitData_t { pub version: u16, pub length: u16, pub cpu_plimit: u32, pub gpu_plimit: u32, pub mem_plimit: u32, } #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] #[repr(C)] pub struct KeyData_keyInfo_t { pub data_size: u32, pub data_type: u32, pub data_attributes: u8, } #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] #[repr(C)] pub struct KeyData_t { pub key: u32, pub vers: KeyData_vers_t, pub p_limit_data: KeyData_pLimitData_t, pub key_info: KeyData_keyInfo_t, pub result: u8, pub status: u8, pub data8: u8, pub data32: u32, pub bytes: [i8; 32], // SMCBytes_t } #[allow(dead_code)] pub const KERNEL_INDEX_SMC: i32 = 2; #[allow(dead_code)] pub const SMC_CMD_READ_KEYINFO: u8 = 9; #[allow(dead_code)] pub const SMC_CMD_READ_BYTES: u8 = 5; } #[cfg(all( not(feature = "apple-sandbox"), all( feature = "component", any(target_arch = "x86", target_arch = "x86_64") ), ))] pub use keydata::*; /// Private Apple APIs. #[cfg(all( feature = "component", not(feature = "apple-sandbox"), target_arch = "aarch64" ))] mod private { use std::ptr::NonNull; use objc2_core_foundation::{CFAllocator, CFDictionary}; use objc2_io_kit::{IOHIDEventSystemClient, IOHIDServiceClient}; #[repr(C)] pub struct IOHIDEvent(libc::c_void); objc2_core_foundation::cf_type!( unsafe impl IOHIDEvent {} ); #[allow(non_upper_case_globals)] pub const kIOHIDEventTypeTemperature: i64 = 15; #[inline] #[allow(non_snake_case)] pub fn IOHIDEventFieldBase(event_type: i64) -> i64 { event_type << 16 } #[cfg(not(feature = "apple-sandbox"))] #[link(name = "IOKit", kind = "framework")] unsafe extern "C" { pub fn IOHIDEventSystemClientCreate( allocator: Option<&CFAllocator>, ) -> Option>; pub fn IOHIDEventSystemClientSetMatching( client: &IOHIDEventSystemClient, matches: &CFDictionary, ) -> i32; pub fn IOHIDServiceClientCopyEvent( service: &IOHIDServiceClient, v0: i64, v1: i32, v2: i64, ) -> Option>; pub fn IOHIDEventGetFloatValue(event: &IOHIDEvent, field: i64) -> f64; } pub(crate) const HID_DEVICE_PROPERTY_PRODUCT: &str = "Product"; #[allow(non_upper_case_globals)] pub(crate) const kIOHIDSerialNumberKey: &str = "SerialNumber"; pub(crate) const HID_DEVICE_PROPERTY_PRIMARY_USAGE: &str = "PrimaryUsage"; pub(crate) const HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE: &str = "PrimaryUsagePage"; #[allow(non_upper_case_globals)] pub(crate) const kHIDPage_AppleVendor: i32 = 0xff00; #[allow(non_upper_case_globals)] pub(crate) const kHIDUsage_AppleVendor_TemperatureSensor: i32 = 0x0005; } #[cfg(all( feature = "component", not(feature = "apple-sandbox"), target_arch = "aarch64" ))] pub use private::*; GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/mod.rs000066400000000000000000000023741506747262600236140ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod ffi; cfg_if! { if #[cfg(all(feature = "system", not(feature = "apple-sandbox")))] { pub(crate) mod cpu; pub mod system; pub mod process; } if #[cfg(all(feature = "system", feature = "apple-sandbox"))] { pub use crate::sys::app_store::process; } if #[cfg(any( feature = "disk", all( not(feature = "apple-sandbox"), any( feature = "system", all( feature = "component", any(target_arch = "x86", target_arch = "x86_64") ) ) ), ))] { pub(crate) mod utils; } if #[cfg(feature = "disk")] { pub mod disk; } if #[cfg(feature = "apple-sandbox")] { #[cfg(feature = "component")] pub use crate::sys::app_store::component; } else if #[cfg(feature = "component")] { pub mod component; } } // Make formattable by rustfmt. #[cfg(any())] mod component; #[cfg(any())] mod cpu; #[cfg(any())] mod disk; #[cfg(any())] mod process; #[cfg(any())] mod system; #[cfg(any())] mod utils; GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/process.rs000066400000000000000000000610101506747262600245030ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::ffi::{OsStr, OsString}; use std::mem::{self, MaybeUninit}; use std::os::unix::ffi::{OsStrExt, OsStringExt}; use std::path::{Path, PathBuf}; use std::process::ExitStatus; use libc::{c_int, c_void, kill}; use crate::{DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Signal, Uid}; use crate::sys::process::ThreadStatus; use crate::sys::system::Wrap; use crate::unix::utils::cstr_to_rust_with_size; pub(crate) struct ProcessInner { pub(crate) name: OsString, pub(crate) cmd: Vec, pub(crate) exe: Option, pid: Pid, parent: Option, pub(crate) environ: Vec, cwd: Option, pub(crate) root: Option, pub(crate) memory: u64, pub(crate) virtual_memory: u64, old_utime: u64, old_stime: u64, start_time: u64, run_time: u64, pub(crate) updated: bool, cpu_usage: f32, user_id: Option, effective_user_id: Option, group_id: Option, effective_group_id: Option, pub(crate) process_status: ProcessStatus, /// Status of process (running, stopped, waiting, etc). `None` means `sysinfo` doesn't have /// enough rights to get this information. /// /// This is very likely this one that you want instead of `process_status`. pub(crate) status: Option, pub(crate) old_read_bytes: u64, pub(crate) old_written_bytes: u64, pub(crate) read_bytes: u64, pub(crate) written_bytes: u64, accumulated_cpu_time: u64, exists: bool, } impl ProcessInner { pub(crate) fn new_empty(pid: Pid) -> Self { Self { name: OsString::new(), pid, parent: None, cmd: Vec::new(), environ: Vec::new(), exe: None, cwd: None, root: None, memory: 0, virtual_memory: 0, cpu_usage: 0., old_utime: 0, old_stime: 0, updated: true, start_time: 0, run_time: 0, user_id: None, effective_user_id: None, group_id: None, effective_group_id: None, process_status: ProcessStatus::Unknown(0), status: None, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, accumulated_cpu_time: 0, exists: true, } } pub(crate) fn new(pid: Pid, parent: Option, start_time: u64, run_time: u64) -> Self { Self { name: OsString::new(), pid, parent, cmd: Vec::new(), environ: Vec::new(), exe: None, cwd: None, root: None, memory: 0, virtual_memory: 0, cpu_usage: 0., old_utime: 0, old_stime: 0, updated: true, start_time, run_time, user_id: None, effective_user_id: None, group_id: None, effective_group_id: None, process_status: ProcessStatus::Unknown(0), status: None, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, accumulated_cpu_time: 0, exists: true, } } pub(crate) fn kill_with(&self, signal: Signal) -> Option { let c_signal = crate::sys::system::convert_signal(signal)?; unsafe { Some(kill(self.pid.0, c_signal) == 0) } } pub(crate) fn name(&self) -> &OsStr { &self.name } pub(crate) fn cmd(&self) -> &[OsString] { &self.cmd } pub(crate) fn exe(&self) -> Option<&Path> { self.exe.as_deref() } pub(crate) fn pid(&self) -> Pid { self.pid } pub(crate) fn environ(&self) -> &[OsString] { &self.environ } pub(crate) fn cwd(&self) -> Option<&Path> { self.cwd.as_deref() } pub(crate) fn root(&self) -> Option<&Path> { self.root.as_deref() } pub(crate) fn memory(&self) -> u64 { self.memory } pub(crate) fn virtual_memory(&self) -> u64 { self.virtual_memory } pub(crate) fn parent(&self) -> Option { self.parent } pub(crate) fn status(&self) -> ProcessStatus { // If the status is `Run`, then it's very likely wrong so we instead // return a `ProcessStatus` converted from the `ThreadStatus`. if self.process_status == ProcessStatus::Run && let Some(thread_status) = self.status { return ProcessStatus::from(thread_status); } self.process_status } pub(crate) fn start_time(&self) -> u64 { self.start_time } pub(crate) fn run_time(&self) -> u64 { self.run_time } pub(crate) fn cpu_usage(&self) -> f32 { self.cpu_usage } pub(crate) fn accumulated_cpu_time(&self) -> u64 { self.accumulated_cpu_time } pub(crate) fn disk_usage(&self) -> DiskUsage { DiskUsage { read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, } } pub(crate) fn user_id(&self) -> Option<&Uid> { self.user_id.as_ref() } pub(crate) fn effective_user_id(&self) -> Option<&Uid> { self.effective_user_id.as_ref() } pub(crate) fn group_id(&self) -> Option { self.group_id } pub(crate) fn effective_group_id(&self) -> Option { self.effective_group_id } pub(crate) fn wait(&self) -> Option { crate::unix::utils::wait_process(self.pid) } pub(crate) fn session_id(&self) -> Option { unsafe { let session_id = libc::getsid(self.pid.0); if session_id < 0 { None } else { Some(Pid(session_id)) } } } pub(crate) fn switch_updated(&mut self) -> bool { std::mem::replace(&mut self.updated, false) } pub(crate) fn set_nonexistent(&mut self) { self.exists = false; } pub(crate) fn exists(&self) -> bool { self.exists } } #[allow(deprecated)] // Because of libc::mach_absolute_time. pub(crate) fn compute_cpu_usage( p: &mut ProcessInner, task_info: libc::proc_taskinfo, system_time: u64, user_time: u64, time_interval: Option, ) { if let Some(time_interval) = time_interval { let total_existing_time = p.old_stime.saturating_add(p.old_utime); if time_interval > 0.000001 && total_existing_time > 0 { let total_current_time = task_info .pti_total_system .saturating_add(task_info.pti_total_user); let total_time_diff = total_current_time.saturating_sub(total_existing_time); if total_time_diff > 0 { p.cpu_usage = (total_time_diff as f64 / time_interval * 100.) as f32; } } p.old_stime = task_info.pti_total_system; p.old_utime = task_info.pti_total_user; } else { unsafe { // This is the "backup way" of CPU computation. let time = libc::mach_absolute_time(); let task_time = user_time .saturating_add(system_time) .saturating_add(task_info.pti_total_user) .saturating_add(task_info.pti_total_system); let system_time_delta = if task_time < p.old_utime { task_time } else { task_time.saturating_sub(p.old_utime) }; let time_delta = if time < p.old_stime { time } else { time.saturating_sub(p.old_stime) }; p.old_utime = task_time; p.old_stime = time; p.cpu_usage = if time_delta == 0 { 0f32 } else { (system_time_delta as f64 * 100f64 / time_delta as f64) as f32 }; } } } unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo { unsafe { let mut task_info = mem::zeroed::(); // If it doesn't work, we just don't have memory information for this process // so it's "fine". libc::proc_pidinfo( pid.0, libc::PROC_PIDTASKINFO, 0, &mut task_info as *mut libc::proc_taskinfo as *mut c_void, mem::size_of::() as _, ); task_info } } #[inline] fn check_if_pid_is_alive(pid: Pid, check_if_alive: bool) -> bool { // In case we are iterating all pids we got from `proc_listallpids`, then // there is no point checking if the process is alive since it was returned // from this function. if !check_if_alive { return true; } unsafe { if kill(pid.0, 0) == 0 { return true; } // `kill` failed but it might not be because the process is dead. let errno = crate::unix::libc_errno(); // If errno is equal to ESCHR, it means the process is dead. !errno.is_null() && *errno != libc::ESRCH } } unsafe fn get_bsd_info(pid: Pid) -> Option { unsafe { let mut info = mem::zeroed::(); if libc::proc_pidinfo( pid.0, libc::PROC_PIDTBSDINFO, 0, &mut info as *mut _ as *mut _, mem::size_of::() as _, ) != mem::size_of::() as c_int { None } else { Some(info) } } } fn get_parent(info: &libc::proc_bsdinfo) -> Option { match info.pbi_ppid as i32 { 0 => None, p => Some(Pid(p)), } } unsafe fn create_new_process( pid: Pid, now: u64, refresh_kind: ProcessRefreshKind, info: Option, timebase_to_ms: f64, ) -> Result, ()> { let info = match info { Some(info) => info, None => { let mut p = ProcessInner::new_empty(pid); unsafe { if get_exe_and_name_backup(&mut p, refresh_kind, false) { get_cwd_root(&mut p, refresh_kind); return Ok(Some(Process { inner: p })); } // If we can't even have the name, no point in keeping it. return Err(()); } } }; let parent = get_parent(&info); let start_time = info.pbi_start_tvsec; let run_time = now.saturating_sub(start_time); let mut p = ProcessInner::new(pid, parent, start_time, run_time); unsafe { if !get_process_infos(&mut p, refresh_kind) && !get_exe_and_name_backup(&mut p, refresh_kind, false) { // If we can't even have the name, no point in keeping it. return Err(()); } get_cwd_root(&mut p, refresh_kind); if refresh_kind.cpu() || refresh_kind.memory() { let task_info = get_task_info(pid); p.old_stime = task_info.pti_total_system; p.old_utime = task_info.pti_total_user; if refresh_kind.cpu() { p.accumulated_cpu_time = (task_info .pti_total_user .saturating_add(task_info.pti_total_system) as f64 * timebase_to_ms) as u64; } if refresh_kind.memory() { p.memory = task_info.pti_resident_size; p.virtual_memory = task_info.pti_virtual_size; } } p.user_id = Some(Uid(info.pbi_ruid)); p.effective_user_id = Some(Uid(info.pbi_uid)); p.group_id = Some(Gid(info.pbi_rgid)); p.effective_group_id = Some(Gid(info.pbi_gid)); p.process_status = ProcessStatus::from(info.pbi_status); if refresh_kind.disk_usage() { update_proc_disk_activity(&mut p); } Ok(Some(Process { inner: p })) } } /// Less efficient way to retrieve `exe` and `name`. unsafe fn get_exe_and_name_backup( process: &mut ProcessInner, refresh_kind: ProcessRefreshKind, force_check: bool, ) -> bool { let exe_needs_update = refresh_kind.exe().needs_update(|| process.exe.is_none()); if !process.name.is_empty() && !exe_needs_update && !force_check { return true; } let mut buffer: Vec = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _); unsafe { match libc::proc_pidpath( process.pid.0, buffer.as_mut_ptr() as *mut _, libc::PROC_PIDPATHINFO_MAXSIZE as _, ) { x if x > 0 => { buffer.set_len(x as _); let tmp = OsString::from_vec(buffer); let exe = PathBuf::from(tmp); if process.name.is_empty() { exe.file_name() .unwrap_or_default() .clone_into(&mut process.name); } if exe_needs_update { process.exe = Some(exe); } true } _ => false, } } } unsafe fn convert_node_path_info(node: &libc::vnode_info_path) -> Option { if node.vip_vi.vi_stat.vst_dev == 0 { return None; } cstr_to_rust_with_size( node.vip_path.as_ptr() as _, Some(mem::size_of_val(&node.vip_path)), ) .map(PathBuf::from) } unsafe fn get_cwd_root(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) { let cwd_needs_update = refresh_kind.cwd().needs_update(|| process.cwd.is_none()); let root_needs_update = refresh_kind.root().needs_update(|| process.root.is_none()); if !cwd_needs_update && !root_needs_update { return; } unsafe { let mut vnodepathinfo = mem::zeroed::(); let result = libc::proc_pidinfo( process.pid.0, libc::PROC_PIDVNODEPATHINFO, 0, &mut vnodepathinfo as *mut _ as *mut _, mem::size_of::() as _, ); if result < 1 { sysinfo_debug!("Failed to retrieve cwd and root for {}", process.pid.0); return; } if cwd_needs_update { process.cwd = convert_node_path_info(&vnodepathinfo.pvi_cdir); } if root_needs_update { process.root = convert_node_path_info(&vnodepathinfo.pvi_rdir); } } } unsafe fn get_process_infos(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) -> bool { /* * /---------------\ 0x00000000 * | ::::::::::::: | * |---------------| <-- Beginning of data returned by sysctl() is here. * | argc | * |---------------| * | exec_path | * |---------------| * | 0 | * |---------------| * | arg[0] | * |---------------| * | 0 | * |---------------| * | arg[n] | * |---------------| * | 0 | * |---------------| * | env[0] | * |---------------| * | 0 | * |---------------| * | env[n] | * |---------------| * | ::::::::::::: | * |---------------| <-- Top of stack. * : : * : : * \---------------/ 0xffffffff */ let mut mib: [libc::c_int; 3] = [libc::CTL_KERN, libc::KERN_PROCARGS2, process.pid.0 as _]; let mut arg_max = 0; unsafe { // First we retrieve the size we will need for our data (in `arg_max`). if libc::sysctl( mib.as_mut_ptr(), mib.len() as _, std::ptr::null_mut(), &mut arg_max, std::ptr::null_mut(), 0, ) == -1 { sysinfo_debug!( "couldn't get arguments and environment size for PID {}", process.pid.0 ); return false; // not enough rights I assume? } let mut proc_args: Vec = Vec::with_capacity(arg_max as _); if libc::sysctl( mib.as_mut_ptr(), mib.len() as _, proc_args.as_mut_slice().as_mut_ptr() as *mut _, &mut arg_max, std::ptr::null_mut(), 0, ) == -1 { sysinfo_debug!( "couldn't get arguments and environment for PID {}", process.pid.0 ); return false; // What changed since the previous call? Dark magic! } proc_args.set_len(arg_max); if proc_args.is_empty() { return false; } // We copy the number of arguments (`argc`) to `n_args`. let mut n_args: c_int = 0; libc::memcpy( &mut n_args as *mut _ as *mut _, proc_args.as_slice().as_ptr() as *const _, mem::size_of::(), ); // We skip `argc`. let proc_args = &proc_args[mem::size_of::()..]; let (exe, proc_args) = get_exe(proc_args); if process.name.is_empty() { exe.file_name() .unwrap_or_default() .clone_into(&mut process.name); } if refresh_kind.exe().needs_update(|| process.exe.is_none()) { process.exe = Some(exe.to_owned()); } let environ_needs_update = refresh_kind .environ() .needs_update(|| process.environ.is_empty()); let cmd_needs_update = refresh_kind.cmd().needs_update(|| process.cmd.is_empty()); if !environ_needs_update && !cmd_needs_update { // Nothing else to be done! return true; } let proc_args = get_arguments(&mut process.cmd, proc_args, n_args, cmd_needs_update); if environ_needs_update { get_environ(&mut process.environ, proc_args); } true } } fn get_exe(data: &[u8]) -> (&Path, &[u8]) { let pos = data.iter().position(|c| *c == 0).unwrap_or(data.len()); let (exe, proc_args) = data.split_at(pos); (Path::new(OsStr::from_bytes(exe)), proc_args) } fn get_arguments<'a>( cmd: &mut Vec, mut data: &'a [u8], mut n_args: c_int, refresh_cmd: bool, ) -> &'a [u8] { if refresh_cmd { cmd.clear(); } if n_args < 1 { return data; } while data.first() == Some(&0) { data = &data[1..]; } while n_args > 0 && !data.is_empty() { let pos = data.iter().position(|c| *c == 0).unwrap_or(data.len()); let arg = &data[..pos]; if !arg.is_empty() && refresh_cmd { cmd.push(OsStr::from_bytes(arg).to_os_string()); } data = &data[pos..]; while data.first() == Some(&0) { data = &data[1..]; } n_args -= 1; } data } fn get_environ(environ: &mut Vec, mut data: &[u8]) { environ.clear(); while data.first() == Some(&0) { data = &data[1..]; } while !data.is_empty() { let pos = data.iter().position(|c| *c == 0).unwrap_or(data.len()); let arg = &data[..pos]; if arg.is_empty() { return; } environ.push(OsStr::from_bytes(arg).to_os_string()); data = &data[pos..]; while data.first() == Some(&0) { data = &data[1..]; } } } pub(crate) fn update_process( wrap: &Wrap, pid: Pid, time_interval: Option, now: u64, refresh_kind: ProcessRefreshKind, check_if_alive: bool, timebase_to_ms: f64, ) -> Result, ()> { unsafe { if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) { let p = &mut p.inner; let mut extra_checked = false; if let Some(info) = get_bsd_info(pid) { if info.pbi_start_tvsec != p.start_time { // We don't want it to be removed, just replaced. p.updated = true; // To ensure the name and exe path will be updated. p.name.clear(); p.exe = None; // The owner of this PID changed. return create_new_process(pid, now, refresh_kind, Some(info), timebase_to_ms); } let parent = get_parent(&info); // Update the parent if it changed. if p.parent != parent { p.parent = parent; } } else { // Weird that we can't get this information. Sometimes, mac can list PIDs that do // not exist anymore. So let's ensure that the process is actually still alive. if !get_exe_and_name_backup(p, refresh_kind, true) { // So it's not actually alive, then let's un-update it so it will be removed. p.updated = false; return Ok(None); } extra_checked = true; } if !get_process_infos(p, refresh_kind) && !extra_checked { get_exe_and_name_backup(p, refresh_kind, false); } get_cwd_root(p, refresh_kind); if refresh_kind.disk_usage() { update_proc_disk_activity(p); } let mut thread_info = mem::zeroed::(); let (user_time, system_time, thread_status) = if libc::proc_pidinfo( pid.0, libc::PROC_PIDTHREADINFO, 0, &mut thread_info as *mut libc::proc_threadinfo as *mut c_void, mem::size_of::() as _, ) != 0 { ( thread_info.pth_user_time, thread_info.pth_system_time, Some(ThreadStatus::from(thread_info.pth_run_state)), ) } else { // It very likely means that the process is dead... if check_if_pid_is_alive(pid, check_if_alive) { (0, 0, Some(ThreadStatus::Running)) } else { return Err(()); } }; p.status = thread_status; p.run_time = now.saturating_sub(p.start_time); if refresh_kind.cpu() || refresh_kind.memory() { let task_info = get_task_info(pid); if refresh_kind.cpu() { compute_cpu_usage(p, task_info, system_time, user_time, time_interval); p.accumulated_cpu_time = (task_info .pti_total_user .saturating_add(task_info.pti_total_system) as f64 * timebase_to_ms) as u64; } if refresh_kind.memory() { p.memory = task_info.pti_resident_size; p.virtual_memory = task_info.pti_virtual_size; } } p.updated = true; Ok(None) } else { create_new_process(pid, now, refresh_kind, get_bsd_info(pid), timebase_to_ms) } } } fn update_proc_disk_activity(p: &mut ProcessInner) { p.old_read_bytes = p.read_bytes; p.old_written_bytes = p.written_bytes; let mut pidrusage = MaybeUninit::::uninit(); unsafe { let retval = libc::proc_pid_rusage( p.pid().0 as _, libc::RUSAGE_INFO_V2, pidrusage.as_mut_ptr() as _, ); if retval < 0 { sysinfo_debug!("proc_pid_rusage failed: {:?}", retval); } else { let pidrusage = pidrusage.assume_init(); p.read_bytes = pidrusage.ri_diskio_bytesread; p.written_bytes = pidrusage.ri_diskio_byteswritten; } } } #[allow(clippy::uninit_vec)] pub(crate) fn get_proc_list() -> Option> { unsafe { let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); if count < 1 { return None; } let mut pids: Vec = Vec::with_capacity(count as usize); pids.set_len(count as usize); let count = count * mem::size_of::() as i32; let x = libc::proc_listallpids(pids.as_mut_ptr() as *mut c_void, count); if x < 1 || x as usize >= pids.len() { None } else { pids.set_len(x as usize); Some(pids) } } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/system.rs000066400000000000000000000175351506747262600243660ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[allow(deprecated)] use libc::{mach_timebase_info, mach_timebase_info_data_t}; use libc::{ _SC_CLK_TCK, PROCESSOR_CPU_LOAD_INFO, host_processor_info, mach_port_t, munmap, natural_t, processor_cpu_load_info, processor_cpu_load_info_t, sysconf, vm_page_size, }; use std::ptr::null_mut; use std::time::Instant; struct ProcessorCpuLoadInfo { cpu_load: processor_cpu_load_info_t, cpu_count: natural_t, } impl ProcessorCpuLoadInfo { fn new(port: mach_port_t) -> Option { let mut info_size = std::mem::size_of::() as _; let mut cpu_count = 0; let mut cpu_load: processor_cpu_load_info_t = null_mut(); unsafe { if host_processor_info( port, PROCESSOR_CPU_LOAD_INFO, &mut cpu_count, &mut cpu_load as *mut _ as *mut _, &mut info_size, ) != 0 { sysinfo_debug!("host_processor_info failed, not updating CPU ticks usage..."); None } else if cpu_count < 1 || cpu_load.is_null() { None } else { Some(Self { cpu_load, cpu_count, }) } } } } impl Drop for ProcessorCpuLoadInfo { fn drop(&mut self) { unsafe { munmap(self.cpu_load as _, vm_page_size); } } } pub(crate) struct SystemTimeInfo { timebase_to_ns: f64, pub(crate) timebase_to_ms: f64, clock_per_sec: f64, old_cpu_info: ProcessorCpuLoadInfo, last_update: Option, prev_time_interval: f64, } unsafe impl Send for SystemTimeInfo {} unsafe impl Sync for SystemTimeInfo {} impl SystemTimeInfo { #[allow(deprecated)] // Everything related to mach_timebase_info_data_t pub fn new(port: mach_port_t) -> Option { unsafe { let clock_ticks_per_sec = sysconf(_SC_CLK_TCK); // FIXME: Maybe check errno here? Problem is that if errno is not 0 before this call, // we will get an error which isn't related... // if let Some(er) = std::io::Error::last_os_error().raw_os_error() { // if err != 0 { // println!("==> {:?}", er); // sysinfo_debug!("Failed to get _SC_CLK_TCK value, using old CPU tick measure system"); // return None; // } // } let mut info = mach_timebase_info_data_t { numer: 0, denom: 0 }; if mach_timebase_info(&mut info) != libc::KERN_SUCCESS { sysinfo_debug!("mach_timebase_info failed, using default value of 1"); info.numer = 1; info.denom = 1; } let old_cpu_info = match ProcessorCpuLoadInfo::new(port) { Some(cpu_info) => cpu_info, None => { sysinfo_debug!("host_processor_info failed, using old CPU tick measure system"); return None; } }; let nano_per_seconds = 1_000_000_000.; let timebase_to_ns = info.numer as f64 / info.denom as f64; sysinfo_debug!(""); Some(Self { timebase_to_ns, // We convert from nano (10^-9) to ms (10^3). timebase_to_ms: timebase_to_ns / 1_000_000., clock_per_sec: nano_per_seconds / clock_ticks_per_sec as f64, old_cpu_info, last_update: None, prev_time_interval: 0., }) } } pub fn get_time_interval(&mut self, port: mach_port_t) -> f64 { let need_cpu_usage_update = self .last_update .map(|last_update| last_update.elapsed() >= crate::MINIMUM_CPU_UPDATE_INTERVAL) .unwrap_or(true); if need_cpu_usage_update { let mut total = 0; let new_cpu_info = match ProcessorCpuLoadInfo::new(port) { Some(cpu_info) => cpu_info, None => return 0., }; let cpu_count = std::cmp::min(self.old_cpu_info.cpu_count, new_cpu_info.cpu_count); unsafe { for i in 0..cpu_count { let new_load: &processor_cpu_load_info = &*new_cpu_info.cpu_load.offset(i as _); let old_load: &processor_cpu_load_info = &*self.old_cpu_info.cpu_load.offset(i as _); for (new, old) in new_load.cpu_ticks.iter().zip(old_load.cpu_ticks.iter()) { if new > old { total += new.saturating_sub(*old); } } } } self.old_cpu_info = new_cpu_info; self.last_update = Some(Instant::now()); // Now we convert the ticks to nanoseconds (if the interval is less than // `MINIMUM_CPU_UPDATE_INTERVAL`, we replace it with it instead): let base_interval = total as f64 / cpu_count as f64 * self.clock_per_sec; let smallest = crate::MINIMUM_CPU_UPDATE_INTERVAL.as_secs_f64() * 1_000_000_000.0; self.prev_time_interval = if base_interval < smallest { smallest } else { base_interval / self.timebase_to_ns }; self.prev_time_interval } else { self.prev_time_interval } } } // Get a property value from the IOPlatformExpertDevice. #[cfg(not(feature = "apple-sandbox"))] pub(crate) fn get_io_platform_property(key: &str) -> Option { use crate::sys::macos::utils::IOReleaser; use objc2_core_foundation::{CFData, CFGetTypeID, CFString, ConcreteType, kCFAllocatorDefault}; use objc2_io_kit::{ IORegistryEntryCreateCFProperty, IOServiceGetMatchingService, IOServiceMatching, kIOMasterPortDefault, }; use std::ffi::CStr; let matching = match unsafe { IOServiceMatching(c"IOPlatformExpertDevice".as_ptr().cast()) } { Some(matching) => matching, None => { sysinfo_debug!("IOServiceMatching call failed, `IOPlatformExpertDevice` not found"); return None; } }; let result = unsafe { IOServiceGetMatchingService(kIOMasterPortDefault, Some(matching.as_opaque().into())) }; if result == 0 { sysinfo_debug!("IOServiceGetMatchingService failed"); return None; } let _r = IOReleaser::new(result); let key_cfstring = CFString::from_str(key); let properties = unsafe { IORegistryEntryCreateCFProperty(result, Some(&key_cfstring), kCFAllocatorDefault, 0) }?; if CFGetTypeID(Some(&*properties)) == CFString::type_id() { properties .downcast::() .ok() .map(|s| s.to_string()) } else { properties.downcast::().ok().and_then(|s| { CStr::from_bytes_with_nul(&s.to_vec()) .ok() .and_then(|cstr| cstr.to_str().ok()) .map(str::to_owned) }) } } #[cfg(test)] mod test { use super::*; /// Regression test for . #[test] fn test_getting_time_interval() { if !crate::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } #[allow(deprecated)] let port = unsafe { libc::mach_host_self() }; let mut info = SystemTimeInfo::new(port).unwrap(); info.get_time_interval(port); std::thread::sleep(crate::MINIMUM_CPU_UPDATE_INTERVAL.saturating_mul(5)); let val = info.get_time_interval(port); assert_ne!( val, crate::MINIMUM_CPU_UPDATE_INTERVAL.as_secs_f64() * 1_000_000_000.0 ); } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/macos/utils.rs000066400000000000000000000013721506747262600241720ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::num::NonZeroU32; use objc2_io_kit::IOObjectRelease; type IoObject = NonZeroU32; pub(crate) struct IOReleaser(IoObject); impl IOReleaser { pub(crate) fn new(obj: u32) -> Option { IoObject::new(obj).map(Self) } #[cfg(feature = "disk")] pub(crate) unsafe fn new_unchecked(obj: u32) -> Self { // Chance at catching in-development mistakes debug_assert_ne!(obj, 0); unsafe { Self(IoObject::new_unchecked(obj)) } } #[inline] pub(crate) fn inner(&self) -> u32 { self.0.get() } } impl Drop for IOReleaser { fn drop(&mut self) { unsafe { IOObjectRelease(self.0.get() as _) }; } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/mod.rs000066400000000000000000000041231506747262600225040ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] pub(crate) mod app_store; mod ffi; mod utils; cfg_if! { if #[cfg(all(target_os = "macos", any(feature = "disk", feature = "system", feature = "component")))] { pub(crate) mod macos; pub(crate) use self::macos as inner; } else if #[cfg(all(target_os = "ios", any(feature = "system", feature = "component")))] { pub(crate) mod ios; pub(crate) use self::ios as inner; } } cfg_if! { if #[cfg(feature = "system")] { pub mod cpu; pub mod motherboard; pub mod process; pub mod product; pub mod system; pub(crate) use self::cpu::CpuInner; pub(crate) use self::motherboard::MotherboardInner; pub(crate) use self::process::ProcessInner; pub(crate) use self::product::ProductInner; pub(crate) use self::system::SystemInner; pub use self::system::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS}; } if #[cfg(feature = "disk")] { pub mod disk; pub(crate) use self::disk::DiskInner; pub(crate) use crate::unix::DisksInner; } if #[cfg(feature = "component")] { pub mod component; pub(crate) use self::component::{ComponentInner, ComponentsInner}; } if #[cfg(feature = "network")] { pub mod network; pub(crate) use self::network::{NetworkDataInner, NetworksInner}; } if #[cfg(feature = "user")] { pub mod users; pub(crate) use crate::unix::groups::get_groups; pub(crate) use crate::unix::users::{get_users, UserInner}; } } #[doc = include_str!("../../../md_doc/is_supported.md")] pub const IS_SUPPORTED_SYSTEM: bool = true; // Make formattable by rustfmt. #[cfg(any())] mod component; #[cfg(any())] mod cpu; #[cfg(any())] mod disk; #[cfg(any())] mod ios; #[cfg(any())] mod macos; #[cfg(any())] mod motherboard; #[cfg(any())] mod network; #[cfg(any())] mod process; #[cfg(any())] mod product; #[cfg(any())] mod system; #[cfg(any())] mod users; GuillaumeGomez-sysinfo-067dd61/src/unix/apple/motherboard.rs000066400000000000000000000030151506747262600242320ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] use crate::sys::macos::system::get_io_platform_property; pub(crate) struct MotherboardInner; impl MotherboardInner { pub(crate) fn new() -> Option { Some(Self) } pub(crate) fn name(&self) -> Option { cfg_if! { if #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] { get_io_platform_property("board-id") } else { None } } } pub(crate) fn vendor_name(&self) -> Option { cfg_if! { if #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] { get_io_platform_property("manufacturer") } else { None } } } pub(crate) fn version(&self) -> Option { cfg_if! { if #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] { get_io_platform_property("version") } else { None } } } pub(crate) fn serial_number(&self) -> Option { cfg_if! { if #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] { get_io_platform_property("IOPlatformSerialNumber") } else { None } } } pub(crate) fn asset_tag(&self) -> Option { None } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/network.rs000066400000000000000000000263151506747262600234250ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use libc::{ self, CTL_NET, IFNAMSIZ, NET_RT_IFLIST2, PF_ROUTE, RTM_IFINFO2, c_char, c_int, c_uint, if_data64, if_msghdr2, sysctl, }; use std::collections::{HashMap, hash_map}; use std::mem::{MaybeUninit, size_of}; use std::ptr::null_mut; use crate::network::refresh_networks_addresses; use crate::{IpNetwork, MacAddr, NetworkData}; // FIXME: To be removed once https://github.com/rust-lang/libc/pull/4022 is merged and released. #[repr(C)] struct ifmibdata { ifmd_name: [c_char; IFNAMSIZ], ifmd_pcount: c_uint, ifmd_flags: c_uint, ifmd_snd_len: c_uint, ifmd_snd_maxlen: c_uint, ifmd_snd_drops: c_uint, ifmd_filler: [c_uint; 4], ifmd_data: if_data64, } // FIXME: To be removed once https://github.com/rust-lang/libc/pull/4022 is merged and released. pub const IFDATA_GENERAL: c_int = 1; // FIXME: To be removed once https://github.com/rust-lang/libc/pull/4022 is merged and released. pub const IFMIB_IFDATA: c_int = 2; // FIXME: To be removed once https://github.com/rust-lang/libc/pull/4022 is merged and released. pub const NETLINK_GENERIC: c_int = 0; #[inline] fn update_field(old_field: &mut u64, new_field: &mut u64, value: u64) { *old_field = *new_field; *new_field = value; } fn update_network_data(inner: &mut NetworkDataInner, data: &if_data64) { update_field(&mut inner.old_out, &mut inner.current_out, data.ifi_obytes); update_field(&mut inner.old_in, &mut inner.current_in, data.ifi_ibytes); update_field( &mut inner.old_packets_out, &mut inner.packets_out, data.ifi_opackets, ); update_field( &mut inner.old_packets_in, &mut inner.packets_in, data.ifi_ipackets, ); update_field( &mut inner.old_errors_in, &mut inner.errors_in, data.ifi_ierrors, ); update_field( &mut inner.old_errors_out, &mut inner.errors_out, data.ifi_oerrors, ); } pub(crate) struct NetworksInner { pub(crate) interfaces: HashMap, } impl NetworksInner { pub(crate) fn new() -> Self { Self { interfaces: HashMap::new(), } } pub(crate) fn list(&self) -> &HashMap { &self.interfaces } pub(crate) fn refresh(&mut self, remove_not_listed_interfaces: bool) { self.update_networks(); if remove_not_listed_interfaces { self.interfaces.retain(|_, i| { if !i.inner.updated { return false; } i.inner.updated = false; true }); } refresh_networks_addresses(&mut self.interfaces); } #[allow(clippy::cast_ptr_alignment)] #[allow(clippy::uninit_vec)] fn update_networks(&mut self) { let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0]; let mib2 = &mut [ CTL_NET, libc::PF_LINK, NETLINK_GENERIC, IFMIB_IFDATA, 0, IFDATA_GENERAL, ]; let mut len = 0; unsafe { if sysctl( mib.as_mut_ptr(), mib.len() as _, null_mut(), &mut len, null_mut(), 0, ) < 0 { // TODO: might be nice to put an error in here... return; } let mut buf = Vec::with_capacity(len); buf.set_len(len); if sysctl( mib.as_mut_ptr(), mib.len() as _, buf.as_mut_ptr(), &mut len, null_mut(), 0, ) < 0 { // TODO: might be nice to put an error in here... return; } let buf = buf.as_ptr() as *const c_char; let lim = buf.add(len); let mut next = buf; while next < lim { let ifm = next as *const libc::if_msghdr; next = next.offset((*ifm).ifm_msglen as isize); if (*ifm).ifm_type == RTM_IFINFO2 as u8 { // The interface (line description) name stored at ifname will be returned in // the default coded character set identifier (CCSID) currently in effect for // the job. If this is not a single byte CCSID, then storage greater than // IFNAMSIZ (16) bytes may be needed. 22 bytes is large enough for all CCSIDs. let mut name = vec![0u8; libc::IFNAMSIZ + 6]; let if2m: *const if_msghdr2 = ifm as *const if_msghdr2; let pname = libc::if_indextoname((*if2m).ifm_index as _, name.as_mut_ptr() as _); if pname.is_null() { continue; } name.set_len(libc::strlen(pname)); let name = String::from_utf8_unchecked(name); let mtu = (*if2m).ifm_data.ifi_mtu as u64; // Because data size is capped at 32 bits with the previous sysctl call for some // reasons, we need to make another sysctl call to get the actual values // we originally got into `ifm.ifm_data`... // // Issue: https://github.com/GuillaumeGomez/sysinfo/issues/1378 let mut mib_data: MaybeUninit = MaybeUninit::uninit(); mib2[4] = (*if2m).ifm_index as _; let ret = sysctl( mib2.as_mut_ptr(), mib2.len() as _, mib_data.as_mut_ptr() as *mut _, &mut size_of::(), null_mut(), 0, ); match self.interfaces.entry(name) { hash_map::Entry::Occupied(mut e) => { let interface = e.get_mut(); let interface = &mut interface.inner; if ret < 0 { sysinfo_debug!( "Cannot get network interface data usage: sysctl failed: {ret}" ); } else { let data = mib_data.assume_init(); update_network_data(interface, &data.ifmd_data); } if interface.mtu != mtu { interface.mtu = mtu } interface.updated = true; } hash_map::Entry::Vacant(e) => { let current_in; let current_out; let packets_in; let packets_out; let errors_in; let errors_out; if ret < 0 { sysinfo_debug!( "Cannot get network interface data usage: sysctl failed: {ret}" ); current_in = 0; current_out = 0; packets_in = 0; packets_out = 0; errors_in = 0; errors_out = 0; } else { let data = mib_data.assume_init(); let data = data.ifmd_data; current_in = data.ifi_ibytes; current_out = data.ifi_obytes; packets_in = data.ifi_ipackets; packets_out = data.ifi_opackets; errors_in = data.ifi_ierrors; errors_out = data.ifi_oerrors; } e.insert(NetworkData { inner: NetworkDataInner { current_in, old_in: current_in, current_out, old_out: current_out, packets_in, old_packets_in: packets_in, packets_out, old_packets_out: packets_out, errors_in, old_errors_in: errors_in, errors_out, old_errors_out: errors_out, updated: true, mac_addr: MacAddr::UNSPECIFIED, ip_networks: vec![], mtu, }, }); } } } } } } } #[derive(PartialEq, Eq)] pub(crate) struct NetworkDataInner { current_in: u64, old_in: u64, current_out: u64, old_out: u64, packets_in: u64, old_packets_in: u64, packets_out: u64, old_packets_out: u64, errors_in: u64, old_errors_in: u64, errors_out: u64, old_errors_out: u64, updated: bool, /// MAC address pub(crate) mac_addr: MacAddr, /// IP networks pub(crate) ip_networks: Vec, /// Interface Maximum Transfer Unit (MTU) mtu: u64, } impl NetworkDataInner { pub(crate) fn received(&self) -> u64 { self.current_in.saturating_sub(self.old_in) } pub(crate) fn total_received(&self) -> u64 { self.current_in } pub(crate) fn transmitted(&self) -> u64 { self.current_out.saturating_sub(self.old_out) } pub(crate) fn total_transmitted(&self) -> u64 { self.current_out } pub(crate) fn packets_received(&self) -> u64 { self.packets_in.saturating_sub(self.old_packets_in) } pub(crate) fn total_packets_received(&self) -> u64 { self.packets_in } pub(crate) fn packets_transmitted(&self) -> u64 { self.packets_out.saturating_sub(self.old_packets_out) } pub(crate) fn total_packets_transmitted(&self) -> u64 { self.packets_out } pub(crate) fn errors_on_received(&self) -> u64 { self.errors_in.saturating_sub(self.old_errors_in) } pub(crate) fn total_errors_on_received(&self) -> u64 { self.errors_in } pub(crate) fn errors_on_transmitted(&self) -> u64 { self.errors_out.saturating_sub(self.old_errors_out) } pub(crate) fn total_errors_on_transmitted(&self) -> u64 { self.errors_out } pub(crate) fn mac_address(&self) -> MacAddr { self.mac_addr } pub(crate) fn ip_networks(&self) -> &[IpNetwork] { &self.ip_networks } pub(crate) fn mtu(&self) -> u64 { self.mtu } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/process.rs000066400000000000000000000062621506747262600234110ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::fmt; use crate::ProcessStatus; pub(crate) use crate::sys::inner::process::*; #[doc(hidden)] impl From for ProcessStatus { fn from(status: u32) -> ProcessStatus { match status { libc::SIDL => ProcessStatus::Idle, libc::SRUN => ProcessStatus::Run, libc::SSLEEP => ProcessStatus::Sleep, libc::SSTOP => ProcessStatus::Stop, libc::SZOMB => ProcessStatus::Zombie, x => ProcessStatus::Unknown(x), } } } #[doc(hidden)] impl From for ProcessStatus { fn from(status: ThreadStatus) -> ProcessStatus { match status { ThreadStatus::Running => ProcessStatus::Run, ThreadStatus::Stopped => ProcessStatus::Stop, ThreadStatus::Waiting => ProcessStatus::Sleep, ThreadStatus::Uninterruptible => ProcessStatus::Dead, ThreadStatus::Halted => ProcessStatus::Parked, ThreadStatus::Unknown(x) => ProcessStatus::Unknown(x as _), } } } impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { ProcessStatus::Idle => "Idle", ProcessStatus::Run => "Runnable", ProcessStatus::Sleep => "Sleeping", ProcessStatus::Stop => "Stopped", ProcessStatus::Zombie => "Zombie", _ => "Unknown", }) } } /// Enum describing the different status of a thread. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum ThreadStatus { /// Thread is running normally. Running, /// Thread is stopped. Stopped, /// Thread is waiting normally. Waiting, /// Thread is in an uninterruptible wait Uninterruptible, /// Thread is halted at a clean point. Halted, /// Unknown. Unknown(i32), } impl From for ThreadStatus { fn from(status: i32) -> ThreadStatus { match status { libc::TH_STATE_RUNNING => ThreadStatus::Running, libc::TH_STATE_STOPPED => ThreadStatus::Stopped, libc::TH_STATE_WAITING => ThreadStatus::Waiting, libc::TH_STATE_UNINTERRUPTIBLE => ThreadStatus::Uninterruptible, libc::TH_STATE_HALTED => ThreadStatus::Halted, x => ThreadStatus::Unknown(x), } } } impl ProcessInner { pub(crate) fn open_files(&self) -> Option { let buffer_size_bytes = unsafe { libc::proc_pidinfo( self.pid().0, libc::PROC_PIDLISTFDS, 0, std::ptr::null_mut(), 0, ) }; if buffer_size_bytes < 0 { sysinfo_debug!("proc_pidinfo failed"); None } else { Some(buffer_size_bytes as usize / std::mem::size_of::()) } } // FIXME: Should query the value, because it can be changed with setrlimit and other means. // For now, cannot find where to get this information sadly... pub(crate) fn open_files_limit(&self) -> Option { crate::System::open_files_limit() } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/product.rs000066400000000000000000000037371506747262600234170ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::cpu::get_sysctl_str; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] use crate::sys::macos::system::get_io_platform_property; pub(crate) struct ProductInner; impl ProductInner { pub(crate) fn family() -> Option { Some(get_sysctl_str(b"hw.model\0")) } pub(crate) fn name() -> Option { Self::family().or_else(|| { cfg_if! { if #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] { get_io_platform_property("product-name") } else { None } } }) } pub(crate) fn serial_number() -> Option { cfg_if! { if #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] { use objc2_io_kit::kIOPlatformSerialNumberKey; get_io_platform_property(unsafe { std::str::from_utf8_unchecked(kIOPlatformSerialNumberKey.to_bytes()) }) } else { None } } } pub(crate) fn stock_keeping_unit() -> Option { None } pub(crate) fn uuid() -> Option { cfg_if! { if #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] { use objc2_io_kit::kIOPlatformUUIDKey; get_io_platform_property(unsafe { std::str::from_utf8_unchecked(kIOPlatformUUIDKey.to_bytes()) }) } else { None } } } pub(crate) fn version() -> Option { cfg_if! { if #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] { get_io_platform_property("version") } else { None } } } pub(crate) fn vendor_name() -> Option { crate::Motherboard::new().and_then(|m| m.vendor_name()) } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/system.rs000066400000000000000000000454431506747262600232630ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::cpu::*; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] use crate::sys::process::*; use crate::sys::utils::{get_sys_value, get_sys_value_by_name}; use crate::{ Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind, ProcessesToUpdate, }; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] use std::cell::UnsafeCell; use std::collections::HashMap; use std::ffi::CStr; use std::mem; use std::time::Duration; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] use std::time::SystemTime; use libc::{ _SC_PAGESIZE, c_int, c_void, host_statistics64, mach_port_t, sysconf, sysctl, timeval, vm_statistics64, }; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] declare_signals! { libc::c_int, Signal::Hangup => libc::SIGHUP, Signal::Interrupt => libc::SIGINT, Signal::Quit => libc::SIGQUIT, Signal::Illegal => libc::SIGILL, Signal::Trap => libc::SIGTRAP, Signal::Abort => libc::SIGABRT, Signal::IOT => libc::SIGIOT, Signal::Bus => libc::SIGBUS, Signal::FloatingPointException => libc::SIGFPE, Signal::Kill => libc::SIGKILL, Signal::User1 => libc::SIGUSR1, Signal::Segv => libc::SIGSEGV, Signal::User2 => libc::SIGUSR2, Signal::Pipe => libc::SIGPIPE, Signal::Alarm => libc::SIGALRM, Signal::Term => libc::SIGTERM, Signal::Child => libc::SIGCHLD, Signal::Continue => libc::SIGCONT, Signal::Stop => libc::SIGSTOP, Signal::TSTP => libc::SIGTSTP, Signal::TTIN => libc::SIGTTIN, Signal::TTOU => libc::SIGTTOU, Signal::Urgent => libc::SIGURG, Signal::XCPU => libc::SIGXCPU, Signal::XFSZ => libc::SIGXFSZ, Signal::VirtualAlarm => libc::SIGVTALRM, Signal::Profiling => libc::SIGPROF, Signal::Winch => libc::SIGWINCH, Signal::IO => libc::SIGIO, // SIGPOLL doesn't exist on apple targets but since it's an equivalent of SIGIO on unix, // we simply use the SIGIO constant. Signal::Poll => libc::SIGIO, Signal::Sys => libc::SIGSYS, _ => None, } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] declare_signals! { libc::c_int, _ => None, } #[doc = include_str!("../../../md_doc/supported_signals.md")] pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals(); #[doc = include_str!("../../../md_doc/minimum_cpu_update_interval.md")] pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200); pub(crate) struct SystemInner { process_list: HashMap, mem_total: u64, mem_free: u64, mem_used: u64, mem_available: u64, swap_total: u64, swap_free: u64, page_size_b: u64, port: mach_port_t, #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] clock_info: Option, cpus: CpusWrapper, } #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] pub(crate) struct Wrap<'a>(pub UnsafeCell<&'a mut HashMap>); #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] unsafe impl Send for Wrap<'_> {} #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] unsafe impl Sync for Wrap<'_> {} fn boot_time() -> u64 { let mut boot_time = timeval { tv_sec: 0, tv_usec: 0, }; let mut len = std::mem::size_of::(); let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME]; unsafe { if sysctl( mib.as_mut_ptr(), mib.len() as _, &mut boot_time as *mut timeval as *mut _, &mut len, std::ptr::null_mut(), 0, ) < 0 { 0 } else { boot_time.tv_sec as _ } } } #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] fn get_now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .map(|n| n.as_secs()) .unwrap_or(0) } impl SystemInner { pub(crate) fn new() -> Self { unsafe { #[allow(deprecated)] let port = libc::mach_host_self(); Self { process_list: HashMap::with_capacity(200), mem_total: 0, mem_free: 0, mem_available: 0, mem_used: 0, swap_total: 0, swap_free: 0, page_size_b: sysconf(_SC_PAGESIZE) as _, port, #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] clock_info: crate::sys::macos::system::SystemTimeInfo::new(port), cpus: CpusWrapper::new(), } } } pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) { let mut mib = [libc::CTL_VM as _, libc::VM_SWAPUSAGE as _]; unsafe { if refresh_kind.swap() { // get system values // get swap info let mut xs: libc::xsw_usage = mem::zeroed::(); if get_sys_value( mem::size_of::(), &mut xs as *mut _ as *mut c_void, &mut mib, ) { self.swap_total = xs.xsu_total; self.swap_free = xs.xsu_avail; } } if refresh_kind.ram() { mib[0] = libc::CTL_HW as _; mib[1] = libc::HW_MEMSIZE as _; // get ram info if self.mem_total < 1 { get_sys_value( mem::size_of::(), &mut self.mem_total as *mut u64 as *mut c_void, &mut mib, ); } let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _; let mut stat = mem::zeroed::(); if host_statistics64( self.port, libc::HOST_VM_INFO64, &mut stat as *mut vm_statistics64 as *mut _, &mut count, ) == libc::KERN_SUCCESS { // From the apple documentation: // // /* // * NB: speculative pages are already accounted for in "free_count", // * so "speculative_count" is the number of "free" pages that are // * used to hold data that was read speculatively from disk but // * haven't actually been used by anyone so far. // */ self.mem_available = u64::from(stat.free_count) .saturating_add(u64::from(stat.inactive_count)) .saturating_add(u64::from(stat.purgeable_count)) .saturating_sub(u64::from(stat.compressor_page_count)) .saturating_mul(self.page_size_b); self.mem_used = u64::from(stat.active_count) .saturating_add(u64::from(stat.wire_count)) .saturating_add(u64::from(stat.compressor_page_count)) .saturating_add(u64::from(stat.speculative_count)) .saturating_mul(self.page_size_b); self.mem_free = u64::from(stat.free_count) .saturating_sub(u64::from(stat.speculative_count)) .saturating_mul(self.page_size_b); } } } } pub(crate) fn cgroup_limits(&self) -> Option { None } pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { self.cpus.refresh(refresh_kind, self.port); } pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) { self.cpus = CpusWrapper::new(); self.cpus.refresh(refresh_kind, self.port); } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] pub(crate) fn refresh_processes_specifics( &mut self, _processes_to_update: ProcessesToUpdate<'_>, _refresh_kind: ProcessRefreshKind, ) -> usize { 0 } #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] pub(crate) fn refresh_processes_specifics( &mut self, processes_to_update: ProcessesToUpdate<'_>, refresh_kind: ProcessRefreshKind, ) -> usize { use crate::utils::into_iter; use std::sync::atomic::{AtomicUsize, Ordering}; unsafe { let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); if count < 1 { return 0; } } if let Some(pids) = get_proc_list() { #[inline(always)] fn real_filter(e: Pid, filter: &[Pid]) -> bool { filter.contains(&e) } #[inline(always)] fn empty_filter(_e: Pid, _filter: &[Pid]) -> bool { true } #[allow(clippy::type_complexity)] let (filter, filter_callback): ( &[Pid], &(dyn Fn(Pid, &[Pid]) -> bool + Sync + Send), ) = match processes_to_update { ProcessesToUpdate::All => (&[], &empty_filter), ProcessesToUpdate::Some(pids_to_refresh) => { if pids_to_refresh.is_empty() { return 0; } (pids_to_refresh, &real_filter) } }; let nb_updated = AtomicUsize::new(0); let now = get_now(); let port = self.port; let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); let timebase_to_ms = self .clock_info .as_ref() .map(|c| c.timebase_to_ms) .unwrap_or_default(); let entries: Vec = { let wrap = &Wrap(UnsafeCell::new(&mut self.process_list)); #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; into_iter(pids) .flat_map(|pid| { if !filter_callback(pid, filter) { return None; } nb_updated.fetch_add(1, Ordering::Relaxed); update_process( wrap, pid, time_interval, now, refresh_kind, false, timebase_to_ms, ) .unwrap_or_default() }) .collect() }; entries.into_iter().for_each(|entry| { self.process_list.insert(entry.pid(), entry); }); nb_updated.into_inner() } else { 0 } } // COMMON PART // // Need to be moved into a "common" file to avoid duplication. pub(crate) fn processes(&self) -> &HashMap { &self.process_list } pub(crate) fn processes_mut(&mut self) -> &mut HashMap { &mut self.process_list } pub(crate) fn process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } pub(crate) fn global_cpu_usage(&self) -> f32 { self.cpus.global_cpu.percent() } pub(crate) fn cpus(&self) -> &[Cpu] { &self.cpus.cpus } pub(crate) fn total_memory(&self) -> u64 { self.mem_total } pub(crate) fn free_memory(&self) -> u64 { self.mem_free } pub(crate) fn available_memory(&self) -> u64 { self.mem_available } pub(crate) fn used_memory(&self) -> u64 { self.mem_used } pub(crate) fn total_swap(&self) -> u64 { self.swap_total } pub(crate) fn free_swap(&self) -> u64 { self.swap_free } // TODO: need to be checked pub(crate) fn used_swap(&self) -> u64 { self.swap_total - self.swap_free } pub(crate) fn uptime() -> u64 { unsafe { let csec = libc::time(::std::ptr::null_mut()); libc::difftime(csec, Self::boot_time() as _) as _ } } pub(crate) fn load_average() -> LoadAvg { let mut loads = vec![0f64; 3]; unsafe { libc::getloadavg(loads.as_mut_ptr(), 3); LoadAvg { one: loads[0], five: loads[1], fifteen: loads[2], } } } pub(crate) fn boot_time() -> u64 { boot_time() } pub(crate) fn name() -> Option { get_system_info(libc::KERN_OSTYPE, Some("Darwin")) } pub(crate) fn long_os_version() -> Option { #[cfg(target_os = "macos")] { let Some(os_version) = Self::os_version() else { return Some("macOS".to_owned()); }; // https://en.wikipedia.org/wiki/MacOS_version_history for (version_prefix, macos_spelling, friendly_name) in [ ("15", "macOS", "Sequoia"), ("14", "macOS", "Sonoma"), ("13", "macOS", "Ventura"), ("12", "macOS", "Monterey"), ("11", "macOS", "Big Sur"), // Big Sur identifies itself as 10.16 in some situations. // https://en.wikipedia.org/wiki/MacOS_Big_Sur#Development_history ("10.16", "macOS", "Big Sur"), ("10.15", "macOS", "Catalina"), ("10.14", "macOS", "Mojave"), ("10.13", "macOS", "High Sierra"), ("10.12", "macOS", "Sierra"), ("10.11", "OS X", "El Capitan"), ("10.10", "OS X", "Yosemite"), ("10.9", "OS X", "Mavericks"), ("10.8", "OS X", "Mountain Lion"), ("10.7", "Mac OS X", "Lion"), ("10.6", "Mac OS X", "Snow Leopard"), ("10.5", "Mac OS X", "Leopard"), ("10.4", "Mac OS X", "Tiger"), ("10.3", "Mac OS X", "Panther"), ("10.2", "Mac OS X", "Jaguar"), ("10.1", "Mac OS X", "Puma"), ("10.0", "Mac OS X", "Cheetah"), ] { if os_version.starts_with(version_prefix) { return Some(format!("{macos_spelling} {os_version} {friendly_name}")); } } Some(format!("macOS {os_version}")) } #[cfg(target_os = "ios")] { let mut long_name = "iOS".to_owned(); if let Some(os_version) = Self::os_version() { long_name.push(' '); long_name.push_str(&os_version); } Some(long_name) } } pub(crate) fn host_name() -> Option { get_system_info(libc::KERN_HOSTNAME, None) } pub(crate) fn kernel_version() -> Option { get_system_info(libc::KERN_OSRELEASE, None) } pub(crate) fn os_version() -> Option { unsafe { // get the size for the buffer first let mut size = 0; if get_sys_value_by_name(b"kern.osproductversion\0", &mut size, std::ptr::null_mut()) && size > 0 { // now create a buffer with the size and get the real value let mut buf = vec![0_u8; size as _]; if get_sys_value_by_name( b"kern.osproductversion\0", &mut size, buf.as_mut_ptr() as *mut c_void, ) { if let Some(pos) = buf.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes buf.resize(pos, 0); } String::from_utf8(buf).ok() } else { // getting the system value failed None } } else { // getting the system value failed, or did not return a buffer size None } } } pub(crate) fn distribution_id() -> String { std::env::consts::OS.to_owned() } pub(crate) fn distribution_id_like() -> Vec { Vec::new() } pub(crate) fn kernel_name() -> Option<&'static str> { Some("Darwin") } pub(crate) fn cpu_arch() -> Option { let mut arch_str: [u8; 32] = [0; 32]; let mut mib = [libc::CTL_HW as _, libc::HW_MACHINE as _]; unsafe { if get_sys_value( mem::size_of::<[u8; 32]>(), arch_str.as_mut_ptr() as *mut _, &mut mib, ) { CStr::from_bytes_until_nul(&arch_str) .ok() .and_then(|res| match res.to_str() { Ok(arch) => Some(arch.to_string()), Err(_) => None, }) } else { None } } } pub(crate) fn physical_core_count() -> Option { physical_core_count() } // FIXME: Would be better to query this information instead of using a "default" value like this. pub(crate) fn open_files_limit() -> Option { #[cfg(target_os = "ios")] { Some(256) } #[cfg(not(target_os = "ios"))] { Some(10_240) } } } fn get_system_info(value: c_int, default: Option<&str>) -> Option { let mut mib: [c_int; 2] = [libc::CTL_KERN, value]; let mut size = 0; unsafe { // Call first to get size sysctl( mib.as_mut_ptr(), mib.len() as _, std::ptr::null_mut(), &mut size, std::ptr::null_mut(), 0, ); // exit early if we did not update the size if size == 0 { default.map(|s| s.to_owned()) } else { // set the buffer to the correct size let mut buf = vec![0_u8; size as _]; if sysctl( mib.as_mut_ptr(), mib.len() as _, buf.as_mut_ptr() as _, &mut size, std::ptr::null_mut(), 0, ) == -1 { // If command fails return default default.map(|s| s.to_owned()) } else { if let Some(pos) = buf.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes buf.resize(pos, 0); } String::from_utf8(buf).ok() } } } } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/users.rs000066400000000000000000000055411506747262600230730ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. // This was the OSX-based solution. It provides enough information, but what a mess! // pub fn get_users_list() -> Vec { // let mut users = Vec::new(); // let node_name = b"/Local/Default\0"; // unsafe { // let node_name = ffi::CFStringCreateWithCStringNoCopy( // std::ptr::null_mut(), // node_name.as_ptr() as *const c_char, // ffi::kCFStringEncodingMacRoman, // ffi::kCFAllocatorNull as *mut c_void, // ); // let node_ref = ffi::ODNodeCreateWithName( // ffi::kCFAllocatorDefault, // ffi::kODSessionDefault, // node_name, // std::ptr::null_mut(), // ); // let query = ffi::ODQueryCreateWithNode( // ffi::kCFAllocatorDefault, // node_ref, // ffi::kODRecordTypeUsers as _, // kODRecordTypeGroups // std::ptr::null(), // 0, // std::ptr::null(), // std::ptr::null(), // 0, // std::ptr::null_mut(), // ); // if query.is_null() { // return users; // } // let results = ffi::ODQueryCopyResults( // query, // false as _, // std::ptr::null_mut(), // ); // let len = ffi::CFArrayGetCount(results); // for i in 0..len { // let name = match get_user_name(ffi::CFArrayGetValueAtIndex(results, i)) { // Some(n) => n, // None => continue, // }; // users.push(User { name }); // } // ffi::CFRelease(results as *const c_void); // ffi::CFRelease(query as *const c_void); // ffi::CFRelease(node_ref as *const c_void); // ffi::CFRelease(node_name as *const c_void); // } // users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap()); // return users; // } // fn get_user_name(result: *const c_void) -> Option { // let user_name = ffi::ODRecordGetRecordName(result as _); // let ptr = ffi::CFStringGetCharactersPtr(user_name); // String::from_utf16(&if ptr.is_null() { // let len = ffi::CFStringGetLength(user_name); // It returns the len in UTF-16 code pairs. // if len == 0 { // continue; // } // let mut v = Vec::with_capacity(len as _); // for x in 0..len { // v.push(ffi::CFStringGetCharacterAtIndex(user_name, x)); // } // v // } else { // let mut v: Vec = Vec::new(); // let mut x = 0; // loop { // let letter = *ptr.offset(x); // if letter == 0 { // break; // } // v.push(letter); // x += 1; // } // v // }.ok() // } GuillaumeGomez-sysinfo-067dd61/src/unix/apple/utils.rs000066400000000000000000000014171506747262600230700ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "system")] pub(crate) unsafe fn get_sys_value( mut len: usize, value: *mut libc::c_void, mib: &mut [i32], ) -> bool { unsafe { libc::sysctl( mib.as_mut_ptr(), mib.len() as _, value, &mut len as *mut _, std::ptr::null_mut(), 0, ) == 0 } } #[cfg(feature = "system")] pub(crate) unsafe fn get_sys_value_by_name( name: &[u8], len: &mut usize, value: *mut libc::c_void, ) -> bool { unsafe { libc::sysctlbyname( name.as_ptr() as *const _, value, len, std::ptr::null_mut(), 0, ) == 0 } } GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/000077500000000000000000000000001506747262600216705ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/component.rs000066400000000000000000000075151506747262600242500ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::get_sys_value_by_name; use crate::Component; pub(crate) struct ComponentInner { id: Vec, component_id: String, label: String, temperature: Option, max: f32, pub(crate) updated: bool, } impl ComponentInner { pub(crate) fn new(id: Vec, temperature: f32, core: usize) -> ComponentInner { ComponentInner { id, component_id: format!("cpu_{}", core + 1), label: format!("CPU {}", core + 1), temperature: Some(temperature), max: temperature, updated: true, } } pub(crate) fn temperature(&self) -> Option { self.temperature } pub(crate) fn max(&self) -> Option { Some(self.max) } pub(crate) fn critical(&self) -> Option { None } pub(crate) fn id(&self) -> Option<&str> { Some(&self.component_id) } pub(crate) fn label(&self) -> &str { &self.label } pub(crate) fn refresh(&mut self) { unsafe { self.temperature = refresh_component(&self.id); if let Some(temperature) = self.temperature && temperature > self.max { self.max = temperature; } } } } unsafe fn refresh_component(id: &[u8]) -> Option { let mut temperature: libc::c_int = 0; if unsafe { !get_sys_value_by_name(id, &mut temperature) } { None } else { // convert from Kelvin (x 10 -> 273.2 x 10) to Celsius Some((temperature - 2732) as f32 / 10.) } } pub(crate) struct ComponentsInner { nb_cpus: usize, pub(crate) components: Vec, } impl ComponentsInner { pub(crate) fn new() -> Self { let nb_cpus = unsafe { super::utils::get_nb_cpus() }; Self { nb_cpus, components: Vec::with_capacity(nb_cpus), } } pub(crate) fn from_vec(components: Vec) -> Self { Self { nb_cpus: unsafe { super::utils::get_nb_cpus() }, components, } } pub(crate) fn into_vec(self) -> Vec { self.components } pub(crate) fn list(&self) -> &[Component] { &self.components } pub(crate) fn list_mut(&mut self) -> &mut [Component] { &mut self.components } pub(crate) fn refresh(&mut self) { if self.components.len() != self.nb_cpus { for core in 0..self.nb_cpus { unsafe { let id = format!("dev.cpu.{core}.temperature\0").as_bytes().to_vec(); if let Some(temperature) = refresh_component(&id) { self.components.push(Component { inner: ComponentInner::new(id, temperature, core), }); } } } } else { for c in self.components.iter_mut() { c.refresh(); c.inner.updated = true; } } } } #[cfg(test)] mod tests { use crate::Component; use crate::unix::freebsd::{ComponentInner, ComponentsInner}; #[test] fn test_components() { let component1 = Component { inner: ComponentInner::new(b"dev.cpu.0.temperature\0".to_vec(), 1.234, 0), }; let component2 = Component { inner: ComponentInner::new(b"dev.cpu.1.temperature\0".to_vec(), 5.678, 1), }; assert_eq!(component1.id(), Some("cpu_1")); assert_eq!(component1.label(), "CPU 1"); assert_eq!(component1.temperature(), Some(1.234)); assert_eq!(component2.id(), Some("cpu_2")); assert_eq!(component2.label(), "CPU 2"); assert_eq!(component2.temperature(), Some(5.678)); } } GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/cpu.rs000066400000000000000000000143611506747262600230320ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::{ get_sys_value_array, get_sys_value_by_name, get_sys_value_str_by_name, init_mib, }; use crate::{Cpu, CpuRefreshKind}; use libc::{c_int, c_ulong}; pub(crate) struct CpusWrapper { pub(crate) global_cpu_usage: f32, pub(crate) cpus: Vec, got_cpu_frequency: bool, mib_cp_time: [c_int; 2], mib_cp_times: [c_int; 2], // For the global CPU usage. cp_time: VecSwitcher, // For each CPU usage. cp_times: VecSwitcher, nb_cpus: usize, } impl CpusWrapper { pub(crate) fn new() -> Self { let mut mib_cp_time = [0; 2]; let mut mib_cp_times = [0; 2]; unsafe { let nb_cpus = super::utils::get_nb_cpus(); init_mib(b"kern.cp_time\0", &mut mib_cp_time); init_mib(b"kern.cp_times\0", &mut mib_cp_times); Self { global_cpu_usage: 0., cpus: Vec::with_capacity(nb_cpus), got_cpu_frequency: false, mib_cp_time, mib_cp_times, cp_time: VecSwitcher::new(vec![0; libc::CPUSTATES as usize]), cp_times: VecSwitcher::new(vec![0; nb_cpus * libc::CPUSTATES as usize]), nb_cpus, } } } pub(crate) fn refresh(&mut self, refresh_kind: CpuRefreshKind) { if self.cpus.is_empty() { let mut frequency = 0; // We get the CPU vendor ID in here. let vendor_id = get_sys_value_str_by_name(b"hw.model\0").unwrap_or_else(|| "".to_owned()); for pos in 0..self.nb_cpus { if refresh_kind.frequency() { unsafe { frequency = get_frequency_for_cpu(pos); } } self.cpus.push(Cpu { inner: CpuInner::new(format!("cpu {pos}"), vendor_id.clone(), frequency), }); } self.got_cpu_frequency = refresh_kind.frequency(); } else if refresh_kind.frequency() && !self.got_cpu_frequency { for (pos, proc_) in self.cpus.iter_mut().enumerate() { unsafe { proc_.inner.frequency = get_frequency_for_cpu(pos); } } self.got_cpu_frequency = true; } if refresh_kind.cpu_usage() { self.get_cpu_usage(); } } fn get_cpu_usage(&mut self) { unsafe { get_sys_value_array(&self.mib_cp_time, self.cp_time.get_mut()); get_sys_value_array(&self.mib_cp_times, self.cp_times.get_mut()); } fn compute_cpu_usage(new_cp_time: &[c_ulong], old_cp_time: &[c_ulong]) -> f32 { let mut total_new: u64 = 0; let mut total_old: u64 = 0; let mut cp_diff: c_ulong = 0; for i in 0..(libc::CPUSTATES as usize) { // We obviously don't want to get the idle part of the CPU usage, otherwise // we would always be at 100%... if i != libc::CP_IDLE as usize { cp_diff = cp_diff.saturating_add(new_cp_time[i].saturating_sub(old_cp_time[i])); } total_new = total_new.saturating_add(new_cp_time[i] as _); total_old = total_old.saturating_add(old_cp_time[i] as _); } let total_diff = total_new.saturating_sub(total_old); if total_diff < 1 { 0. } else { cp_diff as f32 / total_diff as f32 * 100. } } self.global_cpu_usage = compute_cpu_usage(self.cp_time.get_new(), self.cp_time.get_old()); let old_cp_times = self.cp_times.get_old(); let new_cp_times = self.cp_times.get_new(); for (pos, cpu) in self.cpus.iter_mut().enumerate() { let index = pos * libc::CPUSTATES as usize; cpu.inner.cpu_usage = compute_cpu_usage(&new_cp_times[index..], &old_cp_times[index..]); } } } pub(crate) struct CpuInner { pub(crate) cpu_usage: f32, name: String, pub(crate) vendor_id: String, pub(crate) frequency: u64, } impl CpuInner { pub(crate) fn new(name: String, vendor_id: String, frequency: u64) -> Self { Self { cpu_usage: 0., name, vendor_id, frequency, } } pub(crate) fn cpu_usage(&self) -> f32 { self.cpu_usage } pub(crate) fn name(&self) -> &str { &self.name } pub(crate) fn frequency(&self) -> u64 { self.frequency } pub(crate) fn vendor_id(&self) -> &str { &self.vendor_id } pub(crate) fn brand(&self) -> &str { "" } } pub(crate) fn physical_core_count() -> Option { let mut physical_core_count: u32 = 0; unsafe { if get_sys_value_by_name(b"hw.ncpu\0", &mut physical_core_count) { Some(physical_core_count as _) } else { None } } } unsafe fn get_frequency_for_cpu(cpu_nb: usize) -> u64 { let mut frequency: c_int = 0; // The information can be missing if it's running inside a VM. unsafe { if !get_sys_value_by_name( format!("dev.cpu.{cpu_nb}.freq\0").as_bytes(), &mut frequency, ) { frequency = 0; } } frequency as _ } /// This struct is used to switch between the "old" and "new" every time you use "get_mut". #[derive(Debug)] pub(crate) struct VecSwitcher { v1: Vec, v2: Vec, first: bool, } impl VecSwitcher { pub fn new(v1: Vec) -> Self { let v2 = v1.clone(); Self { v1, v2, first: true, } } pub fn get_mut(&mut self) -> &mut [T] { self.first = !self.first; if self.first { // It means that `v2` will be the "new". &mut self.v2 } else { // It means that `v1` will be the "new". &mut self.v1 } } pub fn get_old(&self) -> &[T] { if self.first { &self.v1 } else { &self.v2 } } pub fn get_new(&self) -> &[T] { if self.first { &self.v2 } else { &self.v1 } } } GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/disk.rs000066400000000000000000000366511506747262600232030ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::HashMap; use std::ffi::{OsStr, OsString}; use std::marker::PhantomData; use std::os::unix::ffi::OsStringExt; use std::path::{Path, PathBuf}; use std::ptr::{NonNull, null_mut}; use std::sync::OnceLock; use libc::{c_void, devstat, devstat_getversion}; use super::ffi::{ DEVSTAT_READ, DEVSTAT_WRITE, geom_stats_open, geom_stats_snapshot_free, geom_stats_snapshot_get, geom_stats_snapshot_next, geom_stats_snapshot_reset, }; use super::utils::{c_buf_to_utf8_str, get_sys_value_str_by_name}; use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage}; #[derive(Debug)] pub(crate) struct DiskInner { name: OsString, c_mount_point: Vec, dev_id: Option, mount_point: PathBuf, total_space: u64, available_space: u64, file_system: OsString, is_removable: bool, is_read_only: bool, read_bytes: u64, old_read_bytes: u64, written_bytes: u64, old_written_bytes: u64, updated: bool, } impl DiskInner { pub(crate) fn kind(&self) -> DiskKind { // Currently don't know how to retrieve this information on FreeBSD. DiskKind::Unknown(-1) } pub(crate) fn name(&self) -> &OsStr { &self.name } pub(crate) fn file_system(&self) -> &OsStr { &self.file_system } pub(crate) fn mount_point(&self) -> &Path { &self.mount_point } pub(crate) fn total_space(&self) -> u64 { self.total_space } pub(crate) fn available_space(&self) -> u64 { self.available_space } pub(crate) fn is_removable(&self) -> bool { self.is_removable } pub(crate) fn is_read_only(&self) -> bool { self.is_read_only } pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool { refresh_disk(self, refresh_kind) } pub(crate) fn usage(&self) -> DiskUsage { DiskUsage { read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, } } } impl crate::DisksInner { pub(crate) fn new() -> Self { Self { disks: Vec::with_capacity(2), } } pub(crate) fn refresh_specifics( &mut self, remove_not_listed_disks: bool, refresh_kind: DiskRefreshKind, ) { unsafe { get_all_list(&mut self.disks, remove_not_listed_disks, refresh_kind) } } pub(crate) fn list(&self) -> &[Disk] { &self.disks } pub(crate) fn list_mut(&mut self) -> &mut [Disk] { &mut self.disks } } trait GetValues { fn update_old(&mut self); fn get_read(&mut self) -> &mut u64; fn get_written(&mut self) -> &mut u64; fn dev_id(&self) -> Option<&String>; } impl GetValues for crate::Disk { fn update_old(&mut self) { self.inner.update_old() } fn get_read(&mut self) -> &mut u64 { self.inner.get_read() } fn get_written(&mut self) -> &mut u64 { self.inner.get_written() } fn dev_id(&self) -> Option<&String> { self.inner.dev_id() } } impl GetValues for &mut DiskInner { fn update_old(&mut self) { self.old_read_bytes = self.read_bytes; self.old_written_bytes = self.written_bytes; } fn get_read(&mut self) -> &mut u64 { &mut self.read_bytes } fn get_written(&mut self) -> &mut u64 { &mut self.written_bytes } fn dev_id(&self) -> Option<&String> { self.dev_id.as_ref() } } impl GetValues for DiskInner { fn update_old(&mut self) { self.old_read_bytes = self.read_bytes; self.old_written_bytes = self.written_bytes; } fn get_read(&mut self) -> &mut u64 { &mut self.read_bytes } fn get_written(&mut self) -> &mut u64 { &mut self.written_bytes } fn dev_id(&self) -> Option<&String> { self.dev_id.as_ref() } } /// Returns `(total_space, available_space, is_read_only)`. unsafe fn get_statvfs( c_mount_point: &[libc::c_char], vfs: &mut libc::statvfs, ) -> Option<(u64, u64, bool)> { if unsafe { libc::statvfs(c_mount_point.as_ptr() as *const _, vfs as *mut _) < 0 } { sysinfo_debug!("statvfs failed"); None } else { let block_size: u64 = vfs.f_frsize as _; Some(( vfs.f_blocks.saturating_mul(block_size), vfs.f_favail.saturating_mul(block_size), (vfs.f_flag & libc::ST_RDONLY) != 0, )) } } fn refresh_disk(disk: &mut DiskInner, refresh_kind: DiskRefreshKind) -> bool { if refresh_kind.storage() { unsafe { let mut vfs: libc::statvfs = std::mem::zeroed(); if let Some((total_space, available_space, is_read_only)) = get_statvfs(&disk.c_mount_point, &mut vfs) { disk.total_space = total_space; disk.available_space = available_space; disk.is_read_only = is_read_only; } } } if refresh_kind.io_usage() { unsafe { refresh_disk_io(&mut [disk]); } } true } unsafe fn initialize_geom() -> Result<(), ()> { let version = unsafe { devstat_getversion(null_mut()) }; if version != 6 { // For now we only handle the devstat 6 version. sysinfo_debug!("version {version} of devstat is not supported"); return Err(()); } let r = unsafe { geom_stats_open() }; if r != 0 { sysinfo_debug!("`geom_stats_open` failed: {r}"); Err(()) } else { Ok(()) } } unsafe fn refresh_disk_io(disks: &mut [T]) { static GEOM_STATS: OnceLock> = OnceLock::new(); if GEOM_STATS .get_or_init(|| unsafe { initialize_geom() }) .is_err() { return; } let snap = unsafe { GeomSnapshot::new() }; let Some(mut snap) = snap else { return; }; for device in snap.iter() { let device = unsafe { device.devstat.as_ref() }; let Some(device_name) = c_buf_to_utf8_str(&device.device_name) else { continue; }; let dev_stat_name = format!("{device_name}{}", device.unit_number); for disk in disks .iter_mut() .filter(|d| d.dev_id().is_some_and(|id| *id == dev_stat_name)) { disk.update_old(); *disk.get_read() = device.bytes[DEVSTAT_READ]; *disk.get_written() = device.bytes[DEVSTAT_WRITE]; } } // thread_local! { // static DEV_INFO: RefCell = RefCell::new(DevInfoWrapper::new()); // } // DEV_INFO.with_borrow_mut(|dev_info| { // let Some(stat_info) = dev_info.get_devs() else { return }; // let dinfo = (*stat_info).dinfo; // let numdevs = (*dinfo).numdevs; // if numdevs < 0 { // return; // } // let devices: &mut [devstat] = std::slice::from_raw_parts_mut((*dinfo).devices, numdevs as _); // for device in devices { // let Some(device_name) = c_buf_to_utf8_str(&device.device_name) else { continue }; // let dev_stat_name = format!("{device_name}{}", device.unit_number); // for disk in disks.iter_mut().filter(|d| d.dev_id().is_some_and(|id| *id == dev_stat_name)) { // disk.update_old(); // let mut read = 0u64; // // This code cannot work because `devstat_compute_statistics` expects a // // `long double` as 3rd argument, making it impossible for rust to call it... // devstat_compute_statistics( // device, // null_mut(), // 0, // DSM_TOTAL_BYTES_READ, // &mut read, // DSM_TOTAL_BYTES_WRITE, // disk.get_written(), // DSM_NONE, // ); // *disk.get_read() = read; // } // } // }); } fn get_disks_mapping() -> HashMap { let mut disk_mapping = HashMap::new(); let Some(mapping) = get_sys_value_str_by_name(b"kern.geom.conftxt\0") else { return disk_mapping; }; let mut last_id = String::new(); for line in mapping.lines() { let mut parts = line.split_whitespace(); let Some(kind) = parts.next() else { continue }; #[allow(clippy::collapsible_if)] if kind == "0" { if let Some("DISK") = parts.next() && let Some(id) = parts.next() { last_id.clear(); last_id.push_str(id); } } else if kind == "2" && !last_id.is_empty() { if let Some("LABEL") = parts.next() && let Some(path) = parts.next() { disk_mapping.insert(format!("/dev/{path}"), last_id.clone()); } } } disk_mapping } pub unsafe fn get_all_list( container: &mut Vec, remove_not_listed_disks: bool, refresh_kind: DiskRefreshKind, ) { let mut fs_infos: *mut libc::statfs = null_mut(); let count = unsafe { libc::getmntinfo(&mut fs_infos, libc::MNT_WAIT) }; if count < 1 { return; } let disk_mapping = get_disks_mapping(); let fs_infos: &[libc::statfs] = unsafe { std::slice::from_raw_parts(fs_infos as _, count as _) }; for fs_info in fs_infos { if fs_info.f_mntfromname[0] == 0 || fs_info.f_mntonname[0] == 0 { // If we have missing information, no need to look any further... continue; } let fs_type: Vec = { let len = fs_info .f_fstypename .iter() .position(|x| *x == 0) .unwrap_or(fs_info.f_fstypename.len()); fs_info.f_fstypename[..len] .iter() .map(|c| *c as u8) .collect() }; match &fs_type[..] { b"autofs" | b"devfs" | b"linprocfs" | b"procfs" | b"fdesckfs" | b"tmpfs" | b"linsysfs" => { sysinfo_debug!( "Memory filesystem `{:?}`, ignoring it.", c_buf_to_utf8_str(&fs_info.f_fstypename).unwrap(), ); continue; } _ => {} } let mount_point = match c_buf_to_utf8_str(&fs_info.f_mntonname) { Some(m) => m, None => { sysinfo_debug!("Cannot get disk mount point, ignoring it."); continue; } }; if mount_point == "/boot/efi" { continue; } let name = if mount_point == "/" { OsString::from("root") } else { OsString::from(mount_point) }; if let Some(disk) = container.iter_mut().find(|d| { d.inner.name == name && d.inner .file_system .as_encoded_bytes() .iter() .zip(fs_type.iter()) .all(|(a, b)| a == b) }) { // I/O usage is updated for all disks at once at the end. refresh_disk(&mut disk.inner, refresh_kind.without_io_usage()); disk.inner.updated = true; } else { let dev_mount_point = c_buf_to_utf8_str(&fs_info.f_mntfromname).unwrap_or(""); // USB keys and CDs are removable. let is_removable = if refresh_kind.storage() { [b"USB", b"usb"].iter().any(|b| *b == &fs_type[..]) || fs_type.starts_with(b"/dev/cd") } else { false }; let mut disk = DiskInner { name, c_mount_point: fs_info.f_mntonname.to_vec(), mount_point: PathBuf::from(mount_point), dev_id: disk_mapping.get(dev_mount_point).map(ToString::to_string), total_space: 0, available_space: 0, file_system: OsString::from_vec(fs_type), is_removable, is_read_only: false, read_bytes: 0, old_read_bytes: 0, written_bytes: 0, old_written_bytes: 0, updated: true, }; // I/O usage is updated for all disks at once at the end. refresh_disk(&mut disk, refresh_kind.without_io_usage()); container.push(Disk { inner: disk }); } } if remove_not_listed_disks { container.retain_mut(|disk| { if !disk.inner.updated { return false; } disk.inner.updated = false; true }); } else { for c in container.iter_mut() { c.inner.updated = false; } } if refresh_kind.io_usage() { unsafe { refresh_disk_io(container.as_mut_slice()); } } } // struct DevInfoWrapper { // info: statinfo, // } // impl DevInfoWrapper { // fn new() -> Self { // Self { // info: unsafe { std::mem::zeroed() }, // } // } // unsafe fn get_devs(&mut self) -> Option<&statinfo> { // let version = devstat_getversion(null_mut()); // if version != 6 { // // For now we only handle the devstat 6 version. // sysinfo_debug!("version {version} of devstat is not supported"); // return None; // } // if self.info.dinfo.is_null() { // self.info.dinfo = libc::calloc(1, std::mem::size_of::()) as *mut _; // if self.info.dinfo.is_null() { // return None; // } // } // if devstat_getdevs(null_mut(), &mut self.info as *mut _) != -1 { // Some(&self.info) // } else { // None // } // } // } // impl Drop for DevInfoWrapper { // fn drop(&mut self) { // if !self.info.dinfo.is_null() { // unsafe { libc::free(self.info.dinfo as *mut _); } // } // } // } // Most of this code was adapted from `gstat-rs` (https://github.com/asomers/gstat-rs). struct GeomSnapshot(NonNull); impl GeomSnapshot { unsafe fn new() -> Option { match NonNull::new(unsafe { geom_stats_snapshot_get() }) { Some(n) => Some(Self(n)), None => { sysinfo_debug!("geom_stats_snapshot_get failed"); None } } } fn iter(&mut self) -> GeomSnapshotIter<'_> { GeomSnapshotIter(self) } fn reset(&mut self) { unsafe { geom_stats_snapshot_reset(self.0.as_mut()) } } } impl Drop for GeomSnapshot { fn drop(&mut self) { unsafe { geom_stats_snapshot_free(self.0.as_mut()) }; } } #[repr(transparent)] struct Devstat<'a> { devstat: NonNull, phantom: PhantomData<&'a devstat>, } struct GeomSnapshotIter<'a>(&'a mut GeomSnapshot); impl<'a> Iterator for GeomSnapshotIter<'a> { type Item = Devstat<'a>; fn next(&mut self) -> Option { let raw = unsafe { geom_stats_snapshot_next(self.0.0.as_mut()) }; NonNull::new(raw).map(|devstat| Devstat { devstat, phantom: PhantomData, }) } } impl Drop for GeomSnapshotIter<'_> { fn drop(&mut self) { self.0.reset(); } } GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/ffi.rs000066400000000000000000000025021506747262600230010ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![allow(non_camel_case_types, dead_code)] use libc::{c_int, c_void}; // definitions come from: // https://github.com/freebsd/freebsd-src/blob/main/lib/libdevstat/devstat.h // https://github.com/freebsd/freebsd-src/blob/main/sys/sys/devicestat.h // #[repr(C)] // pub(crate) struct statinfo { // pub(crate) cp_time: [c_long; CPUSTATES as usize], // pub(crate) tk_nin: c_long, // pub(crate) tk_nout: c_long, // pub(crate) dinfo: *mut devinfo, // pub(crate) snap_time: c_long_double, // } pub(crate) const DEVSTAT_READ: usize = 0x01; pub(crate) const DEVSTAT_WRITE: usize = 0x02; // pub(crate) const DSM_NONE: c_int = 0; // pub(crate) const DSM_TOTAL_BYTES_READ: c_int = 2; // pub(crate) const DSM_TOTAL_BYTES_WRITE: c_int = 3; // extern "C" { // pub(crate) fn devstat_compute_statistics(current: *mut devstat, previous: *mut devstat, etime: c_long_double, ...) -> c_int; // } #[link(name = "geom")] unsafe extern "C" { pub(crate) fn geom_stats_open() -> c_int; pub(crate) fn geom_stats_snapshot_get() -> *mut c_void; pub(crate) fn geom_stats_snapshot_next(arg: *mut c_void) -> *mut libc::devstat; pub(crate) fn geom_stats_snapshot_reset(arg: *mut c_void); pub(crate) fn geom_stats_snapshot_free(arg: *mut c_void); } GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/mod.rs000066400000000000000000000032351506747262600230200ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub(crate) mod utils; cfg_if! { if #[cfg(feature = "system")] { pub mod cpu; pub mod motherboard; pub mod process; pub mod product; pub mod system; pub(crate) use self::cpu::CpuInner; pub(crate) use self::motherboard::MotherboardInner; pub(crate) use self::process::ProcessInner; pub(crate) use self::product::ProductInner; pub(crate) use self::system::SystemInner; pub use self::system::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS}; } if #[cfg(feature = "disk")] { pub mod disk; pub(crate) use self::disk::DiskInner; pub(crate) use crate::unix::DisksInner; } if #[cfg(any(feature = "disk", feature = "system"))] { pub mod ffi; } if #[cfg(feature = "component")] { pub mod component; pub(crate) use self::component::{ComponentInner, ComponentsInner}; } if #[cfg(feature = "network")] { pub mod network; pub(crate) use self::network::{NetworkDataInner, NetworksInner}; } if #[cfg(feature = "user")] { pub(crate) use crate::unix::groups::get_groups; pub(crate) use crate::unix::users::{get_users, UserInner}; } } #[doc = include_str!("../../../md_doc/is_supported.md")] pub const IS_SUPPORTED_SYSTEM: bool = true; // Make formattable by rustfmt. #[cfg(any())] mod component; #[cfg(any())] mod cpu; #[cfg(any())] mod disk; #[cfg(any())] mod ffi; #[cfg(any())] mod motherboard; #[cfg(any())] mod network; #[cfg(any())] mod process; #[cfg(any())] mod product; #[cfg(any())] mod system; GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/motherboard.rs000066400000000000000000000014341506747262600245460ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::get_kenv_var; pub(crate) struct MotherboardInner; impl MotherboardInner { pub(crate) fn new() -> Option { Some(Self) } pub(crate) fn asset_tag(&self) -> Option { get_kenv_var(b"smbios.planar.tag\0") } pub(crate) fn name(&self) -> Option { get_kenv_var(b"smbios.planar.product\0") } pub(crate) fn vendor_name(&self) -> Option { get_kenv_var(b"smbios.planar.maker\0") } pub(crate) fn version(&self) -> Option { get_kenv_var(b"smbios.planar.version\0") } pub(crate) fn serial_number(&self) -> Option { get_kenv_var(b"smbios.planar.serial\0") } } GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/network.rs000066400000000000000000000165171506747262600237410ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::{HashMap, hash_map}; use std::mem::MaybeUninit; use super::utils; use crate::network::refresh_networks_addresses; use crate::{IpNetwork, MacAddr, NetworkData}; macro_rules! old_and_new { ($ty_:expr, $name:ident, $old:ident, $data:expr) => {{ $ty_.$old = $ty_.$name; $ty_.$name = $data.$name; }}; } pub(crate) struct NetworksInner { pub(crate) interfaces: HashMap, } impl NetworksInner { pub(crate) fn new() -> Self { Self { interfaces: HashMap::new(), } } pub(crate) fn list(&self) -> &HashMap { &self.interfaces } pub(crate) fn refresh(&mut self, remove_not_listed_interfaces: bool) { unsafe { self.refresh_interfaces(true); } if remove_not_listed_interfaces { // Remove interfaces which are gone. self.interfaces.retain(|_, i| { if !i.inner.updated { return false; } i.inner.updated = false; true }); } refresh_networks_addresses(&mut self.interfaces); } unsafe fn refresh_interfaces(&mut self, refresh_all: bool) { let mut nb_interfaces: libc::c_int = 0; if unsafe { !utils::get_sys_value( &[ libc::CTL_NET, libc::PF_LINK, libc::NETLINK_GENERIC, libc::IFMIB_SYSTEM, libc::IFMIB_IFCOUNT, ], &mut nb_interfaces, ) } { return; } if refresh_all { // We don't need to update this value if we're not updating all interfaces. for interface in self.interfaces.values_mut() { interface.inner.updated = false; } } let mut data: libc::ifmibdata = unsafe { MaybeUninit::zeroed().assume_init() }; for row in 1..=nb_interfaces { let mib = [ libc::CTL_NET, libc::PF_LINK, libc::NETLINK_GENERIC, libc::IFMIB_IFDATA, row, libc::IFDATA_GENERAL, ]; if unsafe { !utils::get_sys_value(&mib, &mut data) } { continue; } if let Some(name) = utils::c_buf_to_utf8_string(&data.ifmd_name) { let data = &data.ifmd_data; let mtu = data.ifi_mtu as u64; match self.interfaces.entry(name) { hash_map::Entry::Occupied(mut e) => { let interface = e.get_mut(); let interface = &mut interface.inner; old_and_new!(interface, ifi_ibytes, old_ifi_ibytes, data); old_and_new!(interface, ifi_obytes, old_ifi_obytes, data); old_and_new!(interface, ifi_ipackets, old_ifi_ipackets, data); old_and_new!(interface, ifi_opackets, old_ifi_opackets, data); old_and_new!(interface, ifi_ierrors, old_ifi_ierrors, data); old_and_new!(interface, ifi_oerrors, old_ifi_oerrors, data); if interface.mtu != mtu { interface.mtu = mtu; } interface.updated = true; } hash_map::Entry::Vacant(e) => { if !refresh_all { // This is simply a refresh, we don't want to add new interfaces! continue; } e.insert(NetworkData { inner: NetworkDataInner { ifi_ibytes: data.ifi_ibytes, old_ifi_ibytes: 0, ifi_obytes: data.ifi_obytes, old_ifi_obytes: 0, ifi_ipackets: data.ifi_ipackets, old_ifi_ipackets: 0, ifi_opackets: data.ifi_opackets, old_ifi_opackets: 0, ifi_ierrors: data.ifi_ierrors, old_ifi_ierrors: 0, ifi_oerrors: data.ifi_oerrors, old_ifi_oerrors: 0, updated: true, mac_addr: MacAddr::UNSPECIFIED, ip_networks: vec![], mtu, }, }); } } } } } } pub(crate) struct NetworkDataInner { /// Total number of bytes received over interface. ifi_ibytes: u64, old_ifi_ibytes: u64, /// Total number of bytes transmitted over interface. ifi_obytes: u64, old_ifi_obytes: u64, /// Total number of packets received. ifi_ipackets: u64, old_ifi_ipackets: u64, /// Total number of packets transmitted. ifi_opackets: u64, old_ifi_opackets: u64, /// Shows the total number of packets received with error. This includes /// too-long-frames errors, ring-buffer overflow errors, CRC errors, /// frame alignment errors, fifo overruns, and missed packets. ifi_ierrors: u64, old_ifi_ierrors: u64, /// similar to `ifi_ierrors` ifi_oerrors: u64, old_ifi_oerrors: u64, /// Whether or not the above data has been updated during refresh updated: bool, /// MAC address pub(crate) mac_addr: MacAddr, /// IP networks pub(crate) ip_networks: Vec, /// Interface Maximum Transfer Unit (MTU) mtu: u64, } impl NetworkDataInner { pub(crate) fn received(&self) -> u64 { self.ifi_ibytes.saturating_sub(self.old_ifi_ibytes) } pub(crate) fn total_received(&self) -> u64 { self.ifi_ibytes } pub(crate) fn transmitted(&self) -> u64 { self.ifi_obytes.saturating_sub(self.old_ifi_obytes) } pub(crate) fn total_transmitted(&self) -> u64 { self.ifi_obytes } pub(crate) fn packets_received(&self) -> u64 { self.ifi_ipackets.saturating_sub(self.old_ifi_ipackets) } pub(crate) fn total_packets_received(&self) -> u64 { self.ifi_ipackets } pub(crate) fn packets_transmitted(&self) -> u64 { self.ifi_opackets.saturating_sub(self.old_ifi_opackets) } pub(crate) fn total_packets_transmitted(&self) -> u64 { self.ifi_opackets } pub(crate) fn errors_on_received(&self) -> u64 { self.ifi_ierrors.saturating_sub(self.old_ifi_ierrors) } pub(crate) fn total_errors_on_received(&self) -> u64 { self.ifi_ierrors } pub(crate) fn errors_on_transmitted(&self) -> u64 { self.ifi_oerrors.saturating_sub(self.old_ifi_oerrors) } pub(crate) fn total_errors_on_transmitted(&self) -> u64 { self.ifi_oerrors } pub(crate) fn mac_address(&self) -> MacAddr { self.mac_addr } pub(crate) fn ip_networks(&self) -> &[IpNetwork] { &self.ip_networks } pub(crate) fn mtu(&self) -> u64 { self.mtu } } GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/process.rs000066400000000000000000000274071506747262600237260ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Signal, Uid}; use std::ffi::{OsStr, OsString}; use std::fmt; use std::path::{Path, PathBuf}; use std::process::ExitStatus; use super::utils::{WrapMap, get_sys_value_str, get_sysctl_raw}; #[doc(hidden)] impl From for ProcessStatus { fn from(status: libc::c_char) -> ProcessStatus { match status { libc::SIDL => ProcessStatus::Idle, libc::SRUN => ProcessStatus::Run, libc::SSLEEP => ProcessStatus::Sleep, libc::SSTOP => ProcessStatus::Stop, libc::SZOMB => ProcessStatus::Zombie, libc::SWAIT => ProcessStatus::Dead, libc::SLOCK => ProcessStatus::LockBlocked, x => ProcessStatus::Unknown(x as _), } } } impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { ProcessStatus::Idle => "Idle", ProcessStatus::Run => "Runnable", ProcessStatus::Sleep => "Sleeping", ProcessStatus::Stop => "Stopped", ProcessStatus::Zombie => "Zombie", ProcessStatus::Dead => "Dead", ProcessStatus::LockBlocked => "LockBlocked", _ => "Unknown", }) } } pub(crate) struct ProcessInner { pub(crate) name: OsString, pub(crate) cmd: Vec, pub(crate) exe: Option, pub(crate) pid: Pid, parent: Option, pub(crate) environ: Vec, pub(crate) cwd: Option, pub(crate) root: Option, pub(crate) memory: u64, pub(crate) virtual_memory: u64, pub(crate) updated: bool, cpu_usage: f32, start_time: u64, run_time: u64, pub(crate) status: ProcessStatus, user_id: Uid, effective_user_id: Uid, group_id: Gid, effective_group_id: Gid, read_bytes: u64, old_read_bytes: u64, written_bytes: u64, old_written_bytes: u64, accumulated_cpu_time: u64, exists: bool, } impl ProcessInner { pub(crate) fn kill_with(&self, signal: Signal) -> Option { let c_signal = crate::sys::system::convert_signal(signal)?; unsafe { Some(libc::kill(self.pid.0, c_signal) == 0) } } pub(crate) fn name(&self) -> &OsStr { &self.name } pub(crate) fn cmd(&self) -> &[OsString] { &self.cmd } pub(crate) fn exe(&self) -> Option<&Path> { self.exe.as_deref() } pub(crate) fn pid(&self) -> Pid { self.pid } pub(crate) fn environ(&self) -> &[OsString] { &self.environ } pub(crate) fn cwd(&self) -> Option<&Path> { self.cwd.as_deref() } pub(crate) fn root(&self) -> Option<&Path> { self.root.as_deref() } pub(crate) fn memory(&self) -> u64 { self.memory } pub(crate) fn virtual_memory(&self) -> u64 { self.virtual_memory } pub(crate) fn parent(&self) -> Option { self.parent } pub(crate) fn status(&self) -> ProcessStatus { self.status } pub(crate) fn start_time(&self) -> u64 { self.start_time } pub(crate) fn run_time(&self) -> u64 { self.run_time } pub(crate) fn cpu_usage(&self) -> f32 { self.cpu_usage } pub(crate) fn accumulated_cpu_time(&self) -> u64 { self.accumulated_cpu_time } pub(crate) fn disk_usage(&self) -> DiskUsage { DiskUsage { written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, } } pub(crate) fn user_id(&self) -> Option<&Uid> { Some(&self.user_id) } pub(crate) fn effective_user_id(&self) -> Option<&Uid> { Some(&self.effective_user_id) } pub(crate) fn group_id(&self) -> Option { Some(self.group_id) } pub(crate) fn effective_group_id(&self) -> Option { Some(self.effective_group_id) } pub(crate) fn wait(&self) -> Option { crate::unix::utils::wait_process(self.pid) } pub(crate) fn session_id(&self) -> Option { unsafe { let session_id = libc::getsid(self.pid.0); if session_id < 0 { None } else { Some(Pid(session_id)) } } } pub(crate) fn switch_updated(&mut self) -> bool { std::mem::replace(&mut self.updated, false) } pub(crate) fn set_nonexistent(&mut self) { self.exists = false; } pub(crate) fn exists(&self) -> bool { self.exists } pub(crate) fn open_files(&self) -> Option { let mib = &[ libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_FILEDESC, self.pid.0 as _, ]; let mut len = 0; unsafe { if get_sysctl_raw(mib, std::ptr::null_mut(), &mut len).is_none() { sysinfo_debug!("Failed to query `open_files` info"); return None; } let Some(data) = AllocatedPtr::<()>::new(len) else { sysinfo_debug!("Failed to allocate memory to get `open_files` info"); return None; }; // No clue why, it's done this way in `freebsd/lib/libutil/kinfo_getfile.c` so I suppose // they have a good reason... len = len * 4 / 3; if get_sysctl_raw(mib, data.0, &mut len).is_none() { sysinfo_debug!("Couldn't retrieve `open_files` data"); return None; } let mut current = data.0; let end = current.byte_add(len); let mut count = 0; while current < end { let t = current as *mut libc::kinfo_file; if t.is_null() || (*t).kf_structsize == 0 { break; } current = current.byte_add((*t).kf_structsize as _); count += 1; } Some(count) } } pub(crate) fn open_files_limit(&self) -> Option { crate::System::open_files_limit() } } struct AllocatedPtr(*mut T); impl AllocatedPtr { fn new(size: libc::size_t) -> Option { unsafe { let ptr = libc::malloc(size); if ptr.is_null() { None } else { Some(Self(ptr as _)) } } } } impl Drop for AllocatedPtr { fn drop(&mut self) { if !self.0.is_null() { unsafe { libc::free(self.0 as _); } } } } #[inline] fn get_accumulated_cpu_time(kproc: &libc::kinfo_proc) -> u64 { // from FreeBSD source /bin/ps/print.c kproc.ki_runtime / 1_000 } pub(crate) unsafe fn get_process_data( kproc: &libc::kinfo_proc, wrap: &WrapMap, page_size: isize, fscale: f32, now: u64, refresh_kind: ProcessRefreshKind, ) -> Result, ()> { if kproc.ki_pid != 1 && (kproc.ki_flag as libc::c_int & libc::P_SYSTEM) != 0 { // We filter out the kernel threads. return Err(()); } // We now get the values needed for both new and existing process. let cpu_usage = if refresh_kind.cpu() { Some((100 * kproc.ki_pctcpu) as f32 / fscale) } else { None }; // Processes can be reparented apparently? let parent = if kproc.ki_ppid != 0 { Some(Pid(kproc.ki_ppid)) } else { None }; let status = ProcessStatus::from(kproc.ki_stat); // from FreeBSD source /src/usr.bin/top/machine.c let (virtual_memory, memory) = if refresh_kind.memory() { ( kproc.ki_size as _, (kproc.ki_rssize as u64).saturating_mul(page_size as _), ) } else { (0, 0) }; // FIXME: This is to get the "real" run time (in micro-seconds). // let run_time = (kproc.ki_runtime + 5_000) / 10_000; let start_time = kproc.ki_start.tv_sec as u64; if let Some(proc_) = unsafe { (*wrap.0.get()).get_mut(&Pid(kproc.ki_pid)) } { let proc_ = &mut proc_.inner; proc_.updated = true; // If the `start_time` we just got is different from the one stored, it means it's not the // same process. if proc_.start_time == start_time { if let Some(cpu_usage) = cpu_usage { proc_.cpu_usage = cpu_usage; } proc_.parent = parent; proc_.status = status; if refresh_kind.memory() { proc_.virtual_memory = virtual_memory; proc_.memory = memory; } proc_.run_time = now.saturating_sub(proc_.start_time); if refresh_kind.disk_usage() { proc_.old_read_bytes = proc_.read_bytes; proc_.read_bytes = kproc.ki_rusage.ru_inblock as _; proc_.old_written_bytes = proc_.written_bytes; proc_.written_bytes = kproc.ki_rusage.ru_oublock as _; } if refresh_kind.cpu() { proc_.accumulated_cpu_time = get_accumulated_cpu_time(kproc); } return Ok(None); } } // This is a new process, we need to get more information! // For some reason, it can return completely invalid path like `p\u{5}`. So we need to use // procstat to get around this problem. // let cwd = get_sys_value_str( // &[ // libc::CTL_KERN, // libc::KERN_PROC, // libc::KERN_PROC_CWD, // kproc.ki_pid, // ], // &mut buffer, // ) // .map(|s| s.into()) // .unwrap_or_else(PathBuf::new); Ok(Some(Process { inner: ProcessInner { pid: Pid(kproc.ki_pid), parent, user_id: Uid(kproc.ki_ruid), effective_user_id: Uid(kproc.ki_uid), group_id: Gid(kproc.ki_rgid), effective_group_id: Gid(kproc.ki_svgid), start_time, run_time: now.saturating_sub(start_time), cpu_usage: cpu_usage.unwrap_or(0.), virtual_memory, memory, // procstat_getfiles cwd: None, exe: None, // kvm_getargv isn't thread-safe so we get it in the main thread. name: OsString::new(), // kvm_getargv isn't thread-safe so we get it in the main thread. cmd: Vec::new(), // kvm_getargv isn't thread-safe so we get it in the main thread. root: None, // kvm_getenvv isn't thread-safe so we get it in the main thread. environ: Vec::new(), status, read_bytes: kproc.ki_rusage.ru_inblock as _, old_read_bytes: 0, written_bytes: kproc.ki_rusage.ru_oublock as _, old_written_bytes: 0, accumulated_cpu_time: if refresh_kind.cpu() { get_accumulated_cpu_time(kproc) } else { 0 }, updated: true, exists: true, }, })) } pub(crate) unsafe fn get_exe( exe: &mut Option, pid: crate::Pid, refresh_kind: ProcessRefreshKind, ) { if refresh_kind.exe().needs_update(|| exe.is_none()) { let mut buffer = [0; libc::PATH_MAX as usize + 1]; unsafe { *exe = get_sys_value_str( &[ libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PATHNAME, pid.0, ], &mut buffer, ) .map(PathBuf::from); } } } GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/product.rs000066400000000000000000000016101506747262600237140ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::get_kenv_var; pub(crate) struct ProductInner; impl ProductInner { pub(crate) fn family() -> Option { get_kenv_var(b"smbios.system.family\0") } pub(crate) fn name() -> Option { get_kenv_var(b"smbios.system.product\0") } pub(crate) fn serial_number() -> Option { get_kenv_var(b"smbios.system.serial\0") } pub(crate) fn stock_keeping_unit() -> Option { get_kenv_var(b"smbios.system.sku\0") } pub(crate) fn uuid() -> Option { get_kenv_var(b"smbios.system.uuid\0") } pub(crate) fn version() -> Option { get_kenv_var(b"smbios.system.version\0") } pub(crate) fn vendor_name() -> Option { get_kenv_var(b"smbios.system.maker\0") } } GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/system.rs000066400000000000000000000574131506747262600235740ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessInner, ProcessRefreshKind, ProcessesToUpdate, }; use std::cell::UnsafeCell; use std::collections::HashMap; use std::ffi::CStr; use std::mem::MaybeUninit; use std::path::{Path, PathBuf}; use std::ptr::NonNull; use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::{Duration, SystemTime}; use crate::sys::cpu::{CpusWrapper, physical_core_count}; use crate::sys::process::get_exe; use crate::sys::utils::{ self, boot_time, c_buf_to_os_string, c_buf_to_utf8_string, from_cstr_array, get_sys_value, get_sys_value_by_name, init_mib, }; use libc::c_int; declare_signals! { c_int, Signal::Hangup => libc::SIGHUP, Signal::Interrupt => libc::SIGINT, Signal::Quit => libc::SIGQUIT, Signal::Illegal => libc::SIGILL, Signal::Trap => libc::SIGTRAP, Signal::Abort => libc::SIGABRT, Signal::IOT => libc::SIGIOT, Signal::Bus => libc::SIGBUS, Signal::FloatingPointException => libc::SIGFPE, Signal::Kill => libc::SIGKILL, Signal::User1 => libc::SIGUSR1, Signal::Segv => libc::SIGSEGV, Signal::User2 => libc::SIGUSR2, Signal::Pipe => libc::SIGPIPE, Signal::Alarm => libc::SIGALRM, Signal::Term => libc::SIGTERM, Signal::Child => libc::SIGCHLD, Signal::Continue => libc::SIGCONT, Signal::Stop => libc::SIGSTOP, Signal::TSTP => libc::SIGTSTP, Signal::TTIN => libc::SIGTTIN, Signal::TTOU => libc::SIGTTOU, Signal::Urgent => libc::SIGURG, Signal::XCPU => libc::SIGXCPU, Signal::XFSZ => libc::SIGXFSZ, Signal::VirtualAlarm => libc::SIGVTALRM, Signal::Profiling => libc::SIGPROF, Signal::Winch => libc::SIGWINCH, Signal::IO => libc::SIGIO, Signal::Sys => libc::SIGSYS, _ => None, } #[doc = include_str!("../../../md_doc/supported_signals.md")] pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals(); #[doc = include_str!("../../../md_doc/minimum_cpu_update_interval.md")] pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(100); pub(crate) struct SystemInner { process_list: HashMap, mem_total: u64, mem_free: u64, mem_used: u64, swap_total: u64, swap_used: u64, system_info: SystemInfo, cpus: CpusWrapper, } impl SystemInner { pub(crate) fn new() -> Self { Self { process_list: HashMap::with_capacity(200), mem_total: 0, mem_free: 0, mem_used: 0, swap_total: 0, swap_used: 0, system_info: SystemInfo::new(), cpus: CpusWrapper::new(), } } pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) { if refresh_kind.ram() { if self.mem_total == 0 { self.mem_total = self.system_info.get_total_memory(); } self.mem_used = self.system_info.get_used_memory(); self.mem_free = self.system_info.get_free_memory(); } if refresh_kind.swap() { let (swap_used, swap_total) = self.system_info.get_swap_info(); self.swap_total = swap_total; self.swap_used = swap_used; } } pub(crate) fn cgroup_limits(&self) -> Option { None } pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { self.cpus.refresh(refresh_kind) } pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) { self.cpus = CpusWrapper::new(); self.cpus.refresh(refresh_kind); } pub(crate) fn refresh_processes_specifics( &mut self, processes_to_update: ProcessesToUpdate<'_>, refresh_kind: ProcessRefreshKind, ) -> usize { unsafe { self.refresh_procs(processes_to_update, refresh_kind) } } // COMMON PART // // Need to be moved into a "common" file to avoid duplication. pub(crate) fn processes(&self) -> &HashMap { &self.process_list } pub(crate) fn processes_mut(&mut self) -> &mut HashMap { &mut self.process_list } pub(crate) fn process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } pub(crate) fn global_cpu_usage(&self) -> f32 { self.cpus.global_cpu_usage } pub(crate) fn cpus(&self) -> &[Cpu] { &self.cpus.cpus } pub(crate) fn total_memory(&self) -> u64 { self.mem_total } pub(crate) fn free_memory(&self) -> u64 { self.mem_free } pub(crate) fn available_memory(&self) -> u64 { self.mem_free } pub(crate) fn used_memory(&self) -> u64 { self.mem_used } pub(crate) fn total_swap(&self) -> u64 { self.swap_total } pub(crate) fn free_swap(&self) -> u64 { self.swap_total - self.swap_used } // TODO: need to be checked pub(crate) fn used_swap(&self) -> u64 { self.swap_used } pub(crate) fn uptime() -> u64 { unsafe { let csec = libc::time(std::ptr::null_mut()); libc::difftime(csec, Self::boot_time() as _) as u64 } } pub(crate) fn boot_time() -> u64 { boot_time() } pub(crate) fn load_average() -> LoadAvg { let mut loads = vec![0f64; 3]; unsafe { libc::getloadavg(loads.as_mut_ptr(), 3); LoadAvg { one: loads[0], five: loads[1], fifteen: loads[2], } } } pub(crate) fn name() -> Option { let mut os_type: [c_int; 2] = [0; 2]; unsafe { init_mib(b"kern.ostype\0", &mut os_type); get_system_info(&os_type, Some("FreeBSD")) } } pub(crate) fn os_version() -> Option { let mut os_release: [c_int; 2] = [0; 2]; unsafe { init_mib(b"kern.osrelease\0", &mut os_release); // It returns something like "13.0-RELEASE". We want to keep everything until the "-". get_system_info(&os_release, None) .and_then(|s| s.split('-').next().map(|s| s.to_owned())) } } pub(crate) fn long_os_version() -> Option { let mut os_release: [c_int; 2] = [0; 2]; unsafe { init_mib(b"kern.version\0", &mut os_release); get_system_info(&os_release, None) } } pub(crate) fn host_name() -> Option { let mut hostname: [c_int; 2] = [0; 2]; unsafe { init_mib(b"kern.hostname\0", &mut hostname); get_system_info(&hostname, None) } } pub(crate) fn kernel_version() -> Option { unsafe { let mut kern_version: libc::c_int = 0; if get_sys_value_by_name(b"kern.osrevision\0", &mut kern_version) { Some(kern_version.to_string()) } else { None } } } pub(crate) fn distribution_id() -> String { std::env::consts::OS.to_owned() } pub(crate) fn distribution_id_like() -> Vec { Vec::new() } pub(crate) fn kernel_name() -> Option<&'static str> { Some("FreeBSD") } pub(crate) fn cpu_arch() -> Option { let mut arch_str: [u8; 32] = [0; 32]; let mib = [libc::CTL_HW as _, libc::HW_MACHINE as _]; unsafe { if get_sys_value(&mib, &mut arch_str) { CStr::from_bytes_until_nul(&arch_str) .ok() .and_then(|res| match res.to_str() { Ok(arch) => Some(arch.to_string()), Err(_) => None, }) } else { None } } } pub(crate) fn physical_core_count() -> Option { physical_core_count() } pub(crate) fn open_files_limit() -> Option { let mut value = 0u32; unsafe { if get_sys_value_by_name(b"kern.maxfilesperproc\0", &mut value) { Some(value as _) } else { None } } } } impl SystemInner { unsafe fn refresh_procs( &mut self, processes_to_update: ProcessesToUpdate<'_>, refresh_kind: ProcessRefreshKind, ) -> usize { let (op, arg) = match processes_to_update { ProcessesToUpdate::Some(&[]) => return 0, ProcessesToUpdate::Some(&[pid]) => (libc::KERN_PROC_PID, pid.as_u32() as c_int), _ => (libc::KERN_PROC_PROC, 0), }; let mut count = 0; let kvm_procs = unsafe { libc::kvm_getprocs(self.system_info.kd.as_ptr(), op, arg, &mut count) }; if count < 1 { sysinfo_debug!("kvm_getprocs returned nothing..."); return 0; } #[inline(always)] fn real_filter(e: &libc::kinfo_proc, filter: &[Pid]) -> bool { filter.contains(&Pid(e.ki_pid)) } #[inline(always)] fn empty_filter(_e: &libc::kinfo_proc, _filter: &[Pid]) -> bool { true } #[allow(clippy::type_complexity)] let (filter, filter_callback): ( &[Pid], &(dyn Fn(&libc::kinfo_proc, &[Pid]) -> bool + Sync + Send), ) = match processes_to_update { ProcessesToUpdate::All => (&[], &empty_filter), ProcessesToUpdate::Some(pids) => { if pids.is_empty() { return 0; } (pids, &real_filter) } }; let nb_updated = AtomicUsize::new(0); let new_processes = { #[cfg(feature = "multithread")] use rayon::iter::{ParallelIterator, ParallelIterator as IterTrait}; #[cfg(not(feature = "multithread"))] use std::iter::Iterator as IterTrait; unsafe { let kvm_procs: &mut [utils::KInfoProc] = std::slice::from_raw_parts_mut(kvm_procs as _, count as _); let fscale = self.system_info.fscale; let page_size = self.system_info.page_size as isize; let now = get_now(); let proc_list = utils::WrapMap(UnsafeCell::new(&mut self.process_list)); IterTrait::filter_map(crate::utils::into_iter(kvm_procs), |kproc| { if !filter_callback(kproc, filter) { return None; } let ret = super::process::get_process_data( kproc, &proc_list, page_size, fscale, now, refresh_kind, ) .ok()?; nb_updated.fetch_add(1, Ordering::Relaxed); ret }) .collect::>() } }; for process in new_processes { self.process_list.insert(process.inner.pid, process); } unsafe { let kvm_procs: &mut [utils::KInfoProc] = std::slice::from_raw_parts_mut(kvm_procs as _, count as _); for kproc in kvm_procs { if let Some(process) = self.process_list.get_mut(&Pid(kproc.ki_pid)) { add_missing_proc_info(&mut self.system_info, kproc, process, refresh_kind); } } } nb_updated.into_inner() } } unsafe fn add_missing_proc_info( system_info: &mut SystemInfo, kproc: &libc::kinfo_proc, proc_: &mut Process, refresh_kind: ProcessRefreshKind, ) { { let kd = system_info.kd.as_ptr(); let proc_inner = &mut proc_.inner; let cmd_needs_update = refresh_kind .cmd() .needs_update(|| proc_inner.cmd.is_empty()); if proc_inner.name.is_empty() || cmd_needs_update { let cmd = unsafe { from_cstr_array(libc::kvm_getargv(kd, kproc, 0) as _) }; if !cmd.is_empty() { // First, we try to retrieve the name from the command line. let p = Path::new(&cmd[0]); if let Some(name) = p.file_name() { name.clone_into(&mut proc_inner.name); } if cmd_needs_update { proc_inner.cmd = cmd; } } } unsafe { get_exe(&mut proc_inner.exe, proc_inner.pid, refresh_kind); system_info.get_proc_missing_info(kproc, proc_inner, refresh_kind); } if proc_inner.name.is_empty() { // The name can be cut short because the `ki_comm` field size is limited, // which is why we prefer to get the name from the command line as much as // possible. proc_inner.name = c_buf_to_os_string(&kproc.ki_comm); } if refresh_kind .environ() .needs_update(|| proc_inner.environ.is_empty()) { proc_inner.environ = unsafe { from_cstr_array(libc::kvm_getenvv(kd, kproc, 0) as _) }; } } } #[derive(Debug)] struct Zfs { enabled: bool, mib_arcstats_size: [c_int; 5], } impl Zfs { fn new() -> Self { let mut zfs = Self { enabled: false, mib_arcstats_size: Default::default(), }; unsafe { init_mib( b"kstat.zfs.misc.arcstats.size\0", &mut zfs.mib_arcstats_size, ); let mut arc_size: u64 = 0; if get_sys_value(&zfs.mib_arcstats_size, &mut arc_size) { zfs.enabled = arc_size != 0; } } zfs } fn arc_size(&self) -> Option { if self.enabled { let mut arc_size: u64 = 0; unsafe { get_sys_value(&self.mib_arcstats_size, &mut arc_size); Some(arc_size) } } else { None } } } /// This struct is used to get system information more easily. #[derive(Debug)] struct SystemInfo { hw_physical_memory: [c_int; 2], page_size: c_int, virtual_page_count: [c_int; 4], virtual_wire_count: [c_int; 4], virtual_active_count: [c_int; 4], virtual_cache_count: [c_int; 4], virtual_inactive_count: [c_int; 4], virtual_free_count: [c_int; 4], buf_space: [c_int; 2], kd: NonNull, /// From FreeBSD manual: "The kernel fixed-point scale factor". It's used when computing /// processes' CPU usage. fscale: f32, procstat: *mut libc::procstat, zfs: Zfs, } // This is needed because `kd: *mut libc::kvm_t` isn't thread-safe. unsafe impl Send for SystemInfo {} unsafe impl Sync for SystemInfo {} impl SystemInfo { fn new() -> Self { unsafe { let mut errbuf = MaybeUninit::<[libc::c_char; libc::_POSIX2_LINE_MAX as usize]>::uninit(); let kd = NonNull::new(libc::kvm_openfiles( std::ptr::null(), c"/dev/null".as_ptr() as *const _, std::ptr::null(), 0, errbuf.as_mut_ptr() as *mut _, )) .expect("kvm_openfiles failed"); let mut si = SystemInfo { hw_physical_memory: Default::default(), page_size: 0, virtual_page_count: Default::default(), virtual_wire_count: Default::default(), virtual_active_count: Default::default(), virtual_cache_count: Default::default(), virtual_inactive_count: Default::default(), virtual_free_count: Default::default(), buf_space: Default::default(), kd, fscale: 0., procstat: std::ptr::null_mut(), zfs: Zfs::new(), }; let mut fscale: c_int = 0; if !get_sys_value_by_name(b"kern.fscale\0", &mut fscale) { // Default value used in htop. fscale = 2048; } si.fscale = fscale as f32; if !get_sys_value_by_name(b"vm.stats.vm.v_page_size\0", &mut si.page_size) { panic!("cannot get page size..."); } init_mib(b"hw.physmem\0", &mut si.hw_physical_memory); init_mib(b"vm.stats.vm.v_page_count\0", &mut si.virtual_page_count); init_mib(b"vm.stats.vm.v_wire_count\0", &mut si.virtual_wire_count); init_mib( b"vm.stats.vm.v_active_count\0", &mut si.virtual_active_count, ); init_mib(b"vm.stats.vm.v_cache_count\0", &mut si.virtual_cache_count); init_mib( b"vm.stats.vm.v_inactive_count\0", &mut si.virtual_inactive_count, ); init_mib(b"vm.stats.vm.v_free_count\0", &mut si.virtual_free_count); init_mib(b"vfs.bufspace\0", &mut si.buf_space); si } } /// Returns (used, total). fn get_swap_info(&self) -> (u64, u64) { // Magic number used in htop. Cannot find how they got it when reading `kvm_getswapinfo` // source code so here we go... const LEN: usize = 16; let mut swap = MaybeUninit::<[libc::kvm_swap; LEN]>::uninit(); unsafe { let nswap = libc::kvm_getswapinfo(self.kd.as_ptr(), swap.as_mut_ptr() as *mut _, LEN as _, 0) as usize; if nswap < 1 { return (0, 0); } let swap = std::slice::from_raw_parts(swap.as_ptr() as *mut libc::kvm_swap, nswap.min(LEN)); let (used, total) = swap.iter().fold((0, 0), |(used, total): (u64, u64), swap| { ( used.saturating_add(swap.ksw_used as _), total.saturating_add(swap.ksw_total as _), ) }); ( used.saturating_mul(self.page_size as _), total.saturating_mul(self.page_size as _), ) } } fn get_total_memory(&self) -> u64 { let mut nb_pages: u64 = 0; unsafe { if get_sys_value(&self.virtual_page_count, &mut nb_pages) { return nb_pages.saturating_mul(self.page_size as _); } // This is a fallback. It includes all the available memory, not just the one available // for the users. let mut total_memory: u64 = 0; get_sys_value(&self.hw_physical_memory, &mut total_memory); total_memory } } fn get_used_memory(&self) -> u64 { let mut mem_active: u64 = 0; let mut mem_wire: u64 = 0; unsafe { get_sys_value(&self.virtual_active_count, &mut mem_active); get_sys_value(&self.virtual_wire_count, &mut mem_wire); let mut mem_wire = mem_wire.saturating_mul(self.page_size as _); // We need to subtract "ZFS ARC" from the "wired memory" because it should belongs to cache // but the kernel reports it as "wired memory" instead... if let Some(arc_size) = self.zfs.arc_size() { mem_wire = mem_wire.saturating_sub(arc_size); } mem_active .saturating_mul(self.page_size as _) .saturating_add(mem_wire) } } fn get_free_memory(&self) -> u64 { let mut buffers_mem: u64 = 0; let mut inactive_mem: u64 = 0; let mut cached_mem: u64 = 0; let mut free_mem: u64 = 0; unsafe { get_sys_value(&self.buf_space, &mut buffers_mem); get_sys_value(&self.virtual_inactive_count, &mut inactive_mem); get_sys_value(&self.virtual_cache_count, &mut cached_mem); get_sys_value(&self.virtual_free_count, &mut free_mem); // For whatever reason, buffers_mem is already the right value... buffers_mem .saturating_add(inactive_mem.saturating_mul(self.page_size as _)) .saturating_add(cached_mem.saturating_mul(self.page_size as _)) .saturating_add(free_mem.saturating_mul(self.page_size as _)) } } #[allow(clippy::collapsible_if)] // I keep as is for readability reasons. unsafe fn get_proc_missing_info( &mut self, kproc: &libc::kinfo_proc, proc_: &mut ProcessInner, refresh_kind: ProcessRefreshKind, ) { let mut done = 0; let cwd_needs_update = refresh_kind.cwd().needs_update(|| proc_.cwd().is_none()); let root_needs_update = refresh_kind.root().needs_update(|| proc_.root().is_none()); if cwd_needs_update { done += 1; } if root_needs_update { done += 1; } if done == 0 { return; } if self.procstat.is_null() { unsafe { self.procstat = libc::procstat_open_sysctl(); if self.procstat.is_null() { sysinfo_debug!("procstat_open_sysctl failed"); return; } } } unsafe { let head = libc::procstat_getfiles(self.procstat, kproc as *const _ as usize as *mut _, 0); if head.is_null() { return; } let mut entry = (*head).stqh_first; while !entry.is_null() && done > 0 { { let tmp = &*entry; if tmp.fs_uflags & libc::PS_FST_UFLAG_CDIR != 0 { if cwd_needs_update && !tmp.fs_path.is_null() { if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { proc_.cwd = Some(PathBuf::from(p)); done -= 1; } } } else if tmp.fs_uflags & libc::PS_FST_UFLAG_RDIR != 0 { if root_needs_update && !tmp.fs_path.is_null() { if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { proc_.root = Some(PathBuf::from(p)); done -= 1; } } } } entry = (*entry).next.stqe_next; } libc::procstat_freefiles(self.procstat, head); } } } impl Drop for SystemInfo { fn drop(&mut self) { unsafe { libc::kvm_close(self.kd.as_ptr()); if !self.procstat.is_null() { libc::procstat_close(self.procstat); } } } } fn get_system_info(mib: &[c_int], default: Option<&str>) -> Option { let mut size = 0; unsafe { // Call first to get size libc::sysctl( mib.as_ptr(), mib.len() as _, std::ptr::null_mut(), &mut size, std::ptr::null_mut(), 0, ); // exit early if we did not update the size if size == 0 { default.map(|s| s.to_owned()) } else { // set the buffer to the correct size let mut buf: Vec = vec![0; size as _]; if libc::sysctl( mib.as_ptr(), mib.len() as _, buf.as_mut_ptr() as _, &mut size, std::ptr::null_mut(), 0, ) == -1 { // If command fails return default default.map(|s| s.to_owned()) } else { c_buf_to_utf8_string(&buf) } } } } fn get_now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .map(|n| n.as_secs()) .unwrap_or(0) } GuillaumeGomez-sysinfo-067dd61/src/unix/freebsd/utils.rs000066400000000000000000000201361506747262600234000ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "system")] use std::ffi::{CStr, OsStr, OsString}; #[cfg(feature = "system")] use std::os::unix::ffi::OsStrExt; #[cfg(feature = "system")] #[inline] pub unsafe fn init_mib(name: &[u8], mib: &mut [libc::c_int]) { let mut len = mib.len(); unsafe { libc::sysctlnametomib(name.as_ptr() as _, mib.as_mut_ptr(), &mut len); } } #[cfg(feature = "system")] pub(crate) fn boot_time() -> u64 { let mut boot_time = libc::timeval { tv_sec: 0, tv_usec: 0, }; let mut len = std::mem::size_of::(); let mut mib: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME]; unsafe { if libc::sysctl( mib.as_mut_ptr(), mib.len() as _, &mut boot_time as *mut libc::timeval as *mut _, &mut len, std::ptr::null_mut(), 0, ) < 0 { 0 } else { boot_time.tv_sec as _ } } } #[cfg(any(feature = "system", feature = "network"))] pub(crate) unsafe fn get_sys_value(mib: &[libc::c_int], value: &mut T) -> bool { let mut len = std::mem::size_of::() as libc::size_t; unsafe { libc::sysctl( mib.as_ptr(), mib.len() as _, value as *mut _ as *mut _, &mut len, std::ptr::null_mut(), 0, ) == 0 } } #[cfg(feature = "system")] pub(crate) unsafe fn get_sys_value_array(mib: &[libc::c_int], value: &mut [T]) -> bool { let mut len = std::mem::size_of_val(value) as libc::size_t; unsafe { libc::sysctl( mib.as_ptr(), mib.len() as _, value.as_mut_ptr() as *mut _, &mut len as *mut _, std::ptr::null_mut(), 0, ) == 0 } } #[cfg(any(feature = "disk", feature = "system", feature = "network"))] pub(crate) fn c_buf_to_utf8_str(buf: &[libc::c_char]) -> Option<&str> { unsafe { let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()); std::str::from_utf8(if let Some(pos) = buf.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes &buf[..pos] } else { buf }) .ok() } } #[cfg(any(feature = "disk", feature = "system", feature = "network"))] pub(crate) fn c_buf_to_utf8_string(buf: &[libc::c_char]) -> Option { c_buf_to_utf8_str(buf).map(|s| s.to_owned()) } #[cfg(feature = "system")] pub(crate) fn c_buf_to_os_str(buf: &[libc::c_char]) -> &OsStr { unsafe { let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()); OsStr::from_bytes(if let Some(pos) = buf.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes &buf[..pos] } else { buf }) } } #[cfg(feature = "system")] pub(crate) fn c_buf_to_os_string(buf: &[libc::c_char]) -> OsString { c_buf_to_os_str(buf).to_owned() } #[cfg(feature = "system")] pub(crate) unsafe fn get_sysctl_raw( mib: &[libc::c_int], data: *mut (), len: &mut libc::size_t, ) -> Option<()> { unsafe { if libc::sysctl( mib.as_ptr(), mib.len() as _, data as _, len, std::ptr::null_mut(), 0, ) != 0 { None } else { Some(()) } } } #[cfg(feature = "system")] pub(crate) unsafe fn get_sys_value_str( mib: &[libc::c_int], buf: &mut [libc::c_char], ) -> Option { let mut len = std::mem::size_of_val(buf) as libc::size_t; unsafe { if libc::sysctl( mib.as_ptr(), mib.len() as _, buf.as_mut_ptr() as *mut _, &mut len, std::ptr::null_mut(), 0, ) != 0 { return None; } } Some(c_buf_to_os_string( &buf[..len / std::mem::size_of::()], )) } #[cfg(any(feature = "system", feature = "component"))] pub(crate) unsafe fn get_sys_value_by_name(name: &[u8], value: &mut T) -> bool { let mut len = std::mem::size_of::() as libc::size_t; let original = len; unsafe { libc::sysctlbyname( name.as_ptr() as *const libc::c_char, value as *mut _ as *mut _, &mut len, std::ptr::null_mut(), 0, ) == 0 && original == len } } #[cfg(any(feature = "system", feature = "disk"))] pub(crate) fn get_sys_value_str_by_name(name: &[u8]) -> Option { let mut size = 0; unsafe { if libc::sysctlbyname( name.as_ptr() as *const libc::c_char, std::ptr::null_mut(), &mut size, std::ptr::null_mut(), 0, ) == 0 && size > 0 { // now create a buffer with the size and get the real value let mut buf: Vec = vec![0; size as _]; if libc::sysctlbyname( name.as_ptr() as *const libc::c_char, buf.as_mut_ptr() as *mut _, &mut size, std::ptr::null_mut(), 0, ) == 0 && size > 0 { c_buf_to_utf8_string(&buf) } else { // getting the system value failed None } } else { None } } } #[cfg(feature = "system")] pub(crate) unsafe fn from_cstr_array(ptr: *const *const libc::c_char) -> Vec { if ptr.is_null() { return Vec::new(); } let mut max = 0; loop { unsafe { let ptr = ptr.add(max); if (*ptr).is_null() { break; } } max += 1; } if max == 0 { return Vec::new(); } let mut ret = Vec::with_capacity(max); for pos in 0..max { unsafe { let p = ptr.add(pos); ret.push(OsStr::from_bytes(CStr::from_ptr(*p).to_bytes()).to_os_string()); } } ret } #[cfg(any(feature = "system", feature = "component"))] pub(crate) unsafe fn get_nb_cpus() -> usize { let mut smp: libc::c_int = 0; let mut nb_cpus: libc::c_int = 1; unsafe { if !get_sys_value_by_name(b"kern.smp.active\0", &mut smp) { smp = 0; } #[allow(clippy::collapsible_if)] // I keep as is for readability reasons. if smp != 0 { if !get_sys_value_by_name(b"kern.smp.cpus\0", &mut nb_cpus) || nb_cpus < 1 { nb_cpus = 1; } } } nb_cpus as usize } // All this is needed because `kinfo_proc` doesn't implement `Send` (because it contains pointers). #[cfg(feature = "system")] pub(crate) struct WrapMap<'a>( pub std::cell::UnsafeCell<&'a mut std::collections::HashMap>, ); #[cfg(feature = "system")] unsafe impl Send for WrapMap<'_> {} #[cfg(feature = "system")] unsafe impl Sync for WrapMap<'_> {} #[cfg(feature = "system")] #[repr(transparent)] pub(crate) struct KInfoProc(libc::kinfo_proc); #[cfg(feature = "system")] unsafe impl Send for KInfoProc {} #[cfg(feature = "system")] unsafe impl Sync for KInfoProc {} #[cfg(feature = "system")] impl std::ops::Deref for KInfoProc { type Target = libc::kinfo_proc; fn deref(&self) -> &Self::Target { &self.0 } } #[cfg(feature = "system")] // Get the value of a kernel environment variable. pub(crate) fn get_kenv_var(name: &[u8]) -> Option { let mut buf: [libc::c_char; libc::KENV_MVALLEN as usize] = [0; libc::KENV_MVALLEN as usize]; let size = unsafe { libc::kenv( libc::KENV_GET as _, name.as_ptr() as _, buf.as_mut_ptr() as _, buf.len() as _, ) as isize }; // returns a strictly negative number in case of error // (see: https://man.freebsd.org/cgi/man.cgi?query=kenv&sektion=2&manpath=FreeBSD+15.0-CURRENT) if size < 0 { return None; } c_buf_to_utf8_string(&buf[..size as usize]) } GuillaumeGomez-sysinfo-067dd61/src/unix/groups.rs000066400000000000000000000025021506747262600221420ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Gid, Group, GroupInner}; impl GroupInner { pub(crate) fn new(id: crate::Gid, name: String) -> Self { Self { id, name } } pub(crate) fn id(&self) -> &crate::Gid { &self.id } pub(crate) fn name(&self) -> &str { &self.name } } pub(crate) fn get_groups(groups: &mut Vec) { groups.clear(); let mut groups_map = std::collections::HashMap::with_capacity(10); unsafe { libc::setgrent(); loop { let gr = libc::getgrent(); if gr.is_null() { // The call was interrupted by a signal, retrying. if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted { continue; } break; } if let Some(name) = crate::unix::utils::cstr_to_rust((*gr).gr_name) { if groups_map.contains_key(&name) { continue; } let gid = (*gr).gr_gid; groups_map.insert(name, Gid(gid)); } } libc::endgrent(); } for (name, gid) in groups_map { groups.push(Group { inner: GroupInner::new(gid, name), }); } } GuillaumeGomez-sysinfo-067dd61/src/unix/linux/000077500000000000000000000000001506747262600214155ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/unix/linux/component.rs000066400000000000000000000606631506747262600240000ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. // Information about values readable from `hwmon` sysfs. // // Values in /sys/class/hwmonN are `c_long` or `c_ulong` // transposed to rust we only read `u32` or `i32` values. use crate::Component; use std::collections::HashMap; use std::ffi::OsStr; use std::fs::{File, read_dir}; use std::io::Read; use std::path::{Path, PathBuf}; #[derive(Default)] pub(crate) struct ComponentInner { /// Optional associated device of a `Component`. device_model: Option, /// ID of a `Component`. id: Option, /// The chip name. /// /// Kernel documentation extract: /// /// ```txt /// This should be a short, lowercase string, not containing /// whitespace, dashes, or the wildcard character '*'. /// This attribute represents the chip name. It is the only /// mandatory attribute. /// I2C devices get this attribute created automatically. /// ``` name: String, /// Temperature current value /// - Read in: `temp[1-*]_input`. /// - Unit: read as millidegree Celsius converted to Celsius. temperature: Option, /// Maximum value computed by `sysinfo`. max: Option, // /// Max threshold provided by the chip/kernel // /// - Read in:`temp[1-*]_max` // /// - Unit: read as millidegree Celsius converted to Celsius. // threshold_max: Option, // /// Min threshold provided by the chip/kernel. // /// - Read in:`temp[1-*]_min` // /// - Unit: read as millidegree Celsius converted to Celsius. // threshold_min: Option, /// Critical threshold provided by the chip/kernel previous user write. /// Read in `temp[1-*]_crit`: /// Typically greater than corresponding temp_max values. /// - Unit: read as millidegree Celsius converted to Celsius. threshold_critical: Option, /// Sensor type, not common but can exist! /// /// Read in: `temp[1-*]_type` Sensor type selection. /// Values integer: /// /// - 1: CPU embedded diode /// - 2: 3904 transistor /// - 3: thermal diode /// - 4: thermistor /// - 5: AMD AMDSI /// - 6: Intel PECI /// /// Not all types are supported by all chips. sensor_type: Option, /// Component Label /// /// ## Linux implementation details /// /// read n: `temp[1-*]_label` Suggested temperature channel label. /// Value: Text string /// /// Should only be created if the driver has hints about what /// this temperature channel is being used for, and user-space /// doesn't. In all other cases, the label is provided by user-space. label: String, // Historical minimum temperature // - Read in:`temp[1-*]_lowest // - Unit: millidegree Celsius // // Temperature critical min value, typically lower than // corresponding temp_min values. // - Read in:`temp[1-*]_lcrit` // - Unit: millidegree Celsius // // Temperature emergency max value, for chips supporting more than // two upper temperature limits. Must be equal or greater than // corresponding temp_crit values. // - temp[1-*]_emergency // - Unit: millidegree Celsius /// File to read current temperature shall be `temp[1-*]_input` /// It may be absent but we don't continue if absent. input_file: Option, /// `temp[1-*]_highest file` to read if available highest value. highest_file: Option, pub(crate) updated: bool, } impl ComponentInner { fn update_from( &mut self, Component { inner: ComponentInner { temperature, max, input_file, highest_file, .. }, }: Component, ) { if let Some(temp) = temperature { self.temperature = Some(temp); } match (max, self.max) { (Some(new_max), Some(old_max)) => self.max = Some(new_max.max(old_max)), (Some(max), None) => self.max = Some(max), _ => {} } if input_file.is_some() && input_file != self.input_file { self.input_file = input_file; } if highest_file.is_some() && highest_file != self.highest_file { self.highest_file = highest_file; } self.updated = true; } } // Read arbitrary data from sysfs. fn get_file_line(file: &Path, capacity: usize) -> Option { let mut reader = String::with_capacity(capacity); let mut f = File::open(file).ok()?; f.read_to_string(&mut reader).ok()?; reader.truncate(reader.trim_end().len()); Some(reader) } /// Designed at first for reading an `i32` or `u32` aka `c_long` /// from a `/sys/class/hwmon` sysfs file. fn read_number_from_file(file: &Path) -> Option where N: std::str::FromStr, { let mut reader = [0u8; 32]; let mut f = File::open(file).ok()?; let n = f.read(&mut reader).ok()?; // parse and trim would complain about `\0`. let number = &reader[..n]; let number = std::str::from_utf8(number).ok()?; let number = number.trim(); // Assert that we cleaned a little bit that string. if cfg!(feature = "debug") { assert!(!number.contains('\n') && !number.contains('\0')); } number.parse().ok() } // Read a temperature from a `tempN_item` sensor form the sysfs. // number returned will be in mili-celsius. // // Don't call it on `label`, `name` or `type` file. #[inline] fn get_temperature_from_file(file: &Path) -> Option { let temp = read_number_from_file(file); convert_temp_celsius(temp) } /// Takes a raw temperature in mili-celsius and convert it to celsius. #[inline] fn convert_temp_celsius(temp: Option) -> Option { temp.map(|n| (n as f32) / 1000f32) } /// Information about thermal sensor. It may be unavailable as it's /// kernel module and chip dependent. enum ThermalSensorType { /// 1: CPU embedded diode CPUEmbeddedDiode, /// 2: 3904 transistor Transistor3904, /// 3: thermal diode ThermalDiode, /// 4: thermistor Thermistor, /// 5: AMD AMDSI AMDAMDSI, /// 6: Intel PECI IntelPECI, /// Not all types are supported by all chips so we keep space for unknown sensors. #[allow(dead_code)] Unknown(u8), } impl From for ThermalSensorType { fn from(input: u8) -> Self { match input { 0 => Self::CPUEmbeddedDiode, 1 => Self::Transistor3904, 3 => Self::ThermalDiode, 4 => Self::Thermistor, 5 => Self::AMDAMDSI, 6 => Self::IntelPECI, n => Self::Unknown(n), } } } /// Check given `item` dispatch to read the right `file` with the right parsing and store data in /// given `component`. `id` is provided for `label` creation. fn fill_component(component: &mut ComponentInner, item: &str, folder: &Path, file: &str) { let hwmon_file = folder.join(file); match item { "type" => { component.sensor_type = read_number_from_file::(&hwmon_file).map(ThermalSensorType::from) } "input" => { let temperature = get_temperature_from_file(&hwmon_file); component.input_file = Some(hwmon_file); component.temperature = temperature; // Maximum know try to get it from `highest` if not available // use current temperature if component.max.is_none() { component.max = temperature; } } "label" => component.label = get_file_line(&hwmon_file, 10).unwrap_or_default(), "highest" => { component.max = get_temperature_from_file(&hwmon_file).or(component.temperature); component.highest_file = Some(hwmon_file); } // "max" => component.threshold_max = get_temperature_from_file(&hwmon_file), // "min" => component.threshold_min = get_temperature_from_file(&hwmon_file), "crit" => component.threshold_critical = get_temperature_from_file(&hwmon_file), _ => { sysinfo_debug!( "This hwmon-temp file is still not supported! Contributions are appreciated.;) {:?}", hwmon_file, ); } } } impl ComponentInner { /// Read out `hwmon` info (hardware monitor) from `folder` /// to get values' path to be used on refresh as well as files containing `max`, /// `critical value` and `label`. Then we store everything into `components`. /// /// Note that a thermal [Component] must have a way to read its temperature. /// If not, it will be ignored and not added into `components`. /// /// ## What is read: /// /// - Mandatory: `name` the name of the `hwmon`. /// - Mandatory: `tempN_input` Drop [Component] if missing /// - Optional: sensor `label`, in the general case content of `tempN_label` /// see below for special cases /// - Optional: `label` /// - Optional: `/device/model` /// - Optional: highest historic value in `tempN_highest`. /// - Optional: max threshold value defined in `tempN_max` /// - Optional: critical threshold value defined in `tempN_crit` /// /// Where `N` is a `u32` associated to a sensor like `temp1_max`, `temp1_input`. /// /// ## Doc to Linux kernel API. /// /// Kernel hwmon API: https://www.kernel.org/doc/html/latest/hwmon/hwmon-kernel-api.html /// DriveTemp kernel API: https://docs.kernel.org/gpu/amdgpu/thermal.html#hwmon-interfaces /// Amdgpu hwmon interface: https://www.kernel.org/doc/html/latest/hwmon/drivetemp.html fn from_hwmon(components: &mut Vec, folder: &Path) -> Option<()> { let dir = read_dir(folder).ok()?; let mut matchings: HashMap = HashMap::with_capacity(10); for entry in dir.flatten() { if !entry.file_type().is_ok_and(|file_type| !file_type.is_dir()) { continue; } let entry = entry.path(); let filename = entry.file_name().and_then(|x| x.to_str()).unwrap_or(""); let Some((id, item)) = filename .strip_prefix("temp") .and_then(|f| f.split_once('_')) .and_then(|(id, item)| Some((id.parse::().ok()?, item))) else { continue; }; let component = matchings.entry(id).or_insert_with(|| Component { inner: ComponentInner::default(), }); let component = &mut component.inner; let name = get_file_line(&folder.join("name"), 16); let component_id = folder .file_name() .and_then(OsStr::to_str) .map(|f| format!("{f}_{id}")); component.name = name.unwrap_or_default(); component.id = component_id; let device_model = get_file_line(&folder.join("device/model"), 16); component.device_model = device_model; fill_component(component, item, folder, filename); } for (id, mut new_comp) in matchings .into_iter() // Remove components without `tempN_input` file termal. `Component` doesn't support this // kind of sensors yet .filter(|(_, c)| c.inner.input_file.is_some()) { // compute label from known data new_comp.inner.label = new_comp.inner.format_label("temp", id); if let Some(comp) = components .iter_mut() .find(|comp| comp.inner.label == new_comp.inner.label) { comp.inner.update_from(new_comp); } else { new_comp.inner.updated = true; components.push(new_comp); } } Some(()) } /// Compute a label out of available information. /// See the table in `Component::label`'s documentation. fn format_label(&self, class: &str, id: u32) -> String { let ComponentInner { device_model, name, label, .. } = self; let has_label = !label.is_empty(); match (has_label, device_model) { (true, Some(device_model)) => { format!("{name} {label} {device_model}") } (true, None) => format!("{name} {label}"), (false, Some(device_model)) => format!("{name} {device_model}"), (false, None) => format!("{name} {class}{id}"), } } pub(crate) fn temperature(&self) -> Option { self.temperature } pub(crate) fn max(&self) -> Option { self.max } pub(crate) fn critical(&self) -> Option { self.threshold_critical } pub(crate) fn label(&self) -> &str { &self.label } pub(crate) fn id(&self) -> Option<&str> { self.id.as_deref() } pub(crate) fn refresh(&mut self) { let current = self .input_file .as_ref() .and_then(|file| get_temperature_from_file(file.as_path())); // tries to read out kernel highest if not compute something from temperature. let max = self .highest_file .as_ref() .and_then(|file| get_temperature_from_file(file.as_path())) .or_else(|| { let last = self.temperature?; let current = current?; Some(last.max(current)) }); self.max = max; self.temperature = current; } } fn read_temp_dir(path: &Path, starts_with: &str, mut f: F) { if let Ok(dir) = read_dir(path) { for entry in dir.flatten() { if !entry .file_name() .to_str() .unwrap_or("") .starts_with(starts_with) { continue; } let path = entry.path(); if !path.is_file() { f(path); } } } } pub(crate) struct ComponentsInner { pub(crate) components: Vec, } impl ComponentsInner { pub(crate) fn new() -> Self { Self { components: Vec::with_capacity(4), } } pub(crate) fn from_vec(components: Vec) -> Self { Self { components } } pub(crate) fn into_vec(self) -> Vec { self.components } pub(crate) fn list(&self) -> &[Component] { &self.components } pub(crate) fn list_mut(&mut self) -> &mut [Component] { &mut self.components } pub(crate) fn refresh(&mut self) { self.refresh_from_sys_class_path(Path::new("/sys/class")); } fn refresh_from_sys_class_path(&mut self, path: &Path) { read_temp_dir(&path.join("hwmon"), "hwmon", |path| { ComponentInner::from_hwmon(&mut self.components, &path); }); if self.components.is_empty() { // Normally should only be used by raspberry pi. read_temp_dir(&path.join("thermal"), "thermal_", |path| { let temp = path.join("temp"); if temp.exists() { let Some(name) = get_file_line(&path.join("type"), 16) else { return; }; let component_id = path.file_name().and_then(OsStr::to_str).map(str::to_string); let mut component = ComponentInner { name, id: component_id, ..Default::default() }; fill_component(&mut component, "input", &path, "temp"); self.components.push(Component { inner: component }); } }); } } } #[cfg(test)] mod tests { use super::*; use std::fs; use tempfile; #[test] fn test_component_refresh_simple() { let temp_dir = tempfile::tempdir().expect("failed to create temporary directory"); let hwmon0_dir = temp_dir.path().join("hwmon/hwmon0"); fs::create_dir_all(temp_dir.path().join("hwmon/hwmon0")) .expect("failed to create hwmon/hwmon0 directory"); fs::write(hwmon0_dir.join("name"), "test_name").expect("failed to write to name file"); fs::write(hwmon0_dir.join("temp1_input"), "1234") .expect("failed to write to temp1_input file"); let mut components = ComponentsInner::new(); components.refresh_from_sys_class_path(temp_dir.path()); let components = components.into_vec(); assert_eq!(components.len(), 1); assert_eq!(components[0].inner.name, "test_name"); assert_eq!(components[0].label(), "test_name temp1"); assert_eq!(components[0].temperature(), Some(1.234)); assert_eq!(components[0].id(), Some("hwmon0_1")); } #[test] fn test_component_refresh_with_more_data() { let temp_dir = tempfile::tempdir().expect("failed to create temporary directory"); let hwmon0_dir = temp_dir.path().join("hwmon/hwmon0"); // create hwmon0 file including device/model file fs::create_dir_all(hwmon0_dir.join("device")) .expect("failed to create hwmon/hwmon0 directory"); fs::write(hwmon0_dir.join("name"), "test_name").expect("failed to write to name file"); fs::write(hwmon0_dir.join("device/model"), "test_model") .expect("failed to write to model file"); fs::write(hwmon0_dir.join("temp1_label"), "test_label1") .expect("failed to write to temp1_label file"); fs::write(hwmon0_dir.join("temp1_input"), "1234") .expect("failed to write to temp1_input file"); fs::write(hwmon0_dir.join("temp1_crit"), "100").expect("failed to write to temp1_min file"); let mut components = ComponentsInner::new(); components.refresh_from_sys_class_path(temp_dir.path()); let components = components.into_vec(); assert_eq!(components.len(), 1); assert_eq!(components[0].inner.name, "test_name"); assert_eq!(components[0].label(), "test_name test_label1 test_model"); assert_eq!(components[0].temperature(), Some(1.234)); assert_eq!(components[0].max(), Some(1.234)); assert_eq!(components[0].critical(), Some(0.1)); assert_eq!(components[0].id(), Some("hwmon0_1")); } #[test] fn test_component_refresh_multiple_sensors() { let temp_dir = tempfile::tempdir().expect("failed to create temporary directory"); let hwmon0_dir = temp_dir.path().join("hwmon/hwmon0"); fs::create_dir_all(&hwmon0_dir).expect("failed to create hwmon/hwmon0 directory"); fs::write(hwmon0_dir.join("name"), "test_name").expect("failed to write to name file"); fs::write(hwmon0_dir.join("temp1_label"), "test_label1") .expect("failed to write to temp1_label file"); fs::write(hwmon0_dir.join("temp1_input"), "1234") .expect("failed to write to temp1_input file"); fs::write(hwmon0_dir.join("temp1_crit"), "100").expect("failed to write to temp1_min file"); fs::write(hwmon0_dir.join("temp2_label"), "test_label2") .expect("failed to write to temp2_label file"); fs::write(hwmon0_dir.join("temp2_input"), "5678") .expect("failed to write to temp2_input file"); fs::write(hwmon0_dir.join("temp2_crit"), "200").expect("failed to write to temp2_min file"); let mut components = ComponentsInner::new(); components.refresh_from_sys_class_path(temp_dir.path()); let mut components = components.into_vec(); components.sort_by_key(|c| c.inner.label.clone()); assert_eq!(components.len(), 2); assert_eq!(components[0].inner.name, "test_name"); assert_eq!(components[0].label(), "test_name test_label1"); assert_eq!(components[0].temperature(), Some(1.234)); assert_eq!(components[0].max(), Some(1.234)); assert_eq!(components[0].id(), Some("hwmon0_1")); assert_eq!(components[0].critical(), Some(0.1)); assert_eq!(components[1].inner.name, "test_name"); assert_eq!(components[1].label(), "test_name test_label2"); assert_eq!(components[1].temperature(), Some(5.678)); assert_eq!(components[1].max(), Some(5.678)); assert_eq!(components[1].id(), Some("hwmon0_2")); assert_eq!(components[1].critical(), Some(0.2)); } #[test] fn test_component_refresh_multiple_sensors_with_device_model() { let temp_dir = tempfile::tempdir().expect("failed to create temporary directory"); let hwmon0_dir = temp_dir.path().join("hwmon/hwmon0"); // create hwmon0 file including device/model file fs::create_dir_all(hwmon0_dir.join("device")) .expect("failed to create hwmon/hwmon0 directory"); fs::write(hwmon0_dir.join("name"), "test_name").expect("failed to write to name file"); fs::write(hwmon0_dir.join("device/model"), "test_model") .expect("failed to write to model file"); fs::write(hwmon0_dir.join("temp1_label"), "test_label1") .expect("failed to write to temp1_label file"); fs::write(hwmon0_dir.join("temp1_input"), "1234") .expect("failed to write to temp1_input file"); fs::write(hwmon0_dir.join("temp1_crit"), "100").expect("failed to write to temp1_min file"); fs::write(hwmon0_dir.join("temp2_label"), "test_label2") .expect("failed to write to temp2_label file"); fs::write(hwmon0_dir.join("temp2_input"), "5678") .expect("failed to write to temp2_input file"); fs::write(hwmon0_dir.join("temp2_crit"), "200").expect("failed to write to temp2_min file"); let mut components = ComponentsInner::new(); components.refresh_from_sys_class_path(temp_dir.path()); let mut components = components.into_vec(); components.sort_by_key(|c| c.inner.label.clone()); assert_eq!(components.len(), 2); assert_eq!(components[0].inner.name, "test_name"); assert_eq!(components[0].label(), "test_name test_label1 test_model"); assert_eq!(components[0].temperature(), Some(1.234)); assert_eq!(components[0].max(), Some(1.234)); assert_eq!(components[0].critical(), Some(0.1)); assert_eq!(components[0].id(), Some("hwmon0_1")); assert_eq!(components[1].inner.name, "test_name"); assert_eq!(components[1].label(), "test_name test_label2 test_model"); assert_eq!(components[1].temperature(), Some(5.678)); assert_eq!(components[1].max(), Some(5.678)); assert_eq!(components[1].critical(), Some(0.2)); assert_eq!(components[1].id(), Some("hwmon0_2")); } #[test] fn test_thermal_zone() { let temp_dir = tempfile::tempdir().expect("failed to create temporary directory"); let thermal_zone0_dir = temp_dir.path().join("thermal/thermal_zone0"); let thermal_zone1_dir = temp_dir.path().join("thermal/thermal_zone1"); // create thermal zone files fs::create_dir_all(thermal_zone0_dir.join("device")) .expect("failed to create thermal/thermal_zone0 directory"); fs::write(thermal_zone0_dir.join("type"), "test_name") .expect("failed to write to name file"); fs::write(thermal_zone0_dir.join("temp"), "1234").expect("failed to write to temp file"); // create thermal zone files fs::create_dir_all(thermal_zone1_dir.join("device")) .expect("failed to create thermal/thermal_zone1 directory"); fs::write(thermal_zone1_dir.join("type"), "test_name2") .expect("failed to write to name file"); fs::write(thermal_zone1_dir.join("temp"), "5678").expect("failed to write to temp file"); let mut components = ComponentsInner::new(); components.refresh_from_sys_class_path(temp_dir.path()); let mut components = components.into_vec(); components.sort_by_key(|c| c.inner.name.clone()); assert_eq!(components.len(), 2); assert_eq!(components[0].inner.name, "test_name"); assert_eq!(components[0].label(), ""); assert_eq!(components[0].temperature(), Some(1.234)); assert_eq!(components[0].max(), Some(1.234)); assert_eq!(components[0].id(), Some("thermal_zone0")); assert_eq!(components[1].inner.name, "test_name2"); assert_eq!(components[1].label(), ""); assert_eq!(components[1].temperature(), Some(5.678)); assert_eq!(components[1].max(), Some(5.678)); assert_eq!(components[1].id(), Some("thermal_zone1")); } } GuillaumeGomez-sysinfo-067dd61/src/unix/linux/cpu.rs000066400000000000000000000740741506747262600225660ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![allow(clippy::too_many_arguments)] use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::{BufRead, BufReader, Read}; use std::time::Instant; use crate::sys::utils::to_u64; use crate::{Cpu, CpuRefreshKind}; macro_rules! to_str { ($e:expr) => { unsafe { std::str::from_utf8_unchecked($e) } }; } pub(crate) struct CpusWrapper { pub(crate) global_cpu: CpuUsage, pub(crate) cpus: Vec, got_cpu_frequency: bool, /// This field is needed to prevent updating when not enough time passed since last update. last_update: Option, } impl CpusWrapper { pub(crate) fn new() -> Self { Self { global_cpu: CpuUsage::default(), cpus: Vec::with_capacity(4), got_cpu_frequency: false, last_update: None, } } pub(crate) fn refresh_if_needed( &mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind, ) { self.refresh(only_update_global_cpu, refresh_kind); } pub(crate) fn refresh(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) { let need_cpu_usage_update = self .last_update .map(|last_update| last_update.elapsed() >= crate::MINIMUM_CPU_UPDATE_INTERVAL) .unwrap_or(true); let first = self.cpus.is_empty(); let mut vendors_brands = if first { get_vendor_id_and_brand() } else { HashMap::new() }; // If the last CPU usage update is too close (less than `MINIMUM_CPU_UPDATE_INTERVAL`), // we don't want to update CPUs times. if need_cpu_usage_update { self.last_update = Some(Instant::now()); let f = match File::open("/proc/stat") { Ok(f) => f, Err(_e) => { sysinfo_debug!("failed to retrieve CPU information: {:?}", _e); return; } }; let buf = BufReader::new(f); let mut i: usize = 0; let mut it = buf.split(b'\n'); if first || refresh_kind.cpu_usage() { if let Some(Ok(line)) = it.next() { if line.len() < 4 || &line[..4] != b"cpu " { return; } let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()).skip(1); self.global_cpu.set( parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), ); } if first || !only_update_global_cpu { while let Some(Ok(line)) = it.next() { if line.len() < 3 || &line[..3] != b"cpu" { break; } let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); if first { let (vendor_id, brand) = match vendors_brands.remove(&i) { Some((vendor_id, brand)) => (vendor_id, brand), None => (String::new(), String::new()), }; self.cpus.push(Cpu { inner: CpuInner::new_with_values( to_str!(parts.next().unwrap_or(&[])), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), 0, vendor_id, brand, ), }); } else { parts.next(); // we don't want the name again if let Some(cpu) = self.cpus.get_mut(i) { cpu.inner.set( parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), ); } else { // A new CPU was added, so let's ignore it. If they want it into // the list, they need to use `refresh_cpu_list`. sysinfo_debug!("ignoring new CPU added"); } } i += 1; } } if i < self.cpus.len() { sysinfo_debug!("{} CPU(s) seem to have been removed", self.cpus.len() - i); } } } if refresh_kind.frequency() { #[cfg(feature = "multithread")] use rayon::iter::{ IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator, }; #[cfg(feature = "multithread")] // This function is voluntarily made generic in case we want to generalize it. fn iter_mut<'a, T>( val: &'a mut T, ) -> <&'a mut T as rayon::iter::IntoParallelIterator>::Iter where &'a mut T: rayon::iter::IntoParallelIterator, { val.par_iter_mut() } #[cfg(not(feature = "multithread"))] fn iter_mut(val: &mut [Cpu]) -> std::slice::IterMut<'_, Cpu> { val.iter_mut() } // `get_cpu_frequency` is very slow, so better run it in parallel. iter_mut(&mut self.cpus) .enumerate() .for_each(|(pos, proc_)| proc_.inner.frequency = get_cpu_frequency(pos)); self.got_cpu_frequency = true; } } pub(crate) fn get_global_raw_times(&self) -> (u64, u64) { (self.global_cpu.total_time, self.global_cpu.old_total_time) } pub(crate) fn len(&self) -> usize { self.cpus.len() } pub(crate) fn is_empty(&self) -> bool { self.cpus.is_empty() } } /// Struct containing values to compute a CPU usage. #[derive(Clone, Copy, Debug, Default)] pub(crate) struct CpuValues { user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, } impl CpuValues { /// Sets the given argument to the corresponding fields. pub fn set( &mut self, user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, ) { // `guest` is already accounted in `user`. self.user = user.saturating_sub(guest); // `guest_nice` is already accounted in `nice`. self.nice = nice.saturating_sub(guest_nice); self.system = system; self.idle = idle; self.iowait = iowait; self.irq = irq; self.softirq = softirq; self.steal = steal; self.guest = guest; self.guest_nice = guest_nice; } #[inline] pub fn work_time(&self) -> u64 { self.user.saturating_add(self.nice) } #[inline] pub fn system_time(&self) -> u64 { self.system .saturating_add(self.irq) .saturating_add(self.softirq) } #[inline] pub fn idle_time(&self) -> u64 { self.idle.saturating_add(self.iowait) } #[inline] pub fn virtual_time(&self) -> u64 { self.guest.saturating_add(self.guest_nice) } #[inline] pub fn total_time(&self) -> u64 { self.work_time() .saturating_add(self.system_time()) .saturating_add(self.idle_time()) .saturating_add(self.virtual_time()) .saturating_add(self.steal) } } #[derive(Default)] pub(crate) struct CpuUsage { percent: f32, old_values: CpuValues, new_values: CpuValues, total_time: u64, old_total_time: u64, } impl CpuUsage { pub(crate) fn new_with_values( user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, ) -> Self { let mut new_values = CpuValues::default(); new_values.set( user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, ); Self { old_values: CpuValues::default(), new_values, percent: 0f32, total_time: 0, old_total_time: 0, } } pub(crate) fn set( &mut self, user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, ) { macro_rules! min { ($a:expr, $b:expr, $def:expr) => { if $a > $b { ($a - $b) as f32 } else { $def } }; } self.old_values = self.new_values; self.new_values.set( user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, ); self.total_time = self.new_values.total_time(); self.old_total_time = self.old_values.total_time(); let nice_period = self.new_values.nice.saturating_sub(self.old_values.nice); let user_period = self.new_values.user.saturating_sub(self.old_values.user); let steal_period = self.new_values.steal.saturating_sub(self.old_values.steal); let guest_period = self .new_values .virtual_time() .saturating_sub(self.old_values.virtual_time()); let system_period = self .new_values .system_time() .saturating_sub(self.old_values.system_time()); let total = min!(self.total_time, self.old_total_time, 1.); let nice = nice_period as f32 / total; let user = user_period as f32 / total; let system = system_period as f32 / total; let irq = (steal_period + guest_period) as f32 / total; self.percent = (nice + user + system + irq) * 100.; if self.percent > 100. { self.percent = 100.; // to prevent the percentage to go above 100% } } pub(crate) fn usage(&self) -> f32 { self.percent } } pub(crate) struct CpuInner { usage: CpuUsage, pub(crate) name: String, pub(crate) frequency: u64, pub(crate) vendor_id: String, pub(crate) brand: String, } impl CpuInner { pub(crate) fn new_with_values( name: &str, user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, frequency: u64, vendor_id: String, brand: String, ) -> Self { Self { usage: CpuUsage::new_with_values( user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, ), name: name.to_owned(), frequency, vendor_id, brand, } } pub(crate) fn set( &mut self, user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, ) { self.usage.set( user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, ); } pub(crate) fn cpu_usage(&self) -> f32 { self.usage.percent } pub(crate) fn name(&self) -> &str { &self.name } /// Returns the CPU frequency in MHz. pub(crate) fn frequency(&self) -> u64 { self.frequency } pub(crate) fn vendor_id(&self) -> &str { &self.vendor_id } pub(crate) fn brand(&self) -> &str { &self.brand } } pub(crate) fn get_cpu_frequency(cpu_core_index: usize) -> u64 { let mut s = String::new(); if File::open(format!( "/sys/devices/system/cpu/cpu{cpu_core_index}/cpufreq/scaling_cur_freq", )) .and_then(|mut f| f.read_to_string(&mut s)) .is_ok() { let freq_option = s.trim().split('\n').next(); if let Some(freq_string) = freq_option && let Ok(freq) = freq_string.parse::() { return freq / 1000; } } s.clear(); if File::open("/proc/cpuinfo") .and_then(|mut f| f.read_to_string(&mut s)) .is_err() { return 0; } let find_cpu_mhz = s.split('\n').find(|line| { cpuinfo_is_key(line, b"cpu MHz\t") || cpuinfo_is_key(line, b"CPU MHz\t") || cpuinfo_is_key(line, b"BogoMIPS") || cpuinfo_is_key(line, b"clock\t") || cpuinfo_is_key(line, b"bogomips per cpu") }); find_cpu_mhz .and_then(|line| line.split(':').next_back()) .and_then(|val| val.replace("MHz", "").trim().parse::().ok()) .map(|speed| speed as u64) .unwrap_or_default() } #[allow(unused_assignments)] pub(crate) fn get_physical_core_count() -> Option { let mut s = String::new(); if let Err(_e) = File::open("/proc/cpuinfo").and_then(|mut f| f.read_to_string(&mut s)) { sysinfo_debug!("Cannot read `/proc/cpuinfo` file: {:?}", _e); return None; } macro_rules! add_core { ($core_ids_and_physical_ids:ident, $core_id:ident, $physical_id:ident, $cpu:ident) => {{ if !$core_id.is_empty() && !$physical_id.is_empty() { $core_ids_and_physical_ids.insert(format!("{} {}", $core_id, $physical_id)); } else if !$cpu.is_empty() { // On systems with only physical cores like raspberry, there is no "core id" or // "physical id" fields. So if one of them is missing, we simply use the "CPU" // info and count it as a physical core. $core_ids_and_physical_ids.insert($cpu.to_owned()); } $core_id = ""; $physical_id = ""; $cpu = ""; }}; } let mut core_ids_and_physical_ids: HashSet = HashSet::new(); let mut core_id = ""; let mut physical_id = ""; let mut cpu = ""; for line in s.lines() { if line.is_empty() { add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu); } else if line.starts_with("processor") { cpu = line .splitn(2, ':') .last() .map(|x| x.trim()) .unwrap_or_default(); } else if line.starts_with("core id") { core_id = line .splitn(2, ':') .last() .map(|x| x.trim()) .unwrap_or_default(); } else if line.starts_with("physical id") { physical_id = line .splitn(2, ':') .last() .map(|x| x.trim()) .unwrap_or_default(); } } add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu); Some(core_ids_and_physical_ids.len()) } /// Obtain the implementer of this CPU core. /// /// This has been obtained from util-linux's lscpu implementation, see /// https://github.com/util-linux/util-linux/blob/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L240 /// /// This list will have to be updated every time a new vendor appears, please keep it synchronized /// with util-linux and update the link above with the commit you have used. fn get_arm_implementer(implementer: u32) -> Option<&'static str> { Some(match implementer { 0x41 => "ARM", 0x42 => "Broadcom", 0x43 => "Cavium", 0x44 => "DEC", 0x46 => "FUJITSU", 0x48 => "HiSilicon", 0x49 => "Infineon", 0x4d => "Motorola/Freescale", 0x4e => "NVIDIA", 0x50 => "APM", 0x51 => "Qualcomm", 0x53 => "Samsung", 0x56 => "Marvell", 0x61 => "Apple", 0x66 => "Faraday", 0x69 => "Intel", 0x70 => "Phytium", 0xc0 => "Ampere", _ => return None, }) } /// Obtain the part of this CPU core. /// /// This has been obtained from util-linux's lscpu implementation, see /// https://github.com/util-linux/util-linux/blob/eb788e20b82d0e1001a30867c71c8bfb2bb86819/sys-utils/lscpu-arm.c#L25 /// /// This list will have to be updated every time a new core appears, please keep it synchronized /// with util-linux and update the link above with the commit you have used. fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> { Some(match (implementer, part) { // ARM (0x41, 0x810) => "ARM810", (0x41, 0x920) => "ARM920", (0x41, 0x922) => "ARM922", (0x41, 0x926) => "ARM926", (0x41, 0x940) => "ARM940", (0x41, 0x946) => "ARM946", (0x41, 0x966) => "ARM966", (0x41, 0xa20) => "ARM1020", (0x41, 0xa22) => "ARM1022", (0x41, 0xa26) => "ARM1026", (0x41, 0xb02) => "ARM11 MPCore", (0x41, 0xb36) => "ARM1136", (0x41, 0xb56) => "ARM1156", (0x41, 0xb76) => "ARM1176", (0x41, 0xc05) => "Cortex-A5", (0x41, 0xc07) => "Cortex-A7", (0x41, 0xc08) => "Cortex-A8", (0x41, 0xc09) => "Cortex-A9", (0x41, 0xc0d) => "Cortex-A17", // Originally A12 (0x41, 0xc0f) => "Cortex-A15", (0x41, 0xc0e) => "Cortex-A17", (0x41, 0xc14) => "Cortex-R4", (0x41, 0xc15) => "Cortex-R5", (0x41, 0xc17) => "Cortex-R7", (0x41, 0xc18) => "Cortex-R8", (0x41, 0xc20) => "Cortex-M0", (0x41, 0xc21) => "Cortex-M1", (0x41, 0xc23) => "Cortex-M3", (0x41, 0xc24) => "Cortex-M4", (0x41, 0xc27) => "Cortex-M7", (0x41, 0xc60) => "Cortex-M0+", (0x41, 0xd01) => "Cortex-A32", (0x41, 0xd02) => "Cortex-A34", (0x41, 0xd03) => "Cortex-A53", (0x41, 0xd04) => "Cortex-A35", (0x41, 0xd05) => "Cortex-A55", (0x41, 0xd06) => "Cortex-A65", (0x41, 0xd07) => "Cortex-A57", (0x41, 0xd08) => "Cortex-A72", (0x41, 0xd09) => "Cortex-A73", (0x41, 0xd0a) => "Cortex-A75", (0x41, 0xd0b) => "Cortex-A76", (0x41, 0xd0c) => "Neoverse-N1", (0x41, 0xd0d) => "Cortex-A77", (0x41, 0xd0e) => "Cortex-A76AE", (0x41, 0xd13) => "Cortex-R52", (0x41, 0xd15) => "Cortex-R82", (0x41, 0xd16) => "Cortex-R52+", (0x41, 0xd20) => "Cortex-M23", (0x41, 0xd21) => "Cortex-M33", (0x41, 0xd22) => "Cortex-R55", (0x41, 0xd23) => "Cortex-R85", (0x41, 0xd40) => "Neoverse-V1", (0x41, 0xd41) => "Cortex-A78", (0x41, 0xd42) => "Cortex-A78AE", (0x41, 0xd43) => "Cortex-A65AE", (0x41, 0xd44) => "Cortex-X1", (0x41, 0xd46) => "Cortex-A510", (0x41, 0xd47) => "Cortex-A710", (0x41, 0xd48) => "Cortex-X2", (0x41, 0xd49) => "Neoverse-N2", (0x41, 0xd4a) => "Neoverse-E1", (0x41, 0xd4b) => "Cortex-A78C", (0x41, 0xd4c) => "Cortex-X1C", (0x41, 0xd4d) => "Cortex-A715", (0x41, 0xd4e) => "Cortex-X3", (0x41, 0xd4f) => "Neoverse-V2", (0x41, 0xd80) => "Cortex-A520", (0x41, 0xd81) => "Cortex-A720", (0x41, 0xd82) => "Cortex-X4", (0x41, 0xd84) => "Neoverse-V3", (0x41, 0xd85) => "Cortex-X925", (0x41, 0xd87) => "Cortex-A725", (0x41, 0xd8e) => "Neoverse-N3", // Broadcom (0x42, 0x00f) => "Brahma-B15", (0x42, 0x100) => "Brahma-B53", (0x42, 0x516) => "ThunderX2", // Cavium (0x43, 0x0a0) => "ThunderX", (0x43, 0x0a1) => "ThunderX-88XX", (0x43, 0x0a2) => "ThunderX-81XX", (0x43, 0x0a3) => "ThunderX-83XX", (0x43, 0x0af) => "ThunderX2-99xx", // DEC (0x44, 0xa10) => "SA110", (0x44, 0xa11) => "SA1100", // Fujitsu (0x46, 0x001) => "A64FX", // HiSilicon (0x48, 0xd01) => "Kunpeng-920", // aka tsv110 // NVIDIA (0x4e, 0x000) => "Denver", (0x4e, 0x003) => "Denver 2", (0x4e, 0x004) => "Carmel", // APM (0x50, 0x000) => "X-Gene", // Qualcomm (0x51, 0x00f) => "Scorpion", (0x51, 0x02d) => "Scorpion", (0x51, 0x04d) => "Krait", (0x51, 0x06f) => "Krait", (0x51, 0x201) => "Kryo", (0x51, 0x205) => "Kryo", (0x51, 0x211) => "Kryo", (0x51, 0x800) => "Falkor-V1/Kryo", (0x51, 0x801) => "Kryo-V2", (0x51, 0x802) => "Kryo-3XX-Gold", (0x51, 0x803) => "Kryo-3XX-Silver", (0x51, 0x804) => "Kryo-4XX-Gold", (0x51, 0x805) => "Kryo-4XX-Silver", (0x51, 0xc00) => "Falkor", (0x51, 0xc01) => "Saphira", // Samsung (0x53, 0x001) => "exynos-m1", // Marvell (0x56, 0x131) => "Feroceon-88FR131", (0x56, 0x581) => "PJ4/PJ4b", (0x56, 0x584) => "PJ4B-MP", // Apple (0x61, 0x020) => "Icestorm-A14", (0x61, 0x021) => "Firestorm-A14", (0x61, 0x022) => "Icestorm-M1", (0x61, 0x023) => "Firestorm-M1", (0x61, 0x024) => "Icestorm-M1-Pro", (0x61, 0x025) => "Firestorm-M1-Pro", (0x61, 0x028) => "Icestorm-M1-Max", (0x61, 0x029) => "Firestorm-M1-Max", (0x61, 0x030) => "Blizzard-A15", (0x61, 0x031) => "Avalanche-A15", (0x61, 0x032) => "Blizzard-M2", (0x61, 0x033) => "Avalanche-M2", // Faraday (0x66, 0x526) => "FA526", (0x66, 0x626) => "FA626", // Intel (0x69, 0x200) => "i80200", (0x69, 0x210) => "PXA250A", (0x69, 0x212) => "PXA210A", (0x69, 0x242) => "i80321-400", (0x69, 0x243) => "i80321-600", (0x69, 0x290) => "PXA250B/PXA26x", (0x69, 0x292) => "PXA210B", (0x69, 0x2c2) => "i80321-400-B0", (0x69, 0x2c3) => "i80321-600-B0", (0x69, 0x2d0) => "PXA250C/PXA255/PXA26x", (0x69, 0x2d2) => "PXA210C", (0x69, 0x411) => "PXA27x", (0x69, 0x41c) => "IPX425-533", (0x69, 0x41d) => "IPX425-400", (0x69, 0x41f) => "IPX425-266", (0x69, 0x682) => "PXA32x", (0x69, 0x683) => "PXA930/PXA935", (0x69, 0x688) => "PXA30x", (0x69, 0x689) => "PXA31x", (0x69, 0xb11) => "SA1110", (0x69, 0xc12) => "IPX1200", // Phytium (0x70, 0x660) => "FTC660", (0x70, 0x661) => "FTC661", (0x70, 0x662) => "FTC662", (0x70, 0x663) => "FTC663", _ => return None, }) } /// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs). pub(crate) fn get_vendor_id_and_brand() -> HashMap { let mut s = String::new(); if File::open("/proc/cpuinfo") .and_then(|mut f| f.read_to_string(&mut s)) .is_err() { return HashMap::new(); } get_vendor_id_and_brand_inner(&s) } #[inline] fn cpuinfo_is_key(line: &str, key: &[u8]) -> bool { let line = line.as_bytes(); line.len() > key.len() && line[..key.len()].eq_ignore_ascii_case(key) } fn get_vendor_id_and_brand_inner(data: &str) -> HashMap { fn get_value(s: &str) -> String { s.split(':') .next_back() .map(|x| x.trim().to_owned()) .unwrap_or_default() } fn get_hex_value(s: &str) -> u32 { s.split(':') .next_back() .map(|x| x.trim()) .filter(|x| x.starts_with("0x")) .map(|x| u32::from_str_radix(&x[2..], 16).unwrap()) .unwrap_or_default() } #[inline] fn is_new_processor(line: &str) -> bool { line.starts_with("processor\t") } #[derive(Default)] struct CpuInfo { index: usize, vendor_id: Option, brand: Option, implementer: Option, part: Option, } impl CpuInfo { fn has_all_info(&self) -> bool { (self.brand.is_some() && self.vendor_id.is_some()) || (self.implementer.is_some() && self.part.is_some()) } fn convert(mut self) -> (usize, String, String) { let (vendor_id, brand) = if let (Some(implementer), Some(part)) = (self.implementer.take(), self.part.take()) { let vendor_id = get_arm_implementer(implementer).map(String::from); // It's possible to "model name" even with an ARM CPU, so just in case we can't retrieve // the brand from "CPU part", we will then use the value from "model name". // // Example from raspberry pi 3B+: // // ``` // model name : ARMv7 Processor rev 4 (v7l) // CPU implementer : 0x41 // CPU part : 0xd03 // ``` let brand = get_arm_part(implementer, part) .map(String::from) .or_else(|| self.brand.take()); (vendor_id, brand) } else { (self.vendor_id.take(), self.brand.take()) }; ( self.index, vendor_id.unwrap_or_default(), brand.unwrap_or_default(), ) } } let mut cpus: HashMap = HashMap::new(); let mut lines = data.split('\n').peekable(); while let Some(line) = lines.next() { if is_new_processor(line) { let index = match line .split(':') .nth(1) .and_then(|i| i.trim().parse::().ok()) { Some(index) => index, None => { sysinfo_debug!("Couldn't get processor ID from {line:?}, ignoring this core"); continue; } }; let mut info = CpuInfo { index, ..Default::default() }; #[allow(clippy::while_let_on_iterator)] while let Some(line) = lines.peek() { if cpuinfo_is_key(line, b"vendor_id\t") { info.vendor_id = Some(get_value(line)); } else if cpuinfo_is_key(line, b"model name\t") { info.brand = Some(get_value(line)); } else if cpuinfo_is_key(line, b"CPU implementer\t") { info.implementer = Some(get_hex_value(line)); } else if cpuinfo_is_key(line, b"CPU part\t") { info.part = Some(get_hex_value(line)); } else if info.has_all_info() || is_new_processor(line) { break; } lines.next(); } let (index, vendor_id, brand) = info.convert(); cpus.insert(index, (vendor_id, brand)); } } cpus } #[cfg(test)] mod test { use super::get_vendor_id_and_brand_inner; // The iterator was skipping the `is_new_processor` check because we already moved past // the line where `processor]\t` is located. // // Regression test for . #[test] fn test_cpu_retrieval() { const DATA: &str = r#" processor : 1 cpu model : Loongson-3 V0.4 FPU V0.1 model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz CPU MHz : 1800.00 core : 1 processor : 2 cpu model : Loongson-3 V0.4 FPU V0.1 model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz CPU MHz : 1800.00 package : 0 core : 2 processor : 3 cpu model : Loongson-3 V0.4 FPU V0.1 model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz CPU MHz : 1800.00 core : 3 processor : 4 cpu model : Loongson-3 V0.4 FPU V0.1 model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz CPU MHz : 1800.00 core : 0 processor : 5 cpu model : Loongson-3 V0.4 FPU V0.1 model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz CPU MHz : 1800.00 core : 1 processor : 6 cpu model : Loongson-3 V0.4 FPU V0.1 model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz CPU MHz : 1800.00 core : 2 processor : 7 cpu model : Loongson-3 V0.4 FPU V0.1 model name : Loongson-3A R4 (Loongson-3B4000) @ 1800MHz CPU MHz : 1800.00 core : 3"#; let cpus = get_vendor_id_and_brand_inner(DATA); assert_eq!(cpus.len(), 7); } } GuillaumeGomez-sysinfo-067dd61/src/unix/linux/disk.rs000066400000000000000000000516111506747262600227210ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::{get_all_utf8_data, to_cpath}; use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage}; use libc::statvfs; use std::collections::HashMap; use std::ffi::{OsStr, OsString}; use std::fs; use std::mem::MaybeUninit; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::str::FromStr; /// Copied from [`psutil`]: /// /// "man iostat" states that sectors are equivalent with blocks and have /// a size of 512 bytes. Despite this value can be queried at runtime /// via /sys/block/{DISK}/queue/hw_sector_size and results may vary /// between 1k, 2k, or 4k... 512 appears to be a magic constant used /// throughout Linux source code: /// * /// * /// * /// * /// * /// /// [`psutil`]: const SECTOR_SIZE: u64 = 512; macro_rules! cast { ($x:expr) => { u64::from($x) }; } pub(crate) struct DiskInner { type_: DiskKind, device_name: OsString, actual_device_name: Option, file_system: OsString, mount_point: PathBuf, total_space: u64, available_space: u64, is_removable: bool, is_read_only: bool, old_written_bytes: u64, old_read_bytes: u64, written_bytes: u64, read_bytes: u64, updated: bool, } impl DiskInner { pub(crate) fn kind(&self) -> DiskKind { self.type_ } pub(crate) fn name(&self) -> &OsStr { &self.device_name } pub(crate) fn file_system(&self) -> &OsStr { &self.file_system } pub(crate) fn mount_point(&self) -> &Path { &self.mount_point } pub(crate) fn total_space(&self) -> u64 { self.total_space } pub(crate) fn available_space(&self) -> u64 { self.available_space } pub(crate) fn is_removable(&self) -> bool { self.is_removable } pub(crate) fn is_read_only(&self) -> bool { self.is_read_only } pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool { self.efficient_refresh(refresh_kind, &disk_stats(&refresh_kind), false) } fn efficient_refresh( &mut self, refresh_kind: DiskRefreshKind, procfs_disk_stats: &HashMap, first: bool, ) -> bool { if refresh_kind.io_usage() { if self.actual_device_name.is_none() { self.actual_device_name = Some(get_actual_device_name(&self.device_name)); } if let Some(stat) = self .actual_device_name .as_ref() .and_then(|actual_device_name| procfs_disk_stats.get(actual_device_name)) { self.old_read_bytes = self.read_bytes; self.old_written_bytes = self.written_bytes; self.read_bytes = stat.sectors_read * SECTOR_SIZE; self.written_bytes = stat.sectors_written * SECTOR_SIZE; } else { sysinfo_debug!("Failed to update disk i/o stats"); } } if refresh_kind.kind() && self.type_ == DiskKind::Unknown(-1) { self.type_ = find_type_for_device_name(&self.device_name); } if refresh_kind.storage() && let Some((total_space, available_space, is_read_only)) = unsafe { load_statvfs_values(&self.mount_point) } { self.total_space = total_space; self.available_space = available_space; if first { self.is_read_only = is_read_only; } } true } pub(crate) fn usage(&self) -> DiskUsage { DiskUsage { read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, } } } impl crate::DisksInner { pub(crate) fn new() -> Self { Self { disks: Vec::with_capacity(2), } } pub(crate) fn refresh_specifics( &mut self, remove_not_listed_disks: bool, refresh_kind: DiskRefreshKind, ) { get_all_list( &mut self.disks, &get_all_utf8_data("/proc/mounts", 16_385).unwrap_or_default(), refresh_kind, ); if remove_not_listed_disks { self.disks.retain_mut(|disk| { if !disk.inner.updated { return false; } disk.inner.updated = false; true }); } else { for c in self.disks.iter_mut() { c.inner.updated = false; } } } pub(crate) fn list(&self) -> &[Disk] { &self.disks } pub(crate) fn list_mut(&mut self) -> &mut [Disk] { &mut self.disks } } /// Resolves the actual device name for a specified `device` from `/proc/mounts` /// /// This function is inspired by the [`bottom`] crate implementation and essentially does the following: /// 1. Canonicalizes the specified device path to its absolute form /// 2. Strips the "/dev" prefix from the canonicalized path /// /// [`bottom`]: fn get_actual_device_name(device: &OsStr) -> String { let device_path = PathBuf::from(device); std::fs::canonicalize(&device_path) .ok() .and_then(|path| path.strip_prefix("/dev").ok().map(Path::to_path_buf)) .unwrap_or(device_path) .to_str() .map(str::to_owned) .unwrap_or_default() } unsafe fn load_statvfs_values(mount_point: &Path) -> Option<(u64, u64, bool)> { let mount_point_cpath = to_cpath(mount_point); let mut stat: MaybeUninit = MaybeUninit::uninit(); if unsafe { retry_eintr!(statvfs( mount_point_cpath.as_ptr() as *const _, stat.as_mut_ptr() )) } == 0 { let stat = unsafe { stat.assume_init() }; let bsize = cast!(stat.f_bsize); let blocks = cast!(stat.f_blocks); let bavail = cast!(stat.f_bavail); let total = bsize.saturating_mul(blocks); if total == 0 { return None; } let available = bsize.saturating_mul(bavail); let is_read_only = (stat.f_flag & libc::ST_RDONLY) != 0; Some((total, available, is_read_only)) } else { None } } fn new_disk( device_name: &OsStr, mount_point: &Path, file_system: &OsStr, removable_entries: &[PathBuf], procfs_disk_stats: &HashMap, refresh_kind: DiskRefreshKind, ) -> Disk { let is_removable = removable_entries .iter() .any(|e| e.as_os_str() == device_name); let mut disk = Disk { inner: DiskInner { type_: DiskKind::Unknown(-1), device_name: device_name.to_owned(), actual_device_name: None, file_system: file_system.to_owned(), mount_point: mount_point.to_owned(), total_space: 0, available_space: 0, is_removable, is_read_only: false, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, updated: true, }, }; disk.inner .efficient_refresh(refresh_kind, procfs_disk_stats, true); disk } #[allow(clippy::manual_range_contains)] fn find_type_for_device_name(device_name: &OsStr) -> DiskKind { // The format of devices are as follows: // - device_name is symbolic link in the case of /dev/mapper/ // and /dev/root, and the target is corresponding device under // /sys/block/ // - In the case of /dev/sd, the format is /dev/sd[a-z][1-9], // corresponding to /sys/block/sd[a-z] // - In the case of /dev/nvme, the format is /dev/nvme[0-9]n[0-9]p[0-9], // corresponding to /sys/block/nvme[0-9]n[0-9] // - In the case of /dev/mmcblk, the format is /dev/mmcblk[0-9]p[0-9], // corresponding to /sys/block/mmcblk[0-9] let device_name_path = device_name.to_str().unwrap_or_default(); let real_path = fs::canonicalize(device_name).unwrap_or_else(|_| PathBuf::from(device_name)); let mut real_path = real_path.to_str().unwrap_or_default(); if device_name_path.starts_with("/dev/mapper/") { // Recursively solve, for example /dev/dm-0 if real_path != device_name_path { return find_type_for_device_name(OsStr::new(&real_path)); } } else if device_name_path.starts_with("/dev/sd") || device_name_path.starts_with("/dev/vd") { // Turn "sda1" into "sda" or "vda1" into "vda" real_path = real_path.trim_start_matches("/dev/"); real_path = real_path.trim_end_matches(|c| c >= '0' && c <= '9'); } else if device_name_path.starts_with("/dev/nvme") { // Turn "nvme0n1p1" into "nvme0n1" real_path = match real_path.find('p') { Some(idx) => &real_path["/dev/".len()..idx], None => &real_path["/dev/".len()..], }; } else if device_name_path.starts_with("/dev/root") { // Recursively solve, for example /dev/mmcblk0p1 if real_path != device_name_path { return find_type_for_device_name(OsStr::new(&real_path)); } } else if device_name_path.starts_with("/dev/mmcblk") { // Turn "mmcblk0p1" into "mmcblk0" real_path = match real_path.find('p') { Some(idx) => &real_path["/dev/".len()..idx], None => &real_path["/dev/".len()..], }; } else { // Default case: remove /dev/ and expects the name presents under /sys/block/ // For example, /dev/dm-0 to dm-0 real_path = real_path.trim_start_matches("/dev/"); } let trimmed: &OsStr = OsStrExt::from_bytes(real_path.as_bytes()); let path = Path::new("/sys/block/") .to_owned() .join(trimmed) .join("queue/rotational"); // Normally, this file only contains '0' or '1' but just in case, we get 8 bytes... match get_all_utf8_data(path, 8) .unwrap_or_default() .trim() .parse() .ok() { // The disk is marked as rotational so it's a HDD. Some(1) => DiskKind::HDD, // The disk is marked as non-rotational so it's very likely a SSD. Some(0) => DiskKind::SSD, // Normally it shouldn't happen but welcome to the wonderful world of IT! :D Some(x) => DiskKind::Unknown(x), // The information isn't available... None => DiskKind::Unknown(-1), } } fn get_all_list(container: &mut Vec, content: &str, refresh_kind: DiskRefreshKind) { // The goal of this array is to list all removable devices (the ones whose name starts with // "usb-"). let removable_entries = match fs::read_dir("/dev/disk/by-id/") { Ok(r) => r .filter_map(|res| Some(res.ok()?.path())) .filter_map(|e| { if e.file_name() .and_then(|x| Some(x.to_str()?.starts_with("usb-"))) .unwrap_or_default() { e.canonicalize().ok() } else { None } }) .collect::>(), _ => Vec::new(), }; let procfs_disk_stats = disk_stats(&refresh_kind); for (fs_spec, fs_file, fs_vfstype) in content .lines() .map(|line| { let line = line.trim_start(); // mounts format // http://man7.org/linux/man-pages/man5/fstab.5.html // fs_specfs_filefs_vfstypeother fields let mut fields = line.split_whitespace(); let fs_spec = fields.next().unwrap_or(""); let fs_file = fields .next() .unwrap_or("") .replace("\\134", "\\") .replace("\\040", " ") .replace("\\011", "\t") .replace("\\012", "\n"); let fs_vfstype = fields.next().unwrap_or(""); (fs_spec, fs_file, fs_vfstype) }) .filter(|(fs_spec, fs_file, fs_vfstype)| { // Check if fs_vfstype is one of our 'ignored' file systems. let filtered = match *fs_vfstype { "rootfs" | // https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt "sysfs" | // pseudo file system for kernel objects "proc" | // another pseudo file system "devtmpfs" | "cgroup" | "cgroup2" | "pstore" | // https://www.kernel.org/doc/Documentation/ABI/testing/pstore "squashfs" | // squashfs is a compressed read-only file system (for snaps) "rpc_pipefs" | // The pipefs pseudo file system service "iso9660" | // optical media "devpts" | // https://www.kernel.org/doc/Documentation/filesystems/devpts.txt "hugetlbfs" | // https://www.kernel.org/doc/Documentation/vm/hugetlbfs_reserv.txt "mqueue" // https://man7.org/linux/man-pages/man7/mq_overview.7.html => true, "tmpfs" => !cfg!(feature = "linux-tmpfs"), // calling statvfs on a mounted CIFS or NFS or through autofs may hang, when they are mounted with option: hard "cifs" | "nfs" | "nfs4" | "autofs" => !cfg!(feature = "linux-netdevs"), _ => false, }; !(filtered || fs_file.starts_with("/sys") || // check if fs_file is an 'ignored' mount point fs_file.starts_with("/proc") || (fs_file.starts_with("/run") && !fs_file.starts_with("/run/media")) || fs_spec.starts_with("sunrpc")) }) { let mount_point = Path::new(&fs_file); if let Some(disk) = container.iter_mut().find(|d| { d.inner.mount_point == mount_point && d.inner.device_name == fs_spec && d.inner.file_system == fs_vfstype }) { disk.inner .efficient_refresh(refresh_kind, &procfs_disk_stats, false); disk.inner.updated = true; continue; } container.push(new_disk( fs_spec.as_ref(), mount_point, fs_vfstype.as_ref(), &removable_entries, &procfs_disk_stats, refresh_kind, )); } } /// Disk IO stat information from `/proc/diskstats` file. /// /// To fully understand these fields, please see the /// [iostats.txt](https://www.kernel.org/doc/Documentation/iostats.txt) kernel documentation. /// /// This type only contains the value `sysinfo` is interested into. /// /// The fields of this file are: /// 1. major number /// 2. minor number /// 3. device name /// 4. reads completed successfully /// 5. reads merged /// 6. sectors read /// 7. time spent reading (ms) /// 8. writes completed /// 9. writes merged /// 10. sectors written /// 11. time spent writing (ms) /// 12. I/Os currently in progress /// 13. time spent doing I/Os (ms) /// 14. weighted time spent doing I/Os (ms) /// /// Doc reference: https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats /// /// Doc reference: https://www.kernel.org/doc/Documentation/iostats.txt #[derive(Debug, PartialEq)] struct DiskStat { sectors_read: u64, sectors_written: u64, } impl DiskStat { /// Returns the name and the values we're interested into. fn new_from_line(line: &str) -> Option<(String, Self)> { let mut iter = line.split_whitespace(); // 3rd field let name = iter.nth(2).map(ToString::to_string)?; // 6th field let sectors_read = iter.nth(2).and_then(|v| u64::from_str(v).ok()).unwrap_or(0); // 10th field let sectors_written = iter.nth(3).and_then(|v| u64::from_str(v).ok()).unwrap_or(0); Some(( name, Self { sectors_read, sectors_written, }, )) } } fn disk_stats(refresh_kind: &DiskRefreshKind) -> HashMap { if refresh_kind.io_usage() { let path = "/proc/diskstats"; match fs::read_to_string(path) { Ok(content) => disk_stats_inner(&content), Err(_error) => { sysinfo_debug!("failed to read {path:?}: {_error:?}"); HashMap::new() } } } else { Default::default() } } // We split this function out to make it possible to test it. fn disk_stats_inner(content: &str) -> HashMap { let mut data = HashMap::new(); for line in content.lines() { let line = line.trim(); if line.is_empty() { continue; } if let Some((name, stats)) = DiskStat::new_from_line(line) { data.insert(name, stats); } } data } #[cfg(test)] mod test { use super::{DiskStat, disk_stats_inner}; use std::collections::HashMap; #[test] fn test_disk_stat_parsing() { // Content of a (very nicely formatted) `/proc/diskstats` file. let file_content = "\ 259 0 nvme0n1 571695 101559 38943220 165643 9824246 1076193 462375378 4140037 0 1038904 4740493 254020 0 1436922320 68519 306875 366293 259 1 nvme0n1p1 240 2360 15468 48 2 0 2 0 0 21 50 8 0 2373552 2 0 0 259 2 nvme0n1p2 243 10 11626 26 63 39 616 125 0 84 163 44 0 1075280 11 0 0 259 3 nvme0n1p3 571069 99189 38910302 165547 9824180 1076154 462374760 4139911 0 1084855 4373964 253968 0 1433473488 68505 0 0 253 0 dm-0 670206 0 38909056 259490 10900330 0 462374760 12906518 0 1177098 13195902 253968 0 1433473488 29894 0 0 252 0 zram0 2382 0 20984 11 260261 0 2082088 2063 0 1964 2074 0 0 0 0 0 0 1 2 bla 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 "; let data = disk_stats_inner(file_content); let expected_data: HashMap = HashMap::from([ ( "nvme0n1".to_string(), DiskStat { sectors_read: 38943220, sectors_written: 462375378, }, ), ( "nvme0n1p1".to_string(), DiskStat { sectors_read: 15468, sectors_written: 2, }, ), ( "nvme0n1p2".to_string(), DiskStat { sectors_read: 11626, sectors_written: 616, }, ), ( "nvme0n1p3".to_string(), DiskStat { sectors_read: 38910302, sectors_written: 462374760, }, ), ( "dm-0".to_string(), DiskStat { sectors_read: 38909056, sectors_written: 462374760, }, ), ( "zram0".to_string(), DiskStat { sectors_read: 20984, sectors_written: 2082088, }, ), // This one ensures that we read the correct fields. ( "bla".to_string(), DiskStat { sectors_read: 6, sectors_written: 10, }, ), ]); assert_eq!(data, expected_data); } #[test] fn disk_entry_with_less_information() { let file_content = "\ systemd-1 /efi autofs rw,relatime,fd=181,pgrp=1,timeout=120,minproto=5,maxproto=5,direct,pipe_ino=8311 0 0 /dev/nvme0n1p1 /efi vfat rw,nosuid,nodev,noexec,relatime,nosymfollow,fmask=0077,dmask=0077 0 0 "; let data = disk_stats_inner(file_content); let expected_data: HashMap = HashMap::from([ ( "autofs".to_string(), DiskStat { sectors_read: 0, sectors_written: 0, }, ), ( "vfat".to_string(), DiskStat { sectors_read: 0, sectors_written: 0, }, ), ]); assert_eq!(data, expected_data); } } GuillaumeGomez-sysinfo-067dd61/src/unix/linux/mod.rs000066400000000000000000000030571506747262600225470ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub(crate) mod utils; cfg_if! { if #[cfg(feature = "system")] { pub mod cpu; pub mod motherboard; pub mod process; pub mod product; pub mod system; pub(crate) use self::cpu::CpuInner; pub(crate) use self::motherboard::MotherboardInner; pub(crate) use self::process::ProcessInner; pub(crate) use self::product::ProductInner; pub(crate) use self::system::SystemInner; pub use self::system::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS}; } if #[cfg(feature = "disk")] { pub mod disk; pub(crate) use self::disk::DiskInner; pub(crate) use crate::unix::DisksInner; } if #[cfg(feature = "component")] { pub mod component; pub(crate) use self::component::{ComponentInner, ComponentsInner}; } if #[cfg(feature = "network")] { pub mod network; pub(crate) use self::network::{NetworkDataInner, NetworksInner}; } if #[cfg(feature = "user")] { pub(crate) use crate::unix::groups::get_groups; pub(crate) use crate::unix::users::{get_users, UserInner}; } } #[doc = include_str!("../../../md_doc/is_supported.md")] pub const IS_SUPPORTED_SYSTEM: bool = true; // Make formattable by rustfmt. #[cfg(any())] mod component; #[cfg(any())] mod cpu; #[cfg(any())] mod disk; #[cfg(any())] mod motherboard; #[cfg(any())] mod network; #[cfg(any())] mod process; #[cfg(any())] mod product; #[cfg(any())] mod system; GuillaumeGomez-sysinfo-067dd61/src/unix/linux/motherboard.rs000066400000000000000000000041711506747262600242740ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::fs::{read, read_to_string}; pub(crate) struct MotherboardInner; impl MotherboardInner { pub(crate) fn new() -> Option { Some(Self) } pub(crate) fn asset_tag(&self) -> Option { read_to_string("/sys/devices/virtual/dmi/id/board_asset_tag") .ok() .map(|s| s.trim().to_owned()) } pub(crate) fn name(&self) -> Option { read_to_string("/sys/devices/virtual/dmi/id/board_name") .ok() .or_else(|| { read_to_string("/proc/device-tree/board") .ok() .or_else(|| Some(parse_device_tree_compatible()?.1)) }) .map(|s| s.trim().to_owned()) } pub(crate) fn vendor_name(&self) -> Option { read_to_string("/sys/devices/virtual/dmi/id/board_vendor") .ok() .or_else(|| Some(parse_device_tree_compatible()?.0)) .map(|s| s.trim().to_owned()) } pub(crate) fn version(&self) -> Option { read_to_string("/sys/devices/virtual/dmi/id/board_version") .ok() .map(|s| s.trim().to_owned()) } pub(crate) fn serial_number(&self) -> Option { read_to_string("/sys/devices/virtual/dmi/id/board_serial") .ok() .map(|s| s.trim().to_owned()) } } // Parses the first entry of the file `/proc/device-tree/compatible`, to extract the vendor and // motherboard name. This file contains several `\0` separated strings; the first one include the // vendor and the motherboard name, separated by a comma. // // According to the specification: https://github.com/devicetree-org/devicetree-specification // a compatible string must contain only one comma. fn parse_device_tree_compatible() -> Option<(String, String)> { let bytes = read("/proc/device-tree/compatible").ok()?; let first_line = bytes.split(|&b| b == 0).next()?; std::str::from_utf8(first_line) .ok()? .split_once(',') .map(|(a, b)| (a.to_owned(), b.to_owned())) } GuillaumeGomez-sysinfo-067dd61/src/unix/linux/network.rs000066400000000000000000000241461506747262600234630ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::{HashMap, hash_map}; use std::fs::File; use std::io::Read; use std::path::Path; use crate::network::refresh_networks_addresses; use crate::{IpNetwork, MacAddr, NetworkData}; macro_rules! old_and_new { ($ty_:expr, $name:ident, $old:ident) => {{ $ty_.$old = $ty_.$name; $ty_.$name = $name; }}; ($ty_:expr, $name:ident, $old:ident, $path:expr) => {{ let _tmp = $path; $ty_.$old = $ty_.$name; $ty_.$name = _tmp; }}; } #[allow(clippy::ptr_arg)] fn read>(parent: P, path: &str, data: &mut Vec) -> u64 { if let Ok(mut f) = File::open(parent.as_ref().join(path)) && let Ok(size) = f.read(data) { let mut i = 0; let mut ret = 0; while i < size && i < data.len() && data[i] >= b'0' && data[i] <= b'9' { ret *= 10; ret += (data[i] - b'0') as u64; i += 1; } return ret; } 0 } fn refresh_networks_list_from_sysfs( interfaces: &mut HashMap, remove_not_listed_interfaces: bool, sysfs_net: &Path, ) { if let Ok(dir) = std::fs::read_dir(sysfs_net) { let mut data = vec![0; 30]; for stats in interfaces.values_mut() { stats.inner.updated = false; } for entry in dir.flatten() { let parent = &entry.path().join("statistics"); let entry_path = &entry.path(); let entry = match entry.file_name().into_string() { Ok(entry) => entry, Err(_) => continue, }; let rx_bytes = read(parent, "rx_bytes", &mut data); let tx_bytes = read(parent, "tx_bytes", &mut data); let rx_packets = read(parent, "rx_packets", &mut data); let tx_packets = read(parent, "tx_packets", &mut data); let rx_errors = read(parent, "rx_errors", &mut data); let tx_errors = read(parent, "tx_errors", &mut data); // let rx_compressed = read(parent, "rx_compressed", &mut data); // let tx_compressed = read(parent, "tx_compressed", &mut data); let mtu = read(entry_path, "mtu", &mut data); match interfaces.entry(entry) { hash_map::Entry::Occupied(mut e) => { let interface = e.get_mut(); let interface = &mut interface.inner; old_and_new!(interface, rx_bytes, old_rx_bytes); old_and_new!(interface, tx_bytes, old_tx_bytes); old_and_new!(interface, rx_packets, old_rx_packets); old_and_new!(interface, tx_packets, old_tx_packets); old_and_new!(interface, rx_errors, old_rx_errors); old_and_new!(interface, tx_errors, old_tx_errors); // old_and_new!(e, rx_compressed, old_rx_compressed); // old_and_new!(e, tx_compressed, old_tx_compressed); if interface.mtu != mtu { interface.mtu = mtu; } interface.updated = true; } hash_map::Entry::Vacant(e) => { e.insert(NetworkData { inner: NetworkDataInner { rx_bytes, old_rx_bytes: rx_bytes, tx_bytes, old_tx_bytes: tx_bytes, rx_packets, old_rx_packets: rx_packets, tx_packets, old_tx_packets: tx_packets, rx_errors, old_rx_errors: rx_errors, tx_errors, old_tx_errors: tx_errors, mac_addr: MacAddr::UNSPECIFIED, ip_networks: vec![], // rx_compressed, // old_rx_compressed: rx_compressed, // tx_compressed, // old_tx_compressed: tx_compressed, mtu, updated: true, }, }); } }; } } // We do this here because `refresh_networks_list_remove_interface` test is checking that // this is working as expected. if remove_not_listed_interfaces { // Remove interfaces which are gone. interfaces.retain(|_, i| { if !i.inner.updated { return false; } i.inner.updated = false; true }); } } pub(crate) struct NetworksInner { pub(crate) interfaces: HashMap, } impl NetworksInner { pub(crate) fn new() -> Self { Self { interfaces: HashMap::new(), } } pub(crate) fn list(&self) -> &HashMap { &self.interfaces } pub(crate) fn refresh(&mut self, remove_not_listed_interfaces: bool) { refresh_networks_list_from_sysfs( &mut self.interfaces, remove_not_listed_interfaces, Path::new("/sys/class/net/"), ); refresh_networks_addresses(&mut self.interfaces); } } pub(crate) struct NetworkDataInner { /// Total number of bytes received over interface. rx_bytes: u64, old_rx_bytes: u64, /// Total number of bytes transmitted over interface. tx_bytes: u64, old_tx_bytes: u64, /// Total number of packets received. rx_packets: u64, old_rx_packets: u64, /// Total number of packets transmitted. tx_packets: u64, old_tx_packets: u64, /// Shows the total number of packets received with error. This includes /// too-long-frames errors, ring-buffer overflow errors, CRC errors, /// frame alignment errors, fifo overruns, and missed packets. rx_errors: u64, old_rx_errors: u64, /// similar to `rx_errors` tx_errors: u64, old_tx_errors: u64, /// MAC address pub(crate) mac_addr: MacAddr, pub(crate) ip_networks: Vec, /// Interface Maximum Transfer Unit (MTU) mtu: u64, // /// Indicates the number of compressed packets received by this // /// network device. This value might only be relevant for interfaces // /// that support packet compression (e.g: PPP). // rx_compressed: usize, // old_rx_compressed: usize, // /// Indicates the number of transmitted compressed packets. Note // /// this might only be relevant for devices that support // /// compression (e.g: PPP). // tx_compressed: usize, // old_tx_compressed: usize, /// Whether or not the above data has been updated during refresh updated: bool, } impl NetworkDataInner { pub(crate) fn received(&self) -> u64 { self.rx_bytes.saturating_sub(self.old_rx_bytes) } pub(crate) fn total_received(&self) -> u64 { self.rx_bytes } pub(crate) fn transmitted(&self) -> u64 { self.tx_bytes.saturating_sub(self.old_tx_bytes) } pub(crate) fn total_transmitted(&self) -> u64 { self.tx_bytes } pub(crate) fn packets_received(&self) -> u64 { self.rx_packets.saturating_sub(self.old_rx_packets) } pub(crate) fn total_packets_received(&self) -> u64 { self.rx_packets } pub(crate) fn packets_transmitted(&self) -> u64 { self.tx_packets.saturating_sub(self.old_tx_packets) } pub(crate) fn total_packets_transmitted(&self) -> u64 { self.tx_packets } pub(crate) fn errors_on_received(&self) -> u64 { self.rx_errors.saturating_sub(self.old_rx_errors) } pub(crate) fn total_errors_on_received(&self) -> u64 { self.rx_errors } pub(crate) fn errors_on_transmitted(&self) -> u64 { self.tx_errors.saturating_sub(self.old_tx_errors) } pub(crate) fn total_errors_on_transmitted(&self) -> u64 { self.tx_errors } pub(crate) fn mac_address(&self) -> MacAddr { self.mac_addr } pub(crate) fn ip_networks(&self) -> &[IpNetwork] { &self.ip_networks } pub(crate) fn mtu(&self) -> u64 { self.mtu } } #[cfg(test)] mod test { use super::refresh_networks_list_from_sysfs; use std::collections::HashMap; use std::fs; #[test] fn refresh_networks_list_add_interface() { let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory"); fs::create_dir(sys_net_dir.path().join("itf1")).expect("failed to create subdirectory"); let mut interfaces = HashMap::new(); refresh_networks_list_from_sysfs(&mut interfaces, false, sys_net_dir.path()); assert_eq!(interfaces.keys().collect::>(), ["itf1"]); fs::create_dir(sys_net_dir.path().join("itf2")).expect("failed to create subdirectory"); refresh_networks_list_from_sysfs(&mut interfaces, false, sys_net_dir.path()); let mut itf_names: Vec = interfaces.keys().map(|n| n.to_owned()).collect(); itf_names.sort(); assert_eq!(itf_names, ["itf1", "itf2"]); } #[test] fn refresh_networks_list_remove_interface() { let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory"); let itf1_dir = sys_net_dir.path().join("itf1"); let itf2_dir = sys_net_dir.path().join("itf2"); fs::create_dir(&itf1_dir).expect("failed to create subdirectory"); fs::create_dir(itf2_dir).expect("failed to create subdirectory"); let mut interfaces = HashMap::new(); refresh_networks_list_from_sysfs(&mut interfaces, false, sys_net_dir.path()); let mut itf_names: Vec = interfaces.keys().map(|n| n.to_owned()).collect(); itf_names.sort(); assert_eq!(itf_names, ["itf1", "itf2"]); fs::remove_dir(&itf1_dir).expect("failed to remove subdirectory"); refresh_networks_list_from_sysfs(&mut interfaces, true, sys_net_dir.path()); assert_eq!(interfaces.keys().collect::>(), ["itf2"]); } } GuillaumeGomez-sysinfo-067dd61/src/unix/linux/process.rs000066400000000000000000001007711506747262600234470ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::cell::UnsafeCell; use std::collections::{HashMap, HashSet}; use std::ffi::{OsStr, OsString}; use std::fmt; use std::fs::{self, DirEntry, File, read_dir}; use std::io::Read; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::process::ExitStatus; use std::str::{self, FromStr}; use std::sync::atomic::{AtomicUsize, Ordering}; use libc::{c_ulong, gid_t, uid_t}; use crate::sys::system::SystemInfo; use crate::sys::utils::{ PathHandler, PathPush, get_all_data_from_file, get_all_utf8_data, realpath, }; use crate::{ DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, ProcessesToUpdate, Signal, ThreadKind, Uid, }; use crate::sys::system::remaining_files; #[doc(hidden)] impl From for ProcessStatus { fn from(status: char) -> ProcessStatus { match status { 'R' => ProcessStatus::Run, 'S' => ProcessStatus::Sleep, 'I' => ProcessStatus::Idle, 'D' => ProcessStatus::UninterruptibleDiskSleep, 'Z' => ProcessStatus::Zombie, 'T' => ProcessStatus::Stop, 't' => ProcessStatus::Tracing, 'X' | 'x' => ProcessStatus::Dead, 'K' => ProcessStatus::Wakekill, 'W' => ProcessStatus::Waking, 'P' => ProcessStatus::Parked, x => ProcessStatus::Unknown(x as u32), } } } impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { ProcessStatus::Idle => "Idle", ProcessStatus::Run => "Runnable", ProcessStatus::Sleep => "Sleeping", ProcessStatus::Stop => "Stopped", ProcessStatus::Zombie => "Zombie", ProcessStatus::Tracing => "Tracing", ProcessStatus::Dead => "Dead", ProcessStatus::Wakekill => "Wakekill", ProcessStatus::Waking => "Waking", ProcessStatus::Parked => "Parked", ProcessStatus::UninterruptibleDiskSleep => "UninterruptibleDiskSleep", _ => "Unknown", }) } } #[allow(dead_code)] #[repr(usize)] enum ProcIndex { Pid = 0, State, ParentPid, GroupId, SessionId, Tty, ForegroundProcessGroupId, Flags, MinorFaults, ChildrenMinorFaults, MajorFaults, ChildrenMajorFaults, UserTime, SystemTime, ChildrenUserTime, ChildrenKernelTime, Priority, Nice, NumberOfThreads, IntervalTimerSigalarm, StartTime, VirtualSize, ResidentSetSize, // More exist but we only use the listed ones. For more, take a look at `man proc`. } pub(crate) struct ProcessInner { pub(crate) name: OsString, pub(crate) cmd: Vec, pub(crate) exe: Option, pub(crate) pid: Pid, parent: Option, pub(crate) environ: Vec, pub(crate) cwd: Option, pub(crate) root: Option, pub(crate) memory: u64, pub(crate) virtual_memory: u64, utime: u64, stime: u64, old_utime: u64, old_stime: u64, start_time_without_boot_time: u64, start_time: u64, start_time_raw: u64, run_time: u64, pub(crate) updated: bool, cpu_usage: f32, user_id: Option, effective_user_id: Option, group_id: Option, effective_group_id: Option, pub(crate) status: ProcessStatus, pub(crate) tasks: Option>, stat_file: Option, old_read_bytes: u64, old_written_bytes: u64, read_bytes: u64, written_bytes: u64, thread_kind: Option, proc_path: PathBuf, accumulated_cpu_time: u64, exists: bool, } impl ProcessInner { pub(crate) fn new(pid: Pid, proc_path: PathBuf) -> Self { Self { name: OsString::new(), pid, parent: None, cmd: Vec::new(), environ: Vec::new(), exe: None, cwd: None, root: None, memory: 0, virtual_memory: 0, cpu_usage: 0., utime: 0, stime: 0, old_utime: 0, old_stime: 0, updated: true, start_time_without_boot_time: 0, start_time: 0, start_time_raw: 0, run_time: 0, user_id: None, effective_user_id: None, group_id: None, effective_group_id: None, status: ProcessStatus::Unknown(0), tasks: None, stat_file: None, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, thread_kind: None, proc_path, accumulated_cpu_time: 0, exists: true, } } pub(crate) fn kill_with(&self, signal: Signal) -> Option { let c_signal = crate::sys::system::convert_signal(signal)?; unsafe { Some(libc::kill(self.pid.0, c_signal) == 0) } } pub(crate) fn name(&self) -> &OsStr { &self.name } pub(crate) fn cmd(&self) -> &[OsString] { &self.cmd } pub(crate) fn exe(&self) -> Option<&Path> { self.exe.as_deref() } pub(crate) fn pid(&self) -> Pid { self.pid } pub(crate) fn environ(&self) -> &[OsString] { &self.environ } pub(crate) fn cwd(&self) -> Option<&Path> { self.cwd.as_deref() } pub(crate) fn root(&self) -> Option<&Path> { self.root.as_deref() } pub(crate) fn memory(&self) -> u64 { self.memory } pub(crate) fn virtual_memory(&self) -> u64 { self.virtual_memory } pub(crate) fn parent(&self) -> Option { self.parent } pub(crate) fn status(&self) -> ProcessStatus { self.status } pub(crate) fn start_time(&self) -> u64 { self.start_time } pub(crate) fn run_time(&self) -> u64 { self.run_time } pub(crate) fn cpu_usage(&self) -> f32 { self.cpu_usage } pub(crate) fn accumulated_cpu_time(&self) -> u64 { self.accumulated_cpu_time } pub(crate) fn disk_usage(&self) -> DiskUsage { DiskUsage { written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, } } pub(crate) fn user_id(&self) -> Option<&Uid> { self.user_id.as_ref() } pub(crate) fn effective_user_id(&self) -> Option<&Uid> { self.effective_user_id.as_ref() } pub(crate) fn group_id(&self) -> Option { self.group_id } pub(crate) fn effective_group_id(&self) -> Option { self.effective_group_id } pub(crate) fn wait(&self) -> Option { // If anything fails when trying to retrieve the start time, better to return `None`. let (data, _) = _get_stat_data_and_file(&self.proc_path).ok()?; let parts = parse_stat_file(&data)?; if start_time_raw(&parts) != self.start_time_raw { sysinfo_debug!("Seems to not be the same process anymore"); return None; } crate::unix::utils::wait_process(self.pid) } pub(crate) fn session_id(&self) -> Option { unsafe { let session_id = libc::getsid(self.pid.0); if session_id < 0 { None } else { Some(Pid(session_id)) } } } pub(crate) fn thread_kind(&self) -> Option { self.thread_kind } pub(crate) fn switch_updated(&mut self) -> bool { std::mem::replace(&mut self.updated, false) } pub(crate) fn set_nonexistent(&mut self) { self.exists = false; } pub(crate) fn exists(&self) -> bool { self.exists } pub(crate) fn open_files(&self) -> Option { let open_files_dir = self.proc_path.as_path().join("fd"); match fs::read_dir(&open_files_dir) { Ok(entries) => Some(entries.count() as _), Err(_error) => { sysinfo_debug!( "Failed to get open files in `{}`: {_error:?}", open_files_dir.display(), ); None } } } pub(crate) fn open_files_limit(&self) -> Option { let limits_files = self.proc_path.as_path().join("limits"); match fs::read_to_string(&limits_files) { Ok(content) => { for line in content.lines() { if let Some(line) = line.strip_prefix("Max open files ") && let Some(nb) = line.split_whitespace().find(|p| !p.is_empty()) { return usize::from_str(nb).ok(); } } None } Err(_error) => { sysinfo_debug!( "Failed to get limits in `{}`: {_error:?}", limits_files.display() ); None } } } } pub(crate) fn compute_cpu_usage(p: &mut ProcessInner, total_time: f32, max_value: f32) { // First time updating the values without reference, wait for a second cycle to update cpu_usage if p.old_utime == 0 && p.old_stime == 0 { return; } // We use `max_value` to ensure that the process CPU usage will never get bigger than: // `"number of CPUs" * 100.` p.cpu_usage = (p .utime .saturating_sub(p.old_utime) .saturating_add(p.stime.saturating_sub(p.old_stime)) as f32 / total_time * 100.) .min(max_value); } pub(crate) fn set_time(p: &mut ProcessInner, utime: u64, stime: u64) { p.old_utime = p.utime; p.old_stime = p.stime; p.utime = utime; p.stime = stime; } pub(crate) fn update_process_disk_activity(p: &mut ProcessInner, path: &mut PathHandler) { let data = match get_all_utf8_data(path.replace_and_join("io"), 16_384) { Ok(d) => d, Err(_) => return, }; let mut done = 0; for line in data.split('\n') { let mut parts = line.split(": "); match parts.next() { Some("read_bytes") => { p.old_read_bytes = p.read_bytes; p.read_bytes = parts .next() .and_then(|x| x.parse::().ok()) .unwrap_or(p.old_read_bytes); } Some("write_bytes") => { p.old_written_bytes = p.written_bytes; p.written_bytes = parts .next() .and_then(|x| x.parse::().ok()) .unwrap_or(p.old_written_bytes); } _ => continue, } done += 1; if done > 1 { // No need to continue the reading. break; } } } struct Wrap<'a, T>(UnsafeCell<&'a mut T>); impl<'a, T> Wrap<'a, T> { fn get(&self) -> &'a mut T { unsafe { *(self.0.get()) } } } #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for Wrap<'_, T> {} unsafe impl Sync for Wrap<'_, T> {} #[inline(always)] fn start_time_raw(parts: &Parts<'_>) -> u64 { u64::from_str(parts.str_parts[ProcIndex::StartTime as usize]).unwrap_or(0) } #[inline(always)] fn compute_start_time_without_boot_time(parts: &Parts<'_>, info: &SystemInfo) -> (u64, u64) { let raw = start_time_raw(parts); // To be noted that the start time is invalid here, it still needs to be converted into // "real" time. (raw, raw / info.clock_cycle) } fn _get_stat_data_and_file(path: &Path) -> Result<(Vec, File), ()> { let mut file = File::open(path.join("stat")).map_err(|_| ())?; let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; Ok((data, file)) } fn _get_stat_data(path: &Path, stat_file: &mut Option) -> Result, ()> { let (data, file) = _get_stat_data_and_file(path)?; *stat_file = FileCounter::new(file); Ok(data) } #[inline(always)] fn get_status(p: &mut ProcessInner, part: &str) { p.status = part .chars() .next() .map(ProcessStatus::from) .unwrap_or_else(|| ProcessStatus::Unknown(0)); } fn refresh_user_group_ids( p: &mut ProcessInner, path: &mut PathHandler, refresh_kind: ProcessRefreshKind, ) { if !refresh_kind.user().needs_update(|| p.user_id.is_none()) { return; } if let Some(((user_id, effective_user_id), (group_id, effective_group_id))) = get_uid_and_gid(path.replace_and_join("status")) { p.user_id = Some(Uid(user_id)); p.effective_user_id = Some(Uid(effective_user_id)); p.group_id = Some(Gid(group_id)); p.effective_group_id = Some(Gid(effective_group_id)); } } #[allow(clippy::too_many_arguments)] fn update_proc_info( p: &mut ProcessInner, parent_pid: Option, refresh_kind: ProcessRefreshKind, proc_path: &mut PathHandler, str_parts: &[&str], uptime: u64, info: &SystemInfo, ) { update_parent_pid(p, parent_pid, str_parts); get_status(p, str_parts[ProcIndex::State as usize]); refresh_user_group_ids(p, proc_path, refresh_kind); if refresh_kind.exe().needs_update(|| p.exe.is_none()) { // Do not use cmd[0] because it is not the same thing. // See https://github.com/GuillaumeGomez/sysinfo/issues/697. p.exe = realpath(proc_path.replace_and_join("exe")); // If the target executable file was modified or removed, linux appends ` (deleted)` at // the end. We need to remove it. // See https://github.com/GuillaumeGomez/sysinfo/issues/1585. let deleted = b" (deleted)"; if let Some(exe) = &mut p.exe && let Some(file_name) = exe.file_name() && file_name.as_encoded_bytes().ends_with(deleted) { let mut file_name = file_name.as_encoded_bytes().to_vec(); file_name.truncate(file_name.len() - deleted.len()); unsafe { exe.set_file_name(OsString::from_encoded_bytes_unchecked(file_name)); } } } if refresh_kind.cmd().needs_update(|| p.cmd.is_empty()) { p.cmd = copy_from_file(proc_path.replace_and_join("cmdline")); } if refresh_kind.environ().needs_update(|| p.environ.is_empty()) { p.environ = copy_from_file(proc_path.replace_and_join("environ")); } if refresh_kind.cwd().needs_update(|| p.cwd.is_none()) { p.cwd = realpath(proc_path.replace_and_join("cwd")); } if refresh_kind.root().needs_update(|| p.root.is_none()) { p.root = realpath(proc_path.replace_and_join("root")); } update_time_and_memory(proc_path, p, str_parts, uptime, info, refresh_kind); if refresh_kind.disk_usage() { update_process_disk_activity(p, proc_path); } // Needs to be after `update_time_and_memory`. if refresh_kind.cpu() { // The external values for CPU times are in "ticks", which are // scaled by "HZ", which is pegged externally at 100 ticks/second. p.accumulated_cpu_time = p.utime.saturating_add(p.stime).saturating_mul(1_000) / info.clock_cycle; } p.updated = true; } fn update_parent_pid(p: &mut ProcessInner, parent_pid: Option, str_parts: &[&str]) { p.parent = match parent_pid { Some(parent_pid) if parent_pid.0 != 0 => Some(parent_pid), _ => match Pid::from_str(str_parts[ProcIndex::ParentPid as usize]) { Ok(p) if p.0 != 0 => Some(p), _ => None, }, }; } fn retrieve_all_new_process_info( pid: Pid, parent_pid: Option, parts: &Parts<'_>, path: &Path, info: &SystemInfo, refresh_kind: ProcessRefreshKind, uptime: u64, ) -> Process { let mut p = ProcessInner::new(pid, path.to_owned()); let mut proc_path = PathHandler::new(path); let name = parts.short_exe; let (start_time_raw, start_time_without_boot_time) = compute_start_time_without_boot_time(parts, info); p.start_time_raw = start_time_raw; p.start_time_without_boot_time = start_time_without_boot_time; p.start_time = p .start_time_without_boot_time .saturating_add(info.boot_time); p.name = OsStr::from_bytes(name).to_os_string(); if c_ulong::from_str(parts.str_parts[ProcIndex::Flags as usize]) .map(|flags| flags & libc::PF_KTHREAD as c_ulong != 0) .unwrap_or(false) { p.thread_kind = Some(ThreadKind::Kernel); } else if parent_pid.is_some() { p.thread_kind = Some(ThreadKind::Userland); } update_proc_info( &mut p, parent_pid, refresh_kind, &mut proc_path, &parts.str_parts, uptime, info, ); Process { inner: p } } fn update_existing_process( proc: &mut Process, parent_pid: Option, uptime: u64, info: &SystemInfo, refresh_kind: ProcessRefreshKind, tasks: Option>, ) -> Result, ()> { let entry = &mut proc.inner; let data = if let Some(mut f) = entry.stat_file.take() { match get_all_data_from_file(&mut f, 1024) { Ok(data) => { // Everything went fine, we put back the file descriptor. entry.stat_file = Some(f); data } Err(_) => { // It's possible that the file descriptor is no longer valid in case the // original process was terminated and another one took its place. _get_stat_data(&entry.proc_path, &mut entry.stat_file)? } } } else { _get_stat_data(&entry.proc_path, &mut entry.stat_file)? }; entry.tasks = tasks; let parts = parse_stat_file(&data).ok_or(())?; let start_time_raw = start_time_raw(&parts); // It's possible that a new process took this same PID when the "original one" terminated. // If the start time differs, then it means it's not the same process anymore and that we // need to get all its information, hence why we check it here. if start_time_raw == entry.start_time_raw { let mut proc_path = PathHandler::new(&entry.proc_path); update_proc_info( entry, parent_pid, refresh_kind, &mut proc_path, &parts.str_parts, uptime, info, ); refresh_user_group_ids(entry, &mut proc_path, refresh_kind); return Ok(None); } // If we're here, it means that the PID still exists but it's a different process. let p = retrieve_all_new_process_info( entry.pid, parent_pid, &parts, &entry.proc_path, info, refresh_kind, uptime, ); *proc = p; // Since this PID is already in the HashMap, no need to add it again. Ok(None) } #[allow(clippy::too_many_arguments)] pub(crate) fn _get_process_data( path: &Path, proc_list: &mut HashMap, pid: Pid, parent_pid: Option, uptime: u64, info: &SystemInfo, refresh_kind: ProcessRefreshKind, tasks: Option>, ) -> Result, ()> { if let Some(ref mut entry) = proc_list.get_mut(&pid) { return update_existing_process(entry, parent_pid, uptime, info, refresh_kind, tasks); } let mut stat_file = None; let data = _get_stat_data(path, &mut stat_file)?; let parts = parse_stat_file(&data).ok_or(())?; let mut new_process = retrieve_all_new_process_info(pid, parent_pid, &parts, path, info, refresh_kind, uptime); new_process.inner.stat_file = stat_file; new_process.inner.tasks = tasks; Ok(Some(new_process)) } fn old_get_memory(entry: &mut ProcessInner, str_parts: &[&str], info: &SystemInfo) { // rss entry.memory = u64::from_str(str_parts[ProcIndex::ResidentSetSize as usize]) .unwrap_or(0) .saturating_mul(info.page_size_b); // vsz correspond to the Virtual memory size in bytes. // see: https://man7.org/linux/man-pages/man5/proc.5.html entry.virtual_memory = u64::from_str(str_parts[ProcIndex::VirtualSize as usize]).unwrap_or(0); } fn slice_to_nb(s: &[u8]) -> u64 { let mut nb: u64 = 0; for c in s { nb = nb * 10 + (c - b'0') as u64; } nb } fn get_memory(path: &Path, entry: &mut ProcessInner, info: &SystemInfo) -> bool { let mut file = match File::open(path) { Ok(f) => f, Err(_e) => { sysinfo_debug!( "Using old memory information (failed to open {:?}: {_e:?})", path ); return false; } }; let mut buf = Vec::new(); if let Err(_e) = file.read_to_end(&mut buf) { sysinfo_debug!( "Using old memory information (failed to read {:?}: {_e:?})", path ); return false; } let mut parts = buf.split(|c| *c == b' '); entry.virtual_memory = parts .next() .map(slice_to_nb) .unwrap_or(0) .saturating_mul(info.page_size_b); entry.memory = parts .next() .map(slice_to_nb) .unwrap_or(0) .saturating_mul(info.page_size_b); true } #[allow(clippy::too_many_arguments)] fn update_time_and_memory( path: &mut PathHandler, entry: &mut ProcessInner, str_parts: &[&str], uptime: u64, info: &SystemInfo, refresh_kind: ProcessRefreshKind, ) { { #[allow(clippy::collapsible_if)] if refresh_kind.memory() { // Keeping this nested level for readability reasons. if !get_memory(path.replace_and_join("statm"), entry, info) { old_get_memory(entry, str_parts, info); } } set_time( entry, u64::from_str(str_parts[ProcIndex::UserTime as usize]).unwrap_or(0), u64::from_str(str_parts[ProcIndex::SystemTime as usize]).unwrap_or(0), ); entry.run_time = uptime.saturating_sub(entry.start_time_without_boot_time); } } struct ProcAndTasks { pid: Pid, parent_pid: Option, path: PathBuf, tasks: Option>, } #[cfg(feature = "multithread")] #[inline] pub(crate) fn iter(val: T) -> rayon::iter::IterBridge where T: rayon::iter::ParallelBridge, { val.par_bridge() } #[cfg(not(feature = "multithread"))] #[inline] pub(crate) fn iter(val: T) -> T where T: Iterator, { val } /// We're forced to read the whole `/proc` folder because if a process died and another took its /// place, we need to get the task parent (if it's a task). pub(crate) fn refresh_procs( proc_list: &mut HashMap, proc_path: &Path, uptime: u64, info: &SystemInfo, processes_to_update: ProcessesToUpdate<'_>, refresh_kind: ProcessRefreshKind, ) -> usize { #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; let nb_updated = AtomicUsize::new(0); // This code goes through processes (listed in `/proc`) and through tasks (listed in // `/proc/[PID]/task`). However, the stored tasks information is supposed to be already present // in the PIDs listed from `/proc` so there will be no duplicates between PIDs and tasks PID. // // If a task is not listed in `/proc`, then we don't retrieve its information. // // So in short: since we update the `HashMap` itself by adding/removing entries outside of the // parallel iterator, we can safely use it inside the parallel iterator and update its entries // concurrently. let procs = { let pid_iter: Box + Send> = match processes_to_update { ProcessesToUpdate::All => match read_dir(proc_path) { Ok(proc_entries) => Box::new(proc_entries.filter_map(filter_pid_entries)), Err(_err) => { sysinfo_debug!("Failed to read folder {proc_path:?}: {_err:?}"); return 0; } }, ProcessesToUpdate::Some(pids) => Box::new( pids.iter() .map(|pid| (proc_path.join(pid.to_string()), *pid)), ), }; let proc_list = Wrap(UnsafeCell::new(proc_list)); iter(pid_iter) .flat_map(|(path, pid)| { get_proc_and_tasks(path, pid, refresh_kind, processes_to_update) }) .filter_map(|e| { let proc_list = proc_list.get(); let new_process = _get_process_data( e.path.as_path(), proc_list, e.pid, e.parent_pid, uptime, info, refresh_kind, e.tasks, ) .ok()?; nb_updated.fetch_add(1, Ordering::Relaxed); new_process }) .collect::>() }; for proc_ in procs { proc_list.insert(proc_.pid(), proc_); } nb_updated.into_inner() } fn filter_pid_entries(entry: Result) -> Option<(PathBuf, Pid)> { if let Ok(entry) = entry && let Ok(file_type) = entry.file_type() && file_type.is_dir() && let Some(name) = entry.file_name().to_str() && let Ok(pid) = usize::from_str(name) { Some((entry.path(), Pid::from(pid))) } else { None } } fn get_proc_and_tasks( path: PathBuf, pid: Pid, refresh_kind: ProcessRefreshKind, processes_to_update: ProcessesToUpdate<'_>, ) -> Vec { let mut parent_pid = None; let (mut procs, mut tasks) = if refresh_kind.tasks() { let procs = get_proc_tasks(&path, pid); let tasks = procs.iter().map(|ProcAndTasks { pid, .. }| *pid).collect(); (procs, Some(tasks)) } else { (Vec::new(), None) }; if processes_to_update != ProcessesToUpdate::All { // If the process' tgid doesn't match its pid, it is a task if let Some(tgid) = get_tgid(&path.join("status")) && tgid != pid { parent_pid = Some(tgid); tasks = None; } // Don't add the tasks to the list of processes to update procs.clear(); } procs.push(ProcAndTasks { pid, parent_pid, path, tasks, }); procs } fn get_proc_tasks(path: &Path, parent_pid: Pid) -> Vec { let task_path = path.join("task"); read_dir(task_path) .ok() .map(|task_entries| { task_entries .filter_map(filter_pid_entries) // Needed because tasks have their own PID listed in the "task" folder. .filter(|(_, pid)| *pid != parent_pid) .map(|(path, pid)| ProcAndTasks { pid, path, parent_pid: Some(parent_pid), tasks: None, }) .collect() }) .unwrap_or_default() } fn split_content(mut data: &[u8]) -> Vec { let mut out = Vec::with_capacity(10); while let Some(pos) = data.iter().position(|c| *c == 0) { let s = &data[..pos].trim_ascii(); if !s.is_empty() { out.push(OsStr::from_bytes(s).to_os_string()); } data = &data[pos + 1..]; } if !data.is_empty() { let s = data.trim_ascii(); if !s.is_empty() { out.push(OsStr::from_bytes(s).to_os_string()); } } out } fn copy_from_file(entry: &Path) -> Vec { match File::open(entry) { Ok(mut f) => { let mut data = Vec::with_capacity(16_384); if let Err(_e) = f.read_to_end(&mut data) { sysinfo_debug!("Failed to read file in `copy_from_file`: {:?}", _e); Vec::new() } else { split_content(&data) } } Err(_e) => { sysinfo_debug!("Failed to open file in `copy_from_file`: {:?}", _e); Vec::new() } } } // Fetch tuples of real and effective UID and GID. fn get_uid_and_gid(file_path: &Path) -> Option<((uid_t, uid_t), (gid_t, gid_t))> { let status_data = get_all_utf8_data(file_path, 16_385).ok()?; // We're only interested in the lines starting with Uid: and Gid: // here. From these lines, we're looking at the first and second entries to get // the real u/gid. let f = |h: &str, n: &str| -> (Option, Option) { if h.starts_with(n) { let mut ids = h.split_whitespace(); let real = ids.nth(1).unwrap_or("0").parse().ok(); let effective = ids.next().unwrap_or("0").parse().ok(); (real, effective) } else { (None, None) } }; let mut uid = None; let mut effective_uid = None; let mut gid = None; let mut effective_gid = None; for line in status_data.lines() { if let (Some(real), Some(effective)) = f(line, "Uid:") { debug_assert!(uid.is_none() && effective_uid.is_none()); uid = Some(real); effective_uid = Some(effective); } else if let (Some(real), Some(effective)) = f(line, "Gid:") { debug_assert!(gid.is_none() && effective_gid.is_none()); gid = Some(real); effective_gid = Some(effective); } else { continue; } if uid.is_some() && gid.is_some() { break; } } match (uid, effective_uid, gid, effective_gid) { (Some(uid), Some(effective_uid), Some(gid), Some(effective_gid)) => { Some(((uid, effective_uid), (gid, effective_gid))) } _ => None, } } fn get_tgid(file_path: &Path) -> Option { const TGID_KEY: &str = "Tgid:"; let status_data = get_all_utf8_data(file_path, 16_385).ok()?; let tgid_line = status_data .lines() .find(|line| line.starts_with(TGID_KEY))?; tgid_line[TGID_KEY.len()..].trim_start().parse().ok() } struct Parts<'a> { str_parts: Vec<&'a str>, short_exe: &'a [u8], } fn parse_stat_file(data: &[u8]) -> Option> { // The stat file is "interesting" to parse, because spaces cannot // be used as delimiters. The second field stores the command name // surrounded by parentheses. Unfortunately, whitespace and // parentheses are legal parts of the command, so parsing has to // proceed like this: The first field is delimited by the first // whitespace, the second field is everything until the last ')' // in the entire string. All other fields are delimited by // whitespace. let mut str_parts = Vec::with_capacity(51); let mut data_it = data.splitn(2, |&b| b == b' '); str_parts.push(str::from_utf8(data_it.next()?).ok()?); let mut data_it = data_it.next()?.rsplitn(2, |&b| b == b')'); let data = str::from_utf8(data_it.next()?).ok()?; let short_exe = data_it.next()?; str_parts.extend(data.split_whitespace()); Some(Parts { str_parts, short_exe: short_exe.strip_prefix(b"(").unwrap_or(short_exe), }) } /// Type used to correctly handle the `REMAINING_FILES` global. struct FileCounter(File); impl FileCounter { fn new(f: File) -> Option { let any_remaining = remaining_files().fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| { if remaining > 0 { Some(remaining - 1) } else { // All file descriptors we were allowed are being used. None } }); any_remaining.ok().map(|_| Self(f)) } } impl std::ops::Deref for FileCounter { type Target = File; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for FileCounter { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Drop for FileCounter { fn drop(&mut self) { remaining_files().fetch_add(1, Ordering::Relaxed); } } #[cfg(test)] mod tests { use super::split_content; use std::ffi::OsString; // This test ensures that all the parts of the data are split. #[test] fn test_copy_file() { assert_eq!(split_content(b"hello\0"), vec![OsString::from("hello")]); assert_eq!(split_content(b"hello"), vec![OsString::from("hello")]); assert_eq!( split_content(b"hello\0b"), vec![OsString::from("hello"), "b".into()] ); assert_eq!( split_content(b"hello\0\0\0\0b"), vec![OsString::from("hello"), "b".into()] ); } } GuillaumeGomez-sysinfo-067dd61/src/unix/linux/product.rs000066400000000000000000000041611506747262600234450ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub(crate) struct ProductInner; impl ProductInner { pub(crate) fn family() -> Option { std::fs::read_to_string("/sys/devices/virtual/dmi/id/product_family") .ok() .map(|s| s.trim().to_owned()) } pub(crate) fn name() -> Option { std::fs::read_to_string("/sys/devices/virtual/dmi/id/product_name") .ok() .or_else(|| { std::fs::read_to_string("/sys/firmware/devicetree/base/model") .ok() .or_else(|| { std::fs::read_to_string("/sys/firmware/devicetree/base/banner-name").ok() }) .or_else(|| std::fs::read_to_string("/tmp/sysinfo/model").ok()) .map(|s| s.trim_end_matches('\0').to_owned()) }) .map(|s| s.trim().to_owned()) } pub(crate) fn serial_number() -> Option { std::fs::read_to_string("/sys/devices/virtual/dmi/id/product_serial") .ok() .or_else(|| { std::fs::read_to_string("/sys/firmware/devicetree/base/serial-number") .ok() .map(|s| s.trim_end_matches('\0').to_owned()) }) .map(|s| s.trim().to_owned()) } pub(crate) fn stock_keeping_unit() -> Option { std::fs::read_to_string("/sys/devices/virtual/dmi/id/product_sku") .ok() .map(|s| s.trim().to_owned()) } pub(crate) fn uuid() -> Option { std::fs::read_to_string("/sys/devices/virtual/dmi/id/product_uuid") .ok() .map(|s| s.trim().to_owned()) } pub(crate) fn version() -> Option { std::fs::read_to_string("/sys/devices/virtual/dmi/id/product_version") .ok() .map(|s| s.trim().to_owned()) } pub(crate) fn vendor_name() -> Option { std::fs::read_to_string("/sys/devices/virtual/dmi/id/sys_vendor") .ok() .map(|s| s.trim().to_owned()) } } GuillaumeGomez-sysinfo-067dd61/src/unix/linux/system.rs000066400000000000000000001000441506747262600233060ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::cpu::{CpusWrapper, get_physical_core_count}; use crate::sys::process::{compute_cpu_usage, refresh_procs}; use crate::sys::utils::{get_all_utf8_data, to_u64}; use crate::{ Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind, ProcessesToUpdate, }; use libc::{self, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE, c_char, sysconf}; use std::cmp::min; use std::collections::HashMap; use std::ffi::CStr; use std::fs::File; use std::io::Read; use std::mem::MaybeUninit; use std::path::Path; use std::str::FromStr; use std::sync::{OnceLock, atomic::AtomicIsize}; use std::time::Duration; unsafe fn getrlimit() -> Option { let mut limits = libc::rlimit { rlim_cur: 0, rlim_max: 0, }; if unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) } != 0 { None } else { Some(limits) } } pub(crate) fn get_max_nb_fds() -> usize { unsafe { let mut limits = libc::rlimit { rlim_cur: 0, rlim_max: 0, }; if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 { // Most Linux system now defaults to 1024. 1024 / 2 } else { limits.rlim_max as usize / 2 } } } // This whole thing is to prevent having too many files open at once. It could be problematic // for processes using a lot of files and using sysinfo at the same time. pub(crate) fn remaining_files() -> &'static AtomicIsize { static REMAINING_FILES: OnceLock = OnceLock::new(); REMAINING_FILES.get_or_init(|| unsafe { let Some(mut limits) = getrlimit() else { // Most Linux system now defaults to 1024. return AtomicIsize::new(1024 / 2); }; // We save the value in case the update fails. let current = limits.rlim_cur; // The set the soft limit to the hard one. limits.rlim_cur = limits.rlim_max; // In this part, we leave minimum 50% of the available file descriptors to the process // using sysinfo. AtomicIsize::new(if libc::setrlimit(libc::RLIMIT_NOFILE, &limits) == 0 { limits.rlim_cur / 2 } else { current / 2 } as _) }) } declare_signals! { libc::c_int, Signal::Hangup => libc::SIGHUP, Signal::Interrupt => libc::SIGINT, Signal::Quit => libc::SIGQUIT, Signal::Illegal => libc::SIGILL, Signal::Trap => libc::SIGTRAP, Signal::Abort => libc::SIGABRT, Signal::IOT => libc::SIGIOT, Signal::Bus => libc::SIGBUS, Signal::FloatingPointException => libc::SIGFPE, Signal::Kill => libc::SIGKILL, Signal::User1 => libc::SIGUSR1, Signal::Segv => libc::SIGSEGV, Signal::User2 => libc::SIGUSR2, Signal::Pipe => libc::SIGPIPE, Signal::Alarm => libc::SIGALRM, Signal::Term => libc::SIGTERM, Signal::Child => libc::SIGCHLD, Signal::Continue => libc::SIGCONT, Signal::Stop => libc::SIGSTOP, Signal::TSTP => libc::SIGTSTP, Signal::TTIN => libc::SIGTTIN, Signal::TTOU => libc::SIGTTOU, Signal::Urgent => libc::SIGURG, Signal::XCPU => libc::SIGXCPU, Signal::XFSZ => libc::SIGXFSZ, Signal::VirtualAlarm => libc::SIGVTALRM, Signal::Profiling => libc::SIGPROF, Signal::Winch => libc::SIGWINCH, Signal::IO => libc::SIGIO, Signal::Poll => libc::SIGPOLL, Signal::Power => libc::SIGPWR, Signal::Sys => libc::SIGSYS, } #[doc = include_str!("../../../md_doc/supported_signals.md")] pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals(); #[doc = include_str!("../../../md_doc/minimum_cpu_update_interval.md")] pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200); fn boot_time() -> u64 { if let Ok(buf) = File::open("/proc/stat").and_then(|mut f| { let mut buf = Vec::new(); f.read_to_end(&mut buf)?; Ok(buf) }) { let line = buf.split(|c| *c == b'\n').find(|l| l.starts_with(b"btime")); if let Some(line) = line { return line .split(|x| *x == b' ') .filter(|s| !s.is_empty()) .nth(1) .map(to_u64) .unwrap_or(0); } } // Either we didn't find "btime" or "/proc/stat" wasn't available for some reason... unsafe { let mut up: libc::timespec = std::mem::zeroed(); if libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut up) == 0 { up.tv_sec as u64 } else { sysinfo_debug!("clock_gettime failed: boot time cannot be retrieve..."); 0 } } } pub(crate) struct SystemInfo { pub(crate) page_size_b: u64, pub(crate) clock_cycle: u64, pub(crate) boot_time: u64, } impl SystemInfo { fn new() -> Self { unsafe { Self { page_size_b: sysconf(_SC_PAGESIZE) as _, clock_cycle: sysconf(_SC_CLK_TCK) as _, boot_time: boot_time(), } } } } pub(crate) struct SystemInner { process_list: HashMap, mem_total: u64, mem_free: u64, mem_available: u64, mem_buffers: u64, mem_page_cache: u64, mem_shmem: u64, mem_slab_reclaimable: u64, swap_total: u64, swap_free: u64, info: SystemInfo, cpus: CpusWrapper, } impl SystemInner { /// It is sometime possible that a CPU usage computation is bigger than /// `"number of CPUs" * 100`. /// /// To prevent that, we compute ahead of time this maximum value and ensure that processes' /// CPU usage don't go over it. fn get_max_process_cpu_usage(&self) -> f32 { self.cpus.len() as f32 * 100. } fn update_procs_cpu(&mut self, refresh_kind: ProcessRefreshKind) { if !refresh_kind.cpu() { return; } self.cpus .refresh_if_needed(true, CpuRefreshKind::nothing().with_cpu_usage()); if self.cpus.is_empty() { sysinfo_debug!("cannot compute processes CPU usage: no CPU found..."); return; } let (new, old) = self.cpus.get_global_raw_times(); let total_time = if old > new { 1 } else { new - old }; let total_time = total_time as f32 / self.cpus.len() as f32; let max_value = self.get_max_process_cpu_usage(); for proc_ in self.process_list.values_mut() { compute_cpu_usage(&mut proc_.inner, total_time, max_value); } } fn refresh_cpus(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) { self.cpus.refresh(only_update_global_cpu, refresh_kind); } } impl SystemInner { pub(crate) fn new() -> Self { Self { process_list: HashMap::new(), mem_total: 0, mem_free: 0, mem_available: 0, mem_buffers: 0, mem_page_cache: 0, mem_shmem: 0, mem_slab_reclaimable: 0, swap_total: 0, swap_free: 0, cpus: CpusWrapper::new(), info: SystemInfo::new(), } } pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) { if !refresh_kind.ram() && !refresh_kind.swap() { return; } let mut mem_available_found = false; read_table("/proc/meminfo", ':', |key, value_kib| { let field = match key { "MemTotal" => &mut self.mem_total, "MemFree" => &mut self.mem_free, "MemAvailable" => { mem_available_found = true; &mut self.mem_available } "Buffers" => &mut self.mem_buffers, "Cached" => &mut self.mem_page_cache, "Shmem" => &mut self.mem_shmem, "SReclaimable" => &mut self.mem_slab_reclaimable, "SwapTotal" => &mut self.swap_total, "SwapFree" => &mut self.swap_free, _ => return, }; // /proc/meminfo reports KiB, though it says "kB". Convert it. *field = value_kib.saturating_mul(1_024); }); // Linux < 3.14 may not have MemAvailable in /proc/meminfo // So it should fallback to the old way of estimating available memory // https://github.com/KittyKatt/screenFetch/issues/386#issuecomment-249312716 if !mem_available_found { self.mem_available = self .mem_free .saturating_add(self.mem_buffers) .saturating_add(self.mem_page_cache) .saturating_add(self.mem_slab_reclaimable) .saturating_sub(self.mem_shmem); } } pub(crate) fn cgroup_limits(&self) -> Option { crate::CGroupLimits::new(self) } pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { self.refresh_cpus(false, refresh_kind); } pub(crate) fn refresh_processes_specifics( &mut self, processes_to_update: ProcessesToUpdate<'_>, refresh_kind: ProcessRefreshKind, ) -> usize { let uptime = Self::uptime(); let nb_updated = refresh_procs( &mut self.process_list, Path::new("/proc"), uptime, &self.info, processes_to_update, refresh_kind, ); self.update_procs_cpu(refresh_kind); nb_updated } // COMMON PART // // Need to be moved into a "common" file to avoid duplication. pub(crate) fn processes(&self) -> &HashMap { &self.process_list } pub(crate) fn processes_mut(&mut self) -> &mut HashMap { &mut self.process_list } pub(crate) fn process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } pub(crate) fn global_cpu_usage(&self) -> f32 { self.cpus.global_cpu.usage() } pub(crate) fn cpus(&self) -> &[Cpu] { &self.cpus.cpus } pub(crate) fn total_memory(&self) -> u64 { self.mem_total } pub(crate) fn free_memory(&self) -> u64 { self.mem_free } pub(crate) fn available_memory(&self) -> u64 { self.mem_available } pub(crate) fn used_memory(&self) -> u64 { self.mem_total - self.mem_available } pub(crate) fn total_swap(&self) -> u64 { self.swap_total } pub(crate) fn free_swap(&self) -> u64 { self.swap_free } // need to be checked pub(crate) fn used_swap(&self) -> u64 { self.swap_total - self.swap_free } pub(crate) fn uptime() -> u64 { if cfg!(not(target_os = "android")) && let Ok(content) = get_all_utf8_data("/proc/uptime", 50) && let Some(uptime) = content.split('.').next().and_then(|t| t.parse().ok()) { return uptime; } Self::uptime_with_sysinfo() } fn uptime_with_sysinfo() -> u64 { unsafe { let mut s = MaybeUninit::::uninit(); if libc::sysinfo(s.as_mut_ptr()) != 0 { return 0; } let s = s.assume_init(); if s.uptime < 1 { 0 } else { s.uptime as u64 } } } pub(crate) fn boot_time() -> u64 { boot_time() } pub(crate) fn load_average() -> LoadAvg { let mut s = String::new(); if File::open("/proc/loadavg") .and_then(|mut f| f.read_to_string(&mut s)) .is_err() { return LoadAvg::default(); } let loads = s .trim() .split(' ') .take(3) .map(|val| val.parse::().unwrap()) .collect::>(); LoadAvg { one: loads[0], five: loads[1], fifteen: loads[2], } } #[cfg(not(target_os = "android"))] pub(crate) fn name() -> Option { get_system_info_linux( InfoType::Name, Path::new("/etc/os-release"), Path::new("/etc/lsb-release"), ) } #[cfg(target_os = "android")] pub(crate) fn name() -> Option { get_system_info_android(InfoType::Name) } #[cfg(not(target_os = "android"))] pub(crate) fn long_os_version() -> Option { let mut long_name = "Linux".to_owned(); let distro_name = Self::name(); let distro_version = Self::os_version(); if let Some(distro_version) = &distro_version { // "Linux (Ubuntu 24.04)" long_name.push_str(" ("); long_name.push_str(distro_name.as_deref().unwrap_or("unknown")); long_name.push(' '); long_name.push_str(distro_version); long_name.push(')'); } else if let Some(distro_name) = &distro_name { // "Linux (Ubuntu)" long_name.push_str(" ("); long_name.push_str(distro_name); long_name.push(')'); } Some(long_name) } #[cfg(target_os = "android")] pub(crate) fn long_os_version() -> Option { let mut long_name = "Android".to_owned(); if let Some(os_version) = Self::os_version() { long_name.push(' '); long_name.push_str(&os_version); } // Android's name() is extracted from the system property "ro.product.model" // which is documented as "The end-user-visible name for the end product." // So this produces a long_os_version like "Android 15 on Pixel 9 Pro". if let Some(product_name) = Self::name() { long_name.push_str(" on "); long_name.push_str(&product_name); } Some(long_name) } pub(crate) fn host_name() -> Option { unsafe { let hostname_max = sysconf(_SC_HOST_NAME_MAX); let mut buffer = vec![0_u8; hostname_max as usize]; if libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) == 0 { if let Some(pos) = buffer.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes buffer.resize(pos, 0); } String::from_utf8(buffer).ok() } else { sysinfo_debug!("gethostname failed: hostname cannot be retrieved..."); None } } } pub(crate) fn kernel_version() -> Option { let mut raw = MaybeUninit::::zeroed(); unsafe { if libc::uname(raw.as_mut_ptr()) == 0 { let info = raw.assume_init(); let release = info .release .iter() .filter(|c| **c != 0) .map(|c| *c as u8 as char) .collect::(); Some(release) } else { None } } } #[cfg(not(target_os = "android"))] pub(crate) fn os_version() -> Option { get_system_info_linux( InfoType::OsVersion, Path::new("/etc/os-release"), Path::new("/etc/lsb-release"), ) } #[cfg(target_os = "android")] pub(crate) fn os_version() -> Option { get_system_info_android(InfoType::OsVersion) } #[cfg(not(target_os = "android"))] pub(crate) fn distribution_id() -> String { get_system_info_linux( InfoType::DistributionID, Path::new("/etc/os-release"), Path::new(""), ) .unwrap_or_else(|| std::env::consts::OS.to_owned()) } #[cfg(target_os = "android")] pub(crate) fn distribution_id() -> String { // Currently get_system_info_android doesn't support InfoType::DistributionID and always // returns None. This call is done anyway for consistency with non-Android implementation // and to suppress dead-code warning for DistributionID on Android. get_system_info_android(InfoType::DistributionID) .unwrap_or_else(|| std::env::consts::OS.to_owned()) } #[cfg(not(target_os = "android"))] pub(crate) fn distribution_id_like() -> Vec { system_info_as_list(get_system_info_linux( InfoType::DistributionIDLike, Path::new("/etc/os-release"), Path::new(""), )) } #[cfg(target_os = "android")] pub(crate) fn distribution_id_like() -> Vec { // Currently get_system_info_android doesn't support InfoType::DistributionIDLike and always // returns None. This call is done anyway for consistency with non-Android implementation // and to suppress dead-code warning for DistributionIDLike on Android. system_info_as_list(get_system_info_android(InfoType::DistributionIDLike)) } #[cfg(not(target_os = "android"))] pub(crate) fn kernel_name() -> Option<&'static str> { Some("Linux") } #[cfg(target_os = "android")] pub(crate) fn kernel_name() -> Option<&'static str> { Some("Android kernel") } pub(crate) fn cpu_arch() -> Option { let mut raw = MaybeUninit::::uninit(); unsafe { if libc::uname(raw.as_mut_ptr()) != 0 { return None; } let info = raw.assume_init(); // Converting `&[i8]` to `&[u8]`. let machine: &[u8] = std::slice::from_raw_parts(info.machine.as_ptr() as *const _, info.machine.len()); CStr::from_bytes_until_nul(machine) .ok() .and_then(|res| match res.to_str() { Ok(arch) => Some(arch.to_string()), Err(_) => None, }) } } pub(crate) fn physical_core_count() -> Option { get_physical_core_count() } pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) { self.cpus = CpusWrapper::new(); self.refresh_cpu_specifics(refresh_kind); } pub(crate) fn open_files_limit() -> Option { unsafe { match getrlimit() { Some(limits) => Some(limits.rlim_cur as _), None => { sysinfo_debug!("getrlimit failed"); None } } } } } fn read_u64(filename: &str) -> Option { let result = get_all_utf8_data(filename, 16_635) .ok() .and_then(|d| u64::from_str(d.trim()).ok()); if result.is_none() { sysinfo_debug!("Failed to read u64 in filename {}", filename); } result } fn read_table(filename: &str, colsep: char, mut f: F) where F: FnMut(&str, u64), { if let Ok(content) = get_all_utf8_data(filename, 16_635) { content .split('\n') .flat_map(|line| { let mut split = line.split(colsep); let key = split.next()?; let value = split.next()?; let value0 = value.trim_start().split(' ').next()?; let value0_u64 = u64::from_str(value0).ok()?; Some((key, value0_u64)) }) .for_each(|(k, v)| f(k, v)); } } fn read_table_key(filename: &str, target_key: &str, colsep: char) -> Option { if let Ok(content) = get_all_utf8_data(filename, 16_635) { return content.split('\n').find_map(|line| { let mut split = line.split(colsep); let key = split.next()?; if key != target_key { return None; } let value = split.next()?; let value0 = value.trim_start().split(' ').next()?; u64::from_str(value0).ok() }); } None } impl crate::CGroupLimits { fn new(sys: &SystemInner) -> Option { assert!( sys.mem_total != 0, "You need to call System::refresh_memory before trying to get cgroup limits!", ); if let (Some(mem_cur), Some(mem_max), Some(mem_rss)) = ( // cgroups v2 read_u64("/sys/fs/cgroup/memory.current"), // memory.max contains `max` when no limit is set. read_u64("/sys/fs/cgroup/memory.max").or(Some(u64::MAX)), read_table_key("/sys/fs/cgroup/memory.stat", "anon", ' '), ) { let mut limits = Self { total_memory: sys.mem_total, free_memory: sys.mem_free, free_swap: sys.swap_free, rss: mem_rss, }; limits.total_memory = min(mem_max, sys.mem_total); limits.free_memory = limits.total_memory.saturating_sub(mem_cur); if let Some(swap_cur) = read_u64("/sys/fs/cgroup/memory.swap.current") { limits.free_swap = sys.swap_total.saturating_sub(swap_cur); } Some(limits) } else if let (Some(mem_cur), Some(mem_max), Some(mem_rss)) = ( // cgroups v1 read_u64("/sys/fs/cgroup/memory/memory.usage_in_bytes"), read_u64("/sys/fs/cgroup/memory/memory.limit_in_bytes"), read_table_key("/sys/fs/cgroup/memory/memory.stat", "total_rss", ' '), ) { let mut limits = Self { total_memory: sys.mem_total, free_memory: sys.mem_free, free_swap: sys.swap_free, rss: mem_rss, }; limits.total_memory = min(mem_max, sys.mem_total); limits.free_memory = limits.total_memory.saturating_sub(mem_cur); Some(limits) } else { None } } } #[derive(PartialEq, Eq)] enum InfoType { /// The end-user friendly name of: /// - Android: The device model /// - Linux: The distributions name Name, OsVersion, /// Machine-parseable ID of a distribution, see /// https://www.freedesktop.org/software/systemd/man/os-release.html#ID= DistributionID, /// Machine-parseable ID_LIKE of related distributions, see /// DistributionIDLike, } #[cfg(not(target_os = "android"))] fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> Option { if let Ok(buf) = File::open(path).and_then(|mut f| { let mut buf = String::new(); f.read_to_string(&mut buf)?; Ok(buf) }) { let info_str = match info { InfoType::Name => "NAME=", InfoType::OsVersion => "VERSION_ID=", InfoType::DistributionID => "ID=", InfoType::DistributionIDLike => "ID_LIKE=", }; for line in buf.lines() { if let Some(stripped) = line.strip_prefix(info_str) { return Some(stripped.replace('"', "")); } } } // Fallback to `/etc/lsb-release` file for systems where VERSION_ID is not included. // VERSION_ID is not required in the `/etc/os-release` file // per https://www.linux.org/docs/man5/os-release.html // If this fails for some reason, fallback to None let buf = File::open(fallback_path) .and_then(|mut f| { let mut buf = String::new(); f.read_to_string(&mut buf)?; Ok(buf) }) .ok()?; let info_str = match info { InfoType::OsVersion => "DISTRIB_RELEASE=", InfoType::Name => "DISTRIB_ID=", InfoType::DistributionID => { // lsb-release is inconsistent with os-release and unsupported. return None; } InfoType::DistributionIDLike => { // lsb-release doesn't support ID_LIKE. return None; } }; for line in buf.lines() { if let Some(stripped) = line.strip_prefix(info_str) { return Some(stripped.replace('"', "")); } } None } /// Returns a system info value as a list of strings. /// Absence of a value is treated as an empty list. fn system_info_as_list(sysinfo: Option) -> Vec { match sysinfo { Some(value) => value.split_ascii_whitespace().map(String::from).collect(), // For list fields absence of a field is equivalent to an empty list. None => Vec::new(), } } #[cfg(target_os = "android")] fn get_system_info_android(info: InfoType) -> Option { // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/Build.java#58 let name: &'static [u8] = match info { InfoType::Name => b"ro.product.model\0", InfoType::OsVersion => b"ro.build.version.release\0", InfoType::DistributionID => { // Not supported. return None; } InfoType::DistributionIDLike => { // Not supported. return None; } }; let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize]; unsafe { let len = libc::__system_property_get( name.as_ptr() as *const c_char, value_buffer.as_mut_ptr() as *mut c_char, ); if len != 0 { if let Some(pos) = value_buffer.iter().position(|c| *c == 0) { value_buffer.resize(pos, 0); } String::from_utf8(value_buffer).ok() } else { None } } } #[cfg(test)] mod test { use super::InfoType; #[cfg(target_os = "android")] use super::get_system_info_android; #[cfg(not(target_os = "android"))] use super::get_system_info_linux; use super::read_table; use super::read_table_key; use super::system_info_as_list; use std::collections::HashMap; use std::io::Write; use tempfile::NamedTempFile; #[test] fn test_read_table() { // Create a temporary file with test content let mut file = NamedTempFile::new().unwrap(); writeln!(file, "KEY1:100 kB").unwrap(); writeln!(file, "KEY2:200 kB").unwrap(); writeln!(file, "KEY3:300 kB").unwrap(); writeln!(file, "KEY4:invalid").unwrap(); let file_path = file.path().to_str().unwrap(); // Test reading the table let mut result = HashMap::new(); read_table(file_path, ':', |key, value| { result.insert(key.to_string(), value); }); assert_eq!(result.get("KEY1"), Some(&100)); assert_eq!(result.get("KEY2"), Some(&200)); assert_eq!(result.get("KEY3"), Some(&300)); assert_eq!(result.get("KEY4"), None); // Test with different separator and units let mut file = NamedTempFile::new().unwrap(); writeln!(file, "KEY1 400 MB").unwrap(); writeln!(file, "KEY2 500 GB").unwrap(); writeln!(file, "KEY3 600").unwrap(); let file_path = file.path().to_str().unwrap(); let mut result = HashMap::new(); read_table(file_path, ' ', |key, value| { result.insert(key.to_string(), value); }); assert_eq!(result.get("KEY1"), Some(&400)); assert_eq!(result.get("KEY2"), Some(&500)); assert_eq!(result.get("KEY3"), Some(&600)); // Test with empty file let file = NamedTempFile::new().unwrap(); let file_path = file.path().to_str().unwrap(); let mut result = HashMap::new(); read_table(file_path, ':', |key, value| { result.insert(key.to_string(), value); }); assert!(result.is_empty()); // Test with non-existent file let mut result = HashMap::new(); read_table("/nonexistent/file", ':', |key, value| { result.insert(key.to_string(), value); }); assert!(result.is_empty()); } #[test] fn test_read_table_key() { // Create a temporary file with test content let mut file = NamedTempFile::new().unwrap(); writeln!(file, "KEY1:100 kB").unwrap(); writeln!(file, "KEY2:200 kB").unwrap(); writeln!(file, "KEY3:300 kB").unwrap(); let file_path = file.path().to_str().unwrap(); // Test existing keys assert_eq!(read_table_key(file_path, "KEY1", ':'), Some(100)); assert_eq!(read_table_key(file_path, "KEY2", ':'), Some(200)); assert_eq!(read_table_key(file_path, "KEY3", ':'), Some(300)); // Test non-existent key assert_eq!(read_table_key(file_path, "KEY4", ':'), None); // Test with different separator let mut file = NamedTempFile::new().unwrap(); writeln!(file, "KEY1 400 kB").unwrap(); writeln!(file, "KEY2 500 kB").unwrap(); let file_path = file.path().to_str().unwrap(); assert_eq!(read_table_key(file_path, "KEY1", ' '), Some(400)); assert_eq!(read_table_key(file_path, "KEY2", ' '), Some(500)); // Test with invalid file assert_eq!(read_table_key("/nonexistent/file", "KEY1", ':'), None); } #[test] #[cfg(target_os = "android")] fn lsb_release_fallback_android() { assert!(get_system_info_android(InfoType::OsVersion).is_some()); assert!(get_system_info_android(InfoType::Name).is_some()); assert!(get_system_info_android(InfoType::DistributionID).is_none()); assert!(get_system_info_android(InfoType::DistributionIDLike).is_none()); } #[test] #[cfg(not(target_os = "android"))] fn lsb_release_fallback_not_android() { use std::path::Path; let dir = tempfile::tempdir().expect("failed to create temporary directory"); let tmp1 = dir.path().join("tmp1"); let tmp2 = dir.path().join("tmp2"); // /etc/os-release std::fs::write( &tmp1, r#"NAME="Ubuntu" VERSION="20.10 (Groovy Gorilla)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 20.10" VERSION_ID="20.10" VERSION_CODENAME=groovy UBUNTU_CODENAME=groovy "#, ) .expect("Failed to create tmp1"); // /etc/lsb-release std::fs::write( &tmp2, r#"DISTRIB_ID=Ubuntu DISTRIB_RELEASE=20.10 DISTRIB_CODENAME=groovy DISTRIB_DESCRIPTION="Ubuntu 20.10" "#, ) .expect("Failed to create tmp2"); // Check for the "normal" path: "/etc/os-release" assert_eq!( get_system_info_linux(InfoType::OsVersion, &tmp1, Path::new("")), Some("20.10".to_owned()) ); assert_eq!( get_system_info_linux(InfoType::Name, &tmp1, Path::new("")), Some("Ubuntu".to_owned()) ); assert_eq!( get_system_info_linux(InfoType::DistributionID, &tmp1, Path::new("")), Some("ubuntu".to_owned()) ); assert_eq!( get_system_info_linux(InfoType::DistributionIDLike, &tmp1, Path::new("")), Some("debian".to_owned()) ); // Check for the "fallback" path: "/etc/lsb-release" assert_eq!( get_system_info_linux(InfoType::OsVersion, Path::new(""), &tmp2), Some("20.10".to_owned()) ); assert_eq!( get_system_info_linux(InfoType::Name, Path::new(""), &tmp2), Some("Ubuntu".to_owned()) ); assert_eq!( get_system_info_linux(InfoType::DistributionID, Path::new(""), &tmp2), None ); assert_eq!( get_system_info_linux(InfoType::DistributionIDLike, Path::new(""), &tmp2), None ); } #[test] fn test_system_info_as_list() { // No value. assert_eq!(system_info_as_list(None), Vec::::new()); // Empty value. assert_eq!( system_info_as_list(Some("".to_string())), Vec::::new(), ); // Whitespaces only. assert_eq!( system_info_as_list(Some(" ".to_string())), Vec::::new(), ); // Single value. assert_eq!( system_info_as_list(Some("debian".to_string())), vec!["debian".to_string()], ); // Multiple values. assert_eq!( system_info_as_list(Some("rhel fedora".to_string())), vec!["rhel".to_string(), "fedora".to_string()], ); // Multiple spaces. assert_eq!( system_info_as_list(Some("rhel fedora".to_string())), vec!["rhel".to_string(), "fedora".to_string()], ); } } GuillaumeGomez-sysinfo-067dd61/src/unix/linux/utils.rs000066400000000000000000000061071506747262600231270ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(any(feature = "disk", feature = "system"))] use std::fs::File; #[cfg(any(feature = "disk", feature = "system"))] use std::io::{self, Read, Seek}; #[cfg(any(feature = "disk", feature = "system"))] use std::path::Path; #[cfg(feature = "system")] pub(crate) fn get_all_data_from_file(file: &mut File, size: usize) -> io::Result> { let mut buf = Vec::with_capacity(size); file.rewind()?; file.read_to_end(&mut buf)?; Ok(buf) } #[cfg(any(feature = "disk", feature = "system"))] pub(crate) fn get_all_utf8_data_from_file(file: &mut File, size: usize) -> io::Result { let mut buf = String::with_capacity(size); file.rewind()?; file.read_to_string(&mut buf)?; Ok(buf) } #[cfg(any(feature = "disk", feature = "system"))] pub(crate) fn get_all_utf8_data>(file_path: P, size: usize) -> io::Result { let mut file = File::open(file_path.as_ref())?; get_all_utf8_data_from_file(&mut file, size) } #[cfg(feature = "system")] #[allow(clippy::useless_conversion)] pub(crate) fn realpath(path: &Path) -> Option { match std::fs::read_link(path) { Ok(path) => Some(path), Err(_e) => { sysinfo_debug!("failed to get real path for {:?}: {:?}", path, _e); None } } } /// This type is used in `retrieve_all_new_process_info` because we have a "parent" path and /// from it, we `pop`/`join` every time because it's more memory efficient than using `Path::join`. #[cfg(feature = "system")] pub(crate) struct PathHandler(std::path::PathBuf); #[cfg(feature = "system")] impl PathHandler { pub(crate) fn new(path: &Path) -> Self { // `path` is the "parent" for all paths which will follow so we add a fake element at // the end since every `PathHandler::replace_and_join` call will first call `pop` // internally. Self(path.join("a")) } pub(crate) fn as_path(&self) -> &Path { &self.0 } } #[cfg(feature = "system")] pub(crate) trait PathPush { fn replace_and_join(&mut self, p: &str) -> &Path; } #[cfg(feature = "system")] impl PathPush for PathHandler { fn replace_and_join(&mut self, p: &str) -> &Path { self.0.pop(); self.0.push(p); self.as_path() } } // This implementation allows to skip one allocation that is done in `PathHandler`. #[cfg(feature = "system")] impl PathPush for std::path::PathBuf { fn replace_and_join(&mut self, p: &str) -> &Path { self.push(p); self.as_path() } } #[cfg(feature = "system")] pub(crate) fn to_u64(v: &[u8]) -> u64 { let mut x = 0; for c in v { x *= 10; x += u64::from(c - b'0'); } x } /// Converts a path to a NUL-terminated `Vec` suitable for use with C functions. #[cfg(feature = "disk")] pub(crate) fn to_cpath(path: &std::path::Path) -> Vec { use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; let path_os: &OsStr = path.as_ref(); let mut cpath = path_os.as_bytes().to_vec(); cpath.push(0); cpath } GuillaumeGomez-sysinfo-067dd61/src/unix/mod.rs000066400000000000000000000033401506747262600214030ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. cfg_if! { if #[cfg(any(target_os = "macos", target_os = "ios"))] { pub(crate) mod apple; pub(crate) use apple as sys; #[allow(unused_imports)] pub(crate) use libc::__error as libc_errno; } else if #[cfg(any(target_os = "linux", target_os = "android"))] { pub(crate) mod linux; pub(crate) use linux as sys; #[cfg(target_os = "linux")] #[allow(unused_imports)] pub(crate) use libc::__errno_location as libc_errno; #[cfg(target_os = "android")] #[allow(unused_imports)] pub(crate) use libc::__errno as libc_errno; } else if #[cfg(target_os = "freebsd")] { pub(crate) mod freebsd; pub(crate) use freebsd as sys; #[allow(unused_imports)] pub(crate) use libc::__error as libc_errno; } else { compile_error!("Invalid cfg!"); } if #[cfg(feature = "disk")] { pub(crate) struct DisksInner { pub(crate) disks: Vec, } impl DisksInner { pub(crate) fn from_vec(disks: Vec) -> Self { Self { disks } } pub(crate) fn into_vec(self) -> Vec { self.disks } } } if #[cfg(feature = "network")] { pub(crate) mod network_helper; } if #[cfg(feature = "user")] { pub(crate) mod users; pub(crate) mod groups; } } pub(crate) mod utils; // Make formattable by rustfmt. #[cfg(any())] mod apple; #[cfg(any())] mod freebsd; #[cfg(any())] mod groups; #[cfg(any())] mod linux; #[cfg(any())] mod network_helper; #[cfg(any())] mod users; GuillaumeGomez-sysinfo-067dd61/src/unix/network_helper.rs000066400000000000000000000315041506747262600236570ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::HashMap; use std::collections::HashSet; use std::ffi::CStr; use std::mem::MaybeUninit; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; use std::os::raw::c_char; use std::ptr::null_mut; use std::str::from_utf8_unchecked; use std::{io, mem}; use crate::{IpNetwork, MacAddr}; /// This iterator yields an interface name and address. pub(crate) struct InterfaceAddressIterator { /// Pointer to the current `ifaddrs` struct. ifap: *mut libc::ifaddrs, /// Pointer to the first element in linked list. buf: *mut libc::ifaddrs, } impl Iterator for InterfaceAddressIterator { type Item = (String, MacAddr); fn next(&mut self) -> Option { unsafe { while !self.ifap.is_null() { // advance the pointer until a MAC address is found // Safety: `ifap` is already checked as non-null in the loop condition. let ifap = &*self.ifap; self.ifap = ifap.ifa_next; if let Some(addr) = parse_interface_address(ifap) { let ifa_name = ifap.ifa_name; if ifa_name.is_null() { continue; } // libc::IFNAMSIZ + 6 // This size refers to ./apple/network.rs:75 let mut name = vec![0u8; libc::IFNAMSIZ + 6]; libc::strcpy(name.as_mut_ptr() as _, ifap.ifa_name); name.set_len(libc::strlen(ifap.ifa_name)); let name = String::from_utf8_unchecked(name); return Some((name, addr)); } } None } } } impl Drop for InterfaceAddressIterator { fn drop(&mut self) { unsafe { libc::freeifaddrs(self.buf); } } } #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "ios"))] impl From<&libc::sockaddr_dl> for MacAddr { fn from(value: &libc::sockaddr_dl) -> Self { let sdl_data = value.sdl_data; // interface name length, NO trailing 0 let sdl_nlen = value.sdl_nlen as usize; // make sure that it is never out of bound if sdl_nlen + 5 < 12 { MacAddr([ sdl_data[sdl_nlen] as u8, sdl_data[sdl_nlen + 1] as u8, sdl_data[sdl_nlen + 2] as u8, sdl_data[sdl_nlen + 3] as u8, sdl_data[sdl_nlen + 4] as u8, sdl_data[sdl_nlen + 5] as u8, ]) } else { MacAddr::UNSPECIFIED } } } #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "ios"))] unsafe fn parse_interface_address(ifap: &libc::ifaddrs) -> Option { let sock_addr = ifap.ifa_addr; if sock_addr.is_null() { return None; } unsafe { match (*sock_addr).sa_family as libc::c_int { libc::AF_LINK => { let addr = sock_addr as *const libc::sockaddr_dl; Some(MacAddr::from(&*addr)) } _ => None, } } } #[cfg(any(target_os = "linux", target_os = "android"))] unsafe fn parse_interface_address(ifap: &libc::ifaddrs) -> Option { use libc::sockaddr_ll; let sock_addr = ifap.ifa_addr; if sock_addr.is_null() { return None; } unsafe { match (*sock_addr).sa_family as libc::c_int { libc::AF_PACKET => { let addr = sock_addr as *const sockaddr_ll; // Take the first 6 bytes let [addr @ .., _, _] = (*addr).sll_addr; Some(MacAddr(addr)) } _ => None, } } } /// Return an iterator on (interface_name, address) pairs pub(crate) unsafe fn get_interface_address() -> Result { let mut ifap = null_mut(); if unsafe { retry_eintr!(libc::getifaddrs(&mut ifap)) } == 0 && !ifap.is_null() { Ok(InterfaceAddressIterator { ifap, buf: ifap }) } else { Err("failed to call getifaddrs()".to_string()) } } pub(crate) unsafe fn get_interface_ip_networks() -> HashMap> { let mut ifaces: HashMap> = HashMap::new(); let mut addrs: MaybeUninit<*mut libc::ifaddrs> = MaybeUninit::uninit(); // Safety: addrs.as_mut_ptr() is valid, it points to addrs. if unsafe { libc::getifaddrs(addrs.as_mut_ptr()) } != 0 { sysinfo_debug!("Failed to operate libc::getifaddrs as ifaddrs Uninitialized"); return ifaces; } // Safety: If there was an error, we would have already returned. // Therefore, getifaddrs has initialized `addrs`. let addrs = unsafe { addrs.assume_init() }; let mut addr = addrs; while !addr.is_null() { // Safety: We assume that addr is valid for the lifetime of this loop // body, and is not mutated. let addr_ref: &libc::ifaddrs = unsafe { &*addr }; let c_str = addr_ref.ifa_name as *const c_char; // Safety: ifa_name is a null terminated interface name let bytes = unsafe { CStr::from_ptr(c_str).to_bytes() }; // Safety: Interfaces on unix must be valid UTF-8 let mut name = unsafe { from_utf8_unchecked(bytes).to_owned() }; // Interfaces names may be formatted as : if name.contains(':') { if let Some(interface_name) = name.split(':').next() { name = interface_name.to_string() } else { // The sub-interface is malformed, skipping to the next addr addr = addr_ref.ifa_next; continue; } } let ip = sockaddr_to_network_addr(addr_ref.ifa_addr as *const libc::sockaddr); let netmask = sockaddr_to_network_addr(addr_ref.ifa_netmask as *const libc::sockaddr); let prefix = netmask .and_then(|netmask| ip_mask_to_prefix(netmask).ok()) .unwrap_or(0); if let Some(ip) = ip { ifaces .entry(name) .and_modify(|values| { values.insert(IpNetwork { addr: ip, prefix }); }) .or_insert(HashSet::from([IpNetwork { addr: ip, prefix }])); } addr = addr_ref.ifa_next; } // Safety: addrs has been previously allocated through getifaddrs unsafe { libc::freeifaddrs(addrs) }; ifaces } #[cfg(any(target_os = "linux", target_os = "android"))] fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> Option { unsafe { if sa.is_null() || (*sa).sa_family as libc::c_int == libc::AF_PACKET { None } else { let addr = sockaddr_to_addr( &(sa as *const libc::sockaddr_storage).read_unaligned(), mem::size_of::(), ); match addr { Ok(SocketAddr::V4(sa)) => Some(IpAddr::V4(*sa.ip())), Ok(SocketAddr::V6(sa)) => Some(IpAddr::V6(*sa.ip())), _ => None, } } } } #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] pub type InAddrType = libc::c_uint; #[cfg(any(target_os = "illumos", target_os = "solaris"))] pub type InAddrType = libc::c_ulonglong; fn sockaddr_to_addr(storage: &libc::sockaddr_storage, len: usize) -> io::Result { match storage.ss_family as libc::c_int { libc::AF_INET => { assert!(len >= mem::size_of::()); let storage: &libc::sockaddr_in = unsafe { mem::transmute(storage) }; let ip = (storage.sin_addr.s_addr as InAddrType).to_be(); let a = (ip >> 24) as u8; let b = (ip >> 16) as u8; let c = (ip >> 8) as u8; let d = ip as u8; let sockaddrv4 = SocketAddrV4::new(Ipv4Addr::new(a, b, c, d), storage.sin_port.to_be()); Ok(SocketAddr::V4(sockaddrv4)) } libc::AF_INET6 => { assert!(len >= mem::size_of::()); let storage: &libc::sockaddr_in6 = unsafe { mem::transmute(storage) }; let arr: [u16; 8] = unsafe { mem::transmute(storage.sin6_addr.s6_addr) }; let ip = Ipv6Addr::new( arr[0].to_be(), arr[1].to_be(), arr[2].to_be(), arr[3].to_be(), arr[4].to_be(), arr[5].to_be(), arr[6].to_be(), arr[7].to_be(), ); Ok(SocketAddr::V6(SocketAddrV6::new( ip, storage.sin6_port.to_be(), u32::from_be(storage.sin6_flowinfo), storage.sin6_scope_id, ))) } _ => Err(io::Error::new( io::ErrorKind::InvalidData, "expected IPv4 or IPv6 socket", )), } } #[cfg(any( target_os = "openbsd", target_os = "freebsd", target_os = "netbsd", target_os = "illumos", target_os = "solaris", target_os = "macos", target_os = "ios" ))] fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> Option { unsafe { if sa.is_null() || (*sa).sa_family as libc::c_int == 18 { None } else { let addr = sockaddr_to_addr( &(sa as *const libc::sockaddr_storage).read_unaligned(), mem::size_of::(), ); match addr { Ok(SocketAddr::V4(sa)) => Some(IpAddr::V4(*sa.ip())), Ok(SocketAddr::V6(sa)) => Some(IpAddr::V6(*sa.ip())), _ => None, } } } } pub(crate) fn ip_mask_to_prefix(mask: IpAddr) -> Result { match mask { IpAddr::V4(mask) => ipv4_mask_to_prefix(mask), IpAddr::V6(mask) => ipv6_mask_to_prefix(mask), } } pub(crate) fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result { let mask = u32::from(mask); let prefix = (!mask).leading_zeros() as u8; if (u64::from(mask) << prefix) & 0xffff_ffff != 0 { Err("invalid ipv4 prefix") } else { Ok(prefix) } } pub(crate) fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result { let mask = mask.segments(); let mut mask_iter = mask.iter(); // Count the number of set bits from the start of the address let mut prefix = 0; for &segment in &mut mask_iter { if segment == 0xffff { prefix += 16; } else if segment == 0 { // Prefix finishes on a segment boundary break; } else { let prefix_bits = (!segment).leading_zeros() as u8; // Check that the remainder of the bits are all unset if segment << prefix_bits != 0 { return Err("invalid ipv6 prefix"); } prefix += prefix_bits; break; } } // Now check all the remaining bits are unset for &segment in mask_iter { if segment != 0 { return Err("invalid ipv6 prefix"); } } Ok(prefix) } #[cfg(test)] mod tests { use super::*; #[test] fn ipv4_mask() { let mask = Ipv4Addr::new(255, 255, 255, 0); let prefix = ipv4_mask_to_prefix(mask).unwrap(); assert_eq!(prefix, 24); } #[test] fn ipv4_mask_another() { let mask = Ipv4Addr::new(255, 255, 255, 128); let prefix = ipv4_mask_to_prefix(mask).unwrap(); assert_eq!(prefix, 25); } #[test] fn v4_mask_to_prefix_invalid() { let mask = Ipv4Addr::new(255, 128, 255, 0); assert!(ipv4_mask_to_prefix(mask).is_err()); } #[test] fn ipv6_mask() { let mask = Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0, 0, 0, 0, 0); let prefix = ipv6_mask_to_prefix(mask).unwrap(); assert_eq!(prefix, 48); } #[test] fn ipv6_mask_invalid() { let mask = Ipv6Addr::new(0, 0xffff, 0xffff, 0, 0, 0, 0, 0); assert!(ipv6_mask_to_prefix(mask).is_err()); } #[test] fn ip_mask_enum_ipv4() { let mask = IpAddr::from(Ipv4Addr::new(255, 255, 255, 0)); let prefix = ip_mask_to_prefix(mask).unwrap(); assert_eq!(prefix, 24); } #[test] fn ip_mask_enum_ipv4_invalid() { let mask = IpAddr::from(Ipv4Addr::new(255, 0, 255, 0)); assert!(ip_mask_to_prefix(mask).is_err()); } #[test] fn ip_mask_enum_ipv6() { let mask = IpAddr::from(Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0, 0, 0, 0, 0)); let prefix = ip_mask_to_prefix(mask).unwrap(); assert_eq!(prefix, 48); } #[test] fn ip_mask_enum_ipv6_invalid() { let mask = IpAddr::from(Ipv6Addr::new(0xffff, 0xffff, 0xff00, 0xffff, 0, 0, 0, 0)); assert!(ip_mask_to_prefix(mask).is_err()); } } GuillaumeGomez-sysinfo-067dd61/src/unix/users.rs000066400000000000000000000122111506747262600217620ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ Group, User, common::{Gid, Uid}, }; use libc::{getgrgid_r, getgrouplist}; #[cfg(not(target_os = "android"))] use libc::{endpwent, getpwent, setpwent}; // See `https://github.com/rust-lang/libc/issues/3014`. #[cfg(target_os = "android")] unsafe extern "C" { fn getpwent() -> *mut libc::passwd; fn setpwent(); fn endpwent(); } pub(crate) struct UserInner { pub(crate) uid: Uid, pub(crate) gid: Gid, pub(crate) name: String, c_user: Vec, } impl UserInner { pub(crate) fn new(uid: Uid, gid: Gid, name: String) -> Self { let mut c_user = name.as_bytes().to_vec(); c_user.push(0); Self { uid, gid, name, c_user, } } pub(crate) fn id(&self) -> &Uid { &self.uid } pub(crate) fn group_id(&self) -> Gid { self.gid } pub(crate) fn name(&self) -> &str { &self.name } pub(crate) fn groups(&self) -> Vec { unsafe { get_user_groups(self.c_user.as_ptr() as *const _, self.gid.0 as _) } } } pub(crate) unsafe fn get_group_name( id: libc::gid_t, buffer: &mut Vec, ) -> Option { let mut g = std::mem::MaybeUninit::::uninit(); let mut tmp_ptr = std::ptr::null_mut(); let mut last_errno = 0; unsafe { loop { if retry_eintr!(set_to_0 => last_errno => getgrgid_r( id as _, g.as_mut_ptr() as _, buffer.as_mut_ptr(), buffer.capacity() as _, &mut tmp_ptr as _ )) != 0 { // If there was not enough memory, we give it more. if last_errno == libc::ERANGE as libc::c_int { // Needs to be updated for `Vec::reserve` to actually add additional capacity. // In here it's "fine" since we never read from `buffer`. buffer.set_len(buffer.capacity()); buffer.reserve(2048); continue; } return None; } break; } let g = g.assume_init(); super::utils::cstr_to_rust(g.gr_name) } } pub(crate) unsafe fn get_user_groups( name: *const libc::c_char, group_id: libc::gid_t, ) -> Vec { let mut buffer = Vec::with_capacity(2048); let mut groups = Vec::with_capacity(256); loop { unsafe { let mut nb_groups = groups.capacity(); if getgrouplist( name, group_id as _, groups.as_mut_ptr(), &mut nb_groups as *mut _ as *mut _, ) == -1 { // Ensure the length matches the number of returned groups. // Needs to be updated for `Vec::reserve` to actually add additional capacity. groups.set_len(nb_groups as _); groups.reserve(256); continue; } groups.set_len(nb_groups as _); return groups .iter() .filter_map(|group_id| { let name = get_group_name(*group_id as _, &mut buffer)?; Some(Group { inner: crate::GroupInner::new(Gid(*group_id as _), name), }) }) .collect(); } } } pub(crate) fn get_users(users: &mut Vec) { fn filter(shell: *const std::ffi::c_char, uid: u32) -> bool { !endswith(shell, b"/false") && !endswith(shell, b"/uucico") && uid < 65536 } users.clear(); let mut users_map = std::collections::HashMap::with_capacity(10); unsafe { setpwent(); loop { let pw = getpwent(); if pw.is_null() { // The call was interrupted by a signal, retrying. if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted { continue; } break; } if !filter((*pw).pw_shell, (*pw).pw_uid) { // This is not a "real" or "local" user. continue; } if let Some(name) = crate::unix::utils::cstr_to_rust((*pw).pw_name) { if users_map.contains_key(&name) { continue; } let uid = (*pw).pw_uid; let gid = (*pw).pw_gid; users_map.insert(name, (Uid(uid), Gid(gid))); } } endpwent(); } for (name, (uid, gid)) in users_map { users.push(User { inner: UserInner::new(uid, gid, name), }); } } fn endswith(s1: *const std::ffi::c_char, s2: &[u8]) -> bool { if s1.is_null() { return false; } unsafe { let mut len = libc::strlen(s1) as isize - 1; let mut i = s2.len() as isize - 1; while len >= 0 && i >= 0 && *s1.offset(len) == s2[i as usize] as std::ffi::c_char { i -= 1; len -= 1; } i == -1 } } GuillaumeGomez-sysinfo-067dd61/src/unix/utils.rs000066400000000000000000000031731506747262600217700ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "user")] pub(crate) fn cstr_to_rust(c: *const libc::c_char) -> Option { cstr_to_rust_with_size(c, None) } #[cfg(any(feature = "disk", feature = "system", feature = "user"))] #[allow(dead_code)] pub(crate) fn cstr_to_rust_with_size( c: *const libc::c_char, size: Option, ) -> Option { if c.is_null() { return None; } let (mut s, max) = match size { Some(len) => (Vec::with_capacity(len), len as isize), None => (Vec::new(), isize::MAX), }; let mut i = 0; unsafe { loop { let value = *c.offset(i) as u8; if value == 0 { break; } s.push(value); i += 1; if i >= max { break; } } String::from_utf8(s).ok() } } #[cfg(all( feature = "system", not(any( target_os = "ios", all(target_os = "macos", feature = "apple-sandbox",) )) ))] pub(crate) fn wait_process(pid: crate::Pid) -> Option { use std::os::unix::process::ExitStatusExt; let mut status = 0; // attempt waiting unsafe { if retry_eintr!(libc::waitpid(pid.0, &mut status, 0)) < 0 { // attempt failed (non-child process) so loop until process ends let duration = std::time::Duration::from_millis(10); while libc::kill(pid.0, 0) == 0 { std::thread::sleep(duration); } } Some(std::process::ExitStatus::from_raw(status)) } } GuillaumeGomez-sysinfo-067dd61/src/unknown/000077500000000000000000000000001506747262600207725ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/unknown/component.rs000066400000000000000000000023031506747262600233400ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::Component; pub(crate) struct ComponentInner { pub(crate) updated: bool, } impl ComponentInner { pub(crate) fn temperature(&self) -> Option { None } pub(crate) fn max(&self) -> Option { None } pub(crate) fn critical(&self) -> Option { None } pub(crate) fn label(&self) -> &str { "" } pub(crate) fn id(&self) -> Option<&str> { None } pub(crate) fn refresh(&mut self) {} } pub(crate) struct ComponentsInner { pub(crate) components: Vec, } impl ComponentsInner { pub(crate) fn new() -> Self { Self { components: Vec::new(), } } pub(crate) fn from_vec(components: Vec) -> Self { Self { components } } pub(crate) fn into_vec(self) -> Vec { self.components } pub(crate) fn list(&self) -> &[Component] { &self.components } pub(crate) fn list_mut(&mut self) -> &mut [Component] { &mut self.components } pub(crate) fn refresh(&mut self) { // Doesn't do anything. } } GuillaumeGomez-sysinfo-067dd61/src/unknown/cpu.rs000066400000000000000000000006571506747262600221370ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub(crate) struct CpuInner; impl CpuInner { pub(crate) fn cpu_usage(&self) -> f32 { 0.0 } pub(crate) fn name(&self) -> &str { "" } pub(crate) fn frequency(&self) -> u64 { 0 } pub(crate) fn vendor_id(&self) -> &str { "" } pub(crate) fn brand(&self) -> &str { "" } } GuillaumeGomez-sysinfo-067dd61/src/unknown/disk.rs000066400000000000000000000031531506747262600222740ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage}; use std::{ffi::OsStr, path::Path}; pub(crate) struct DiskInner; impl DiskInner { pub(crate) fn kind(&self) -> DiskKind { DiskKind::Unknown(-1) } pub(crate) fn name(&self) -> &OsStr { OsStr::new("") } pub(crate) fn file_system(&self) -> &OsStr { Default::default() } pub(crate) fn mount_point(&self) -> &Path { Path::new("") } pub(crate) fn total_space(&self) -> u64 { 0 } pub(crate) fn available_space(&self) -> u64 { 0 } pub(crate) fn is_removable(&self) -> bool { false } pub(crate) fn is_read_only(&self) -> bool { false } pub(crate) fn refresh_specifics(&mut self, _refreshes: DiskRefreshKind) -> bool { true } pub(crate) fn usage(&self) -> DiskUsage { DiskUsage::default() } } pub(crate) struct DisksInner { pub(crate) disks: Vec, } impl DisksInner { pub(crate) fn new() -> Self { Self { disks: Vec::new() } } pub(crate) fn from_vec(disks: Vec) -> Self { Self { disks } } pub(crate) fn into_vec(self) -> Vec { self.disks } pub(crate) fn refresh_specifics( &mut self, _remove_not_listed_disks: bool, _refreshes: DiskRefreshKind, ) { // Does nothing. } pub(crate) fn list(&self) -> &[Disk] { &self.disks } pub(crate) fn list_mut(&mut self) -> &mut [Disk] { &mut self.disks } } GuillaumeGomez-sysinfo-067dd61/src/unknown/groups.rs000066400000000000000000000004731506747262600226630ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Gid, Group, GroupInner}; impl GroupInner { pub(crate) fn id(&self) -> &Gid { &self.id } pub(crate) fn name(&self) -> &str { &self.name } } pub(crate) fn get_groups(_: &mut Vec) {} GuillaumeGomez-sysinfo-067dd61/src/unknown/mod.rs000066400000000000000000000031121506747262600221140ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. cfg_if! { if #[cfg(feature = "system")] { pub mod cpu; pub mod motherboard; pub mod process; pub mod product; pub mod system; pub(crate) use self::cpu::CpuInner; pub(crate) use self::motherboard::MotherboardInner; pub(crate) use self::process::ProcessInner; pub(crate) use self::product::ProductInner; pub(crate) use self::system::SystemInner; pub use self::system::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS}; } if #[cfg(feature = "disk")] { pub mod disk; pub(crate) use self::disk::{DiskInner, DisksInner}; } if #[cfg(feature = "component")] { pub mod component; pub(crate) use self::component::{ComponentInner, ComponentsInner}; } if #[cfg(feature = "network")] { pub mod network; pub(crate) use self::network::{NetworkDataInner, NetworksInner}; } if #[cfg(feature = "user")] { pub mod groups; pub mod users; pub(crate) use self::groups::get_groups; pub(crate) use self::users::{get_users, UserInner}; } } #[doc = include_str!("../../md_doc/is_supported.md")] pub const IS_SUPPORTED_SYSTEM: bool = false; // Make formattable by rustfmt. #[cfg(any())] mod component; #[cfg(any())] mod cpu; #[cfg(any())] mod disk; #[cfg(any())] mod groups; #[cfg(any())] mod motherboard; #[cfg(any())] mod network; #[cfg(any())] mod process; #[cfg(any())] mod product; #[cfg(any())] mod system; #[cfg(any())] mod users; GuillaumeGomez-sysinfo-067dd61/src/unknown/motherboard.rs000066400000000000000000000011651506747262600236510ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub(crate) struct MotherboardInner; impl MotherboardInner { pub(crate) fn new() -> Option { None } pub(crate) fn name(&self) -> Option { unreachable!() } pub(crate) fn vendor_name(&self) -> Option { unreachable!() } pub(crate) fn version(&self) -> Option { unreachable!() } pub(crate) fn serial_number(&self) -> Option { unreachable!() } pub(crate) fn asset_tag(&self) -> Option { unreachable!() } } GuillaumeGomez-sysinfo-067dd61/src/unknown/network.rs000066400000000000000000000032211506747262600230270ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{IpNetwork, MacAddr, NetworkData}; use std::collections::HashMap; pub(crate) struct NetworksInner { pub(crate) interfaces: HashMap, } impl NetworksInner { pub(crate) fn new() -> Self { Self { interfaces: HashMap::new(), } } pub(crate) fn list(&self) -> &HashMap { &self.interfaces } pub(crate) fn refresh(&mut self, _remove_not_listed_interfaces: bool) {} } pub(crate) struct NetworkDataInner; impl NetworkDataInner { pub(crate) fn received(&self) -> u64 { 0 } pub(crate) fn total_received(&self) -> u64 { 0 } pub(crate) fn transmitted(&self) -> u64 { 0 } pub(crate) fn total_transmitted(&self) -> u64 { 0 } pub(crate) fn packets_received(&self) -> u64 { 0 } pub(crate) fn total_packets_received(&self) -> u64 { 0 } pub(crate) fn packets_transmitted(&self) -> u64 { 0 } pub(crate) fn total_packets_transmitted(&self) -> u64 { 0 } pub(crate) fn errors_on_received(&self) -> u64 { 0 } pub(crate) fn total_errors_on_received(&self) -> u64 { 0 } pub(crate) fn errors_on_transmitted(&self) -> u64 { 0 } pub(crate) fn total_errors_on_transmitted(&self) -> u64 { 0 } pub(crate) fn mac_address(&self) -> MacAddr { MacAddr::UNSPECIFIED } pub(crate) fn ip_networks(&self) -> &[IpNetwork] { &[] } pub(crate) fn mtu(&self) -> u64 { 0 } } GuillaumeGomez-sysinfo-067dd61/src/unknown/process.rs000066400000000000000000000046451506747262600230270ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{DiskUsage, Gid, Pid, ProcessStatus, Signal, Uid}; use std::ffi::{OsStr, OsString}; use std::fmt; use std::path::Path; use std::process::ExitStatus; impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Unknown") } } pub(crate) struct ProcessInner { pid: Pid, parent: Option, } impl ProcessInner { pub(crate) fn kill_with(&self, _signal: Signal) -> Option { None } pub(crate) fn name(&self) -> &OsStr { OsStr::new("") } pub(crate) fn cmd(&self) -> &[OsString] { &[] } pub(crate) fn exe(&self) -> Option<&Path> { None } pub(crate) fn pid(&self) -> Pid { self.pid } pub(crate) fn environ(&self) -> &[OsString] { &[] } pub(crate) fn cwd(&self) -> Option<&Path> { None } pub(crate) fn root(&self) -> Option<&Path> { None } pub(crate) fn memory(&self) -> u64 { 0 } pub(crate) fn virtual_memory(&self) -> u64 { 0 } pub(crate) fn parent(&self) -> Option { self.parent } pub(crate) fn status(&self) -> ProcessStatus { ProcessStatus::Unknown(0) } pub(crate) fn start_time(&self) -> u64 { 0 } pub(crate) fn run_time(&self) -> u64 { 0 } pub(crate) fn cpu_usage(&self) -> f32 { 0.0 } pub(crate) fn accumulated_cpu_time(&self) -> u64 { 0 } pub(crate) fn disk_usage(&self) -> DiskUsage { DiskUsage::default() } pub(crate) fn user_id(&self) -> Option<&Uid> { None } pub(crate) fn effective_user_id(&self) -> Option<&Uid> { None } pub(crate) fn group_id(&self) -> Option { None } pub(crate) fn effective_group_id(&self) -> Option { None } pub(crate) fn wait(&self) -> Option { None } pub(crate) fn session_id(&self) -> Option { None } pub(crate) fn switch_updated(&mut self) -> bool { false } pub(crate) fn set_nonexistent(&mut self) {} pub(crate) fn exists(&self) -> bool { false } pub(crate) fn open_files(&self) -> Option { None } pub(crate) fn open_files_limit(&self) -> Option { None } } GuillaumeGomez-sysinfo-067dd61/src/unknown/product.rs000066400000000000000000000011611506747262600230170ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub(crate) struct ProductInner; impl ProductInner { pub(crate) fn family() -> Option { None } pub(crate) fn name() -> Option { None } pub(crate) fn serial_number() -> Option { None } pub(crate) fn stock_keeping_unit() -> Option { None } pub(crate) fn uuid() -> Option { None } pub(crate) fn version() -> Option { None } pub(crate) fn vendor_name() -> Option { None } } GuillaumeGomez-sysinfo-067dd61/src/unknown/system.rs000066400000000000000000000065041506747262600226710ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind, ProcessesToUpdate, }; use std::collections::HashMap; use std::time::Duration; declare_signals! { (), _ => None, } #[doc = include_str!("../../md_doc/supported_signals.md")] pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals(); #[doc = include_str!("../../md_doc/minimum_cpu_update_interval.md")] pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(0); pub(crate) struct SystemInner { process_list: HashMap, } impl SystemInner { pub(crate) fn new() -> Self { Self { process_list: Default::default(), } } pub(crate) fn refresh_memory_specifics(&mut self, _refresh_kind: MemoryRefreshKind) {} pub(crate) fn cgroup_limits(&self) -> Option { None } pub(crate) fn refresh_cpu_specifics(&mut self, _refresh_kind: CpuRefreshKind) {} pub(crate) fn refresh_cpu_list(&mut self, _refresh_kind: CpuRefreshKind) {} pub(crate) fn refresh_processes_specifics( &mut self, _processes_to_update: ProcessesToUpdate<'_>, _refresh_kind: ProcessRefreshKind, ) -> usize { 0 } // COMMON PART // // Need to be moved into a "common" file to avoid duplication. pub(crate) fn processes(&self) -> &HashMap { &self.process_list } pub(crate) fn processes_mut(&mut self) -> &mut HashMap { &mut self.process_list } pub(crate) fn process(&self, _pid: Pid) -> Option<&Process> { None } pub(crate) fn global_cpu_usage(&self) -> f32 { 0. } pub(crate) fn cpus(&self) -> &[Cpu] { &[] } pub(crate) fn total_memory(&self) -> u64 { 0 } pub(crate) fn free_memory(&self) -> u64 { 0 } pub(crate) fn available_memory(&self) -> u64 { 0 } pub(crate) fn used_memory(&self) -> u64 { 0 } pub(crate) fn total_swap(&self) -> u64 { 0 } pub(crate) fn free_swap(&self) -> u64 { 0 } pub(crate) fn used_swap(&self) -> u64 { 0 } pub(crate) fn uptime() -> u64 { 0 } pub(crate) fn boot_time() -> u64 { 0 } pub(crate) fn load_average() -> LoadAvg { LoadAvg { one: 0., five: 0., fifteen: 0., } } pub(crate) fn name() -> Option { None } pub(crate) fn long_os_version() -> Option { None } pub(crate) fn kernel_version() -> Option { None } pub(crate) fn os_version() -> Option { None } pub(crate) fn distribution_id() -> String { std::env::consts::OS.to_owned() } pub(crate) fn distribution_id_like() -> Vec { Vec::new() } pub(crate) fn kernel_name() -> Option<&'static str> { None } pub(crate) fn host_name() -> Option { None } pub(crate) fn cpu_arch() -> Option { None } pub(crate) fn physical_core_count() -> Option { None } pub(crate) fn open_files_limit() -> Option { None } } GuillaumeGomez-sysinfo-067dd61/src/unknown/users.rs000066400000000000000000000007271506747262600225070ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Gid, Group, Uid, User}; pub(crate) struct UserInner; impl UserInner { pub(crate) fn id(&self) -> &Uid { &Uid(0) } pub(crate) fn group_id(&self) -> Gid { Gid(0) } pub(crate) fn name(&self) -> &str { "" } pub(crate) fn groups(&self) -> Vec { Vec::new() } } pub(crate) fn get_users(_: &mut Vec) {} GuillaumeGomez-sysinfo-067dd61/src/utils.rs000066400000000000000000000045031506747262600210030ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. cfg_if! { if #[cfg(all( feature = "multithread", not(feature = "unknown-ci"), not(all(target_os = "macos", feature = "apple-sandbox")), ))] { /// Converts the value into a parallel iterator if the `multithread` feature is enabled. /// Uses the `rayon::iter::IntoParallelIterator` trait. #[cfg(all( feature = "multithread", not(feature = "unknown-ci"), not(all(target_os = "macos", feature = "apple-sandbox")), ))] #[allow(dead_code)] pub(crate) fn into_iter(val: T) -> T::Iter where T: rayon::iter::IntoParallelIterator, { val.into_par_iter() } // /// Converts the value into a parallel mutable iterator if the `multithread` feature is // /// enabled. Uses the `rayon::iter::IntoParallelRefMutIterator` trait. // #[cfg(feature = "component")] // pub(crate) fn into_iter_mut<'a, T>( // val: &'a mut T, // ) -> >::Iter // where // T: rayon::iter::IntoParallelRefMutIterator<'a> + ?Sized, // { // val.par_iter_mut() // } } else { /// Converts the value into a sequential iterator if the `multithread` feature is disabled. /// Uses the `std::iter::IntoIterator` trait. #[allow(dead_code)] pub(crate) fn into_iter(val: T) -> T::IntoIter where T: IntoIterator, { val.into_iter() } // In the multithreaded version of `into_iter_mut` above, the `&mut` on the argument is // indicating the parallel iterator is an exclusive reference. In the non-multithreaded // case, the `&mut` is already part of `T` and specifying it will result in the argument // being `&mut &mut T`. // /// Converts the value into a sequential mutable iterator if the `multithread` feature is // /// disabled. Uses the `std::iter::IntoIterator` trait. // #[cfg(feature = "component")] // pub(crate) fn into_iter_mut(val: T) -> T::IntoIter // where // T: IntoIterator, // { // val.into_iter() // } } } GuillaumeGomez-sysinfo-067dd61/src/windows/000077500000000000000000000000001506747262600207655ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/src/windows/component.rs000066400000000000000000000231311506747262600233350ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::Component; use windows::Win32::Foundation::{SysAllocString, SysFreeString}; use windows::Win32::Security::PSECURITY_DESCRIPTOR; use windows::Win32::System::Com::{ CLSCTX_INPROC_SERVER, CoCreateInstance, CoInitializeEx, CoInitializeSecurity, CoSetProxyBlanket, EOAC_NONE, RPC_C_AUTHN_LEVEL_CALL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, }; use windows::Win32::System::Rpc::{RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE}; use windows::Win32::System::Variant::{VARIANT, VariantClear}; use windows::Win32::System::Wmi::{ IEnumWbemClassObject, IWbemLocator, IWbemServices, WBEM_FLAG_FORWARD_ONLY, WBEM_FLAG_NONSYSTEM_ONLY, WBEM_FLAG_RETURN_IMMEDIATELY, WBEM_INFINITE, WbemLocator, }; use windows::core::w; use std::cell::OnceCell; use std::sync::OnceLock; pub(crate) struct ComponentInner { temperature: f32, max: f32, critical: Option, label: String, connection: Option, pub(crate) updated: bool, } impl ComponentInner { /// Creates a new `ComponentInner` with the given information. fn new() -> Option { let mut c = Connection::new() .and_then(|x| x.create_instance()) .and_then(|x| x.connect_server()) .and_then(|x| x.set_proxy_blanket()) .and_then(|x| x.exec_query())?; c.temperature(true) .map(|(temperature, critical)| ComponentInner { temperature, label: "Computer".to_owned(), max: temperature, critical, connection: Some(c), updated: true, }) } pub(crate) fn temperature(&self) -> Option { Some(self.temperature) } pub(crate) fn max(&self) -> Option { Some(self.max) } pub(crate) fn critical(&self) -> Option { self.critical } pub(crate) fn label(&self) -> &str { &self.label } pub(crate) fn id(&self) -> Option<&str> { Some(&self.label) } pub(crate) fn refresh(&mut self) { if self.connection.is_none() { self.connection = Connection::new() .and_then(|x| x.create_instance()) .and_then(|x| x.connect_server()) .and_then(|x| x.set_proxy_blanket()); } self.connection = if let Some(x) = self.connection.take() { x.exec_query() } else { None }; if let Some(ref mut connection) = self.connection && let Some((temperature, _)) = connection.temperature(false) { self.temperature = temperature; if self.temperature > self.max { self.max = self.temperature; } } } } pub(crate) struct ComponentsInner { pub(crate) components: Vec, } impl ComponentsInner { pub(crate) fn new() -> Self { Self { components: Vec::new(), } } pub(crate) fn from_vec(components: Vec) -> Self { Self { components } } pub(crate) fn into_vec(self) -> Vec { self.components } pub(crate) fn list(&self) -> &[Component] { &self.components } pub(crate) fn list_mut(&mut self) -> &mut [Component] { &mut self.components } pub(crate) fn refresh(&mut self) { if self.components.is_empty() { self.components = match ComponentInner::new() { Some(c) => vec![Component { inner: c }], None => Vec::new(), }; } else { // There should always be only one here but just in case... for c in self.components.iter_mut() { c.refresh(); c.inner.updated = true; } } } } macro_rules! bstr { ($x:literal) => {{ SysAllocString(w!($x)) }}; } struct Connection { instance: Option, server_connection: Option, enumerator: Option, } #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for Connection {} unsafe impl Sync for Connection {} static SECURITY: OnceLock> = OnceLock::new(); thread_local! { pub static CONNECTION: OnceCell> = const { OnceCell::new() }; } unsafe fn initialize_connection() -> Result<(), ()> { if unsafe { CoInitializeEx(None, Default::default()) }.is_err() { sysinfo_debug!("Failed to initialize connection"); Err(()) } else { Ok(()) } } unsafe fn initialize_security() -> Result<(), ()> { if unsafe { CoInitializeSecurity( Some(PSECURITY_DESCRIPTOR::default()), -1, None, None, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, None, EOAC_NONE, None, ) } .is_err() { sysinfo_debug!("Failed to initialize security"); Err(()) } else { Ok(()) } } impl Connection { #[allow(clippy::unnecessary_wraps)] fn new() -> Option { if CONNECTION .with(|x| *x.get_or_init(|| unsafe { initialize_connection() })) .is_err() || SECURITY .get_or_init(|| unsafe { initialize_security() }) .is_err() { return None; } Some(Connection { instance: None, server_connection: None, enumerator: None, }) } fn create_instance(mut self) -> Option { let instance = unsafe { CoCreateInstance(&WbemLocator, None, CLSCTX_INPROC_SERVER) }.ok()?; self.instance = Some(instance); Some(self) } fn connect_server(mut self) -> Option { let instance = self.instance.as_ref()?; let svc = unsafe { let s = bstr!("root\\WMI"); let res = instance.ConnectServer( &s, &Default::default(), &Default::default(), &Default::default(), 0, &Default::default(), None, ); SysFreeString(&s); res } .ok()?; self.server_connection = Some(svc); Some(self) } fn set_proxy_blanket(self) -> Option { unsafe { CoSetProxyBlanket( self.server_connection.as_ref()?, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, None, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, None, EOAC_NONE, ) } .ok()?; Some(self) } fn exec_query(mut self) -> Option { let server_connection = self.server_connection.as_ref()?; let enumerator = unsafe { let s = bstr!("WQL"); // query kind let query = bstr!("SELECT * FROM MSAcpi_ThermalZoneTemperature"); let hres = server_connection.ExecQuery( &s, &query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, None, ); SysFreeString(&s); SysFreeString(&query); hres } .ok()?; self.enumerator = Some(enumerator); Some(self) } fn temperature(&mut self, get_critical: bool) -> Option<(f32, Option)> { let enumerator = self.enumerator.take()?; let mut nb_returned = 0; let mut obj = [None; 1]; unsafe { let _r = enumerator.Next( WBEM_INFINITE, // Time out obj.as_mut_slice(), &mut nb_returned, ); if nb_returned == 0 { return None; // not enough rights I suppose... } let class_obj = match &mut obj { [Some(co)] => co, _ => return None, }; let _r = class_obj.BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY.0); let mut variant = std::mem::MaybeUninit::::uninit(); // `Get` only initializes the variant if it succeeds, early returning is not a problem // // class_obj .Get( w!("CurrentTemperature"), 0, variant.as_mut_ptr(), None, None, ) .ok()?; let mut variant = variant.assume_init(); // temperature is given in tenth of degrees Kelvin let temp = (variant.Anonymous.decVal.Anonymous2.Lo64 / 10) as f32 - 273.15; let _r = VariantClear(&mut variant); let mut critical = None; if get_critical { class_obj .Get(w!("CriticalTripPoint"), 0, &mut variant, None, None) .ok()?; // temperature is given in tenth of degrees Kelvin critical = Some((variant.Anonymous.decVal.Anonymous2.Lo64 / 10) as f32 - 273.15); let _r = VariantClear(&mut variant); } Some((temp, critical)) } } } impl Drop for Connection { fn drop(&mut self) { // Those three calls are here to enforce that they get dropped in the good order. self.enumerator.take(); self.server_connection.take(); self.instance.take(); } } GuillaumeGomez-sysinfo-067dd61/src/windows/cpu.rs000066400000000000000000000473271506747262600221370ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Cpu, CpuRefreshKind, LoadAvg}; use std::collections::HashMap; use std::ffi::c_void; use std::io::Error; use std::mem; use std::ops::DerefMut; use std::sync::{Mutex, OnceLock}; use windows::Win32::Foundation::{CloseHandle, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS, HANDLE}; use windows::Win32::System::Performance::{ PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE, PDH_HCOUNTER, PDH_HQUERY, PERF_DETAIL_NOVICE, PdhAddEnglishCounterA, PdhAddEnglishCounterW, PdhCloseQuery, PdhCollectQueryData, PdhCollectQueryDataEx, PdhEnumObjectsA, PdhGetFormattedCounterValue, PdhOpenQueryA, PdhRemoveCounter, }; use windows::Win32::System::Power::{ CallNtPowerInformation, PROCESSOR_POWER_INFORMATION, ProcessorInformation, }; use windows::Win32::System::SystemInformation::{self, GetSystemInfo}; use windows::Win32::System::SystemInformation::{ GetLogicalProcessorInformationEx, RelationAll, RelationProcessorCore, SYSTEM_INFO, SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, }; use windows::Win32::System::Threading::{ CreateEventA, INFINITE, RegisterWaitForSingleObject, WT_EXECUTEDEFAULT, }; use windows::core::{PCSTR, PCWSTR, s}; // This formula comes from Linux's include/linux/sched/loadavg.h // https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23 #[allow(clippy::excessive_precision)] const LOADAVG_FACTOR_1F: f64 = 0.9200444146293232478931553241; #[allow(clippy::excessive_precision)] const LOADAVG_FACTOR_5F: f64 = 0.9834714538216174894737477501; #[allow(clippy::excessive_precision)] const LOADAVG_FACTOR_15F: f64 = 0.9944598480048967508795473394; // The time interval in seconds between taking load counts, same as Linux const SAMPLING_INTERVAL: usize = 5; // maybe use a read/write lock instead? fn load_avg() -> &'static Mutex> { static LOAD_AVG: OnceLock>> = OnceLock::new(); LOAD_AVG.get_or_init(|| unsafe { init_load_avg() }) } pub(crate) fn get_load_average() -> LoadAvg { if let Ok(avg) = load_avg().lock() && let Some(avg) = &*avg { return avg.clone(); } LoadAvg::default() } unsafe extern "system" fn load_avg_callback(counter: *mut c_void, _: bool) { let mut display_value = mem::MaybeUninit::::uninit(); unsafe { if PdhGetFormattedCounterValue( PDH_HCOUNTER(counter), PDH_FMT_DOUBLE, None, display_value.as_mut_ptr(), ) != ERROR_SUCCESS.0 { return; } let display_value = display_value.assume_init(); if let Ok(mut avg) = load_avg().lock() && let Some(avg) = avg.deref_mut() { let current_load = display_value.Anonymous.doubleValue; avg.one = avg.one * LOADAVG_FACTOR_1F + current_load * (1.0 - LOADAVG_FACTOR_1F); avg.five = avg.five * LOADAVG_FACTOR_5F + current_load * (1.0 - LOADAVG_FACTOR_5F); avg.fifteen = avg.fifteen * LOADAVG_FACTOR_15F + current_load * (1.0 - LOADAVG_FACTOR_15F); } } } unsafe fn init_load_avg() -> Mutex> { // You can see the original implementation here: https://github.com/giampaolo/psutil let mut query = PDH_HQUERY::default(); unsafe { if PdhOpenQueryA(PCSTR::null(), 0, &mut query) != ERROR_SUCCESS.0 { sysinfo_debug!("init_load_avg: PdhOpenQueryA failed"); return Mutex::new(None); } let counter = 0; if PdhAddEnglishCounterA( query, s!("\\System\\Cpu Queue Length"), 0, &mut PDH_HCOUNTER(counter as *mut c_void), ) != ERROR_SUCCESS.0 { PdhCloseQuery(query); sysinfo_debug!("init_load_avg: failed to get CPU queue length"); return Mutex::new(None); } let event = match CreateEventA(None, false, false, s!("LoadUpdateEvent")) { Ok(ev) => ev, Err(_) => { PdhCloseQuery(query); sysinfo_debug!("init_load_avg: failed to create event `LoadUpdateEvent`"); return Mutex::new(None); } }; if PdhCollectQueryDataEx(query, SAMPLING_INTERVAL as _, event) != ERROR_SUCCESS.0 { PdhCloseQuery(query); sysinfo_debug!("init_load_avg: PdhCollectQueryDataEx failed"); return Mutex::new(None); } let mut wait_handle = HANDLE::default(); if RegisterWaitForSingleObject( &mut wait_handle, event, Some(load_avg_callback), Some(counter as *const c_void), INFINITE, WT_EXECUTEDEFAULT, ) .is_ok() { Mutex::new(Some(LoadAvg::default())) } else { PdhRemoveCounter(PDH_HCOUNTER(counter as *mut c_void)); PdhCloseQuery(query); sysinfo_debug!("init_load_avg: RegisterWaitForSingleObject failed"); Mutex::new(None) } } } struct InternalQuery { query: PDH_HQUERY, event: HANDLE, data: HashMap, } unsafe impl Send for InternalQuery {} unsafe impl Sync for InternalQuery {} impl Drop for InternalQuery { fn drop(&mut self) { unsafe { for (_, counter) in self.data.iter() { PdhRemoveCounter(*counter); } if !self.event.is_invalid() { let _err = CloseHandle(self.event); } if !self.query.is_invalid() { PdhCloseQuery(self.query); } } } } pub(crate) struct Query { internal: InternalQuery, } impl Query { pub fn new(force_reload: bool) -> Option { let mut query = PDH_HQUERY::default(); unsafe { if force_reload { PdhEnumObjectsA( PCSTR::null(), PCSTR::null(), None, &mut 0, PERF_DETAIL_NOVICE, true, ); } if PdhOpenQueryA(PCSTR::null(), 0, &mut query) == ERROR_SUCCESS.0 { let q = InternalQuery { query, event: HANDLE::default(), data: HashMap::new(), }; Some(Query { internal: q }) } else { sysinfo_debug!("Query::new: PdhOpenQueryA failed"); None } } } #[allow(clippy::ptr_arg)] pub fn get(&self, name: &String) -> Option { if let Some(counter) = self.internal.data.get(name) { unsafe { let mut display_value = mem::MaybeUninit::::uninit(); return if PdhGetFormattedCounterValue( *counter, PDH_FMT_DOUBLE, None, display_value.as_mut_ptr(), ) == ERROR_SUCCESS.0 { let display_value = display_value.assume_init(); Some(display_value.Anonymous.doubleValue as f32) } else { sysinfo_debug!("Query::get: PdhGetFormattedCounterValue failed"); Some(0.) }; } } None } #[allow(clippy::ptr_arg)] pub fn add_english_counter(&mut self, name: &String, getter: Vec) -> bool { if self.internal.data.contains_key(name) { sysinfo_debug!("Query::add_english_counter: doesn't have key `{:?}`", name); return false; } unsafe { let mut counter = PDH_HCOUNTER::default(); let ret = PdhAddEnglishCounterW( self.internal.query, PCWSTR::from_raw(getter.as_ptr()), 0, &mut counter, ); if ret == ERROR_SUCCESS.0 { self.internal.data.insert(name.clone(), counter); } else { sysinfo_debug!( "Query::add_english_counter: failed to add counter '{}': {:x}...", name, ret, ); return false; } } true } pub fn refresh(&self) { unsafe { if PdhCollectQueryData(self.internal.query) != ERROR_SUCCESS.0 { sysinfo_debug!("failed to refresh CPU data"); } } } } pub(crate) struct CpusWrapper { pub(crate) global: CpuUsage, cpus: Vec, } impl CpusWrapper { pub fn new() -> Self { Self { global: CpuUsage { percent: 0f32, key_used: None, }, cpus: Vec::new(), } } pub fn global_cpu_usage(&self) -> f32 { self.global.percent } pub fn cpus(&self) -> &[Cpu] { &self.cpus } fn init_if_needed(&mut self, refresh_kind: CpuRefreshKind) { if self.cpus.is_empty() { self.cpus = init_cpus(refresh_kind); } } pub fn len(&mut self) -> usize { self.init_if_needed(CpuRefreshKind::nothing()); self.cpus.len() } pub fn iter_mut(&mut self, refresh_kind: CpuRefreshKind) -> impl Iterator { self.init_if_needed(refresh_kind); self.cpus.iter_mut() } pub fn get_frequencies(&mut self) { let frequencies = get_frequencies(self.cpus.len()); for (cpu, frequency) in self.cpus.iter_mut().zip(frequencies) { cpu.inner.set_frequency(frequency); } } } pub(crate) struct CpuUsage { percent: f32, pub(crate) key_used: Option, } impl CpuUsage { pub(crate) fn set_cpu_usage(&mut self, value: f32) { self.percent = value; } } pub(crate) struct CpuInner { name: String, vendor_id: String, usage: CpuUsage, brand: String, frequency: u64, } impl CpuInner { pub(crate) fn cpu_usage(&self) -> f32 { self.usage.percent } pub(crate) fn name(&self) -> &str { &self.name } pub(crate) fn frequency(&self) -> u64 { self.frequency } pub(crate) fn vendor_id(&self) -> &str { &self.vendor_id } pub(crate) fn brand(&self) -> &str { &self.brand } pub(crate) fn new_with_values( name: String, vendor_id: String, brand: String, frequency: u64, ) -> Self { Self { name, usage: CpuUsage { percent: 0f32, key_used: None, }, vendor_id, brand, frequency, } } pub(crate) fn set_cpu_usage(&mut self, value: f32) { self.usage.set_cpu_usage(value); } pub(crate) fn set_frequency(&mut self, value: u64) { self.frequency = value; } } fn get_vendor_id_not_great(info: &SYSTEM_INFO) -> String { // https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info unsafe { match info.Anonymous.Anonymous.wProcessorArchitecture { SystemInformation::PROCESSOR_ARCHITECTURE_INTEL => "Intel x86", SystemInformation::PROCESSOR_ARCHITECTURE_MIPS => "MIPS", SystemInformation::PROCESSOR_ARCHITECTURE_ALPHA => "RISC Alpha", SystemInformation::PROCESSOR_ARCHITECTURE_PPC => "PPC", SystemInformation::PROCESSOR_ARCHITECTURE_SHX => "SHX", SystemInformation::PROCESSOR_ARCHITECTURE_ARM => "ARM", SystemInformation::PROCESSOR_ARCHITECTURE_IA64 => "Intel Itanium-based x64", SystemInformation::PROCESSOR_ARCHITECTURE_ALPHA64 => "RISC Alpha x64", SystemInformation::PROCESSOR_ARCHITECTURE_MSIL => "MSIL", SystemInformation::PROCESSOR_ARCHITECTURE_AMD64 => "(Intel or AMD) x64", SystemInformation::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 => "Intel Itanium-based x86", SystemInformation::PROCESSOR_ARCHITECTURE_NEUTRAL => "unknown", SystemInformation::PROCESSOR_ARCHITECTURE_ARM64 => "ARM x64", SystemInformation::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 => "ARM", SystemInformation::PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 => "Intel Itanium-based x86", _ => "unknown", } .to_owned() } } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub(crate) fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) { #[cfg(target_arch = "x86")] use std::arch::x86::__cpuid; #[cfg(target_arch = "x86_64")] use std::arch::x86_64::__cpuid; unsafe fn add_u32(v: &mut Vec, i: u32) { let i = &i as *const u32 as *const u8; unsafe { v.push(*i); v.push(*i.offset(1)); v.push(*i.offset(2)); v.push(*i.offset(3)); } } unsafe { // First, we try to get the complete name. let res = __cpuid(0x80000000); let n_ex_ids = res.eax; let brand = if n_ex_ids >= 0x80000004 { let mut extdata = Vec::with_capacity(5); for i in 0x80000000..=n_ex_ids { extdata.push(__cpuid(i)); } // 4 * u32 * nb_entries let mut out = Vec::with_capacity(4 * std::mem::size_of::() * 3); for data in extdata.iter().take(5).skip(2) { add_u32(&mut out, data.eax); add_u32(&mut out, data.ebx); add_u32(&mut out, data.ecx); add_u32(&mut out, data.edx); } let mut pos = 0; for e in out.iter() { if *e == 0 { break; } pos += 1; } match std::str::from_utf8(&out[..pos]) { Ok(s) => s.to_owned(), _ => String::new(), } } else { String::new() }; // Failed to get full name, let's retry for the short version! let res = __cpuid(0); let mut x = Vec::with_capacity(3 * std::mem::size_of::()); add_u32(&mut x, res.ebx); add_u32(&mut x, res.edx); add_u32(&mut x, res.ecx); let mut pos = 0; for e in x.iter() { if *e == 0 { break; } pos += 1; } let vendor_id = match std::str::from_utf8(&x[..pos]) { Ok(s) => s.to_owned(), Err(_) => get_vendor_id_not_great(info), }; (vendor_id, brand) } } #[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))] pub(crate) fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) { (get_vendor_id_not_great(info), String::new()) } #[inline] pub(crate) fn get_key_used(p: &mut Cpu) -> &mut Option { &mut p.inner.usage.key_used } // From https://stackoverflow.com/a/43813138: // // If your PC has 64 or fewer logical cpus installed, the above code will work fine. However, // if your PC has more than 64 logical cpus installed, use GetActiveCpuCount() or // GetLogicalCpuInformation() to determine the total number of logical cpus installed. pub(crate) fn get_frequencies(nb_cpus: usize) -> Vec { let size = nb_cpus * mem::size_of::(); let mut infos: Vec = Vec::with_capacity(nb_cpus); unsafe { if CallNtPowerInformation( ProcessorInformation, None, 0, Some(infos.as_mut_ptr() as _), size as _, ) .is_ok() { infos.set_len(nb_cpus); // infos.Number return infos .into_iter() .map(|i| i.CurrentMhz as u64) .collect::>(); } } sysinfo_debug!("get_frequencies: CallNtPowerInformation failed"); vec![0; nb_cpus] } pub(crate) fn get_physical_core_count() -> Option { // We cannot use the number of cpus here to pre calculate the buf size. // `GetLogicalCpuInformationEx` with `RelationProcessorCore` passed to it not only returns // the logical cores but also numa nodes. // // GetLogicalCpuInformationEx: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex let mut needed_size = 0; unsafe { // This function call will always return an error as it only returns "success" when it // has written at least one item in the buffer (which it cannot do here). let _err = GetLogicalProcessorInformationEx(RelationAll, None, &mut needed_size); let mut buf: Vec = Vec::with_capacity(needed_size as _); loop { // Needs to be updated for `Vec::reserve` to actually add additional capacity if // `GetLogicalProcessorInformationEx` fails because the buffer isn't big enough. buf.set_len(needed_size as _); if GetLogicalProcessorInformationEx( RelationAll, Some(buf.as_mut_ptr().cast()), &mut needed_size, ) .is_ok() { break; } else { let e = Error::last_os_error(); // For some reasons, the function might return a size not big enough... match e.raw_os_error() { Some(value) if value == ERROR_INSUFFICIENT_BUFFER.0 as i32 => {} _ => { sysinfo_debug!( "get_physical_core_count: GetLogicalCpuInformationEx failed" ); return None; } } } let reserve = if needed_size as usize > buf.capacity() { needed_size as usize - buf.capacity() } else { 1 }; needed_size = match needed_size.checked_add(reserve as _) { Some(new_size) => new_size, None => { sysinfo_debug!( "get_physical_core_count: buffer size is too big ({} + {})", needed_size, reserve, ); return None; } }; buf.reserve(reserve); } buf.set_len(needed_size as _); let mut i = 0; let raw_buf = buf.as_ptr(); let mut count = 0; while i < buf.len() { let p = &*(raw_buf.add(i) as *const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX); i += p.Size as usize; if p.Relationship == RelationProcessorCore { // Only count the physical cores. count += 1; } } Some(count) } } fn init_cpus(refresh_kind: CpuRefreshKind) -> Vec { unsafe { let mut sys_info = SYSTEM_INFO::default(); GetSystemInfo(&mut sys_info); let (vendor_id, brand) = get_vendor_id_and_brand(&sys_info); let nb_cpus = sys_info.dwNumberOfProcessors as usize; let frequencies = if refresh_kind.frequency() { get_frequencies(nb_cpus) } else { vec![0; nb_cpus] }; let mut ret = Vec::with_capacity(nb_cpus + 1); for (nb, frequency) in frequencies.iter().enumerate() { ret.push(Cpu { inner: CpuInner::new_with_values( format!("CPU {}", nb + 1), vendor_id.clone(), brand.clone(), *frequency, ), }); } ret } } pub(crate) struct KeyHandler { pub unique_id: String, } impl KeyHandler { pub fn new(unique_id: String) -> Self { Self { unique_id } } } GuillaumeGomez-sysinfo-067dd61/src/windows/disk.rs000066400000000000000000000340661506747262600222760ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::HandleWrapper; use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage}; use std::ffi::{OsStr, OsString}; use std::mem::size_of; use std::os::windows::ffi::OsStringExt; use std::path::Path; use windows::Win32::Foundation::MAX_PATH; use windows::Win32::Storage::FileSystem::{ FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDiskFreeSpaceExW, GetDriveTypeW, GetVolumeInformationW, GetVolumePathNamesForVolumeNameW, }; use windows::Win32::System::IO::DeviceIoControl; use windows::Win32::System::Ioctl::{ DEVICE_SEEK_PENALTY_DESCRIPTOR, DISK_PERFORMANCE, IOCTL_DISK_PERFORMANCE, IOCTL_STORAGE_QUERY_PROPERTY, PropertyStandardQuery, STORAGE_PROPERTY_QUERY, StorageDeviceSeekPenaltyProperty, }; use windows::Win32::System::SystemServices::FILE_READ_ONLY_VOLUME; use windows::Win32::System::WindowsProgramming::{DRIVE_FIXED, DRIVE_REMOVABLE}; use windows::core::{Error, HRESULT, PCWSTR}; /// Creates a copy of the first zero-terminated wide string in `buf`. /// The copy includes the zero terminator. fn from_zero_terminated(buf: &[u16]) -> Vec { let end = buf.iter().position(|&x| x == 0).unwrap_or(buf.len()); buf[..=end].to_vec() } // Realistically, volume names are probably not longer than 44 characters, // but the example in the Microsoft documentation uses MAX_PATH as well. // https://learn.microsoft.com/en-us/windows/win32/fileio/displaying-volume-paths const VOLUME_NAME_SIZE: usize = MAX_PATH as usize + 1; const ERROR_NO_MORE_FILES: HRESULT = windows::Win32::Foundation::ERROR_NO_MORE_FILES.to_hresult(); const ERROR_MORE_DATA: HRESULT = windows::Win32::Foundation::ERROR_MORE_DATA.to_hresult(); /// Returns a list of zero-terminated wide strings containing volume GUID paths. /// Volume GUID paths have the form `\\?\{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\`. /// /// Rather confusingly, the Win32 API _also_ calls these "volume names". pub(crate) fn get_volume_guid_paths() -> Vec> { let mut volume_names = Vec::new(); unsafe { let mut buf = Box::new([0u16; VOLUME_NAME_SIZE]); let Ok(handle) = FindFirstVolumeW(&mut buf[..]) else { sysinfo_debug!( "Error: FindFirstVolumeW() = {:?}", Error::from_win32().code() ); return Vec::new(); }; volume_names.push(from_zero_terminated(&buf[..])); loop { if FindNextVolumeW(handle, &mut buf[..]).is_err() { if Error::from_win32().code() != ERROR_NO_MORE_FILES { sysinfo_debug!("Error: FindNextVolumeW = {}", Error::from_win32().code()); } break; } volume_names.push(from_zero_terminated(&buf[..])); } if FindVolumeClose(handle).is_err() { sysinfo_debug!("Error: FindVolumeClose = {:?}", Error::from_win32().code()); }; } volume_names } /// Given a volume GUID path (`\\?\{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\`), returns all /// volume paths (drive letters and mount paths) associated with it /// as zero terminated wide strings. /// /// # Safety /// `volume_name` must contain a zero-terminated wide string. pub(crate) unsafe fn get_volume_path_names_for_volume_name( volume_guid_path: &[u16], ) -> Vec> { let volume_guid_path = PCWSTR::from_raw(volume_guid_path.as_ptr()); // Initial buffer size is just a guess. There is no direct connection between MAX_PATH // the output of GetVolumePathNamesForVolumeNameW. let mut path_names_buf = vec![0u16; MAX_PATH as usize]; let mut path_names_output_size = 0u32; for _ in 0..10 { let volume_path_names = unsafe { GetVolumePathNamesForVolumeNameW( volume_guid_path, Some(path_names_buf.as_mut_slice()), &mut path_names_output_size, ) }; let code = volume_path_names.map_err(|_| Error::from_win32().code()); match code { Ok(()) => break, Err(ERROR_MORE_DATA) => { // We need a bigger buffer. path_names_output_size contains the required buffer size. path_names_buf = vec![0u16; path_names_output_size as usize]; continue; } Err(_e) => { sysinfo_debug!("Error: GetVolumePathNamesForVolumeNameW() = {}", _e); return Vec::new(); } } } // path_names_buf contains multiple zero terminated wide strings. // An additional zero terminates the list. let mut path_names = Vec::new(); let mut buf = &path_names_buf[..]; while !buf.is_empty() && buf[0] != 0 { let path = from_zero_terminated(buf); buf = &buf[path.len()..]; path_names.push(path); } path_names } pub(crate) struct DiskInner { type_: DiskKind, name: OsString, file_system: OsString, mount_point: Vec, s_mount_point: OsString, total_space: u64, available_space: u64, is_removable: bool, is_read_only: bool, device_path: Vec, old_written_bytes: u64, old_read_bytes: u64, written_bytes: u64, read_bytes: u64, updated: bool, } impl DiskInner { pub(crate) fn kind(&self) -> DiskKind { self.type_ } pub(crate) fn name(&self) -> &OsStr { &self.name } pub(crate) fn file_system(&self) -> &OsStr { &self.file_system } pub(crate) fn mount_point(&self) -> &Path { self.s_mount_point.as_ref() } pub(crate) fn total_space(&self) -> u64 { self.total_space } pub(crate) fn available_space(&self) -> u64 { self.available_space } pub(crate) fn is_removable(&self) -> bool { self.is_removable } pub(crate) fn is_read_only(&self) -> bool { self.is_read_only } pub(crate) fn refresh_specifics(&mut self, refreshes: DiskRefreshKind) -> bool { if refreshes.kind() || refreshes.io_usage() { unsafe { if let Some(handle) = HandleWrapper::new_from_file(&self.device_path, Default::default()) { if refreshes.kind() && self.type_ == DiskKind::Unknown(-1) { self.type_ = get_disk_kind(&handle); } if refreshes.io_usage() { if let Some((read_bytes, written_bytes)) = get_disk_io(handle) { self.old_read_bytes = self.read_bytes; self.old_written_bytes = self.written_bytes; self.read_bytes = read_bytes; self.written_bytes = written_bytes; } else { sysinfo_debug!("Failed to update disk i/o stats"); } } } } } if refreshes.storage() && let Some((total_space, available_space)) = unsafe { get_drive_size(&self.mount_point) } { self.total_space = total_space; self.available_space = available_space; } true } pub(crate) fn usage(&self) -> DiskUsage { DiskUsage { read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, } } } pub(crate) struct DisksInner { pub(crate) disks: Vec, } impl DisksInner { pub(crate) fn new() -> Self { Self { disks: Vec::with_capacity(2), } } pub(crate) fn from_vec(disks: Vec) -> Self { Self { disks } } pub(crate) fn into_vec(self) -> Vec { self.disks } pub(crate) fn refresh_specifics( &mut self, remove_not_listed_disks: bool, refreshes: DiskRefreshKind, ) { unsafe { get_list(&mut self.disks, remove_not_listed_disks, refreshes); } } pub(crate) fn list(&self) -> &[Disk] { &self.disks } pub(crate) fn list_mut(&mut self) -> &mut [Disk] { &mut self.disks } } unsafe fn get_drive_size(mount_point: &[u16]) -> Option<(u64, u64)> { let mut total_size = 0; let mut available_space = 0; let lpdirectoryname = PCWSTR::from_raw(mount_point.as_ptr()); if unsafe { GetDiskFreeSpaceExW( lpdirectoryname, None, Some(&mut total_size), Some(&mut available_space), ) } .is_ok() { Some((total_size, available_space)) } else { None } } pub(crate) unsafe fn get_list( disks: &mut Vec, remove_not_listed_disks: bool, refreshes: DiskRefreshKind, ) { for volume_name in get_volume_guid_paths() { let mount_paths = unsafe { get_volume_path_names_for_volume_name(&volume_name[..]) }; if mount_paths.is_empty() { continue; } let raw_volume_name = PCWSTR::from_raw(volume_name.as_ptr()); let drive_type = unsafe { GetDriveTypeW(raw_volume_name) }; if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE { continue; } let is_removable = drive_type == DRIVE_REMOVABLE; let mut name = [0u16; MAX_PATH as usize + 1]; let mut file_system = [0u16; 32]; let mut flags = 0; let volume_info_res = unsafe { GetVolumeInformationW( raw_volume_name, Some(&mut name), None, None, Some(&mut flags), Some(&mut file_system), ) } .is_ok(); if !volume_info_res { sysinfo_debug!( "Error: GetVolumeInformationW = {:?}", Error::from_win32().code() ); continue; } let is_read_only = (flags & FILE_READ_ONLY_VOLUME) != 0; // The device path is the volume name without the trailing backslash. let device_path = volume_name[..(volume_name.len() - 2)] .iter() .copied() .chain([0]) .collect::>(); let name = os_string_from_zero_terminated(&name); let file_system = os_string_from_zero_terminated(&file_system); for mount_path in mount_paths { if let Some(disk) = disks .iter_mut() .find(|d| d.inner.mount_point == mount_path && d.inner.file_system == file_system) { disk.refresh_specifics(refreshes); disk.inner.updated = true; continue; } let mut disk = DiskInner { type_: DiskKind::Unknown(-1), name: name.clone(), file_system: file_system.clone(), s_mount_point: OsString::from_wide(&mount_path[..mount_path.len() - 1]), mount_point: mount_path, total_space: 0, available_space: 0, is_removable, is_read_only, device_path: device_path.clone(), old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, updated: true, }; disk.refresh_specifics(refreshes); disks.push(Disk { inner: disk }); } } if remove_not_listed_disks { disks.retain_mut(|disk| { if !disk.inner.updated { return false; } disk.inner.updated = false; true }); } else { for c in disks.iter_mut() { c.inner.updated = false; } } } fn os_string_from_zero_terminated(name: &[u16]) -> OsString { let len = name.iter().position(|&x| x == 0).unwrap_or(name.len()); OsString::from_wide(&name[..len]) } unsafe fn get_disk_kind(handle: &HandleWrapper) -> DiskKind { let spq_trim = STORAGE_PROPERTY_QUERY { PropertyId: StorageDeviceSeekPenaltyProperty, QueryType: PropertyStandardQuery, AdditionalParameters: [0], }; let mut result: DEVICE_SEEK_PENALTY_DESCRIPTOR = unsafe { std::mem::zeroed() }; let mut dw_size = 0; let device_io_control = unsafe { DeviceIoControl( handle.0, IOCTL_STORAGE_QUERY_PROPERTY, Some(&spq_trim as *const STORAGE_PROPERTY_QUERY as *const _), size_of::() as _, Some(&mut result as *mut DEVICE_SEEK_PENALTY_DESCRIPTOR as *mut _), size_of::() as _, Some(&mut dw_size), None, ) .is_ok() }; if !device_io_control || dw_size != size_of::() as u32 { DiskKind::Unknown(-1) } else { let is_hdd = result.IncursSeekPenalty; if is_hdd { DiskKind::HDD } else { DiskKind::SSD } } } /// Returns a tuple consisting of the total number of bytes read and written by the volume with the /// specified device path fn get_disk_io(handle: HandleWrapper) -> Option<(u64, u64)> { let mut disk_perf = DISK_PERFORMANCE::default(); let mut bytes_returned = 0; // SAFETY: the handle is checked for validity above unsafe { // See for reference DeviceIoControl( handle.0, IOCTL_DISK_PERFORMANCE, None, // Must be None as per docs 0, Some(&mut disk_perf as *mut _ as _), size_of::() as u32, Some(&mut bytes_returned), None, ) } .inspect_err(|_err| { sysinfo_debug!( "Error: DeviceIoControl(IOCTL_DISK_PERFORMANCE) = {:?}", _err ); }) .ok()?; Some(( disk_perf.BytesRead.try_into().ok()?, disk_perf.BytesWritten.try_into().ok()?, )) } GuillaumeGomez-sysinfo-067dd61/src/windows/ffi.rs000066400000000000000000000037671506747262600221140ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub trait SMBIOSType { fn length(&self) -> u8; } // Described in table 11 in the standard // little endian: time_low, time_mid, time_hi_and_version // big endian: time_hi_and_version, clock_seq_low, node #[repr(C, packed)] pub struct SMBIOSUuid { pub time_low: u32, pub time_mid: u16, pub time_hi_and_version: u16, pub clock_seq_hi_and_reserved: u8, pub clock_seq_low: u8, pub node: [u8; 6], } impl std::fmt::Display for SMBIOSUuid { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", u32::from_le(self.time_low), u16::from_le(self.time_mid), u16::from_le(self.time_hi_and_version), self.clock_seq_hi_and_reserved, self.clock_seq_low, self.node[0], self.node[1], self.node[2], self.node[3], self.node[4], self.node[5], ) } } #[repr(C, packed)] pub(crate) struct SMBIOSSystemInformation { pub(crate) _type: u8, pub(crate) length: u8, pub(crate) _handle: u16, pub(crate) manufacturer: u8, pub(crate) product_name: u8, pub(crate) version: u8, pub(crate) serial_number: u8, pub(crate) uuid: SMBIOSUuid, pub(crate) wake_up_type: u8, pub(crate) sku_number: u8, pub(crate) family: u8, } impl SMBIOSType for SMBIOSSystemInformation { fn length(&self) -> u8 { self.length } } #[repr(C, packed)] pub(crate) struct SMBIOSBaseboardInformation { pub(crate) _type: u8, pub(crate) length: u8, pub(crate) _handle: u16, pub(crate) manufacturer: u8, pub(crate) product_name: u8, pub(crate) version: u8, pub(crate) serial_number: u8, pub(crate) asset_tag: u8, } impl SMBIOSType for SMBIOSBaseboardInformation { fn length(&self) -> u8 { self.length } } GuillaumeGomez-sysinfo-067dd61/src/windows/groups.rs000066400000000000000000000042431506747262600226550ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::to_utf8_str; use crate::{Gid, Group, GroupInner}; use std::ptr::null_mut; use windows::Win32::Foundation::{ERROR_MORE_DATA, ERROR_SUCCESS}; use windows::Win32::NetworkManagement::NetManagement::{ LOCALGROUP_INFO_0, MAX_PREFERRED_LENGTH, NetApiBufferFree, NetLocalGroupEnum, }; impl GroupInner { pub(crate) fn new(id: Gid, name: String) -> Self { Self { id, name } } pub(crate) fn id(&self) -> &Gid { &self.id } pub(crate) fn name(&self) -> &str { &self.name } } struct NetApiBuffer(*mut LOCALGROUP_INFO_0); impl Drop for NetApiBuffer { fn drop(&mut self) { if !self.0.is_null() { unsafe { NetApiBufferFree(Some(self.0.cast())) }; } } } impl Default for NetApiBuffer { fn default() -> Self { Self(null_mut()) } } impl NetApiBuffer { pub fn inner_mut(&mut self) -> *mut *mut LOCALGROUP_INFO_0 { &mut self.0 as *mut _ } } pub(crate) fn get_groups(groups: &mut Vec) { groups.clear(); unsafe { let mut nb_entries = 0; let mut total_entries_hint = 0; let mut handle = 0; loop { let mut buff = NetApiBuffer::default(); let res = NetLocalGroupEnum( None, 0, // Level. Here, just get the group names. buff.inner_mut() as *mut _, MAX_PREFERRED_LENGTH, &mut nb_entries, &mut total_entries_hint, Some(&mut handle), ); if res != ERROR_SUCCESS.0 && res != ERROR_MORE_DATA.0 { sysinfo_debug!("NetLocalGroupEnum failed: {res:?}"); break; } let entries = std::slice::from_raw_parts(buff.0, nb_entries as usize); for entry in entries { let name = to_utf8_str(entry.lgrpi0_name); groups.push(Group { inner: GroupInner::new(Gid(0), name), }); } if res != ERROR_MORE_DATA.0 { break; } } } } GuillaumeGomez-sysinfo-067dd61/src/windows/mod.rs000066400000000000000000000035251506747262600221170ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. mod utils; cfg_if! { if #[cfg(feature = "system")] { mod cpu; mod ffi; mod motherboard; mod process; mod product; mod system; pub(crate) use self::cpu::CpuInner; pub(crate) use self::motherboard::MotherboardInner; pub(crate) use self::process::ProcessInner; pub(crate) use self::product::ProductInner; pub(crate) use self::system::SystemInner; pub use self::system::{MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS}; } if #[cfg(feature = "disk")] { mod disk; pub(crate) use self::disk::{DiskInner, DisksInner}; } if #[cfg(feature = "component")] { pub mod component; pub(crate) use self::component::{ComponentInner, ComponentsInner}; } if #[cfg(feature = "network")] { mod network; pub(crate) mod network_helper; pub(crate) use self::network::{NetworkDataInner, NetworksInner}; } if #[cfg(feature = "user")] { mod groups; mod users; pub(crate) use self::groups::get_groups; pub(crate) use self::users::get_users; pub(crate) use self::users::UserInner; } if #[cfg(any(feature = "user", feature = "system"))] { mod sid; pub(crate) use self::sid::Sid; } } #[doc = include_str!("../../md_doc/is_supported.md")] pub const IS_SUPPORTED_SYSTEM: bool = true; // Make formattable by rustfmt. #[cfg(any())] mod component; #[cfg(any())] mod cpu; #[cfg(any())] mod disk; #[cfg(any())] mod ffi; #[cfg(any())] mod groups; #[cfg(any())] mod motherboard; #[cfg(any())] mod network; #[cfg(any())] mod network_helper; #[cfg(any())] mod process; #[cfg(any())] mod product; #[cfg(any())] mod sid; #[cfg(any())] mod system; #[cfg(any())] mod users; GuillaumeGomez-sysinfo-067dd61/src/windows/motherboard.rs000066400000000000000000000041311506747262600236400ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::ffi::SMBIOSBaseboardInformation; use super::utils::{get_smbios_table, parse_smbios}; pub(crate) struct MotherboardInner { smbios_table: Vec, } impl MotherboardInner { pub(crate) fn new() -> Option { Some(Self { smbios_table: get_smbios_table()?, }) } pub(crate) fn asset_tag(&self) -> Option { let (info, strings) = parse_smbios::(&self.smbios_table, 2)?; if info.asset_tag == 0 { return None; } strings .get(info.asset_tag as usize - 1) .copied() .map(str::to_string) } pub(crate) fn name(&self) -> Option { let (info, strings) = parse_smbios::(&self.smbios_table, 2)?; if info.product_name == 0 { return None; } strings .get(info.product_name as usize - 1) .copied() .map(str::to_string) } pub(crate) fn vendor_name(&self) -> Option { let (info, strings) = parse_smbios::(&self.smbios_table, 2)?; if info.manufacturer == 0 { return None; } strings .get(info.manufacturer as usize - 1) .copied() .map(str::to_string) } pub(crate) fn version(&self) -> Option { let (info, strings) = parse_smbios::(&self.smbios_table, 2)?; if info.version == 0 { return None; } strings .get(info.version as usize - 1) .copied() .map(str::to_string) } pub(crate) fn serial_number(&self) -> Option { let (info, strings) = parse_smbios::(&self.smbios_table, 2)?; if info.serial_number == 0 { return None; } strings .get(info.serial_number as usize - 1) .copied() .map(str::to_string) } } GuillaumeGomez-sysinfo-067dd61/src/windows/network.rs000066400000000000000000000206341506747262600230310ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::network::refresh_networks_addresses; use crate::{IpNetwork, MacAddr, NetworkData}; use std::collections::{HashMap, hash_map}; use windows::Win32::NetworkManagement::IpHelper::{FreeMibTable, GetIfTable2, MIB_IF_TABLE2}; use windows::Win32::NetworkManagement::Ndis::MediaConnectStateDisconnected; macro_rules! old_and_new { ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ $ty_.$old = $ty_.$name; $ty_.$name = $new_val; }}; } pub(crate) struct NetworksInner { pub(crate) interfaces: HashMap, } impl NetworksInner { pub(crate) fn new() -> Self { Self { interfaces: HashMap::new(), } } pub(crate) fn list(&self) -> &HashMap { &self.interfaces } pub(crate) fn refresh(&mut self, remove_not_listed_interfaces: bool) { let mut table: *mut MIB_IF_TABLE2 = std::ptr::null_mut(); unsafe { if GetIfTable2(&mut table).is_err() { return; } for (_, data) in self.interfaces.iter_mut() { data.inner.updated = false; } // In here, this is tricky: we have to filter out the software interfaces to only keep // the hardware ones. To do so, we first check the connection potential speed (if 0, not // interesting), then we check its state: if not open, not interesting either. And finally, // we count the members of a same group: if there is more than 1, then it's software level. let mut groups = HashMap::new(); let mut indexes = Vec::new(); let ptr = (*table).Table.as_ptr(); for i in 0..(*table).NumEntries { let ptr = &*ptr.offset(i as _); if (ptr.TransmitLinkSpeed == 0 && ptr.ReceiveLinkSpeed == 0) || ptr.MediaConnectState == MediaConnectStateDisconnected || ptr.PhysicalAddressLength == 0 { continue; } let id = vec![ ptr.InterfaceGuid.data2, ptr.InterfaceGuid.data3, ptr.InterfaceGuid.data4[0] as _, ptr.InterfaceGuid.data4[1] as _, ptr.InterfaceGuid.data4[2] as _, ptr.InterfaceGuid.data4[3] as _, ptr.InterfaceGuid.data4[4] as _, ptr.InterfaceGuid.data4[5] as _, ptr.InterfaceGuid.data4[6] as _, ptr.InterfaceGuid.data4[7] as _, ]; let entry = groups.entry(id.clone()).or_insert(0); *entry += 1; if *entry > 1 { continue; } indexes.push((i, id)); } for (i, id) in indexes { let ptr = &*ptr.offset(i as _); if *groups.get(&id).unwrap_or(&0) > 1 { continue; } let mut pos = 0; for x in ptr.Alias.iter() { if *x == 0 { break; } pos += 1; } let interface_name = match String::from_utf16(&ptr.Alias[..pos]) { Ok(s) => s, _ => continue, }; let mtu = ptr.Mtu as u64; match self.interfaces.entry(interface_name) { hash_map::Entry::Occupied(mut e) => { let interface = e.get_mut(); let interface = &mut interface.inner; old_and_new!(interface, current_out, old_out, ptr.OutOctets); old_and_new!(interface, current_in, old_in, ptr.InOctets); old_and_new!( interface, packets_in, old_packets_in, ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts) ); old_and_new!( interface, packets_out, old_packets_out, ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts) ); old_and_new!(interface, errors_in, old_errors_in, ptr.InErrors); old_and_new!(interface, errors_out, old_errors_out, ptr.OutErrors); if interface.mtu != mtu { interface.mtu = mtu; } interface.updated = true; } hash_map::Entry::Vacant(e) => { let packets_in = ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts); let packets_out = ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts); e.insert(NetworkData { inner: NetworkDataInner { current_out: ptr.OutOctets, old_out: ptr.OutOctets, current_in: ptr.InOctets, old_in: ptr.InOctets, packets_in, old_packets_in: packets_in, packets_out, old_packets_out: packets_out, errors_in: ptr.InErrors, old_errors_in: ptr.InErrors, errors_out: ptr.OutErrors, old_errors_out: ptr.OutErrors, mac_addr: MacAddr::UNSPECIFIED, ip_networks: vec![], mtu, updated: true, }, }); } } } FreeMibTable(table as _); } if remove_not_listed_interfaces { // Remove interfaces which are gone. self.interfaces.retain(|_, i| { if !i.inner.updated { return false; } i.inner.updated = false; true }); } // Refresh all interfaces' addresses. refresh_networks_addresses(&mut self.interfaces); } } pub(crate) struct NetworkDataInner { current_out: u64, old_out: u64, current_in: u64, old_in: u64, packets_in: u64, old_packets_in: u64, packets_out: u64, old_packets_out: u64, errors_in: u64, old_errors_in: u64, errors_out: u64, old_errors_out: u64, updated: bool, pub(crate) mac_addr: MacAddr, pub(crate) ip_networks: Vec, /// Interface Maximum Transfer Unit (MTU) mtu: u64, } impl NetworkDataInner { pub(crate) fn received(&self) -> u64 { self.current_in.saturating_sub(self.old_in) } pub(crate) fn total_received(&self) -> u64 { self.current_in } pub(crate) fn transmitted(&self) -> u64 { self.current_out.saturating_sub(self.old_out) } pub(crate) fn total_transmitted(&self) -> u64 { self.current_out } pub(crate) fn packets_received(&self) -> u64 { self.packets_in.saturating_sub(self.old_packets_in) } pub(crate) fn total_packets_received(&self) -> u64 { self.packets_in } pub(crate) fn packets_transmitted(&self) -> u64 { self.packets_out.saturating_sub(self.old_packets_out) } pub(crate) fn total_packets_transmitted(&self) -> u64 { self.packets_out } pub(crate) fn errors_on_received(&self) -> u64 { self.errors_in.saturating_sub(self.old_errors_in) } pub(crate) fn total_errors_on_received(&self) -> u64 { self.errors_in } pub(crate) fn errors_on_transmitted(&self) -> u64 { self.errors_out.saturating_sub(self.old_errors_out) } pub(crate) fn total_errors_on_transmitted(&self) -> u64 { self.errors_out } pub(crate) fn mac_address(&self) -> MacAddr { self.mac_addr } pub(crate) fn ip_networks(&self) -> &[IpNetwork] { &self.ip_networks } pub(crate) fn mtu(&self) -> u64 { self.mtu } } GuillaumeGomez-sysinfo-067dd61/src/windows/network_helper.rs000066400000000000000000000143561506747262600243740ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::{HashMap, HashSet}; use std::net::IpAddr; use std::ptr::{NonNull, null_mut}; use windows::Win32::Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS}; use windows::Win32::NetworkManagement::IpHelper::{ GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_MULTICAST, GetAdaptersAddresses, IP_ADAPTER_ADDRESSES_LH, IP_ADAPTER_UNICAST_ADDRESS_LH, }; use windows::Win32::Networking::WinSock::{ AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, }; use crate::{IpNetwork, MacAddr}; /// this iterator yields an interface name and address pub(crate) struct InterfaceAddressIterator { /// The first item in the linked list buf: *mut IP_ADAPTER_ADDRESSES_LH, /// The current adapter adapter: *mut IP_ADAPTER_ADDRESSES_LH, } impl InterfaceAddressIterator { fn new() -> Self { Self { buf: null_mut(), adapter: null_mut(), } } unsafe fn realloc(mut self, size: libc::size_t) -> Result { let new_buf = unsafe { libc::realloc(self.buf as _, size) as *mut IP_ADAPTER_ADDRESSES_LH }; if new_buf.is_null() { // insufficient memory available // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/malloc?view=msvc-170#return-value // malloc is not documented to set the last-error code Err("failed to allocate memory for IP_ADAPTER_ADDRESSES".to_string()) } else { self.buf = new_buf; self.adapter = new_buf; Ok(self) } } } impl Iterator for InterfaceAddressIterator { type Item = (String, MacAddr); fn next(&mut self) -> Option { if self.adapter.is_null() { return None; } unsafe { let adapter = self.adapter; // Move to the next adapter self.adapter = (*adapter).Next; if let Ok(interface_name) = (*adapter).FriendlyName.to_string() { // take the first 6 bytes and return the MAC address instead let [mac @ .., _, _] = (*adapter).PhysicalAddress; Some((interface_name, MacAddr(mac))) } else { // Not sure whether error can occur when parsing adapter name. self.next() } } } } impl InterfaceAddressIterator { pub fn generate_ip_networks(&mut self) -> HashMap> { let mut results = HashMap::new(); while !self.adapter.is_null() { unsafe { let adapter = self.adapter; // Move to the next adapter self.adapter = (*adapter).Next; if let Ok(interface_name) = (*adapter).FriendlyName.to_string() { let ip_networks = get_ip_networks((*adapter).FirstUnicastAddress); results.insert(interface_name, ip_networks); } } } results } } pub(crate) unsafe fn get_interface_ip_networks() -> HashMap> { match unsafe { get_interface_address() } { Ok(mut interface_iter) => interface_iter.generate_ip_networks(), _ => HashMap::new(), } } impl Drop for InterfaceAddressIterator { fn drop(&mut self) { unsafe { libc::free(self.buf as _); } } } pub(crate) unsafe fn get_interface_address() -> Result { // https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#remarks // A 15k buffer is recommended let mut size: u32 = 15 * 1024; let mut ret = ERROR_SUCCESS.0; let mut iterator = InterfaceAddressIterator::new(); // https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#examples // Try to retrieve adapter information up to 3 times for _ in 0..3 { unsafe { iterator = iterator.realloc(size as _)?; ret = GetAdaptersAddresses( AF_UNSPEC.0.into(), GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER, None, Some(iterator.buf), &mut size, ); if ret == ERROR_SUCCESS.0 { return Ok(iterator); } else if ret != ERROR_BUFFER_OVERFLOW.0 { break; } } // if the given memory size is too small to hold the adapter information, // the SizePointer returned will point to the required size of the buffer, // and we should continue. // Otherwise, break the loop and check the return code again } Err(format!("GetAdaptersAddresses() failed with code {ret}")) } fn get_ip_networks(mut prefixes_ptr: *mut IP_ADAPTER_UNICAST_ADDRESS_LH) -> HashSet { let mut ip_networks = HashSet::new(); while !prefixes_ptr.is_null() { let prefix = unsafe { prefixes_ptr.read_unaligned() }; if let Some(socket_address) = NonNull::new(prefix.Address.lpSockaddr) && let Some(ipaddr) = get_ip_address_from_socket_address(socket_address) { ip_networks.insert(IpNetwork { addr: ipaddr, prefix: prefix.OnLinkPrefixLength, }); } prefixes_ptr = prefix.Next; } ip_networks } /// Converts a Windows socket address to an ip address. fn get_ip_address_from_socket_address(socket_address: NonNull) -> Option { let socket_address_family = unsafe { socket_address.as_ref().sa_family }; match socket_address_family { AF_INET => { let socket_address = unsafe { socket_address.cast::().as_ref() }; let address = unsafe { socket_address.sin_addr.S_un.S_addr }; let ipv4_address = IpAddr::from(address.to_ne_bytes()); Some(ipv4_address) } AF_INET6 => { let socket_address = unsafe { socket_address.cast::().as_ref() }; let address = unsafe { socket_address.sin6_addr.u.Byte }; let ipv6_address = IpAddr::from(address); Some(ipv6_address) } _ => None, } } GuillaumeGomez-sysinfo-067dd61/src/windows/process.rs000066400000000000000000001051321506747262600230130ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::system::is_proc_running; use crate::sys::utils::HandleWrapper; use crate::windows::Sid; use crate::{DiskUsage, Gid, Pid, ProcessRefreshKind, ProcessStatus, Signal, Uid}; use std::ffi::{OsStr, OsString}; use std::fmt; #[cfg(feature = "debug")] use std::io; use std::mem::{MaybeUninit, size_of, zeroed}; use std::os::windows::ffi::OsStringExt; use std::os::windows::process::{CommandExt, ExitStatusExt}; use std::path::{Path, PathBuf}; use std::process::{self, ExitStatus}; use std::ptr::null_mut; use std::str::{self, FromStr}; use std::sync::{Arc, OnceLock}; use std::time::Instant; use libc::c_void; use ntapi::ntrtl::RTL_USER_PROCESS_PARAMETERS; use ntapi::ntwow64::{PEB32, RTL_USER_PROCESS_PARAMETERS32}; use windows::Wdk::System::SystemServices::RtlGetVersion; use windows::Wdk::System::Threading::{ NtQueryInformationProcess, PROCESSINFOCLASS, ProcessBasicInformation, ProcessCommandLineInformation, ProcessWow64Information, }; use windows::Win32::Foundation::{ ERROR_INSUFFICIENT_BUFFER, FILETIME, HANDLE, HLOCAL, HMODULE, LocalFree, MAX_PATH, STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH, UNICODE_STRING, }; use windows::Win32::Security::{GetTokenInformation, TOKEN_QUERY, TOKEN_USER, TokenUser}; use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory; use windows::Win32::System::Diagnostics::ToolHelp::PROCESSENTRY32W; use windows::Win32::System::Memory::{ GetProcessHeap, HEAP_ZERO_MEMORY, HeapAlloc, HeapFree, MEMORY_BASIC_INFORMATION, VirtualQueryEx, }; use windows::Win32::System::ProcessStatus::{ GetModuleFileNameExW, GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS_EX, }; use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId; use windows::Win32::System::SystemInformation::OSVERSIONINFOEXW; use windows::Win32::System::Threading::{ CREATE_NO_WINDOW, GetExitCodeProcess, GetProcessHandleCount, GetProcessIoCounters, GetProcessTimes, GetSystemTimes, IO_COUNTERS, OpenProcess, OpenProcessToken, PEB, PROCESS_BASIC_INFORMATION, PROCESS_QUERY_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ, }; use windows::Win32::UI::Shell::CommandLineToArgvW; use windows::core::PCWSTR; use super::MINIMUM_CPU_UPDATE_INTERVAL; const FILETIMES_PER_MILLISECONDS: u64 = 10_000; // 100 nanosecond units impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { ProcessStatus::Run => "Runnable", _ => "Unknown", }) } } fn get_process_handler(pid: Pid) -> Option { if pid.0 == 0 { return None; } let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; HandleWrapper::new(unsafe { OpenProcess(options, false, pid.0 as u32).unwrap_or_default() }) .or_else(|| { sysinfo_debug!( "OpenProcess failed, error: {:?}", io::Error::last_os_error() ); HandleWrapper::new(unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid.0 as u32) .unwrap_or_default() }) }) .or_else(|| { sysinfo_debug!( "OpenProcess limited failed, error: {:?}", io::Error::last_os_error() ); None }) } unsafe fn get_process_user_id(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) { struct HeapWrap(*mut T); impl HeapWrap { unsafe fn new(size: u32) -> Option { unsafe { let ptr = HeapAlloc(GetProcessHeap().ok()?, HEAP_ZERO_MEMORY, size as _) as *mut T; if ptr.is_null() { sysinfo_debug!("HeapAlloc failed"); None } else { Some(Self(ptr)) } } } } impl Drop for HeapWrap { fn drop(&mut self) { if !self.0.is_null() { unsafe { if let Ok(heap) = GetProcessHeap() { let _err = HeapFree(heap, Default::default(), Some(self.0.cast())); } } } } } let handle = match &process.handle { // We get back the pointer so we don't need to clone the wrapping `Arc`. Some(handle) => ***handle, None => return, }; if !refresh_kind .user() .needs_update(|| process.user_id.is_none()) { return; } let mut token = Default::default(); unsafe { if OpenProcessToken(handle, TOKEN_QUERY, &mut token).is_err() { sysinfo_debug!("OpenProcessToken failed"); return; } let token = match HandleWrapper::new(token) { Some(token) => token, None => return, }; let mut size = 0; if let Err(err) = GetTokenInformation(*token, TokenUser, None, 0, &mut size) && err.code() != ERROR_INSUFFICIENT_BUFFER.to_hresult() { sysinfo_debug!("GetTokenInformation failed, error: {:?}", err); return; } let ptu: HeapWrap = match HeapWrap::new(size) { Some(ptu) => ptu, None => return, }; if let Err(_err) = GetTokenInformation(*token, TokenUser, Some(ptu.0.cast()), size, &mut size) { sysinfo_debug!( "GetTokenInformation failed (returned {_err:?}), error: {:?}", io::Error::last_os_error() ); return; } // We force this check to prevent overwriting a `Some` with a `None`. if let Some(uid) = Sid::from_psid((*ptu.0).User.Sid).map(Uid) { process.user_id = Some(uid); } } } #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for HandleWrapper {} unsafe impl Sync for HandleWrapper {} pub(crate) struct ProcessInner { name: OsString, cmd: Vec, exe: Option, pid: Pid, user_id: Option, environ: Vec, cwd: Option, root: Option, pub(crate) memory: u64, pub(crate) virtual_memory: u64, pub(crate) parent: Option, status: ProcessStatus, handle: Option>, cpu_calc_values: CPUsageCalculationValues, start_time: u64, pub(crate) run_time: u64, cpu_usage: f32, pub(crate) updated: bool, old_read_bytes: u64, old_written_bytes: u64, read_bytes: u64, written_bytes: u64, accumulated_cpu_time: u64, exists: bool, } struct CPUsageCalculationValues { old_process_sys_cpu: u64, old_process_user_cpu: u64, old_system_sys_cpu: u64, old_system_user_cpu: u64, last_update: Instant, } impl CPUsageCalculationValues { fn new() -> Self { CPUsageCalculationValues { old_process_sys_cpu: 0, old_process_user_cpu: 0, old_system_sys_cpu: 0, old_system_user_cpu: 0, last_update: Instant::now(), } } } fn windows_8_1_or_newer() -> &'static bool { static WINDOWS_8_1_OR_NEWER: OnceLock = OnceLock::new(); WINDOWS_8_1_OR_NEWER.get_or_init(|| unsafe { let mut version_info: OSVERSIONINFOEXW = MaybeUninit::zeroed().assume_init(); version_info.dwOSVersionInfoSize = std::mem::size_of::() as u32; if RtlGetVersion((&mut version_info as *mut OSVERSIONINFOEXW).cast()).is_err() { return true; } // Windows 8.1 is 6.3 version_info.dwMajorVersion > 6 || version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 3 }) } unsafe fn get_exe(process_handler: &HandleWrapper) -> Option { let mut exe_buf = [0u16; MAX_PATH as usize + 1]; unsafe { GetModuleFileNameExW( Some(**process_handler), Some(HMODULE::default()), exe_buf.as_mut_slice(), ); Some(PathBuf::from(null_terminated_wchar_to_string(&exe_buf))) } } impl ProcessInner { pub(crate) fn new(pid: Pid, parent: Option, now: u64, name: OsString) -> Self { let (handle, start_time, run_time) = if let Some(handle) = get_process_handler(pid) { let (start_time, run_time) = get_start_and_run_time(*handle, now); (Some(Arc::new(handle)), start_time, run_time) } else { (None, 0, 0) }; Self { handle, name, pid, parent, user_id: None, cmd: Vec::new(), environ: Vec::new(), exe: None, cwd: None, root: None, status: ProcessStatus::Run, memory: 0, virtual_memory: 0, cpu_usage: 0., cpu_calc_values: CPUsageCalculationValues::new(), start_time, run_time, updated: true, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, accumulated_cpu_time: 0, exists: true, } } pub(crate) fn update( &mut self, refresh_kind: crate::ProcessRefreshKind, nb_cpus: u64, now: u64, refresh_parent: bool, ) { if refresh_kind.cpu() { compute_cpu_usage(self, nb_cpus); } if refresh_kind.disk_usage() { update_disk_usage(self); } if refresh_kind.memory() { let mut mem_info = PROCESS_MEMORY_COUNTERS_EX::default(); if let Some(handle) = self.get_handle() { if let Err(_error) = unsafe { GetProcessMemoryInfo( handle, &mut mem_info as *mut _ as *mut _, std::mem::size_of_val::(&mem_info) as _, ) } { sysinfo_debug!("GetProcessMemoryInfo failed: {_error:?}"); } else { self.memory = mem_info.WorkingSetSize as _; self.virtual_memory = mem_info.PrivateUsage as _; } } } unsafe { get_process_user_id(self, refresh_kind); get_process_params(self, refresh_kind, refresh_parent); } if refresh_kind.exe().needs_update(|| self.exe.is_none()) { unsafe { self.exe = match self.handle.as_ref() { Some(handle) => get_exe(handle), None => get_executable_path(self.pid), }; } } self.run_time = now.saturating_sub(self.start_time()); self.updated = true; } pub(crate) fn from_process_entry(entry: &PROCESSENTRY32W, now: u64) -> Self { let pid = Pid::from_u32(entry.th32ProcessID); let name = match OsString::from_str( String::from_utf16_lossy(&entry.szExeFile).trim_end_matches('\0'), ) { Ok(name) => name, Err(_) => format!(" Process {pid}").into(), }; let ppid = { if entry.th32ParentProcessID == 0 { // no parent pid None } else { Some(Pid::from_u32(entry.th32ParentProcessID)) } }; Self::new(pid, ppid, now, name) } pub(crate) fn get_handle(&self) -> Option { self.handle.as_ref().map(|h| ***h) } pub(crate) fn kill_with(&self, signal: Signal) -> Option { crate::sys::system::convert_signal(signal)?; let mut kill = process::Command::new("taskkill.exe"); kill.arg("/PID").arg(self.pid.to_string()).arg("/F"); kill.creation_flags(CREATE_NO_WINDOW.0); match kill.output() { Ok(o) => Some(o.status.success()), Err(_) => Some(false), } } pub(crate) fn name(&self) -> &OsStr { &self.name } pub(crate) fn cmd(&self) -> &[OsString] { &self.cmd } pub(crate) fn exe(&self) -> Option<&Path> { self.exe.as_deref() } pub(crate) fn pid(&self) -> Pid { self.pid } pub(crate) fn environ(&self) -> &[OsString] { &self.environ } pub(crate) fn cwd(&self) -> Option<&Path> { self.cwd.as_deref() } pub(crate) fn root(&self) -> Option<&Path> { self.root.as_deref() } pub(crate) fn memory(&self) -> u64 { self.memory } pub(crate) fn virtual_memory(&self) -> u64 { self.virtual_memory } pub(crate) fn parent(&self) -> Option { self.parent } pub(crate) fn status(&self) -> ProcessStatus { self.status } pub(crate) fn start_time(&self) -> u64 { self.start_time } pub(crate) fn run_time(&self) -> u64 { self.run_time } pub(crate) fn cpu_usage(&self) -> f32 { self.cpu_usage } pub(crate) fn accumulated_cpu_time(&self) -> u64 { self.accumulated_cpu_time } pub(crate) fn disk_usage(&self) -> DiskUsage { DiskUsage { written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, } } pub(crate) fn user_id(&self) -> Option<&Uid> { self.user_id.as_ref() } pub(crate) fn effective_user_id(&self) -> Option<&Uid> { None } pub(crate) fn group_id(&self) -> Option { None } pub(crate) fn effective_group_id(&self) -> Option { None } pub(crate) fn wait(&self) -> Option { if let Some(handle) = self.get_handle() { while is_proc_running(handle) { if get_start_time(handle) != self.start_time() { // PID owner changed so the previous process was finished! sysinfo_debug!("PID owner changed so cannot get old process exit status"); return None; } std::thread::sleep(std::time::Duration::from_millis(10)); } let mut exit_status = 0; unsafe { match GetExitCodeProcess(handle, &mut exit_status) { Ok(_) => Some(ExitStatus::from_raw(exit_status)), Err(_error) => { sysinfo_debug!("failed to retrieve process exit status: {_error:?}"); None } } } } else { // In this case, we can't do anything so we just return. sysinfo_debug!("can't wait on this process so returning"); None } } pub(crate) fn session_id(&self) -> Option { unsafe { let mut out = 0; if ProcessIdToSessionId(self.pid.as_u32(), &mut out).is_ok() { return Some(Pid(out as _)); } sysinfo_debug!( "ProcessIdToSessionId failed, error: {:?}", io::Error::last_os_error() ); None } } pub(crate) fn switch_updated(&mut self) -> bool { std::mem::replace(&mut self.updated, false) } pub(crate) fn set_nonexistent(&mut self) { self.exists = false; } pub(crate) fn exists(&self) -> bool { self.exists } pub(crate) fn open_files(&self) -> Option { if let Some(ref handle) = self.handle { let mut handles_count = 0; unsafe { if let Err(_error) = GetProcessHandleCount(***handle, &mut handles_count) { sysinfo_debug!("GetProcessHandleCount failed: {_error:?}"); None } else { Some(handles_count as _) } } } else { None } } pub(crate) fn open_files_limit(&self) -> Option { crate::System::open_files_limit() } } #[inline] unsafe fn get_process_times(handle: HANDLE) -> u64 { unsafe { let mut fstart: FILETIME = zeroed(); let mut x = zeroed(); let _err = GetProcessTimes( handle, &mut fstart as *mut FILETIME, &mut x as *mut FILETIME, &mut x as *mut FILETIME, &mut x as *mut FILETIME, ); filetime_to_u64(fstart) } } // On Windows, the root folder is always the current drive. So we get it from its `cwd`. fn update_root(refresh_kind: ProcessRefreshKind, cwd: &Path, root: &mut Option) { if !refresh_kind.root().needs_update(|| root.is_none()) { return; } if cwd.has_root() { let mut ancestors = cwd.ancestors().peekable(); while let Some(path) = ancestors.next() { if ancestors.peek().is_none() { *root = Some(path.into()); return; } } } else { *root = None; } } #[inline] fn compute_start(process_times: u64) -> u64 { // 11_644_473_600 is the number of seconds between the Windows epoch (1601-01-01) and // the Linux epoch (1970-01-01). process_times / 10_000_000 - 11_644_473_600 } fn get_start_and_run_time(handle: HANDLE, now: u64) -> (u64, u64) { unsafe { let process_times = get_process_times(handle); let start = compute_start(process_times); let run_time = check_sub(now, start); (start, run_time) } } #[inline] pub(crate) fn get_start_time(handle: HANDLE) -> u64 { unsafe { let process_times = get_process_times(handle); compute_start(process_times) } } unsafe fn ph_query_process_variable_size( process_handle: HANDLE, process_information_class: PROCESSINFOCLASS, ) -> Option> { let mut return_length = MaybeUninit::::uninit(); unsafe { if let Err(err) = NtQueryInformationProcess( process_handle, process_information_class as _, null_mut(), 0, return_length.as_mut_ptr() as *mut _, ) .ok() && ![ STATUS_BUFFER_OVERFLOW.into(), STATUS_BUFFER_TOO_SMALL.into(), STATUS_INFO_LENGTH_MISMATCH.into(), ] .contains(&err.code()) { return None; } let mut return_length = return_length.assume_init(); let buf_len = (return_length as usize) / 2; let mut buffer: Vec = Vec::with_capacity(buf_len + 1); if NtQueryInformationProcess( process_handle, process_information_class as _, buffer.as_mut_ptr() as *mut _, return_length, &mut return_length as *mut _, ) .is_err() { return None; } buffer.set_len(buf_len); buffer.push(0); Some(buffer) } } unsafe fn get_cmdline_from_buffer(buffer: PCWSTR) -> Vec { // Get argc and argv from the command line let mut argc = MaybeUninit::::uninit(); unsafe { let argv_p = CommandLineToArgvW(buffer, argc.as_mut_ptr()); if argv_p.is_null() { return Vec::new(); } let argc = argc.assume_init(); let argv = std::slice::from_raw_parts(argv_p, argc as usize); let mut res = Vec::new(); for arg in argv { res.push(OsString::from_wide(arg.as_wide())); } let _err = LocalFree(Some(HLOCAL(argv_p as _))); res } } unsafe fn get_region_size(handle: HANDLE, ptr: *const c_void) -> Result { let mut meminfo = MaybeUninit::::uninit(); unsafe { if VirtualQueryEx( handle, Some(ptr), meminfo.as_mut_ptr().cast(), size_of::(), ) == 0 { return Err("Unable to read process memory information"); } let meminfo = meminfo.assume_init(); Ok((meminfo.RegionSize as isize - ptr.offset_from(meminfo.BaseAddress)) as usize) } } unsafe fn get_process_data( handle: HANDLE, ptr: *const c_void, size: usize, ) -> Result, &'static str> { let mut buffer: Vec = Vec::with_capacity(size / 2 + 1); let mut bytes_read = 0; unsafe { if ReadProcessMemory( handle, ptr, buffer.as_mut_ptr().cast(), size, Some(&mut bytes_read), ) .is_err() { return Err("Unable to read process data"); } // Documentation states that the function fails if not all data is accessible. if bytes_read != size { return Err("ReadProcessMemory returned unexpected number of bytes read"); } buffer.set_len(size / 2); buffer.push(0); } Ok(buffer) } trait RtlUserProcessParameters { fn get_cmdline(&self, handle: HANDLE) -> Result, &'static str>; fn get_cwd(&self, handle: HANDLE) -> Result, &'static str>; fn get_environ(&self, handle: HANDLE) -> Result, &'static str>; } macro_rules! impl_RtlUserProcessParameters { ($t:ty) => { impl RtlUserProcessParameters for $t { fn get_cmdline(&self, handle: HANDLE) -> Result, &'static str> { let ptr = self.CommandLine.Buffer; let size = self.CommandLine.Length; unsafe { get_process_data(handle, ptr as _, size as _) } } fn get_cwd(&self, handle: HANDLE) -> Result, &'static str> { let ptr = self.CurrentDirectory.DosPath.Buffer; let size = self.CurrentDirectory.DosPath.Length; unsafe { get_process_data(handle, ptr as _, size as _) } } fn get_environ(&self, handle: HANDLE) -> Result, &'static str> { let ptr = self.Environment; unsafe { let size = get_region_size(handle, ptr as _)?; get_process_data(handle, ptr as _, size as _) } } } }; } impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS32); impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS); fn has_anything_to_update(process: &ProcessInner, refresh_kind: ProcessRefreshKind) -> bool { refresh_kind.cmd().needs_update(|| process.cmd.is_empty()) || refresh_kind .environ() .needs_update(|| process.environ.is_empty()) || refresh_kind.cwd().needs_update(|| process.cwd.is_none()) || refresh_kind.root().needs_update(|| process.root.is_none()) } unsafe fn get_process_params( process: &mut ProcessInner, refresh_kind: ProcessRefreshKind, refresh_parent: bool, ) { let has_anything_to_update = has_anything_to_update(process, refresh_kind); if !refresh_parent && !has_anything_to_update { return; } let handle = match process.handle.as_ref().map(|handle| handle.0) { Some(h) => h, None => return, }; // First check if target process is running in wow64 compatibility emulator let mut pwow32info = MaybeUninit::<*const c_void>::uninit(); unsafe { if NtQueryInformationProcess( handle, ProcessWow64Information, pwow32info.as_mut_ptr().cast(), size_of::<*const c_void>() as u32, null_mut(), ) .is_err() { sysinfo_debug!("Unable to check WOW64 information about the process"); return; } let pwow32info = pwow32info.assume_init(); // Get parent and PEB64 from PROCESS_BASIC_INFORMATION // PEB64 will only be used if the target is 64 bit process let pinfo = if refresh_parent || pwow32info.is_null() { let mut pbasicinfo = MaybeUninit::::uninit(); if NtQueryInformationProcess( handle, ProcessBasicInformation, pbasicinfo.as_mut_ptr().cast(), size_of::() as u32, null_mut(), ) .is_err() { sysinfo_debug!("Unable to get basic process information"); return; } let pinfo = pbasicinfo.assume_init(); let ppid: usize = pinfo.InheritedFromUniqueProcessId as _; let parent = if ppid != 0 { Some(Pid(pinfo.InheritedFromUniqueProcessId as _)) } else { None }; process.parent = parent; if !has_anything_to_update { return; } Some(pinfo) } else { None }; if pwow32info.is_null() { // target is a 64 bit process let Some(pinfo) = pinfo else { return }; let mut peb = MaybeUninit::::uninit(); if ReadProcessMemory( handle, pinfo.PebBaseAddress.cast(), peb.as_mut_ptr().cast(), size_of::(), None, ) .is_err() { sysinfo_debug!("Unable to read process PEB"); return; } let peb = peb.assume_init(); let mut proc_params = MaybeUninit::::uninit(); if ReadProcessMemory( handle, peb.ProcessParameters.cast(), proc_params.as_mut_ptr().cast(), size_of::(), None, ) .is_err() { sysinfo_debug!("Unable to read process parameters"); return; } let proc_params = proc_params.assume_init(); get_cmd_line(&proc_params, handle, refresh_kind, &mut process.cmd); get_proc_env(&proc_params, handle, refresh_kind, &mut process.environ); get_cwd_and_root( &proc_params, handle, refresh_kind, &mut process.cwd, &mut process.root, ); } // target is a 32 bit process in wow64 mode if !has_anything_to_update { return; } let mut peb32 = MaybeUninit::::uninit(); if ReadProcessMemory( handle, pwow32info, peb32.as_mut_ptr().cast(), size_of::(), None, ) .is_err() { sysinfo_debug!("Unable to read PEB32"); return; } let peb32 = peb32.assume_init(); let mut proc_params = MaybeUninit::::uninit(); if ReadProcessMemory( handle, peb32.ProcessParameters as *mut _, proc_params.as_mut_ptr().cast(), size_of::(), None, ) .is_err() { sysinfo_debug!("Unable to read 32 bit process parameters"); return; } let proc_params = proc_params.assume_init(); get_cmd_line(&proc_params, handle, refresh_kind, &mut process.cmd); get_proc_env(&proc_params, handle, refresh_kind, &mut process.environ); get_cwd_and_root( &proc_params, handle, refresh_kind, &mut process.cwd, &mut process.root, ); } } fn get_cwd_and_root( params: &T, handle: HANDLE, refresh_kind: ProcessRefreshKind, cwd: &mut Option, root: &mut Option, ) { let cwd_needs_update = refresh_kind.cwd().needs_update(|| cwd.is_none()); let root_needs_update = refresh_kind.root().needs_update(|| root.is_none()); if !cwd_needs_update && !root_needs_update { return; } match params.get_cwd(handle) { Ok(buffer) => unsafe { let tmp_cwd = PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())); // Should always be called after we refreshed `cwd`. update_root(refresh_kind, &tmp_cwd, root); if cwd_needs_update { *cwd = Some(tmp_cwd); } }, Err(_e) => { sysinfo_debug!("get_cwd_and_root failed to get data: {:?}", _e); *cwd = None; } } } unsafe fn null_terminated_wchar_to_string(slice: &[u16]) -> OsString { match slice.iter().position(|&x| x == 0) { Some(pos) => OsString::from_wide(&slice[..pos]), None => OsString::from_wide(slice), } } fn get_cmd_line_old(params: &T, handle: HANDLE) -> Vec { match params.get_cmdline(handle) { Ok(buffer) => unsafe { get_cmdline_from_buffer(PCWSTR::from_raw(buffer.as_ptr())) }, Err(_e) => { sysinfo_debug!("get_cmd_line_old failed to get data: {}", _e); Vec::new() } } } #[allow(clippy::cast_ptr_alignment)] fn get_cmd_line_new(handle: HANDLE) -> Vec { unsafe { if let Some(buffer) = ph_query_process_variable_size(handle, ProcessCommandLineInformation) { let buffer = (*(buffer.as_ptr() as *const UNICODE_STRING)).Buffer; get_cmdline_from_buffer(PCWSTR::from_raw(buffer.as_ptr())) } else { vec![] } } } fn get_cmd_line( params: &T, handle: HANDLE, refresh_kind: ProcessRefreshKind, cmd_line: &mut Vec, ) { if !refresh_kind.cmd().needs_update(|| cmd_line.is_empty()) { return; } if *windows_8_1_or_newer() { *cmd_line = get_cmd_line_new(handle); } else { *cmd_line = get_cmd_line_old(params, handle); } } fn get_proc_env( params: &T, handle: HANDLE, refresh_kind: ProcessRefreshKind, environ: &mut Vec, ) { if !refresh_kind.environ().needs_update(|| environ.is_empty()) { return; } match params.get_environ(handle) { Ok(buffer) => { let equals = "=".encode_utf16().next().unwrap(); let raw_env = buffer; environ.clear(); let mut begin = 0; while let Some(offset) = raw_env[begin..].iter().position(|&c| c == 0) { let end = begin + offset; if raw_env[begin..end].contains(&equals) { environ.push(OsString::from_wide(&raw_env[begin..end])); begin = end + 1; } else { break; } } } Err(_e) => { sysinfo_debug!("get_proc_env failed to get data: {}", _e); *environ = Vec::new(); } } } pub(crate) fn get_executable_path(_pid: Pid) -> Option { /*let where_req = format!("ProcessId={}", pid); if let Some(ret) = run_wmi(&["process", "where", &where_req, "get", "ExecutablePath"]) { for line in ret.lines() { if line.is_empty() || line == "ExecutablePath" { continue } return line.to_owned(); } }*/ None } #[inline] fn check_sub(a: u64, b: u64) -> u64 { if a < b { a } else { a - b } } /// Before changing this function, you must consider the following: /// pub(crate) fn compute_cpu_usage(p: &mut ProcessInner, nb_cpus: u64) { let need_update = p.cpu_calc_values.last_update.elapsed() >= MINIMUM_CPU_UPDATE_INTERVAL; unsafe { let mut ftime: FILETIME = zeroed(); let mut fsys: FILETIME = zeroed(); let mut fuser: FILETIME = zeroed(); let mut fglobal_idle_time: FILETIME = zeroed(); let mut fglobal_kernel_time: FILETIME = zeroed(); // notice that it includes idle time let mut fglobal_user_time: FILETIME = zeroed(); if let Some(handle) = p.get_handle() { let _err = GetProcessTimes(handle, &mut ftime, &mut ftime, &mut fsys, &mut fuser); } // system times have changed, we need to get most recent system times // and update the cpu times cache, as well as global_kernel_time and global_user_time let _err = GetSystemTimes( Some(&mut fglobal_idle_time), Some(&mut fglobal_kernel_time), Some(&mut fglobal_user_time), ); p.cpu_calc_values.last_update = Instant::now(); let sys = filetime_to_u64(fsys); let user = filetime_to_u64(fuser); let global_kernel_time = filetime_to_u64(fglobal_kernel_time); let global_user_time = filetime_to_u64(fglobal_user_time); p.accumulated_cpu_time = user.saturating_add(sys) / FILETIMES_PER_MILLISECONDS; if !need_update { return; } let delta_global_kernel_time = check_sub(global_kernel_time, p.cpu_calc_values.old_system_sys_cpu); let delta_global_user_time = check_sub(global_user_time, p.cpu_calc_values.old_system_user_cpu); let delta_user_time = check_sub(user, p.cpu_calc_values.old_process_user_cpu); let delta_sys_time = check_sub(sys, p.cpu_calc_values.old_process_sys_cpu); p.cpu_calc_values.old_process_user_cpu = user; p.cpu_calc_values.old_process_sys_cpu = sys; p.cpu_calc_values.old_system_user_cpu = global_user_time; p.cpu_calc_values.old_system_sys_cpu = global_kernel_time; let denominator = delta_global_user_time.saturating_add(delta_global_kernel_time) as f32; if denominator < 0.00001 { p.cpu_usage = 0.; return; } p.cpu_usage = 100.0 * (delta_user_time.saturating_add(delta_sys_time) as f32 / denominator) * nb_cpus as f32; } } pub(crate) fn update_disk_usage(p: &mut ProcessInner) { let mut counters = MaybeUninit::::uninit(); if let Some(handle) = p.get_handle() { unsafe { if GetProcessIoCounters(handle, counters.as_mut_ptr()).is_err() { sysinfo_debug!("GetProcessIoCounters call failed on process {}", p.pid()); } else { let counters = counters.assume_init(); p.old_read_bytes = p.read_bytes; p.old_written_bytes = p.written_bytes; p.read_bytes = counters.ReadTransferCount; p.written_bytes = counters.WriteTransferCount; } } } } #[inline(always)] const fn filetime_to_u64(ft: FILETIME) -> u64 { ((ft.dwHighDateTime as u64) << 32) | (ft.dwLowDateTime as u64) } GuillaumeGomez-sysinfo-067dd61/src/windows/product.rs000066400000000000000000000052061506747262600230160ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::ffi::SMBIOSSystemInformation; use super::utils::{get_smbios_table, parse_smbios}; pub(crate) struct ProductInner; impl ProductInner { pub(crate) fn family() -> Option { let table = get_smbios_table()?; let (info, strings) = parse_smbios::(&table, 1)?; if info.family == 0 { return None; } strings .get(info.family as usize - 1) .copied() .map(str::to_string) } pub(crate) fn name() -> Option { let table = get_smbios_table()?; let (info, strings) = parse_smbios::(&table, 1)?; if info.product_name == 0 { return None; } strings .get(info.product_name as usize - 1) .copied() .map(str::to_string) } pub(crate) fn serial_number() -> Option { let table = get_smbios_table()?; let (info, strings) = parse_smbios::(&table, 1)?; if info.serial_number == 0 { return None; } strings .get(info.serial_number as usize - 1) .copied() .map(str::to_string) } pub(crate) fn stock_keeping_unit() -> Option { let table = get_smbios_table()?; let (info, strings) = parse_smbios::(&table, 1)?; if info.sku_number == 0 { return None; } strings .get(info.sku_number as usize - 1) .copied() .map(str::to_string) } pub(crate) fn uuid() -> Option { let table = get_smbios_table()?; Some( parse_smbios::(&table, 1)? .0 .uuid .to_string(), ) } pub(crate) fn version() -> Option { let table = get_smbios_table()?; let (info, strings) = parse_smbios::(&table, 1)?; if info.version == 0 { return None; } strings .get(info.version as usize - 1) .copied() .map(str::to_string) } pub(crate) fn vendor_name() -> Option { let table = get_smbios_table()?; let (info, strings) = parse_smbios::(&table, 1)?; if info.manufacturer == 0 { return None; } strings .get(info.manufacturer as usize - 1) .copied() .map(str::to_string) } } GuillaumeGomez-sysinfo-067dd61/src/windows/sid.rs000066400000000000000000000125651506747262600221230ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::{fmt::Display, str::FromStr}; #[cfg(feature = "user")] use windows::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER; use windows::Win32::Foundation::{HLOCAL, LocalFree}; use windows::Win32::Security::Authorization::{ConvertSidToStringSidW, ConvertStringSidToSidW}; use windows::Win32::Security::{CopySid, GetLengthSid, IsValidSid, PSID}; #[cfg(feature = "user")] use windows::Win32::Security::{LookupAccountSidW, SidTypeUnknown}; use windows::core::{PCWSTR, PWSTR}; use crate::sys::utils::to_utf8_str; #[doc = include_str!("../../md_doc/sid.md")] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Sid { sid: Vec, } impl Sid { /// Creates an `Sid` by making a copy of the given raw SID. pub(crate) unsafe fn from_psid(psid: PSID) -> Option { if psid.is_invalid() { return None; } if unsafe { !IsValidSid(psid).as_bool() } { return None; } let length = unsafe { GetLengthSid(psid) }; let mut sid = vec![0; length as usize]; if unsafe { CopySid(length, PSID(sid.as_mut_ptr().cast()), psid).is_err() } { sysinfo_debug!("CopySid failed: {:?}", std::io::Error::last_os_error()); return None; } // We are making assumptions about the SID internal structure, // and these only hold if the revision is 1 // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid // Namely: // 1. SIDs can be compared directly (memcmp). // 2. Following from this, to hash a SID we can just hash its bytes. // These are the basis for deriving PartialEq, Eq, and Hash. // And since we also need PartialOrd and Ord, we might as well derive them // too. The default implementation will be consistent with Eq, // and we don't care about the actual order, just that there is one. // So it should all work out. // Why bother with this? Because it makes the implementation that // much simpler :) assert_eq!(sid[0], 1, "Expected SID revision to be 1"); Some(Self { sid }) } /// Retrieves the account name of this SID. #[cfg(feature = "user")] pub(crate) fn account_name(&self) -> Option { unsafe { let mut name_len = 0; let mut domain_len = 0; let mut name_use = SidTypeUnknown; let sid = PSID((self.sid.as_ptr() as *mut u8).cast()); if let Err(err) = LookupAccountSidW( PCWSTR::null(), sid, None, &mut name_len, None, &mut domain_len, &mut name_use, ) && err.code() != ERROR_INSUFFICIENT_BUFFER.to_hresult() { sysinfo_debug!("LookupAccountSidW failed: {:?}", err); return None; } let mut name = vec![0; name_len as usize]; // Reset length to 0 since we're still passing a NULL pointer // for the domain. domain_len = 0; if LookupAccountSidW( PCWSTR::null(), sid, Some(PWSTR::from_raw(name.as_mut_ptr())), &mut name_len, None, &mut domain_len, &mut name_use, ) .is_err() { sysinfo_debug!( "LookupAccountSidW failed: {:?}", std::io::Error::last_os_error() ); return None; } Some(to_utf8_str(PWSTR::from_raw(name.as_mut_ptr()))) } } } impl Display for Sid { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { unsafe fn convert_sid_to_string_sid(sid: PSID) -> Option { let mut string_sid = PWSTR::null(); unsafe { if let Err(_err) = ConvertSidToStringSidW(sid, &mut string_sid) { sysinfo_debug!("ConvertSidToStringSidW failed: {:?}", _err); return None; } let result = to_utf8_str(string_sid); let _err = LocalFree(Some(HLOCAL(string_sid.0 as _))); Some(result) } } let string_sid = unsafe { convert_sid_to_string_sid(PSID((self.sid.as_ptr() as *mut u8).cast())) }; let string_sid = string_sid.ok_or(std::fmt::Error)?; write!(f, "{string_sid}") } } impl FromStr for Sid { type Err = String; fn from_str(s: &str) -> Result { unsafe { let mut string_sid: Vec = s.encode_utf16().collect(); string_sid.push(0); let mut psid = PSID::default(); if let Err(err) = ConvertStringSidToSidW(PCWSTR::from_raw(string_sid.as_ptr()), &mut psid) { return Err(format!("ConvertStringSidToSidW failed: {err:?}")); } let sid = Self::from_psid(psid); let _err = LocalFree(Some(HLOCAL(psid.0 as _))); // Unwrapping because ConvertStringSidToSidW should've performed // all the necessary validations. If it returned an invalid SID, // we better fail fast. Ok(sid.unwrap()) } } } GuillaumeGomez-sysinfo-067dd61/src/windows/system.rs000066400000000000000000000513221506747262600226620ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, ProcessRefreshKind, ProcessesToUpdate, }; use crate::sys::cpu::*; use crate::{Process, ProcessInner}; use std::collections::HashMap; use std::ffi::OsStr; use std::mem::{size_of, zeroed}; use std::os::windows::ffi::OsStrExt; use std::time::{Duration, SystemTime}; use windows::Win32::Foundation::{self, HANDLE, STILL_ACTIVE}; use windows::Win32::System::Diagnostics::ToolHelp::{ CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW, TH32CS_SNAPPROCESS, }; use windows::Win32::System::ProcessStatus::{K32GetPerformanceInfo, PERFORMANCE_INFORMATION}; use windows::Win32::System::Registry::{ HKEY, HKEY_LOCAL_MACHINE, KEY_READ, REG_NONE, RegCloseKey, RegOpenKeyExW, RegQueryValueExW, }; use windows::Win32::System::SystemInformation::{self, GetSystemInfo}; use windows::Win32::System::SystemInformation::{ ComputerNamePhysicalDnsHostname, GetComputerNameExW, GetTickCount64, GlobalMemoryStatusEx, MEMORYSTATUSEX, SYSTEM_INFO, }; use windows::Win32::System::Threading::GetExitCodeProcess; use windows::core::{Owned, PCWSTR, PWSTR}; declare_signals! { (), Signal::Kill => (), _ => None, } #[doc = include_str!("../../md_doc/supported_signals.md")] pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals(); #[doc = include_str!("../../md_doc/minimum_cpu_update_interval.md")] pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200); const WINDOWS_ELEVEN_BUILD_NUMBER: u32 = 22000; impl SystemInner { fn is_windows_eleven() -> bool { WINDOWS_ELEVEN_BUILD_NUMBER <= Self::kernel_version() .unwrap_or_default() .parse() .unwrap_or(0) } } /// Calculates system boot time in seconds with improved precision. /// Uses nanoseconds throughout to avoid rounding errors in uptime calculation, /// converting to seconds only at the end for stable results. Result is capped /// within u64 limits to handle edge cases. unsafe fn boot_time() -> u64 { match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(n) => { let system_time_ns = n.as_nanos(); // milliseconds to nanoseconds let tick_count_ns = unsafe { GetTickCount64() } as u128 * 1_000_000; // nanoseconds to seconds let boot_time_sec = system_time_ns.saturating_sub(tick_count_ns) / 1_000_000_000; boot_time_sec.try_into().unwrap_or(u64::MAX) } Err(_e) => { sysinfo_debug!("Failed to compute boot time: {:?}", _e); 0 } } } pub(crate) struct SystemInner { process_list: HashMap, mem_total: u64, mem_available: u64, swap_total: u64, swap_used: u64, cpus: CpusWrapper, query: Option, } impl SystemInner { pub(crate) fn new() -> Self { Self { process_list: HashMap::with_capacity(500), mem_total: 0, mem_available: 0, swap_total: 0, swap_used: 0, cpus: CpusWrapper::new(), query: None, } } fn initialize_cpu_counters(&mut self, refresh_kind: CpuRefreshKind) { if let Some(ref mut query) = self.query { add_english_counter( r"\Processor(_Total)\% Idle Time".to_string(), query, &mut self.cpus.global.key_used, "tot_0".to_owned(), ); for (pos, proc_) in self.cpus.iter_mut(refresh_kind).enumerate() { add_english_counter( format!(r"\Processor({pos})\% Idle Time"), query, get_key_used(proc_), format!("{pos}_0"), ); } } } pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { if self.query.is_none() { self.query = Query::new(false); self.initialize_cpu_counters(refresh_kind); } else if self.cpus.global.key_used.is_none() { self.query = Query::new(true); self.initialize_cpu_counters(refresh_kind); } if let Some(ref mut query) = self.query { query.refresh(); let mut total_idle_time = None; if let Some(ref key_used) = self.cpus.global.key_used { total_idle_time = Some( query .get(&key_used.unique_id) .expect("global_key_idle disappeared"), ); } if let Some(total_idle_time) = total_idle_time { self.cpus.global.set_cpu_usage(100.0 - total_idle_time); } for cpu in self.cpus.iter_mut(refresh_kind) { let mut idle_time = None; if let Some(ref key_used) = *get_key_used(cpu) { idle_time = Some( query .get(&key_used.unique_id) .expect("key_used disappeared"), ); } if let Some(idle_time) = idle_time { cpu.inner.set_cpu_usage(100.0 - idle_time); } } if refresh_kind.frequency() { self.cpus.get_frequencies(); } } } pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) { self.cpus = CpusWrapper::new(); self.refresh_cpu_specifics(refresh_kind); } pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) { unsafe { if refresh_kind.ram() { let mut mem_info: MEMORYSTATUSEX = zeroed(); mem_info.dwLength = size_of::() as _; let _err = GlobalMemoryStatusEx(&mut mem_info); self.mem_total = mem_info.ullTotalPhys as _; self.mem_available = mem_info.ullAvailPhys as _; } if refresh_kind.swap() { let mut perf_info: PERFORMANCE_INFORMATION = zeroed(); if K32GetPerformanceInfo(&mut perf_info, size_of::() as _) .as_bool() { let page_size = perf_info.PageSize as u64; let physical_total = perf_info.PhysicalTotal as u64; let commit_limit = perf_info.CommitLimit as u64; let commit_total = perf_info.CommitTotal as u64; self.swap_total = page_size.saturating_mul(commit_limit.saturating_sub(physical_total)); self.swap_used = page_size.saturating_mul(commit_total.saturating_sub(physical_total)); } } } } pub(crate) fn cgroup_limits(&self) -> Option { None } #[allow(clippy::cast_ptr_alignment)] pub(crate) fn refresh_processes_specifics( &mut self, processes_to_update: ProcessesToUpdate<'_>, refresh_kind: ProcessRefreshKind, ) -> usize { #[inline(always)] fn real_filter(e: Pid, filter: &[Pid]) -> bool { filter.contains(&e) } #[inline(always)] fn empty_filter(_e: Pid, _filter: &[Pid]) -> bool { true } #[allow(clippy::type_complexity)] let (filter_array, filter_callback): ( &[Pid], &(dyn Fn(Pid, &[Pid]) -> bool + Sync + Send), ) = match processes_to_update { ProcessesToUpdate::All => (&[], &empty_filter), ProcessesToUpdate::Some(pids) => { if pids.is_empty() { return 0; } (pids, &real_filter) } }; let now = get_now(); let nb_cpus = if refresh_kind.cpu() { self.cpus.len() as u64 } else { 0 }; // Use the amazing and cool CreateToolhelp32Snapshot function. // Take a snapshot of all running processes. Match the result to an error let snapshot = match unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) } { Ok(handle) => handle, Err(_err) => { sysinfo_debug!( "Error capturing process snapshot: CreateToolhelp32Snapshot returned {}", _err ); return 0; } }; // This owns the above handle and makes sure that close will be called when dropped. let snapshot = unsafe { Owned::new(snapshot) }; // https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32w // Microsoft documentation states that for PROCESSENTRY32W, before calling Process32FirstW, // the 'dwSize' field MUST be set to the size of the PROCESSENTRY32W. Otherwise, Process32FirstW fails. let mut process_entry = PROCESSENTRY32W { dwSize: size_of::() as u32, ..Default::default() }; let mut num_procs = 0; // keep track of the number of updated processes let process_list = &mut self.process_list; // process the first process unsafe { if let Err(_error) = Process32FirstW(*snapshot, &mut process_entry) { sysinfo_debug!("Process32FirstW has failed: {_error:?}"); return 0; } } // Iterate over processes in the snapshot. // Use Process32NextW to process the next PROCESSENTRY32W in the snapshot loop { let proc_id = Pid::from_u32(process_entry.th32ProcessID); if filter_callback(proc_id, filter_array) { // exists already if let Some(p) = process_list.get_mut(&proc_id) { // Update with the most recent information let p = &mut p.inner; p.update(refresh_kind, nb_cpus, now, false); // Update parent process let parent = if process_entry.th32ParentProcessID == 0 { None } else { Some(Pid::from_u32(process_entry.th32ParentProcessID)) }; p.parent = parent; } else { // Make a new 'ProcessInner' using the Windows PROCESSENTRY32W struct. let mut p = ProcessInner::from_process_entry(&process_entry, now); p.update(refresh_kind, nb_cpus, now, false); process_list.insert(proc_id, Process { inner: p }); } num_procs += 1; } // nothing else to process if unsafe { Process32NextW(*snapshot, &mut process_entry).is_err() } { break; } } num_procs } pub(crate) fn processes(&self) -> &HashMap { &self.process_list } pub(crate) fn processes_mut(&mut self) -> &mut HashMap { &mut self.process_list } pub(crate) fn process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } pub(crate) fn global_cpu_usage(&self) -> f32 { self.cpus.global_cpu_usage() } pub(crate) fn cpus(&self) -> &[Cpu] { self.cpus.cpus() } pub(crate) fn total_memory(&self) -> u64 { self.mem_total } pub(crate) fn free_memory(&self) -> u64 { // MEMORYSTATUSEX doesn't report free memory self.mem_available } pub(crate) fn available_memory(&self) -> u64 { self.mem_available } pub(crate) fn used_memory(&self) -> u64 { self.mem_total - self.mem_available } pub(crate) fn total_swap(&self) -> u64 { self.swap_total } pub(crate) fn free_swap(&self) -> u64 { self.swap_total - self.swap_used } pub(crate) fn used_swap(&self) -> u64 { self.swap_used } pub(crate) fn uptime() -> u64 { unsafe { GetTickCount64() / 1_000 } } pub(crate) fn boot_time() -> u64 { unsafe { boot_time() } } pub(crate) fn load_average() -> LoadAvg { get_load_average() } pub(crate) fn name() -> Option { Some("Windows".to_owned()) } pub(crate) fn long_os_version() -> Option { if Self::is_windows_eleven() { return get_reg_string_value( HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", ) .map(|product_name| product_name.replace("Windows 10 ", "Windows 11 ")); } get_reg_string_value( HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", ) } pub(crate) fn host_name() -> Option { get_dns_hostname() } pub(crate) fn kernel_version() -> Option { get_reg_string_value( HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentBuildNumber", ) } pub(crate) fn os_version() -> Option { let build_number = get_reg_string_value( HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentBuildNumber", ) .unwrap_or_default(); let major = if Self::is_windows_eleven() { 11u32 } else { u32::from_le_bytes( get_reg_value_u32( HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", ) .unwrap_or_default(), ) }; Some(format!("{major} ({build_number})")) } pub(crate) fn distribution_id() -> String { std::env::consts::OS.to_owned() } pub(crate) fn distribution_id_like() -> Vec { Vec::new() } pub(crate) fn kernel_name() -> Option<&'static str> { Some("Windows") } pub(crate) fn cpu_arch() -> Option { unsafe { // https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info let mut info = SYSTEM_INFO::default(); GetSystemInfo(&mut info); match info.Anonymous.Anonymous.wProcessorArchitecture { SystemInformation::PROCESSOR_ARCHITECTURE_ALPHA => Some("alpha".to_string()), SystemInformation::PROCESSOR_ARCHITECTURE_ALPHA64 => Some("alpha64".to_string()), SystemInformation::PROCESSOR_ARCHITECTURE_AMD64 => Some("x86_64".to_string()), SystemInformation::PROCESSOR_ARCHITECTURE_ARM => Some("arm".to_string()), SystemInformation::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 => Some("arm".to_string()), SystemInformation::PROCESSOR_ARCHITECTURE_ARM64 => Some("arm64".to_string()), SystemInformation::PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 | SystemInformation::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 => { Some("ia32".to_string()) } SystemInformation::PROCESSOR_ARCHITECTURE_IA64 => Some("ia64".to_string()), SystemInformation::PROCESSOR_ARCHITECTURE_INTEL => Some("x86".to_string()), SystemInformation::PROCESSOR_ARCHITECTURE_MIPS => Some("mips".to_string()), SystemInformation::PROCESSOR_ARCHITECTURE_PPC => Some("powerpc".to_string()), _ => None, } } } pub(crate) fn physical_core_count() -> Option { get_physical_core_count() } pub(crate) fn open_files_limit() -> Option { // Apparently when using C run-time libraries, it's limited by _NHANDLE_. // It's a define: // // ``` // #define IOINFO_L2E 6 // #define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E) // #define IOINFO_ARRAYS 128 // #define _NHANDLE_ (IOINFO_ARRAYS * IOINFO_ARRAY_ELTS) // ``` // // So 128 * (1 << 6) = 8192 Some(8192) } } pub(crate) fn is_proc_running(handle: HANDLE) -> bool { let mut exit_code = 0; unsafe { GetExitCodeProcess(handle, &mut exit_code) }.is_ok() && exit_code == STILL_ACTIVE.0 as u32 } fn get_dns_hostname() -> Option { let mut buffer_size = 0; // Running this first to get the buffer size since the DNS name can be longer than MAX_COMPUTERNAME_LENGTH // setting the `lpBuffer` to null will return the buffer size // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw unsafe { let _err = GetComputerNameExW(ComputerNamePhysicalDnsHostname, None, &mut buffer_size); // Setting the buffer with the new length let mut buffer = vec![0_u16; buffer_size as usize]; // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-computer_name_format if GetComputerNameExW( ComputerNamePhysicalDnsHostname, Some(PWSTR::from_raw(buffer.as_mut_ptr())), &mut buffer_size, ) .is_ok() { if let Some(pos) = buffer.iter().position(|c| *c == 0) { buffer.resize(pos, 0); } return String::from_utf16(&buffer).ok(); } } sysinfo_debug!("Failed to get computer hostname"); None } fn add_english_counter( s: String, query: &mut super::cpu::Query, keys: &mut Option, counter_name: String, ) { let mut full = s.encode_utf16().collect::>(); full.push(0); if query.add_english_counter(&counter_name, full) { *keys = Some(KeyHandler::new(counter_name)); } } fn get_now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .map(|n| n.as_secs()) .unwrap_or(0) } fn utf16_str + ?Sized>(text: &S) -> Vec { OsStr::new(text) .encode_wide() .chain(Some(0)) .collect::>() } struct RegKey(HKEY); impl RegKey { unsafe fn open(hkey: HKEY, path: &[u16]) -> Option { let mut new_hkey = Default::default(); if unsafe { RegOpenKeyExW( hkey, PCWSTR::from_raw(path.as_ptr()), Some(0), KEY_READ, &mut new_hkey, ) } .is_err() { return None; } Some(Self(new_hkey)) } unsafe fn get_value( &self, field_name: &[u16], buf: &mut [u8], buf_len: &mut u32, ) -> windows::core::Result<()> { let mut buf_type = REG_NONE; unsafe { RegQueryValueExW( self.0, PCWSTR::from_raw(field_name.as_ptr()), None, Some(&mut buf_type), Some(buf.as_mut_ptr()), Some(buf_len), ) } .ok() } } impl Drop for RegKey { fn drop(&mut self) { let _err = unsafe { RegCloseKey(self.0) }; } } pub(crate) fn get_reg_string_value(hkey: HKEY, path: &str, field_name: &str) -> Option { let c_path = utf16_str(path); let c_field_name = utf16_str(field_name); unsafe { let new_key = RegKey::open(hkey, &c_path)?; let mut buf_len: u32 = 2048; let mut buf: Vec = Vec::with_capacity(buf_len as usize); loop { match new_key.get_value(&c_field_name, &mut buf, &mut buf_len) { Ok(()) => break, Err(err) if err.code() == Foundation::ERROR_MORE_DATA.to_hresult() => { // Needs to be updated for `Vec::reserve` to actually add additional capacity. buf.set_len(buf.capacity()); buf.reserve(buf_len as _); } _ => return None, } } buf.set_len(buf_len as _); let words = std::slice::from_raw_parts(buf.as_ptr() as *const u16, buf.len() / 2); let mut s = String::from_utf16_lossy(words); while s.ends_with('\u{0}') { s.pop(); } Some(s) } } pub(crate) fn get_reg_value_u32(hkey: HKEY, path: &str, field_name: &str) -> Option<[u8; 4]> { let c_path = utf16_str(path); let c_field_name = utf16_str(field_name); unsafe { let new_key = RegKey::open(hkey, &c_path)?; let mut buf_len: u32 = 4; let mut buf = [0u8; 4]; new_key .get_value(&c_field_name, &mut buf, &mut buf_len) .map(|_| buf) .ok() } } GuillaumeGomez-sysinfo-067dd61/src/windows/users.rs000066400000000000000000000221471506747262600225020ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::to_utf8_str; use crate::{Gid, Group, GroupInner, Uid, User, windows::sid::Sid}; use std::ptr::null_mut; use windows::Win32::Foundation::{ERROR_MORE_DATA, LUID}; use windows::Win32::NetworkManagement::NetManagement::{ FILTER_NORMAL_ACCOUNT, LG_INCLUDE_INDIRECT, LOCALGROUP_USERS_INFO_0, MAX_PREFERRED_LENGTH, NERR_Success, NetApiBufferFree, NetUserEnum, NetUserGetInfo, NetUserGetLocalGroups, USER_INFO_0, USER_INFO_23, }; use windows::Win32::Security::Authentication::Identity::{ LsaEnumerateLogonSessions, LsaFreeReturnBuffer, LsaGetLogonSessionData, SECURITY_LOGON_SESSION_DATA, SECURITY_LOGON_TYPE, }; use windows::core::PCWSTR; pub(crate) struct UserInner { pub(crate) uid: Uid, pub(crate) gid: Gid, pub(crate) name: String, c_user_name: Option>, is_local: bool, } impl UserInner { fn new(uid: Uid, name: String, c_name: PCWSTR, is_local: bool) -> Self { let c_user_name = if c_name.is_null() { None } else { Some(unsafe { c_name.as_wide() }.into()) }; Self { uid, gid: Gid(0), name, c_user_name, is_local, } } pub(crate) fn id(&self) -> &Uid { &self.uid } pub(crate) fn group_id(&self) -> Gid { self.gid } pub(crate) fn name(&self) -> &str { &self.name } pub(crate) fn groups(&self) -> Vec { if let (Some(c_user_name), true) = (&self.c_user_name, self.is_local) { // Convert the wide string to a PCWSTR, and ensure it has a null terminator. // Since the Vec is created here, we can ensure it will not be dropped prematurely. let username = { let mut null_terminated = c_user_name.to_vec(); if null_terminated.last().is_some_and(|v| *v != 0) { null_terminated.push(0); } null_terminated }; unsafe { get_groups_for_user(PCWSTR::from_raw(username.as_ptr())) } } else { Vec::new() } } } struct NetApiBuffer(*mut T); impl Drop for NetApiBuffer { fn drop(&mut self) { if !self.0.is_null() { unsafe { NetApiBufferFree(Some(self.0.cast())) }; } } } impl Default for NetApiBuffer { fn default() -> Self { Self(null_mut()) } } impl NetApiBuffer { pub fn inner_mut(&mut self) -> &mut *mut T { assert!(self.0.is_null()); &mut self.0 } pub unsafe fn inner_mut_as_bytes(&mut self) -> &mut *mut u8 { // https://doc.rust-lang.org/std/mem/fn.transmute.html // Turning an &mut T into an &mut U: unsafe { &mut *(self.inner_mut() as *mut *mut T as *mut *mut u8) } } } struct LsaBuffer(*mut T); impl Drop for LsaBuffer { fn drop(&mut self) { if !self.0.is_null() { let _r = unsafe { LsaFreeReturnBuffer(self.0 as *mut _) }; } } } impl Default for LsaBuffer { fn default() -> Self { Self(null_mut()) } } impl LsaBuffer { pub fn inner_mut(&mut self) -> &mut *mut T { assert!(self.0.is_null()); &mut self.0 } } /// Get the groups for a user. /// /// # Safety /// The caller must ensure that the `username` is a valid wide Unicode string with a null terminator. unsafe fn get_groups_for_user(username: PCWSTR) -> Vec { let mut buf: NetApiBuffer = Default::default(); let mut nb_entries = 0; let mut total_entries = 0; let mut groups: Vec; let status = unsafe { NetUserGetLocalGroups( PCWSTR::null(), username, 0, LG_INCLUDE_INDIRECT, buf.inner_mut_as_bytes(), MAX_PREFERRED_LENGTH, &mut nb_entries, &mut total_entries, ) }; if status == NERR_Success { groups = Vec::with_capacity(nb_entries as _); if !buf.0.is_null() { unsafe { let entries = std::slice::from_raw_parts(buf.0, nb_entries as _); groups.extend(entries.iter().map(|entry| Group { inner: GroupInner::new(Gid(0), to_utf8_str(entry.lgrui0_name)), })); } } } else { groups = Vec::new(); sysinfo_debug!("NetUserGetLocalGroups failed with ret code {}", status); } groups } pub(crate) fn get_users(users: &mut Vec) { users.clear(); let mut resume_handle: u32 = 0; unsafe { loop { let mut buffer: NetApiBuffer = Default::default(); let mut nb_read = 0; let mut total = 0; let status = NetUserEnum( PCWSTR::null(), 0, FILTER_NORMAL_ACCOUNT, buffer.inner_mut_as_bytes(), MAX_PREFERRED_LENGTH, &mut nb_read, &mut total, Some(&mut resume_handle), ); if status == NERR_Success || status == ERROR_MORE_DATA.0 { let entries = std::slice::from_raw_parts(buffer.0, nb_read as _); for entry in entries { if entry.usri0_name.is_null() { continue; } let mut user: NetApiBuffer = Default::default(); if NetUserGetInfo( PCWSTR::null(), PCWSTR::from_raw(entry.usri0_name.as_ptr()), 23, user.inner_mut_as_bytes(), ) == NERR_Success && let Some(sid) = Sid::from_psid((*user.0).usri23_user_sid) { // Get the account name from the SID (because it's usually // a better name), but fall back to the name we were given // if this fails. let name = sid .account_name() .unwrap_or_else(|| to_utf8_str(entry.usri0_name)); users.push(User { inner: UserInner::new( Uid(sid), name, PCWSTR(entry.usri0_name.0 as *const _), true, ), }); } } } else { sysinfo_debug!( "NetUserEnum error: {}", if status == windows::Win32::Foundation::ERROR_ACCESS_DENIED.0 { "access denied" } else if status == windows::Win32::Foundation::ERROR_INVALID_LEVEL.0 { "invalid level" } else { "unknown error" } ); } if status != ERROR_MORE_DATA.0 { break; } } // First part done. Second part now! let mut nb_sessions = 0; let mut uids: LsaBuffer = Default::default(); if LsaEnumerateLogonSessions(&mut nb_sessions, uids.inner_mut()).is_err() { sysinfo_debug!("LsaEnumerateLogonSessions failed"); } else { let entries = std::slice::from_raw_parts_mut(uids.0, nb_sessions as _); for entry in entries { let mut data: LsaBuffer = Default::default(); if LsaGetLogonSessionData(entry, data.inner_mut()).is_ok() && !data.0.is_null() { let data = *data.0; if data.LogonType == SECURITY_LOGON_TYPE::Network.0 as u32 { continue; } let sid = match Sid::from_psid(data.Sid) { Some(sid) => sid, None => continue, }; if users.iter().any(|u| u.inner.uid.0 == sid) { continue; } // Get the account name from the SID (because it's usually // a better name), but fall back to the name we were given // if this fails. let name = sid.account_name().unwrap_or_else(|| { String::from_utf16(std::slice::from_raw_parts( data.UserName.Buffer.as_ptr(), data.UserName.Length as usize / std::mem::size_of::(), )) .unwrap_or_else(|_err| { sysinfo_debug!("Failed to convert from UTF-16 string: {}", _err); String::new() }) }); users.push(User { inner: UserInner::new(Uid(sid), name, PCWSTR::null(), false), }); } } } } } GuillaumeGomez-sysinfo-067dd61/src/windows/utils.rs000066400000000000000000000123741506747262600225020ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "disk")] use windows::Win32::Storage::FileSystem::{ CreateFileW, FILE_ACCESS_RIGHTS, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, }; #[cfg(any(feature = "user", feature = "system"))] pub(crate) unsafe fn to_utf8_str(p: windows::core::PWSTR) -> String { if p.is_null() { return String::new(); } unsafe { p.to_string().unwrap_or_else(|_e| { sysinfo_debug!("Failed to convert to UTF-16 string: {}", _e); String::new() }) } } cfg_if! { if #[cfg(any(feature = "disk", feature = "system"))] { use windows::Win32::Foundation::{CloseHandle, HANDLE}; use std::ops::Deref; pub(crate) struct HandleWrapper(pub(crate) HANDLE); impl HandleWrapper { #[cfg(feature = "system")] pub(crate) fn new(handle: HANDLE) -> Option { if handle.is_invalid() { None } else { Some(Self(handle)) } } #[cfg(feature = "disk")] pub(crate) unsafe fn new_from_file( drive_name: &[u16], open_rights: FILE_ACCESS_RIGHTS, ) -> Option { let lpfilename = windows::core::PCWSTR::from_raw(drive_name.as_ptr()); let handle = unsafe { CreateFileW( lpfilename, open_rights.0, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, Default::default(), Some(HANDLE::default()), ) } .ok()?; if handle.is_invalid() { sysinfo_debug!( "Expected handle to {:?} to be valid", String::from_utf16_lossy(drive_name) ); None } else { Some(Self(handle)) } } } impl Deref for HandleWrapper { type Target = HANDLE; fn deref(&self) -> &Self::Target { &self.0 } } impl Drop for HandleWrapper { fn drop(&mut self) { let _err = unsafe { CloseHandle(self.0) }; } } } } cfg_if! { if #[cfg(feature = "system")] { use windows::Win32::System::SystemInformation::{FIRMWARE_TABLE_PROVIDER, GetSystemFirmwareTable}; use super::ffi::SMBIOSType; // Get the SMBIOS table using the WinAPI. pub(crate) fn get_smbios_table() -> Option> { const PROVIDER: FIRMWARE_TABLE_PROVIDER = FIRMWARE_TABLE_PROVIDER(u32::from_be_bytes(*b"RSMB")); let size = unsafe { GetSystemFirmwareTable(PROVIDER, 0, None) }; if size == 0 { return None; } let mut buffer = vec![0u8; size as usize]; let res = unsafe { GetSystemFirmwareTable(PROVIDER, 0, Some(&mut buffer)) }; if res == 0 { return None; } Some(buffer) } // Parses the SMBIOS table to get mainboard information (type number). // Returns a part of struct with its associated strings. // The parsing format is described here: https://wiki.osdev.org/System_Management_BIOS // and here: https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.6.0.pdf pub(crate) fn parse_smbios(table: &[u8], number: u8) -> Option<(T, Vec<&str>)> { // Skip SMBIOS types until type `number` is reached. // All indexes provided by the structure start at 1. // If the index is 0, the value has not been filled in. // At index i: // table[i] is the current SMBIOS type. // table[i + 1] is the length of the current SMBIOS table header // Strings section starts immediately after the SMBIOS header, // and is a list of null-terminated strings, terminated with two \0. let mut i = 0; while i + 1 < table.len() { if table[i] == number { break; } i += table[i + 1] as usize; // Skip strings table (terminated itself by \0) while i < table.len() { if table[i] == 0 && table[i + 1] == 0 { i += 2; break; } i += 1; } } let info: T = unsafe { std::ptr::read_unaligned(table[i..].as_ptr() as *const _) }; // As said in the SMBIOS 3 standard: https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.6.0.pdf, // the strings are necessarily in UTF-8. But sometimes virtual machines may return non-compliant data. let values = table[(i + info.length() as usize)..] .split(|&b| b == 0) .filter_map(|s| std::str::from_utf8(s).ok()) .take_while(|s| !s.is_empty()) .collect(); Some((info, values)) } } } GuillaumeGomez-sysinfo-067dd61/test-unknown/000077500000000000000000000000001506747262600211605ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/test-unknown/Cargo.toml000066400000000000000000000004571506747262600231160ustar00rootroot00000000000000[package] name = "test-unknown" version = "0.1.0" authors = ["Guillaume Gomez "] description = "A project used to test unknown targets in sysinfo" license = "MIT" edition = "2018" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2" sysinfo = { path = "../" } GuillaumeGomez-sysinfo-067dd61/test-unknown/src/000077500000000000000000000000001506747262600217475ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/test-unknown/src/lib.rs000066400000000000000000000002141506747262600230600ustar00rootroot00000000000000use sysinfo::System; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn tester() { let mut s = System::new(); s.refresh_all(); } GuillaumeGomez-sysinfo-067dd61/test_bin/000077500000000000000000000000001506747262600203135ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/test_bin/main.rs000066400000000000000000000013171506747262600216070ustar00rootroot00000000000000// Does nothing and just exits after waiting for 30 seconds. use std::process::{Child, Command}; fn maybe_start_child(last: String, args: &[String]) -> Option { if last == "1" { let mut cmd = Command::new(&args[0]); for arg in &args[1..] { cmd.arg(arg); } Some(cmd.spawn().expect("failed to run command")) } else { None } } fn main() { let mut args: Vec = std::env::args().collect(); let child = args.pop().and_then(|last| maybe_start_child(last, &args)); if child.is_some() { std::thread::sleep(std::time::Duration::from_secs(3)); } else { std::thread::sleep(std::time::Duration::from_secs(30)); } } GuillaumeGomez-sysinfo-067dd61/tests/000077500000000000000000000000001506747262600176465ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/tests/code_checkers/000077500000000000000000000000001506747262600224275ustar00rootroot00000000000000GuillaumeGomez-sysinfo-067dd61/tests/code_checkers/docs.rs000066400000000000000000000101201506747262600237170ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::{TestResult, show_error}; use std::ffi::OsStr; use std::path::Path; fn to_correct_name(s: &str) -> String { let mut out = String::with_capacity(s.len()); for c in s.chars() { if c.is_uppercase() { if !out.is_empty() { out.push('_'); } out.push_str(c.to_lowercase().to_string().as_str()); } else { out.push(c); } } out } fn check_md_doc_path(p: &Path, md_line: &str, ty_line: &str) -> bool { let parts = md_line.split('/').collect::>(); if let Some(md_name) = parts.last().and_then(|n| n.split(".md").next()) { if let Some(name) = ty_line.split_whitespace().filter(|s| !s.is_empty()).nth(2) && let Some(name) = name .split('<') .next() .and_then(|n| n.split('{').next()) .and_then(|n| n.split('(').next()) .and_then(|n| n.split(';').next()) { let correct = to_correct_name(name); if correct.as_str() == md_name { return true; } show_error( p, &format!("Invalid markdown file name `{md_name}`, should have been `{correct}`",), ); return false; } show_error(p, &format!("Cannot extract type name from `{ty_line}`")); } else { show_error(p, &format!("Cannot extract md name from `{md_line}`")); } false } fn check_doc_comments_before(p: &Path, lines: &[&str], start: usize) -> bool { let mut found_docs = false; for pos in (0..start).rev() { let trimmed = lines[pos].trim(); if trimmed.starts_with("///") { if !lines[start].trim().starts_with("pub enum ThreadStatus {") { show_error( p, &format!( "Types should use common documentation by using `#[doc = include_str!(` \ and by putting the markdown file in the `md_doc` folder instead of `{}`", &lines[pos], ), ); return false; } return true; } else if trimmed.starts_with("#[doc = include_str!(") { found_docs = true; if !check_md_doc_path(p, trimmed, lines[start]) { return false; } } else if !trimmed.starts_with("#[") && !trimmed.starts_with("//") { break; } } if !found_docs { show_error( p, &format!( "Missing documentation for public item: `{}` (if it's not supposed to be a public \ item, use `pub(crate)` instead)", lines[start], ), ); return false; } true } pub fn check_docs(content: &str, p: &Path) -> TestResult { let mut res = TestResult { nb_tests: 0, nb_errors: 0, }; // No need to check if we are in the `src` or `src/common` folder or if we are in a `ffi.rs` // file. if p.file_name().unwrap() == OsStr::new("ffi.rs") { return res; } let path = format!( "/{}", p.parent().unwrap().display().to_string().replace('\\', "/") ); if path.ends_with("/src") || path.ends_with("src/common") { return res; } let lines = content.lines().collect::>(); for pos in 1..lines.len() { let line = lines[pos]; let trimmed = line.trim(); if trimmed.starts_with("//!") { show_error(p, "There shouln't be inner doc comments (`//!`)"); res.nb_tests += 1; res.nb_errors += 1; continue; } else if !line.starts_with("pub fn ") && !trimmed.starts_with("pub struct ") && !trimmed.starts_with("pub enum ") { continue; } res.nb_tests += 1; if !check_doc_comments_before(p, &lines, pos) { res.nb_errors += 1; } } res } GuillaumeGomez-sysinfo-067dd61/tests/code_checkers/headers.rs000066400000000000000000000034051506747262600244120ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::{TestResult, show_error}; use std::path::Path; pub fn check_license_header(content: &str, p: &Path) -> TestResult { let mut lines = content.lines(); let next = lines.next(); let header = "// Take a look at the license at the top of the repository in the LICENSE file."; match next { Some(s) if s == header => { let next = lines.next(); match next { Some("") => TestResult { nb_tests: 1, nb_errors: 0, }, Some(s) => { show_error( p, &format!("Expected empty line after license header, found `{s}`"), ); TestResult { nb_tests: 1, nb_errors: 1, } } None => { show_error(p, "This file should very likely not exist..."); TestResult { nb_tests: 1, nb_errors: 1, } } } } Some(s) => { show_error( p, &format!( "Expected license header at the top of the file (`{header}`), found: `{s}`", ), ); TestResult { nb_tests: 1, nb_errors: 1, } } None => { show_error(p, "This (empty?) file should very likely not exist..."); TestResult { nb_tests: 1, nb_errors: 1, } } } } GuillaumeGomez-sysinfo-067dd61/tests/code_checkers/mod.rs000066400000000000000000000022641506747262600235600ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. mod docs; mod headers; mod signals; mod utils; use std::path::Path; use utils::TestResult; #[allow(clippy::type_complexity)] const CHECKS: &[(fn(&str, &Path) -> TestResult, &[&str])] = &[ (headers::check_license_header, &["src", "tests", "examples"]), (signals::check_signals, &["src"]), (docs::check_docs, &["src"]), ]; fn handle_tests(res: &mut [TestResult]) { utils::read_dirs( &["benches", "examples", "src", "tests"], &mut |p: &Path, c: &str| { if let Some(first) = p.iter().next().and_then(|first| first.to_str()) { for (pos, (check, filter)) in CHECKS.iter().enumerate() { if filter.contains(&first) { res[pos] += check(c, p); } } } }, ); } #[test] fn code_checks() { let mut res = Vec::new(); for _ in CHECKS { res.push(TestResult { nb_tests: 0, nb_errors: 0, }); } handle_tests(&mut res); for r in res { assert_eq!(r.nb_errors, 0); assert_ne!(r.nb_tests, 0); } } GuillaumeGomez-sysinfo-067dd61/tests/code_checkers/signals.rs000066400000000000000000000040601506747262600244350ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::{TestResult, show_error}; use std::path::Path; fn check_supported_signals_decl<'a>(lines: &mut impl Iterator, p: &Path) -> usize { for line in lines { let trimmed = line.trim(); if trimmed.starts_with("const SUPPORTED_SIGNALS: &'static [Signal]") { if trimmed != "const SUPPORTED_SIGNALS: &[Signal] = supported_signals();" { show_error( p, "SUPPORTED_SIGNALS should be declared using `supported_signals()`", ); return 1; } break; } } 0 } fn check_kill_decl<'a>(lines: &mut impl Iterator, p: &Path) -> usize { let mut errors = 0; while let Some(line) = lines.next() { let trimmed = line.trim(); if trimmed.starts_with("fn kill(") { show_error(p, "`Process::kill` should not be reimplemented!"); errors += 1; } else if trimmed.starts_with("fn kill_with(") && let Some(line) = lines.next() { let trimmed = line.trim(); if trimmed.ends_with("crate::sys::convert_signal(signal)?;") || trimmed == "None" { continue; } else { show_error(p, "`Process::kill_with` should use `convert_signal`"); errors += 1; } } } errors } pub fn check_signals(content: &str, p: &Path) -> TestResult { let mut lines = content.lines(); let mut res = TestResult { nb_tests: 0, nb_errors: 0, }; while let Some(line) = lines.next() { let trimmed = line.trim(); if trimmed.starts_with("impl SystemInner {") { res.nb_tests += 1; res.nb_errors += check_supported_signals_decl(&mut lines, p); } else if trimmed.starts_with("impl ProcessInner {") { res.nb_tests += 1; res.nb_errors += check_kill_decl(&mut lines, p); } } res } GuillaumeGomez-sysinfo-067dd61/tests/code_checkers/utils.rs000066400000000000000000000032221506747262600241340ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::fs::{self, File}; use std::io::Read; use std::path::Path; pub struct TestResult { pub nb_tests: usize, pub nb_errors: usize, } impl std::ops::AddAssign for TestResult { fn add_assign(&mut self, other: Self) { self.nb_tests += other.nb_tests; self.nb_errors += other.nb_errors; } } pub fn read_dirs, F: FnMut(&Path, &str)>(dirs: &[P], callback: &mut F) { for dir in dirs { read_dir(dir, callback); } } fn read_dir, F: FnMut(&Path, &str)>(dir: P, callback: &mut F) { for entry in fs::read_dir(dir).expect("read_dir failed") { let entry = entry.expect("entry failed"); let file_type = entry.file_type().expect("file_type failed"); let path = entry.path(); if file_type.is_dir() { read_dir(path, callback); } else if path .extension() .map(|ext| ext == "rs" || ext == "c" || ext == "h") .unwrap_or(false) { let content = read_file(&path); callback(&path, &content); } } } fn read_file>(p: P) -> String { let mut f = File::open(&p).expect("read_file::open failed"); let mut content = String::with_capacity(f.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)); if let Err(e) = f.read_to_string(&mut content) { panic!( "read_file::read_to_end failed for `{}: {e:?}", p.as_ref().display() ); } content } pub fn show_error(p: &Path, err: &str) { eprintln!("=> [{}]: {err}", p.display()); } GuillaumeGomez-sysinfo-067dd61/tests/components.rs000066400000000000000000000007011506747262600223770ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "component")] #[test] fn test_components() { use std::env::var; let mut c = sysinfo::Components::new(); assert!(c.is_empty()); // Unfortunately, we can't get components in the CI... if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(windows) || var("CI").is_ok() { return; } c.refresh(false); assert!(!c.is_empty()); } GuillaumeGomez-sysinfo-067dd61/tests/cpu.rs000066400000000000000000000024271506747262600210100ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![cfg(feature = "system")] // This test is used to ensure that the CPUs are not loaded by default. #[test] fn test_cpu() { let mut s = sysinfo::System::new(); assert!(s.cpus().is_empty()); if !sysinfo::IS_SUPPORTED_SYSTEM { return; } s.refresh_cpu_all(); assert!(!s.cpus().is_empty()); let s = sysinfo::System::new_all(); assert!(!s.cpus().is_empty()); assert!(!s.cpus()[0].brand().chars().any(|c| c == '\0')); if !cfg!(target_os = "freebsd") { // This information is currently not retrieved on freebsd... assert!(s.cpus().iter().any(|c| !c.brand().is_empty())); } assert!(s.cpus().iter().any(|c| !c.vendor_id().is_empty())); } #[test] fn test_physical_core_numbers() { if sysinfo::IS_SUPPORTED_SYSTEM { let count = sysinfo::System::physical_core_count(); assert_ne!(count, None); assert!(count.unwrap() > 0); } } #[test] fn test_too_rapid_cpu_refresh() { let mut s = sysinfo::System::new(); assert!(s.cpus().is_empty()); if !sysinfo::IS_SUPPORTED_SYSTEM { return; } s.refresh_cpu_all(); s.refresh_cpu_all(); assert!(s.cpus().iter().any(|c| !c.cpu_usage().is_nan())); } GuillaumeGomez-sysinfo-067dd61/tests/disk.rs000066400000000000000000000147611506747262600211570ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(all(feature = "system", feature = "disk"))] fn should_skip() -> bool { if !sysinfo::IS_SUPPORTED_SYSTEM { return true; } // If we don't have any physical core present, it's very likely that we're inside a VM... sysinfo::System::physical_core_count().unwrap_or_default() == 0 } #[test] #[cfg(all(feature = "system", feature = "disk"))] fn test_disks() { if should_skip() { return; } let mut disks = sysinfo::Disks::new(); assert!(disks.list().is_empty()); disks.refresh(false); assert!(!disks.list().is_empty()); } #[test] #[cfg(all(feature = "system", feature = "disk"))] fn test_disk_refresh_kind() { use itertools::Itertools; use sysinfo::{DiskKind, DiskRefreshKind, Disks}; if should_skip() { return; } for fs in [ DiskRefreshKind::with_kind, DiskRefreshKind::without_kind, DiskRefreshKind::with_storage, DiskRefreshKind::without_storage, DiskRefreshKind::with_io_usage, DiskRefreshKind::without_io_usage, ] .iter() .powerset() { let mut refreshes = DiskRefreshKind::nothing(); for f in fs { refreshes = f(refreshes); } let assertions = |name: &'static str, disks: &Disks| { if refreshes.kind() { // This would ideally assert that *all* are refreshed, but we settle for a weaker // assertion because failures can't be distinguished from "not refreshed" values. #[cfg(not(any(target_os = "freebsd", target_os = "windows")))] assert!( disks .iter() .any(|disk| disk.kind() != DiskKind::Unknown(-1)), "{name}: disk.kind should be refreshed" ); } else { assert!( disks .iter() .all(|disk| disk.kind() == DiskKind::Unknown(-1)), "{name}: disk.kind should not be refreshed" ); } if refreshes.storage() { // These would ideally assert that *all* are refreshed, but we settle for a weaker // assertion because failures can't be distinguished from "not refreshed" values. assert!( disks .iter() .any(|disk| disk.available_space() != Default::default()), "{name}: disk.available_space should be refreshed" ); assert!( disks .iter() .any(|disk| disk.total_space() != Default::default()), "{name}: disk.total_space should be refreshed" ); // We can't assert anything about booleans, since false is indistinguishable from // not-refreshed } else { assert!( disks .iter() .all(|disk| disk.available_space() == Default::default()), "{name}: disk.available_space should not be refreshed" ); assert!( disks .iter() .all(|disk| disk.total_space() == Default::default()), "{name}: disk.total_space should not be refreshed" ); } if refreshes.io_usage() { // This would ideally assert that *all* are refreshed, but we settle for a weaker // assertion because failures can't be distinguished from "not refreshed" values. assert!( disks.iter().any(|disk| disk.usage() != Default::default()), "{name}: disk.usage should be refreshed" ); } else { assert!( disks.iter().all(|disk| disk.usage() == Default::default()), "{name}: disk.usage should not be refreshed" ); } }; // load and refresh with the desired details should work let disks = Disks::new_with_refreshed_list_specifics(refreshes); assertions("full", &disks); // load with minimal `DiskRefreshKind`, then refresh for added detail should also work! let mut disks = Disks::new_with_refreshed_list_specifics(DiskRefreshKind::nothing()); disks.refresh_specifics(false, refreshes); assertions("incremental", &disks); } } #[test] #[cfg(all(feature = "system", feature = "disk"))] fn test_disks_usage() { use std::fs::{File, remove_file}; use std::io::Write; use std::path::{Path, PathBuf}; use std::thread::sleep; use sysinfo::Disks; if should_skip() { return; } // The test always fails in CI on Linux. For some unknown reason, /proc/diskstats just doesn't // update, regardless of how long we wait. Until the root cause is discovered, skip the test // in CI. if cfg!(target_os = "linux") && std::env::var("CI").is_ok() { return; } let mut disks = Disks::new_with_refreshed_list(); let path = match std::env::var("CARGO_TARGET_DIR") { Ok(p) => Path::new(&p).join("data.tmp"), _ => PathBuf::from("target/data.tmp"), }; let mut file = File::create(&path).expect("failed to create temporary file"); // Write 10mb worth of data to the temp file. let data = vec![1u8; 10 * 1024 * 1024]; file.write_all(&data).unwrap(); // The sync_all call is important to ensure all the data is persisted to disk. Without // the call, this test is flaky. file.sync_all().unwrap(); // Wait a bit just in case sleep(std::time::Duration::from_millis(500)); disks.refresh(false); // Depending on the OS and how disks are configured, the disk usage may be the exact same // across multiple disks. To account for this, collect the disk usages and dedup. let mut disk_usages = disks.list().iter().map(|d| d.usage()).collect::>(); disk_usages.dedup(); let mut written_bytes = 0; for disk_usage in disk_usages { written_bytes += disk_usage.written_bytes; } let _ = remove_file(path); // written_bytes should have increased by about 10mb, but this is not fully reliable in CI Linux. For now, // just verify the number is non-zero. assert!(written_bytes > 0); } GuillaumeGomez-sysinfo-067dd61/tests/extras.rs000066400000000000000000000001441506747262600215210ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. mod code_checkers; GuillaumeGomez-sysinfo-067dd61/tests/network.rs000066400000000000000000000006601506747262600217070ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. // This test is used to ensure that the networks are not loaded by default. #[cfg(feature = "network")] #[test] fn test_networks() { use sysinfo::Networks; if sysinfo::IS_SUPPORTED_SYSTEM { let mut n = Networks::new(); assert_eq!(n.iter().count(), 0); n.refresh(false); assert!(n.iter().count() > 0); } } GuillaumeGomez-sysinfo-067dd61/tests/process.rs000066400000000000000000001147321506747262600217020ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![cfg(feature = "system")] use bstr::ByteSlice; use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System, UpdateKind}; macro_rules! start_proc { ($time:literal, $name:literal) => { if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg($time) .arg($name) .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg($time) .stdout(std::process::Stdio::null()) .spawn() .unwrap() } }; } #[test] fn test_cwd() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut p = start_proc!("3", "CwdSignal"); let pid = Pid::from_u32(p.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); let mut s = System::new(); s.refresh_processes_specifics( ProcessesToUpdate::All, false, ProcessRefreshKind::nothing().with_cwd(UpdateKind::Always), ); p.kill().expect("Unable to kill process."); let processes = s.processes(); let p = processes.get(&pid); if let Some(p) = p { assert_eq!(p.pid(), pid); assert_eq!(p.cwd().unwrap(), &std::env::current_dir().unwrap()); } else { panic!("Process not found!"); } } #[test] fn test_cmd() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut p = start_proc!("3", "CmdSignal"); std::thread::sleep(std::time::Duration::from_millis(500)); let mut s = System::new(); assert!(s.processes().is_empty()); s.refresh_processes_specifics( ProcessesToUpdate::All, false, ProcessRefreshKind::nothing().with_cmd(UpdateKind::Always), ); p.kill().expect("Unable to kill process"); assert!(!s.processes().is_empty()); if let Some(process) = s.process(Pid::from_u32(p.id() as _)) { if cfg!(target_os = "windows") { // Sometimes, we get the full path instead for some reasons... So just in case, // we check for the command independently that from the arguments. assert!(process.cmd()[0].as_encoded_bytes().contains_str("waitfor")); assert_eq!(&process.cmd()[1..], &["/t", "3", "CmdSignal"]); } else { assert_eq!(process.cmd(), &["sleep", "3"]); } } else { panic!("Process not found!"); } } fn build_test_binary(file_name: &str) { std::process::Command::new("rustc") .arg("test_bin/main.rs") .arg("-o") .arg(file_name) .stdout(std::process::Stdio::null()) .spawn() .unwrap() .wait() .unwrap(); } #[test] #[allow(clippy::zombie_processes)] fn test_environ() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let file_name = "target/test_binary"; build_test_binary(file_name); let mut p = std::process::Command::new(format!("./{file_name}")) .env("FOO", "BAR") .env("OTHER", "VALUE") .spawn() .unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); let pid = Pid::from_u32(p.id() as _); let mut s = System::new(); s.refresh_processes_specifics( ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::everything(), ); p.kill().expect("Unable to kill process."); let processes = s.processes(); let proc_ = processes.get(&pid); if let Some(proc_) = proc_ { assert_eq!(proc_.pid(), pid); assert!(proc_.environ().iter().any(|e| e == "FOO=BAR")); assert!(proc_.environ().iter().any(|e| e == "OTHER=VALUE")); } else { panic!("Process not found!"); } // Test to ensure that a process with a lot of environment variables doesn't get truncated. // More information in . const SIZE: usize = 30_000; let mut big_env = String::with_capacity(SIZE); for _ in 0..SIZE { big_env.push('a'); } let mut p = std::process::Command::new("./target/test_binary") .env("FOO", &big_env) .spawn() .unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); let pid = Pid::from_u32(p.id() as _); let mut s = System::new(); s.refresh_processes_specifics( ProcessesToUpdate::All, false, ProcessRefreshKind::nothing().with_environ(UpdateKind::Always), ); let processes = s.processes(); let proc_ = processes.get(&pid); if let Some(proc_) = proc_ { p.kill().expect("Unable to kill process."); assert_eq!(proc_.pid(), pid); let env = format!("FOO={big_env}"); assert!(proc_.environ().iter().any(|e| *e == *env)); } else { panic!("Process not found!"); } } #[test] fn test_process_refresh() { let mut s = System::new(); assert_eq!(s.processes().len(), 0); if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } s.refresh_processes( ProcessesToUpdate::Some(&[sysinfo::get_current_pid().expect("failed to get current pid")]), false, ); assert!( s.process(sysinfo::get_current_pid().expect("failed to get current pid")) .is_some() ); assert!( s.processes() .iter() .all(|(_, p)| p.environ().is_empty() && p.cwd().is_none() && p.cmd().is_empty()) ); assert!( s.processes() .iter() .any(|(_, p)| !p.name().is_empty() && p.memory() != 0) ); } #[test] fn test_process_disk_usage() { use std::fs; use std::fs::File; use std::io::prelude::*; use sysinfo::get_current_pid; if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } if std::env::var("FREEBSD_CI").is_ok() { // For an unknown reason, when running this test on Cirrus CI, it fails. It works perfectly // locally though... Dark magic... return; } fn inner() -> System { { let mut file = File::create("test.txt").expect("failed to create file"); file.write_all(b"This is a test file\nwith test data.\n") .expect("failed to write to file"); } fs::remove_file("test.txt").expect("failed to remove file"); // Waiting a bit just in case... std::thread::sleep(std::time::Duration::from_millis(250)); let mut system = System::new(); assert!(system.processes().is_empty()); system.refresh_processes(ProcessesToUpdate::All, false); assert!(!system.processes().is_empty()); system } let mut system = inner(); let mut p = system .process(get_current_pid().expect("Failed retrieving current pid.")) .expect("failed to get process"); if cfg!(any(target_os = "macos", target_os = "ios")) && p.disk_usage().total_written_bytes == 0 { // For whatever reason, sometimes, mac doesn't work on the first time when running // `cargo test`. Two solutions, either run with "cargo test -- --test-threads 1", or // check twice... system = inner(); p = system .process(get_current_pid().expect("Failed retrieving current pid.")) .expect("failed to get process"); } assert!( p.disk_usage().total_written_bytes > 0, "found {} total written bytes...", p.disk_usage().total_written_bytes ); assert!( p.disk_usage().written_bytes > 0, "found {} written bytes...", p.disk_usage().written_bytes ); } #[test] fn cpu_usage_is_not_nan() { let mut system = System::new(); system.refresh_processes(ProcessesToUpdate::All, false); if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } // We need `collect` otherwise we can't have mutable access to `system`. #[allow(clippy::needless_collect)] let first_pids = system .processes() .iter() .take(10) .map(|(&pid, _)| pid) .collect::>(); let mut checked = 0; first_pids.into_iter().for_each(|pid| { system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true); if let Some(p) = system.process(pid) { assert!(!p.cpu_usage().is_nan()); checked += 1; } }); assert!(checked > 0); } #[test] fn test_process_times() { use std::time::{SystemTime, UNIX_EPOCH}; if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let boot_time = System::boot_time(); assert!(boot_time > 0); let mut p = start_proc!("3", "ProcessTimes"); let pid = Pid::from_u32(p.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::All, false); p.kill().expect("Unable to kill process."); if let Some(p) = s.process(pid) { assert_eq!(p.pid(), pid); assert!(p.run_time() >= 1); assert!(p.run_time() <= 2); assert!(p.start_time() > p.run_time()); // On linux, for whatever reason, the uptime seems to be older than the boot time, leading // to this weird `+ 5` to ensure the test is passing as it should... assert!( p.start_time() + 5 > SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(), ); assert!(p.start_time() >= boot_time); } else { panic!("Process not found!"); } } // Checks that `session_id` is working. #[test] fn test_process_session_id() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::All, false); assert!(s.processes().values().any(|p| p.session_id().is_some())); } // Checks that `refresh_processes` is removing dead processes. #[test] fn test_refresh_processes() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut p = start_proc!("300", "RefreshProcesses"); let pid = Pid::from_u32(p.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); // Checks that the process is listed as it should. let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::All, false); assert!(s.process(pid).is_some()); // We will use this `System` instance for another check. let mut old_system = System::new(); old_system.refresh_processes(ProcessesToUpdate::All, false); assert!(old_system.process(pid).is_some()); // Check that the process name is not empty. assert!(!s.process(pid).unwrap().name().is_empty()); p.kill().expect("Unable to kill process."); // We need this, otherwise the process will still be around as a zombie on linux. let _ = p.wait(); // Let's give some time to the system to clean up... std::thread::sleep(std::time::Duration::from_secs(1)); let mut new_system = sysinfo::System::new_with_specifics(RefreshKind::nothing()); new_system.refresh_processes_specifics( ProcessesToUpdate::Some(&[pid]), true, ProcessRefreshKind::nothing(), ); // `new_system` should not have this removed process. assert!(new_system.process(pid).is_none()); s.refresh_processes(ProcessesToUpdate::All, true); // Checks that the process isn't listed anymore. assert!(s.process(pid).is_none()); // And we ensure that refreshing it this way will work too (ie, not listed anymore). old_system.refresh_processes_specifics( ProcessesToUpdate::Some(&[pid]), true, ProcessRefreshKind::nothing(), ); assert!(old_system.process(pid).is_none()); } // This test ensures that if we refresh only one process, then only this process is removed. #[test] fn test_refresh_process_doesnt_remove() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut p1 = start_proc!("300", "RefreshProcessRemove1"); let mut p2 = start_proc!("300", "RefreshProcessRemove2"); let pid1 = Pid::from_u32(p1.id() as _); let pid2 = Pid::from_u32(p2.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); // Checks that the process is listed as it should. let mut s = System::new_with_specifics( RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing()), ); s.refresh_processes(ProcessesToUpdate::All, false); assert!(s.process(pid1).is_some()); assert!(s.process(pid2).is_some()); p1.kill().expect("Unable to kill process."); p2.kill().expect("Unable to kill process."); // We need this, otherwise the process will still be around as a zombie on linux. let _ = p1.wait(); let _ = p2.wait(); // Let's give some time to the system to clean up... std::thread::sleep(std::time::Duration::from_secs(1)); assert_eq!( s.refresh_processes(ProcessesToUpdate::Some(&[pid1]), false), 0 ); // We check that none of the two processes were removed. assert!(s.process(pid1).is_some()); assert!(s.process(pid2).is_some()); assert_eq!( s.refresh_processes(ProcessesToUpdate::Some(&[pid1]), true), 0 ); // We check that only `pid1` was removed. assert!(s.process(pid1).is_none()); assert!(s.process(pid2).is_some()); } // Checks that `refresh_processes` is adding and removing task. #[test] #[cfg(all( any(target_os = "linux", target_os = "android"), not(feature = "unknown-ci") ))] fn test_refresh_tasks() { // Skip if unsupported. if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } // 1) Spawn a thread that waits on a channel, so we control when it exits. let task_name = "controlled_test_thread"; let (tx, rx) = std::sync::mpsc::channel::<()>(); std::thread::Builder::new() .name(task_name.to_string()) .spawn(move || { // Wait until the main thread signals we can exit. let _ = rx.recv(); }) .unwrap(); let pid = Pid::from_u32(std::process::id() as _); let mut sys = System::new(); // Wait until the new thread shows up in the process/tasks list. // We do a short loop and check each time by refreshing processes. const MAX_POLLS: usize = 20; const POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(100); for _ in 0..MAX_POLLS { sys.refresh_processes(ProcessesToUpdate::All, /*refresh_users=*/ false); // Check if our thread is present in two ways: // (a) via parent's tasks // (b) by exact name let parent_proc = sys.process(pid); let tasks_contain_thread = parent_proc .and_then(|p| p.tasks()) .map(|tids| { tids.iter().any(|tid| { sys.process(*tid) .map(|t| t.name() == task_name) .unwrap_or(false) }) }) .unwrap_or(false); let by_exact_name_exists = sys .processes_by_exact_name(task_name.as_ref()) .next() .is_some(); if tasks_contain_thread && by_exact_name_exists { // We confirmed the thread is now visible break; } std::thread::sleep(POLL_INTERVAL); } // At this point we know the task is visible in the system's process/tasks list. // Let's validate a few more things: // * ProcessRefreshKind::nothing() should have task information. // * ProcessRefreshKind::nothing().with_tasks() should have task information. // * ProcessRefreshKind::nothing().without_tasks() shouldn't have task information. // * ProcessRefreshKind::everything() should have task information. // * ProcessRefreshKind::everything() should have task information. // * ProcessRefreshKind::everything().without_tasks() should not have task information. let expectations = [ (ProcessRefreshKind::nothing(), true), (ProcessRefreshKind::nothing().with_tasks(), true), (ProcessRefreshKind::nothing().without_tasks(), false), (ProcessRefreshKind::everything(), true), (ProcessRefreshKind::everything().with_tasks(), true), (ProcessRefreshKind::everything().without_tasks(), false), ]; for (kind, expect_tasks) in expectations.iter() { let mut sys_new = System::new(); sys_new.refresh_processes_specifics(ProcessesToUpdate::All, true, *kind); let proc = sys_new.process(pid).unwrap(); assert_eq!(proc.tasks().is_some(), *expect_tasks); } // 3) Signal the thread to exit. drop(tx); // 4) Wait until the thread is gone from the system’s process/tasks list. for _ in 0..MAX_POLLS { sys.refresh_processes(ProcessesToUpdate::All, /*refresh_users=*/ true); let parent_proc = sys.process(pid as sysinfo::Pid); let tasks_contain_thread = parent_proc .and_then(|p| p.tasks()) .map(|tids| { tids.iter().any(|tid| { sys.process(*tid) .map(|t| t.name() == task_name) .unwrap_or(false) }) }) .unwrap_or(false); let by_exact_name_exists = sys .processes_by_exact_name(task_name.as_ref()) .next() .is_some(); // If it's gone from both checks, we're good. if !tasks_contain_thread && !by_exact_name_exists { break; } std::thread::sleep(POLL_INTERVAL); } } // Checks that `refresh_process` is removing dead processes when asked. #[test] fn test_refresh_process() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut p = start_proc!("300", "RefreshProcess"); let pid = Pid::from_u32(p.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); // Checks that the process is listed as it should. let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false); assert!(s.process(pid).is_some()); // Check that the process name is not empty. assert!(!s.process(pid).unwrap().name().is_empty()); p.kill().expect("Unable to kill process."); // We need this, otherwise the process will still be around as a zombie on linux. let _ = p.wait(); // Let's give some time to the system to clean up... std::thread::sleep(std::time::Duration::from_secs(1)); assert_eq!( s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false), 0 ); // Checks that the process is still listed. assert!(s.process(pid).is_some()); assert_eq!( s.refresh_processes(ProcessesToUpdate::Some(&[pid]), true), 0 ); // Checks that the process is not listed anymore. assert!(s.process(pid).is_none()); } #[test] fn test_wait_child() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let p = start_proc!("300", "WaitChild"); let before = std::time::Instant::now(); let pid = Pid::from_u32(p.id() as _); let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false); let process = s.process(pid).unwrap(); // Kill the child process. process.kill(); // Wait for child process should work. process.wait(); // Child process should not be present. assert_eq!( s.refresh_processes(ProcessesToUpdate::Some(&[pid]), true), 0 ); assert!(before.elapsed() < std::time::Duration::from_millis(1000)); } #[test] fn test_wait_non_child() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let before = std::time::Instant::now(); // spawn non child process. let p = if !cfg!(target_os = "linux") { return; } else { std::process::Command::new("setsid") .arg("-w") .arg("sleep") .arg("2") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; let pid = Pid::from_u32(p.id()); let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false); let process = s.process(pid).expect("Process not found!"); // Wait for a non child process. process.wait(); // Child process should not be present. assert_eq!( s.refresh_processes(ProcessesToUpdate::Some(&[pid]), true), 0 ); // should wait for 2s. assert!( before.elapsed() > std::time::Duration::from_millis(1900), "Elapsed time {:?} is not greater than 1900ms", before.elapsed() ); assert!( before.elapsed() < std::time::Duration::from_millis(3000), "Elapsed time {:?} is not less than 3000ms", before.elapsed() ); } #[test] fn test_process_iterator_lifetimes() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let s = System::new_with_specifics( RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing()), ); let process: Option<&sysinfo::Process>; { let name = String::from(""); // errors before PR #904: name does not live long enough process = s.processes_by_name(name.as_ref()).next(); } process.unwrap(); let process: Option<&sysinfo::Process>; { // worked fine before and after: &'static str lives longer than System, error couldn't appear process = s.processes_by_name("".as_ref()).next(); } process.unwrap(); } // Regression test for . #[test] fn test_process_cpu_usage() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut sys = System::new_all(); std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); sys.refresh_all(); let max_usage = sys.cpus().len() as f32 * 100.; for process in sys.processes().values() { assert!(process.cpu_usage() <= max_usage); } } #[test] fn test_process_creds() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut sys = System::new_all(); sys.refresh_all(); // Just ensure there is at least one process on the system whose credentials can be retrieved. assert!(sys.processes().values().any(|process| { if process.user_id().is_none() { return false; } #[cfg(not(windows))] { if process.group_id().is_none() || process.effective_user_id().is_none() || process.effective_group_id().is_none() { return false; } } true })); // On Windows, make sure no process has real group ID and no effective IDs. #[cfg(windows)] assert!(sys.processes().values().all(|process| { if process.group_id().is_some() || process.effective_user_id().is_some() || process.effective_group_id().is_some() { return false; } true })); } // This test ensures that only the requested information is retrieved. #[test] fn test_process_specific_refresh() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } fn check_empty(s: &System, pid: Pid) { let p = s.process(pid).unwrap(); // Name should never be empty. assert!(!p.name().is_empty()); if cfg!(target_os = "windows") { assert_eq!(p.user_id(), None); } assert_eq!(p.environ().len(), 0); assert_eq!(p.cmd().len(), 0); assert_eq!(p.exe(), None); assert_eq!(p.cwd(), None); assert_eq!(p.root(), None); assert_eq!(p.memory(), 0); assert_eq!(p.virtual_memory(), 0); // These two won't be checked, too much laziness in testing them... assert_eq!(p.disk_usage(), sysinfo::DiskUsage::default()); assert_eq!(p.cpu_usage(), 0.); } let mut s = System::new(); let pid = Pid::from_u32(std::process::id()); macro_rules! update_specific_and_check { (memory) => { s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing()); { let p = s.process(pid).unwrap(); assert_eq!(p.memory(), 0, "failed 0 check for memory"); assert_eq!(p.virtual_memory(), 0, "failed 0 check for virtual memory"); } s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing().with_memory()); { let p = s.process(pid).unwrap(); assert_ne!(p.memory(), 0, "failed non-0 check for memory"); assert_ne!(p.virtual_memory(), 0, "failed non-0 check for virtual memory"); } // And now we check that re-refreshing nothing won't remove the // information. s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing()); { let p = s.process(pid).unwrap(); assert_ne!(p.memory(), 0, "failed non-0 check (number 2) for memory"); assert_ne!(p.virtual_memory(), 0, "failed non-0 check(number 2) for virtual memory"); } }; ($name:ident, $method:ident, $($extra:tt)+) => { s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing()); { let p = s.process(pid).unwrap(); assert_eq!( p.$name()$($extra)+, concat!("failed 0 check check for ", stringify!($name)), ); } s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing().$method(UpdateKind::Always)); { let p = s.process(pid).unwrap(); assert_ne!( p.$name()$($extra)+, concat!("failed non-0 check check for ", stringify!($name)),); } // And now we check that re-refreshing nothing won't remove the // information. s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing()); { let p = s.process(pid).unwrap(); assert_ne!( p.$name()$($extra)+, concat!("failed non-0 check (number 2) check for ", stringify!($name)),); } } } s.refresh_processes_specifics( ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing(), ); check_empty(&s, pid); s.refresh_processes_specifics( ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing(), ); check_empty(&s, pid); update_specific_and_check!(memory); update_specific_and_check!(environ, with_environ, .len(), 0); update_specific_and_check!(cmd, with_cmd, .len(), 0); if !cfg!(any( target_os = "macos", target_os = "ios", feature = "apple-sandbox", )) { update_specific_and_check!(root, with_root, , None); } update_specific_and_check!(exe, with_exe, , None); update_specific_and_check!(cwd, with_cwd, , None); } #[test] fn test_refresh_pids() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let self_pid = sysinfo::get_current_pid().expect("failed to get current pid"); let mut s = System::new(); let mut p = start_proc!("3", "RefreshPids"); let child_pid = Pid::from_u32(p.id() as _); let pids = &[child_pid, self_pid]; std::thread::sleep(std::time::Duration::from_millis(500)); s.refresh_processes(ProcessesToUpdate::Some(pids), false); p.kill().expect("Unable to kill process."); assert_eq!(s.processes().len(), 2); for pid in s.processes().keys() { assert!(pids.contains(pid)); } } #[test] fn test_process_run_time() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut s = System::new(); let current_pid = sysinfo::get_current_pid().expect("failed to get current pid"); s.refresh_processes(ProcessesToUpdate::Some(&[current_pid]), false); let run_time = s.process(current_pid).expect("no process found").run_time(); std::thread::sleep(std::time::Duration::from_secs(2)); s.refresh_processes(ProcessesToUpdate::Some(&[current_pid]), true); let new_run_time = s.process(current_pid).expect("no process found").run_time(); assert!( new_run_time > run_time, "{new_run_time} not superior to {run_time}", ); } // Test that if the parent of a process is removed, then the child PID will be // updated as well. #[test] fn test_parent_change() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") || cfg!(windows) { // Windows never updates its parent PID so no need to check anything. return; } let file_name = "target/test_binary2"; build_test_binary(file_name); let mut p = std::process::Command::new(format!("./{file_name}")) .arg("1") .spawn() .unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); let pid = Pid::from_u32(p.id() as _); let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::All, false); assert_eq!( s.process(pid).expect("process was not created").parent(), sysinfo::get_current_pid().ok(), ); let child_pid = s .processes() .iter() .find(|(_, proc_)| proc_.parent() == Some(pid)) .map(|(pid, _)| *pid) .expect("failed to get child process"); // Waiting for the parent process to stop. p.wait().expect("wait failed"); s.refresh_processes(ProcessesToUpdate::All, true); // Parent should not be around anymore. assert!(s.process(pid).is_none()); let child = s.process(child_pid).expect("child is dead"); // Child should have a different parent now. assert_ne!(child.parent(), Some(pid)); // We kill the child to clean up. child.kill(); } // We want to ensure that if `System::refresh_process*` methods are called // one after the other, it won't badly impact the CPU usage computation. #[test] fn test_multiple_single_process_refresh() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") || cfg!(windows) { // Windows never updates its parent PID so no need to check anything. return; } let file_name = "target/test_binary3"; build_test_binary(file_name); let mut p_a = std::process::Command::new(format!("./{file_name}")) .arg("1") .spawn() .unwrap(); let mut p_b = std::process::Command::new(format!("./{file_name}")) .arg("1") .spawn() .unwrap(); let pid_a = Pid::from_u32(p_a.id() as _); let pid_b = Pid::from_u32(p_b.id() as _); let mut s = System::new(); let process_refresh_kind = ProcessRefreshKind::nothing().with_cpu(); s.refresh_processes_specifics( ProcessesToUpdate::Some(&[pid_a]), false, process_refresh_kind, ); s.refresh_processes_specifics( ProcessesToUpdate::Some(&[pid_b]), false, process_refresh_kind, ); std::thread::sleep(std::time::Duration::from_secs(1)); s.refresh_processes_specifics( ProcessesToUpdate::Some(&[pid_a]), true, process_refresh_kind, ); s.refresh_processes_specifics( ProcessesToUpdate::Some(&[pid_b]), true, process_refresh_kind, ); let cpu_a = s.process(pid_a).unwrap().cpu_usage(); let cpu_b = s.process(pid_b).unwrap().cpu_usage(); p_a.kill().expect("failed to kill process a"); p_b.kill().expect("failed to kill process b"); let _ = p_a.wait(); let _ = p_b.wait(); assert!(cpu_b - 5. < cpu_a && cpu_b + 5. > cpu_a); } #[test] fn accumulated_cpu_time() { fn generate_cpu_usage() { use std::sync::atomic::{AtomicBool, Ordering}; let atomic = std::sync::Arc::new(AtomicBool::new(false)); let thread_atomic = atomic.clone(); std::thread::spawn(move || { while !thread_atomic.load(Ordering::Relaxed) { System::new_all(); } }); std::thread::sleep(std::time::Duration::from_millis(250)); atomic.store(true, Ordering::Relaxed); } if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") || cfg!(target_os = "freebsd") { return; } let mut s = System::new(); let current_pid = sysinfo::get_current_pid().expect("failed to get current pid"); let refresh_kind = ProcessRefreshKind::nothing().with_cpu(); generate_cpu_usage(); s.refresh_processes_specifics(ProcessesToUpdate::Some(&[current_pid]), false, refresh_kind); let acc_time = s .process(current_pid) .expect("no process found") .accumulated_cpu_time(); assert_ne!(acc_time, 0); generate_cpu_usage(); s.refresh_processes_specifics(ProcessesToUpdate::Some(&[current_pid]), true, refresh_kind); let new_acc_time = s .process(current_pid) .expect("no process found") .accumulated_cpu_time(); assert!( new_acc_time > acc_time, "{new_acc_time} not superior to {acc_time}", ); } #[test] fn test_exists() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let file_name = "target/test_binary4"; build_test_binary(file_name); let mut p = std::process::Command::new(format!("./{file_name}")) .arg("1") .spawn() .unwrap(); let pid = Pid::from_u32(p.id() as _); let mut s = System::new(); let process_refresh_kind = ProcessRefreshKind::nothing().with_memory(); s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, process_refresh_kind); assert!(s.process(pid).unwrap().exists()); p.kill().expect("Unable to kill process."); // We need this, otherwise the process will still be around as a zombie on linux. let _ = p.wait(); // Let's give some time to the system to clean up... std::thread::sleep(std::time::Duration::from_secs(1)); s.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, process_refresh_kind); assert!(!s.process(pid).unwrap().exists()); } #[cfg(target_os = "linux")] #[test] fn test_tasks() { use std::collections::HashSet; if !sysinfo::IS_SUPPORTED_SYSTEM { return; } fn get_tasks(system: &System, pid: Pid) -> HashSet { let mut task_pids: HashSet = HashSet::new(); if let Some(process) = system.process(pid) && let Some(tasks) = process.tasks() { task_pids.extend(tasks); } task_pids } let mut system = System::new_with_specifics(RefreshKind::nothing()); system.refresh_processes_specifics(ProcessesToUpdate::All, true, ProcessRefreshKind::nothing()); let pid = sysinfo::get_current_pid().expect("failed to get current pid"); let old_tasks = get_tasks(&system, pid); // Spawn a thread to increase the task count let scheduler = std::thread::spawn(move || { system.refresh_processes_specifics( ProcessesToUpdate::All, true, ProcessRefreshKind::nothing(), ); let mut system_new = System::new_with_specifics(RefreshKind::nothing()); system_new.refresh_processes_specifics( ProcessesToUpdate::All, true, ProcessRefreshKind::nothing(), ); let new_tasks = get_tasks(&system, pid); assert_ne!(old_tasks, new_tasks); assert_eq!(new_tasks, get_tasks(&system_new, pid)); }); scheduler.join().expect("Scheduler panicked"); } #[test] fn open_files() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let pid = sysinfo::get_current_pid().expect("failed to get current pid"); let _file = std::fs::File::create(std::env::temp_dir().join("sysinfo-open-files.test")).unwrap(); let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::Some(&[pid]), false); let cur_process = s.process(pid).unwrap(); assert!( cur_process .open_files() .is_some_and(|open_files| open_files > 0) ); assert!( cur_process .open_files_limit() .is_some_and(|open_files| open_files > 0) ); } #[test] fn test_wait() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let p = start_proc!("2", "TestWait"); let pid = Pid::from_u32(p.id() as _); let mut s = System::new(); s.refresh_processes_specifics(ProcessesToUpdate::All, false, ProcessRefreshKind::nothing()); // We check the result of the exiting process. let exit_status = s.process(pid).unwrap().wait(); assert!(exit_status.is_some()); // Now we check that it doesn't exist anymore. let mut s2 = System::new(); s2.refresh_processes_specifics(ProcessesToUpdate::All, false, ProcessRefreshKind::nothing()); assert!(s2.process(pid).is_none()); // And we check that waiting for it will return `None`. if cfg!(target_os = "linux") { assert_eq!(s.process(pid).unwrap().wait(), None); } else { // On windows we can get the exit status as long as we have a handle. // On mac and freebsd, no clue why. let exit_status = s.process(pid).unwrap().wait(); assert!(exit_status.is_some()); } } // Regression test for . // // On macOS, we didn't set the `old_stime` and `old_utime` when we create processes, meaning // that we can get CPU usage only on the third try. #[test] fn test_cpu_processes_usage() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } if std::env::var("FREEBSD_CI").is_ok() { // FIXME: once I'm able to run a virtual freebsd machine, need to check if this test // is working. return; } let mut sys = System::new_all(); std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true); assert!(sys.processes().iter().any(|(_, p)| p.cpu_usage() > 0.)); } GuillaumeGomez-sysinfo-067dd61/tests/send_sync.rs000066400000000000000000000004721506747262600222040ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[test] #[allow(clippy::extra_unused_type_parameters)] #[cfg(feature = "system")] fn test_send_sync() { fn is_send() {} fn is_sync() {} is_send::(); is_sync::(); } GuillaumeGomez-sysinfo-067dd61/tests/system.rs000066400000000000000000000163711506747262600215500ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![cfg(feature = "system")] #![allow(clippy::assertions_on_constants)] use sysinfo::{ProcessesToUpdate, System}; #[test] fn test_refresh_system() { let mut sys = System::new(); sys.refresh_memory(); sys.refresh_cpu_usage(); // We don't want to test on unsupported systems. if sysinfo::IS_SUPPORTED_SYSTEM { assert!(sys.total_memory() != 0); assert!(sys.free_memory() != 0); } assert!(sys.total_memory() >= sys.free_memory()); assert!(sys.total_swap() >= sys.free_swap()); } #[test] fn test_refresh_process() { let mut sys = System::new(); assert!(sys.processes().is_empty(), "no process should be listed!"); // We don't want to test on unsupported systems. #[cfg(not(feature = "apple-sandbox"))] if sysinfo::IS_SUPPORTED_SYSTEM { assert_eq!( sys.refresh_processes( ProcessesToUpdate::Some(&[ sysinfo::get_current_pid().expect("failed to get current pid") ]), false ), 1, "process not listed", ); // Ensure that the process was really added to the list! assert!( sys.process(sysinfo::get_current_pid().expect("failed to get current pid")) .is_some() ); } } #[test] fn test_get_process() { let mut sys = System::new(); sys.refresh_processes(ProcessesToUpdate::All, false); let current_pid = match sysinfo::get_current_pid() { Ok(pid) => pid, _ => { if !sysinfo::IS_SUPPORTED_SYSTEM { return; } panic!("get_current_pid should work!"); } }; if let Some(p) = sys.process(current_pid) { assert!(p.memory() > 0); } else { #[cfg(not(feature = "apple-sandbox"))] assert!(!sysinfo::IS_SUPPORTED_SYSTEM); } } #[test] fn check_if_send_and_sync() { trait Foo { fn foo(&self) {} } impl Foo for T where T: Send {} trait Bar { fn bar(&self) {} } impl Bar for T where T: Sync {} let mut sys = System::new(); sys.refresh_processes(ProcessesToUpdate::All, false); let current_pid = match sysinfo::get_current_pid() { Ok(pid) => pid, _ => { if !sysinfo::IS_SUPPORTED_SYSTEM { return; } panic!("get_current_pid should work!"); } }; if let Some(p) = sys.process(current_pid) { p.foo(); // If this doesn't compile, it'll simply mean that the Process type // doesn't implement the Send trait. p.bar(); // If this doesn't compile, it'll simply mean that the Process type // doesn't implement the Sync trait. } else { #[cfg(not(feature = "apple-sandbox"))] assert!(!sysinfo::IS_SUPPORTED_SYSTEM); } } #[test] fn check_hostname_has_no_nuls() { if let Some(hostname) = System::host_name() { assert!(!hostname.contains('\u{0}')) } } #[test] fn check_uptime() { let uptime = System::uptime(); if sysinfo::IS_SUPPORTED_SYSTEM { std::thread::sleep(std::time::Duration::from_millis(1000)); let new_uptime = System::uptime(); assert!(uptime < new_uptime); } } #[test] fn check_boot_time() { if sysinfo::IS_SUPPORTED_SYSTEM { assert_ne!(System::boot_time(), 0); } } // This test is used to ensure that the CPU usage computation isn't completely going off // when refreshing it too frequently (ie, multiple times in a row in a very small interval). #[test] fn test_consecutive_cpu_usage_update() { use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; use sysinfo::{Pid, ProcessRefreshKind, System}; if !sysinfo::IS_SUPPORTED_SYSTEM { return; } let mut sys = System::new_all(); assert!(!sys.cpus().is_empty()); sys.refresh_processes_specifics( ProcessesToUpdate::All, true, ProcessRefreshKind::nothing().with_cpu(), ); let stop = Arc::new(AtomicBool::new(false)); // Spawning a few threads to ensure that it will actually have an impact on the CPU usage. for it in 0..sys.cpus().len() / 2 + 1 { let stop_c = Arc::clone(&stop); std::thread::spawn(move || { while !stop_c.load(Ordering::Relaxed) { if it != 0 { // The first thread runs at 100% to be sure it'll be noticeable. std::thread::sleep(Duration::from_millis(1)); } } }); } let mut pids = sys.processes().keys().copied().take(2).collect::>(); let pid = std::process::id(); pids.push(Pid::from_u32(pid)); assert_eq!(pids.len(), 3); for it in 0..3 { std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL + Duration::from_millis(1)); for pid in &pids { sys.refresh_processes_specifics( ProcessesToUpdate::Some(&[*pid]), true, ProcessRefreshKind::nothing().with_cpu(), ); } // To ensure that Linux doesn't give too high numbers. assert!( sys.process(pids[2]).unwrap().cpu_usage() < sys.cpus().len() as f32 * 100., "using ALL CPU: failed at iteration {it}", ); // To ensure it's not 0 either. assert!( sys.process(pids[2]).unwrap().cpu_usage() > 0., "using NO CPU: failed at iteration {it}", ); } stop.store(false, Ordering::Relaxed); } #[test] fn test_refresh_memory() { if !sysinfo::IS_SUPPORTED_SYSTEM { return; } // On linux, since it's the same file, memory information are always retrieved. let is_linux = cfg!(any(target_os = "linux", target_os = "android")); let mut s = System::new(); assert_eq!(s.total_memory(), 0); assert_eq!(s.free_memory(), 0); s.refresh_memory_specifics(sysinfo::MemoryRefreshKind::nothing().with_ram()); assert_ne!(s.total_memory(), 0); assert_ne!(s.free_memory(), 0); if is_linux { assert_ne!(s.total_swap(), 0); assert_ne!(s.free_swap(), 0); } else { assert_eq!(s.total_swap(), 0); assert_eq!(s.free_swap(), 0); } let mut s = System::new(); assert_eq!(s.total_swap(), 0); assert_eq!(s.free_swap(), 0); if std::env::var("APPLE_CI").is_ok() { // Apparently there is no swap for macOS in CIs so can't run further than this point. return; } s.refresh_memory_specifics(sysinfo::MemoryRefreshKind::nothing().with_swap()); // SWAP can be 0 on macOS so this test is disabled #[cfg(not(target_os = "macos"))] { assert_ne!(s.total_swap(), 0); assert_ne!(s.free_swap(), 0); } if is_linux { assert_ne!(s.total_memory(), 0); assert_ne!(s.free_memory(), 0); } else { assert_eq!(s.total_memory(), 0); assert_eq!(s.free_memory(), 0); } let mut s = System::new(); s.refresh_memory(); // SWAP can be 0 on macOS so this test is disabled #[cfg(not(target_os = "macos"))] { assert_ne!(s.total_swap(), 0); assert_ne!(s.free_swap(), 0); } assert_ne!(s.total_memory(), 0); assert_ne!(s.free_memory(), 0); } GuillaumeGomez-sysinfo-067dd61/tests/users.rs000066400000000000000000000023371506747262600213620ustar00rootroot00000000000000// Take a look at the license at the top of the repository in the LICENSE file. // This test is used to ensure that the users are not loaded by default. // // users.groupps() multiple times doesn't return the same output https://github.com/GuillaumeGomez/sysinfo/issues/1233 // // Example: // ---- test_users 1 stdout ---- // user: Administrator group:[Group { inner: GroupInner { id: Gid(0), name: "Administrators" } }] // ---- test_users 2 stdout ---- // user: Administrator group:[] #[cfg(feature = "user")] #[test] fn test_users() { use sysinfo::Users; if !sysinfo::IS_SUPPORTED_SYSTEM { return; } let mut users = Users::new(); assert_eq!(users.iter().count(), 0); users.refresh(); assert!(users.iter().count() > 0); let count = users.first().unwrap().groups().iter().len(); for _ in 1..10 { assert!(users.first().unwrap().groups().iter().len() == count) } } // This test ensures that there are actually groups listed, in particular for Windows. #[cfg(feature = "user")] #[test] fn test_groups() { use sysinfo::Groups; if !sysinfo::IS_SUPPORTED_SYSTEM { return; } let mut groups = Groups::new(); groups.refresh(); assert!(groups.list().len() > 1); }