sysinfo-0.30.13/.cargo_vcs_info.json0000644000000001360000000000100126720ustar { "git": { "sha1": "1639d7476538b7a4d2f5dd1abf2d116c8e756a43" }, "path_in_vcs": "" }sysinfo-0.30.13/.cirrus.yml000064400000000000000000000034611046102023000135760ustar 00000000000000task: name: rust 1.69 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.69 - . $HOME/.cargo/env - rustup --version - rustup component add clippy test_script: - . $HOME/.cargo/env - cargo check - cargo check --no-default-features - cargo clippy -- -D warnings - cargo check --example simple - FREEBSD_CI=1 cargo test -j1 - FREEBSD_CI=1 cargo test --lib -j1 -- --ignored 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 - cargo check --no-default-features - cargo clippy -- -D warnings - cargo check --example simple - FREEBSD_CI=1 cargo test -j1 - FREEBSD_CI=1 cargo test --lib -j1 -- --ignored task: name: rust 1.69 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.69 - source $HOME/.cargo/env - rustup --version - rustup component add clippy test_script: - source $HOME/.cargo/env - cargo check - cargo check --no-default-features - cargo check --no-default-features --features apple-sandbox - cargo clippy -- -D warnings - cargo check --example simple - APPLE_CI=1 cargo test -j1 - APPLE_CI=1 cargo test --lib -j1 -- --ignored sysinfo-0.30.13/.github/FUNDING.yml000064400000000000000000000002121046102023000146320ustar 00000000000000# These are supported funding model platforms github: [GuillaumeGomez] patreon: GuillaumeGomez custom: ["https://paypal.me/imperioland"] sysinfo-0.30.13/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000005341046102023000177010ustar 00000000000000--- 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. sysinfo-0.30.13/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000004521046102023000207330ustar 00000000000000--- 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. sysinfo-0.30.13/.github/workflows/CI.yml000064400000000000000000000167521046102023000161100ustar 00000000000000on: 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 jobs: rustfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true components: rustfmt - run: cargo fmt -- --check clippy: runs-on: ${{ matrix.os }} strategy: matrix: os: - ubuntu-latest - macos-latest - windows-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: nightly override: true components: clippy - run: cargo clippy --all-targets --features serde -- -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.69.0" # minimum supported rust version - stable - nightly steps: - uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.toolchain }} override: true - name: Install cross-target run: rustup target add ${{ matrix.triple.target }} - name: Check uses: actions-rs/cargo@v1 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml -- -D warnings use-cross: ${{ matrix.triple.cross }} - name: Check debug feature uses: actions-rs/cargo@v1 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --features=debug -- -D warnings use-cross: ${{ matrix.triple.cross }} - name: Check without multithreading uses: actions-rs/cargo@v1 with: command: check args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features use-cross: ${{ matrix.triple.cross }} - name: Check example uses: actions-rs/cargo@v1 with: command: check args: --target=${{ matrix.triple.target }} --example simple use-cross: ${{ matrix.triple.cross }} - name: Check (Apple app store restrictions) if: matrix.triple.os == 'macos-latest' uses: actions-rs/cargo@v1 with: command: check args: --features apple-sandbox --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml use-cross: ${{ matrix.triple.cross }} - name: Check without multithreading (Apple app store restrictions) if: matrix.triple.os == 'macos-latest' uses: actions-rs/cargo@v1 with: command: check args: --features apple-sandbox --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features use-cross: ${{ matrix.triple.cross }} 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.69.0" # minimum supported rust version - stable - nightly steps: - uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.toolchain }} override: true - 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@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: make unknown-targets: runs-on: ubuntu-latest strategy: matrix: toolchain: - "1.69.0" # minimum supported rust version - stable steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.toolchain }} override: true 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' sysinfo-0.30.13/.gitignore000064400000000000000000000015361046102023000134570ustar 00000000000000 # Created by https://www.gitignore.io/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 simplesysinfo-0.30.13/ADDING_NEW_PLATFORMS.md000064400000000000000000000002201046102023000150640ustar 00000000000000To get the `target_os` etc: ``` rustc --print cfg ``` In `src/lib.rs` add the matching based on the `cfg` data. Create `src/`. sysinfo-0.30.13/CHANGELOG.md000064400000000000000000000506171046102023000133040ustar 00000000000000# 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`. sysinfo-0.30.13/Cargo.lock0000644000000220600000000000100106450ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crossbeam-deque" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "either" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "ntapi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ "winapi", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "proc-macro2" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "rustix" version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "ryu" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "serde" version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "syn" version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sysinfo" version = "0.30.13" dependencies = [ "cfg-if", "core-foundation-sys", "libc", "ntapi", "once_cell", "rayon", "serde", "serde_json", "tempfile", "windows", ] [[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", "rustix", "windows-sys", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", "windows-targets", ] [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" sysinfo-0.30.13/Cargo.toml0000644000000075360000000000100107030ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.69" name = "sysinfo" version = "0.30.13" authors = ["Guillaume Gomez "] build = false exclude = ["/test-unknown"] autobins = false autoexamples = false autotests = false autobenches = false description = "Library to get system information such as processes, CPUs, disks, components and networks" readme = "README.md" categories = [ "filesystem", "os", "api-bindings", ] license = "MIT" repository = "https://github.com/GuillaumeGomez/sysinfo" [package.metadata.docs.rs] cargo-args = ["-Zbuild-std"] default-target = "x86_64-unknown-linux-gnu" features = ["serde"] rustdoc-args = ["--generate-link-to-definition"] targets = [ "i686-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "i686-pc-windows-msvc", "x86_64-unknown-freebsd", ] [lib] name = "sysinfo" path = "src/lib.rs" [[example]] name = "simple" path = "examples/simple.rs" [[test]] name = "components" path = "tests/components.rs" [[test]] name = "cpu" path = "tests/cpu.rs" [[test]] name = "disk_list" path = "tests/disk_list.rs" [[test]] name = "extras" path = "tests/extras.rs" [[test]] name = "network" path = "tests/network.rs" [[test]] name = "process" path = "tests/process.rs" [[test]] name = "send_sync" path = "tests/send_sync.rs" [[test]] name = "system" path = "tests/system.rs" [[bench]] name = "basic" path = "benches/basic.rs" [dependencies.cfg-if] version = "1.0" [dependencies.rayon] version = "^1.8" optional = true [dependencies.serde] version = "^1.0.190" optional = true [dev-dependencies.serde_json] version = "1.0" [features] apple-app-store = ["apple-sandbox"] apple-sandbox = [] c-interface = [] debug = ["libc/extra_traits"] default = ["multithread"] linux-netdevs = [] linux-tmpfs = [] multithread = ["rayon"] unknown-ci = [] [target.'cfg(all(target_os = "linux", not(target_os = "android")))'.dev-dependencies.tempfile] version = "3.9" [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies.core-foundation-sys] version = "0.8" [target.'cfg(any(windows, target_os = "linux", target_os = "android"))'.dependencies.once_cell] version = "1.18" [target.'cfg(not(any(target_os = "unknown", target_arch = "wasm32")))'.dependencies.libc] version = "^0.2.153" [target."cfg(windows)".dependencies.ntapi] version = "0.4" [target."cfg(windows)".dependencies.windows] version = "0.52" features = [ "Wdk_System_SystemInformation", "Wdk_System_SystemServices", "Wdk_System_Threading", "Win32_Foundation", "Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_NetManagement", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock", "Win32_Security", "Win32_Security_Authentication_Identity", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_LibraryLoader", "Win32_System_Kernel", "Win32_System_Memory", "Win32_System_Ole", "Win32_System_Performance", "Win32_System_Power", "Win32_System_ProcessStatus", "Win32_System_Registry", "Win32_System_RemoteDesktop", "Win32_System_Rpc", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Variant", "Win32_System_WindowsProgramming", "Win32_System_Wmi", "Win32_UI_Shell", ] sysinfo-0.30.13/Cargo.toml.orig000064400000000000000000000054721046102023000143610ustar 00000000000000[package] name = "sysinfo" version = "0.30.13" 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.69" exclude = ["/test-unknown"] categories = ["filesystem", "os", "api-bindings"] edition = "2018" [lib] name = "sysinfo" [features] default = ["multithread"] apple-sandbox = [] apple-app-store = ["apple-sandbox"] c-interface = [] multithread = ["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] cfg-if = "1.0" rayon = { version = "^1.8", optional = true } serde = { version = "^1.0.190", optional = true } [target.'cfg(any(windows, target_os = "linux", target_os = "android"))'.dependencies] once_cell = "1.18" [target.'cfg(windows)'.dependencies] ntapi = "0.4" windows = { version = "0.52", features = [ "Wdk_System_SystemInformation", "Wdk_System_SystemServices", "Wdk_System_Threading", "Win32_Foundation", "Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_NetManagement", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock", "Win32_Security", "Win32_Security_Authentication_Identity", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_LibraryLoader", "Win32_System_Kernel", "Win32_System_Memory", "Win32_System_Ole", "Win32_System_Performance", "Win32_System_Power", "Win32_System_ProcessStatus", "Win32_System_Registry", "Win32_System_RemoteDesktop", "Win32_System_Rpc", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Variant", "Win32_System_WindowsProgramming", "Win32_System_Wmi", "Win32_UI_Shell", ]} [target.'cfg(not(any(target_os = "unknown", target_arch = "wasm32")))'.dependencies] libc = "^0.2.153" [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] core-foundation-sys = "0.8" [target.'cfg(all(target_os = "linux", not(target_os = "android")))'.dev-dependencies] tempfile = "3.9" [dev-dependencies] serde_json = "1.0" # Used in documentation tests. sysinfo-0.30.13/LICENSE000064400000000000000000000020731046102023000124710ustar 00000000000000The 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. sysinfo-0.30.13/Makefile000064400000000000000000000015111046102023000131200ustar 00000000000000# # 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)/*~ sysinfo-0.30.13/README.md000064400000000000000000000166171046102023000127540ustar 00000000000000# 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.69**. ## 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 list of // components, network interfaces, disks and users are already // 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 na 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(); // Refreshing CPU information. 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 issues 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 ### 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/ sysinfo-0.30.13/benches/basic.rs000064400000000000000000000057071046102023000145310ustar 00000000000000#![feature(test)] extern crate test; use sysinfo::get_current_pid; #[bench] fn bench_new(b: &mut test::Bencher) { b.iter(|| { sysinfo::System::new(); }); } #[bench] fn bench_new_all(b: &mut test::Bencher) { b.iter(|| { sysinfo::System::new_all(); }); } #[bench] fn bench_refresh_all(b: &mut test::Bencher) { let mut s = sysinfo::System::new_all(); b.iter(move || { s.refresh_all(); }); } #[bench] fn bench_refresh_processes(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); s.refresh_processes(); // to load the whole processes list a first time. b.iter(move || { s.refresh_processes(); }); } #[bench] fn bench_first_refresh_processes(b: &mut test::Bencher) { b.iter(move || { let mut s = sysinfo::System::new(); s.refresh_processes(); }); } #[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 = get_current_pid().expect("failed to get current pid"); b.iter(move || { s.refresh_process(pid); }); } #[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(); }); } #[bench] fn bench_refresh_disks_list(b: &mut test::Bencher) { let mut disks = sysinfo::Disks::new_with_refreshed_list(); b.iter(move || { disks.refresh_list(); }); } #[bench] fn bench_refresh_networks(b: &mut test::Bencher) { let mut n = sysinfo::Networks::new_with_refreshed_list(); b.iter(move || { n.refresh(); }); } #[bench] fn bench_refresh_networks_list(b: &mut test::Bencher) { let mut n = sysinfo::Networks::new_with_refreshed_list(); b.iter(move || { n.refresh_list(); }); } #[bench] fn bench_refresh_memory(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); b.iter(move || { s.refresh_memory(); }); } #[bench] fn bench_refresh_cpu(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); b.iter(move || { s.refresh_cpu(); }); } #[bench] fn bench_refresh_components(b: &mut test::Bencher) { let mut c = sysinfo::Components::new_with_refreshed_list(); b.iter(move || { c.refresh(); }); } #[bench] fn bench_refresh_components_list(b: &mut test::Bencher) { let mut c = sysinfo::Components::new_with_refreshed_list(); b.iter(move || { c.refresh_list(); }); } #[bench] fn bench_refresh_users_list(b: &mut test::Bencher) { let mut users = sysinfo::Users::new_with_refreshed_list(); b.iter(move || { users.refresh_list(); }); } sysinfo-0.30.13/examples/simple.c000064400000000000000000000070301046102023000147350ustar 00000000000000// 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_list(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; } sysinfo-0.30.13/examples/simple.rs000064400000000000000000000374651046102023000151560ustar 00000000000000// 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, Networks, Pid, Signal, System, Users}; const signals: &[Signal] = &[ Signal::Hangup, Signal::Interrupt, Signal::Quit, Signal::Illegal, Signal::Trap, Signal::Abort, Signal::Bus, Signal::FloatingPointException, Signal::Kill, Signal::User1, Signal::Segv, Signal::User2, Signal::Pipe, Signal::Alarm, Signal::Term, Signal::Child, Signal::Continue, Signal::Stop, Signal::TSTP, Signal::TTIN, Signal::TTOU, Signal::Urgent, Signal::XCPU, Signal::XFSZ, Signal::VirtualAlarm, Signal::Profiling, Signal::Winch, Signal::IO, Signal::Power, Signal::Sys, ]; fn print_help() { writeln!(&mut io::stdout(), "== Help menu =="); writeln!(&mut io::stdout(), "help : show this menu"); writeln!( &mut io::stdout(), "signals : show the available signals" ); writeln!( &mut io::stdout(), "refresh : reloads all processes information" ); writeln!( &mut io::stdout(), "refresh [pid] : reloads corresponding process information" ); writeln!( &mut io::stdout(), "refresh_components : reloads components information" ); writeln!( &mut io::stdout(), "refresh_cpu : reloads CPU information" ); writeln!( &mut io::stdout(), "refresh_disks : reloads disks information" ); writeln!( &mut io::stdout(), "refresh_users : reloads users information" ); writeln!( &mut io::stdout(), "refresh_networks : reloads networks information" ); writeln!( &mut io::stdout(), "show [pid | name] : show information of the given process \ corresponding to [pid | name]" ); writeln!( &mut io::stdout(), "kill [pid] [signal]: send [signal] to the process with this \ [pid]. 0 < [signal] < 32" ); writeln!( &mut io::stdout(), "cpus : Displays CPUs state" ); writeln!( &mut io::stdout(), "memory : Displays memory state" ); writeln!( &mut io::stdout(), "temperature : Displays components' temperature" ); writeln!( &mut io::stdout(), "disks : Displays disks' information" ); writeln!( &mut io::stdout(), "network : Displays network' information" ); writeln!( &mut io::stdout(), "all : Displays all process name and pid" ); writeln!( &mut io::stdout(), "uptime : Displays system uptime" ); writeln!( &mut io::stdout(), "boot_time : Displays system boot time" ); writeln!( &mut io::stdout(), "vendor_id : Displays CPU vendor id" ); writeln!(&mut io::stdout(), "brand : Displays CPU brand"); writeln!( &mut io::stdout(), "load_avg : Displays system load average" ); writeln!( &mut io::stdout(), "frequency : Displays CPU frequency" ); writeln!( &mut io::stdout(), "users : Displays all users and their groups" ); writeln!( &mut io::stdout(), "system : Displays system information (such as name, version and hostname)" ); writeln!( &mut io::stdout(), "pid : Display this example's PID" ); writeln!(&mut io::stdout(), "quit : Exit the program"); } 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" => { writeln!(&mut io::stdout(), "Refreshing disk list..."); disks.refresh_list(); writeln!(&mut io::stdout(), "Done."); } "refresh_users" => { writeln!(&mut io::stdout(), "Refreshing user list..."); users.refresh_list(); writeln!(&mut io::stdout(), "Done."); } "refresh_networks" => { writeln!(&mut io::stdout(), "Refreshing network list..."); networks.refresh_list(); writeln!(&mut io::stdout(), "Done."); } "refresh_components" => { writeln!(&mut io::stdout(), "Refreshing component list..."); components.refresh_list(); writeln!(&mut io::stdout(), "Done."); } "refresh_cpu" => { writeln!(&mut io::stdout(), "Refreshing CPUs..."); sys.refresh_cpu(); writeln!(&mut io::stdout(), "Done."); } "signals" => { let mut nb = 1i32; for sig in signals { writeln!(&mut io::stdout(), "{nb:2}:{sig:?}"); nb += 1; } } "cpus" => { // Note: you should refresh a few times before using this, so that usage statistics // can be ascertained writeln!( &mut io::stdout(), "number of physical cores: {}", sys.physical_core_count() .map(|c| c.to_string()) .unwrap_or_else(|| "Unknown".to_owned()), ); writeln!( &mut io::stdout(), "total CPU usage: {}%", sys.global_cpu_info().cpu_usage() ); for cpu in sys.cpus() { writeln!(&mut io::stdout(), "{cpu:?}"); } } "memory" => { writeln!( &mut io::stdout(), "total memory: {: >10} KB", sys.total_memory() / 1_000 ); writeln!( &mut io::stdout(), "available memory: {: >10} KB", sys.available_memory() / 1_000 ); writeln!( &mut io::stdout(), "used memory: {: >10} KB", sys.used_memory() / 1_000 ); writeln!( &mut io::stdout(), "total swap: {: >10} KB", sys.total_swap() / 1_000 ); writeln!( &mut io::stdout(), "used swap: {: >10} KB", sys.used_swap() / 1_000 ); } "quit" | "exit" => return true, "all" => { for (pid, proc_) in sys.processes() { writeln!( &mut io::stdout(), "{}:{} status={:?}", pid, proc_.name(), proc_.status() ); } } "frequency" => { for cpu in sys.cpus() { writeln!( &mut io::stdout(), "[{}] {} MHz", cpu.name(), cpu.frequency(), ); } } "vendor_id" => { writeln!( &mut io::stdout(), "vendor ID: {}", sys.cpus()[0].vendor_id() ); } "brand" => { writeln!(&mut io::stdout(), "brand: {}", sys.cpus()[0].brand()); } "load_avg" => { let load_avg = System::load_average(); writeln!(&mut io::stdout(), "one minute : {}%", load_avg.one); writeln!(&mut io::stdout(), "five minutes : {}%", load_avg.five); writeln!(&mut io::stdout(), "fifteen minutes: {}%", load_avg.fifteen); } e if e.starts_with("show ") => { let tmp: Vec<&str> = e.split(' ').collect(); if tmp.len() != 2 { writeln!( &mut io::stdout(), "show command takes a pid or a name in parameter!" ); writeln!(&mut io::stdout(), "example: show 1254"); } else if let Ok(pid) = Pid::from_str(tmp[1]) { match sys.process(pid) { Some(p) => writeln!(&mut io::stdout(), "{:?}", *p), None => writeln!(&mut io::stdout(), "pid \"{pid:?}\" not found"), }; } else { let proc_name = tmp[1]; for proc_ in sys.processes_by_name(proc_name) { writeln!(&mut io::stdout(), "==== {} ====", proc_.name()); writeln!(&mut io::stdout(), "{proc_:?}"); } } } "temperature" => { for component in components.iter() { writeln!(&mut io::stdout(), "{component:?}"); } } "network" => { for (interface_name, data) in networks.iter() { writeln!( &mut io::stdout(), "{}:\n ether {}\n input data (new / total): {} / {} B\n output data (new / total): {} / {} B", interface_name, data.mac_address(), data.received(), data.total_received(), data.transmitted(), data.total_transmitted(), ); } } "show" => { writeln!( &mut io::stdout(), "'show' command expects a pid number or a process name" ); } e if e.starts_with("kill ") => { let tmp: Vec<&str> = e.split(' ').collect(); if tmp.len() != 3 { writeln!( &mut io::stdout(), "kill command takes the pid and a signal number in parameter!" ); writeln!(&mut io::stdout(), "example: kill 1254 9"); } else { let pid = Pid::from_str(tmp[1]).unwrap(); let signal = i32::from_str(tmp[2]).unwrap(); if signal < 1 || signal > 31 { writeln!( &mut io::stdout(), "Signal must be between 0 and 32 ! See the signals list with the \ signals command" ); } else { match sys.process(pid) { Some(p) => { if let Some(res) = p.kill_with(*signals.get(signal as usize - 1).unwrap()) { writeln!(&mut io::stdout(), "kill: {res}"); } else { writeln!( &mut io::stdout(), "kill: signal not supported on this platform" ); } } None => { writeln!(&mut io::stdout(), "pid not found"); } }; } } } "disks" => { for disk in disks { writeln!(&mut io::stdout(), "{disk:?}"); } } "users" => { for user in users { writeln!( &mut io::stdout(), "{:?} => {:?}", user.name(), user.groups() ); } } "boot_time" => { writeln!(&mut io::stdout(), "{} 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; writeln!( &mut io::stdout(), "{days} days {hours} hours {minutes} minutes ({up} seconds in total)", ); } x if x.starts_with("refresh") => { if x == "refresh" { writeln!(&mut io::stdout(), "Getting processes' information..."); sys.refresh_all(); writeln!(&mut io::stdout(), "Done."); } else if x.starts_with("refresh ") { writeln!(&mut io::stdout(), "Getting process' information..."); if let Some(pid) = x .split(' ') .filter_map(|pid| pid.parse().ok()) .take(1) .next() { if sys.refresh_process(pid) { writeln!(&mut io::stdout(), "Process `{pid}` updated successfully"); } else { writeln!(&mut io::stdout(), "Process `{pid}` couldn't be updated..."); } } else { writeln!(&mut io::stdout(), "Invalid [pid] received..."); } } else { writeln!( &mut io::stdout(), "\"{x}\": Unknown command. Enter 'help' if you want to get the commands' \ list.", ); } } "pid" => { writeln!( &mut io::stdout(), "PID: {}", sysinfo::get_current_pid().expect("failed to get PID") ); } "system" => { writeln!( &mut io::stdout(), "System name: {}\n\ System kernel version: {}\n\ System OS version: {}\n\ System OS (long) version: {}\n\ System host name: {}", 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()), ); } e => { writeln!( &mut io::stdout(), "\"{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, ); } } sysinfo-0.30.13/md_doc/is_supported.md000064400000000000000000000004251046102023000157520ustar 00000000000000Returns `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?)."); } ``` sysinfo-0.30.13/md_doc/minimum_cpu_update_interval.md000064400000000000000000000004731046102023000210250ustar 00000000000000This 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`). sysinfo-0.30.13/md_doc/pid.md000064400000000000000000000011521046102023000140040ustar 00000000000000Process 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://docs.microsoft.com/en-us/windows/win32/procthread/process-handles-and-identifiers). On unsupported systems, this is also a `usize`. sysinfo-0.30.13/md_doc/serde.md000064400000000000000000000004701046102023000143340ustar 00000000000000 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()); ``` sysinfo-0.30.13/md_doc/sid.md000064400000000000000000000000521046102023000140050ustar 00000000000000Opaque type encapsulating a Windows SID. sysinfo-0.30.13/md_doc/supported_signals.md000064400000000000000000000003421046102023000167750ustar 00000000000000Returns 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); ``` sysinfo-0.30.13/migration_guide.md000064400000000000000000000075551046102023000151660ustar 00000000000000# Migration guide ## 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 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). sysinfo-0.30.13/src/c_interface.rs000064400000000000000000000467501046102023000150750ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Disks, Networks, Pid, Process, 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]. #[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. #[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]. #[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(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_cpu_usage()`][crate::System#method.refresh_cpu_usage]. #[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(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_all()`][crate::System#method.refresh_all]. #[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(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_processes()`][crate::System#method.refresh_processes]. #[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(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_process()`][crate::System#method.refresh_process]. #[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_process(Pid(pid)); } Box::into_raw(system); } } /// Equivalent of [`Disks::new()`][crate::Disks#method.new]. #[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. #[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]. #[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(); } Box::into_raw(disks); } } /// Equivalent of [`Disks::refresh_list()`][crate::Disks#method.refresh_list]. #[no_mangle] pub extern "C" fn sysinfo_disks_refresh_list(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_list(); } Box::into_raw(disks); } } /// Equivalent of [`System::total_memory()`][crate::System#method.total_memory]. #[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; Box::into_raw(system); ret } } /// Equivalent of [`System::free_memory()`][crate::System#method.free_memory]. #[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; Box::into_raw(system); ret } } /// Equivalent of [`System::used_memory()`][crate::System#method.used_memory]. #[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; Box::into_raw(system); ret } /// Equivalent of [`System::total_swap()`][crate::System#method.total_swap]. #[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; Box::into_raw(system); ret } } /// Equivalent of [`System::free_swap()`][crate::System#method.free_swap]. #[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; Box::into_raw(system); ret } } /// Equivalent of [`System::used_swap()`][crate::System#method.used_swap]. #[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; Box::into_raw(system); ret } } /// Equivalent of [`Networks::new()`][crate::Networks#method.new]. #[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. #[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_list()`][crate::Networks#method.refresh_list]. #[no_mangle] pub extern "C" fn sysinfo_networks_refresh_list(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_list(); } Box::into_raw(networks); } } /// Equivalent of [`Networks::refresh()`][crate::Networks#method.refresh]. #[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(); } Box::into_raw(networks); } } /// Equivalent of /// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.received() as size_t)`. #[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) }); Box::into_raw(networks); ret } } /// Equivalent of /// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.transmitted() as size_t)`. #[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) }); 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. #[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; } 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! #[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, process as *const Process as CProcess, data) { break; } } entries.len() as size_t }; 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! #[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)) { process as *const Process as CProcess } else { std::ptr::null() }; 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! #[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, data) { break; } } tasks.len() as size_t } else { 0 } } } else { 0 } } /// Equivalent of [`Process::pid()`][crate::Process#method.pid]. #[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 } } /// Equivalent of [`Process::parent()`][crate::Process#method.parent]. /// /// In case there is no known parent, it returns `0`. #[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 } } /// Equivalent of [`Process::cpu_usage()`][crate::Process#method.cpu_usage]. #[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]. #[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]. #[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]. #[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]. #[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]. #[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()`. #[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()`]. #[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() }; Box::into_raw(system); c_string } } /// Equivalent of [`cpu::brand()`]. #[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() }; Box::into_raw(system); c_string } } /// Equivalent of [`system::physical_core_count()`]. #[no_mangle] pub extern "C" fn sysinfo_cpu_physical_cores(system: CSystem) -> u32 { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let count = system.physical_core_count().unwrap_or(0); Box::into_raw(system); count as u32 } } /// Equivalent of [`cpu::frequency()`]. #[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); Box::into_raw(system); freq } } /// Equivalent of [`System::name()`][crate::System#method.name]. #[no_mangle] pub extern "C" fn sysinfo_system_name() -> RString { let c_string = if let Some(c) = System::name().and_then(|p| CString::new(p).ok()) { c.into_raw() as _ } else { std::ptr::null() }; c_string } /// Equivalent of [`System::version()`][crate::System#method.version]. #[no_mangle] pub extern "C" fn sysinfo_system_version() -> RString { let c_string = if let Some(c) = System::os_version().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() }; c_string } /// Equivalent of [`System::kernel_version()`][crate::System#method.kernel_version]. #[no_mangle] pub extern "C" fn sysinfo_system_kernel_version() -> RString { let c_string = if let Some(c) = System::kernel_version().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() }; c_string } /// Equivalent of [`System::host_name()`][crate::System#method.host_name]. #[no_mangle] pub extern "C" fn sysinfo_system_host_name() -> RString { let c_string = if let Some(c) = System::host_name().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() }; c_string } /// Equivalent of [`System::long_os_version()`][crate::System#method.long_os_version]. #[no_mangle] pub extern "C" fn sysinfo_system_long_version() -> RString { let c_string = if let Some(c) = System::long_os_version().and_then(|c| CString::new(c).ok()) { c.into_raw() as _ } else { std::ptr::null() }; c_string } sysinfo-0.30.13/src/common.rs000064400000000000000000003434751046102023000141270ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ utils::into_iter_mut, ComponentInner, ComponentsInner, CpuInner, NetworkDataInner, NetworksInner, ProcessInner, SystemInner, UserInner, }; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::ffi::OsStr; use std::fmt; use std::path::Path; use std::str::FromStr; /// Structs containing system's information such as processes, memory and CPU. /// /// ``` /// 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::new()) } /// 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::new().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. /// /// ``` /// use sysinfo::{ProcessRefreshKind, RefreshKind, System}; /// /// let mut s = System::new_all(); /// /// // Let's just update processes: /// s.refresh_specifics( /// RefreshKind::new().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(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. /// /// ```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::new().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::new().with_cpu_usage())`. /// /// ```no_run /// use sysinfo::System; /// /// let mut s = System::new_all(); /// 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::new().with_cpu_usage()) } /// Refreshes CPUs frequency information. /// /// Calling this method is the same as calling /// `system.refresh_cpu_specifics(CpuRefreshKind::new().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::new().with_frequency()) } /// Refreshes all information related to CPUs information. /// /// ⚠️ 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::everything())`. /// /// ```no_run /// use sysinfo::System; /// /// let mut s = System::new_all(); /// s.refresh_cpu(); /// ``` /// /// [`MINIMUM_CPU_UPDATE_INTERVAL`]: crate::MINIMUM_CPU_UPDATE_INTERVAL pub fn refresh_cpu(&mut self) { self.refresh_cpu_specifics(CpuRefreshKind::everything()) } /// Refreshes CPUs specific information. /// /// ```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. /// /// It does the same as: /// /// ```no_run /// # use sysinfo::{ProcessRefreshKind, System, UpdateKind}; /// # let mut system = System::new(); /// system.refresh_processes_specifics( /// ProcessRefreshKind::new() /// .with_memory() /// .with_cpu() /// .with_disk_usage() /// .with_exe(UpdateKind::OnlyIfNotSet), /// ); /// ``` /// /// ⚠️ 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]. /// /// Example: /// /// ```no_run /// use sysinfo::System; /// /// let mut s = System::new_all(); /// s.refresh_processes(); /// ``` pub fn refresh_processes(&mut self) { self.refresh_processes_specifics( ProcessRefreshKind::new() .with_memory() .with_cpu() .with_disk_usage() .with_exe(UpdateKind::OnlyIfNotSet), ); } /// Gets all processes and updates the specified information. /// /// ⚠️ 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::{ProcessRefreshKind, System}; /// /// let mut s = System::new_all(); /// s.refresh_processes_specifics(ProcessRefreshKind::new()); /// ``` pub fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { self.inner.refresh_processes_specifics(None, refresh_kind) } /// Gets specified processes and updates their information. /// /// It does the same as: /// /// ```no_run /// # use sysinfo::{Pid, ProcessRefreshKind, System, UpdateKind}; /// # let mut system = System::new(); /// system.refresh_pids_specifics( /// &[Pid::from(1), Pid::from(2)], /// ProcessRefreshKind::new() /// .with_memory() /// .with_cpu() /// .with_disk_usage() /// .with_exe(UpdateKind::OnlyIfNotSet), /// ); /// ``` /// /// ⚠️ 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]. /// /// Example: /// /// ```no_run /// use sysinfo::System; /// /// let mut s = System::new_all(); /// s.refresh_processes(); /// ``` pub fn refresh_pids(&mut self, pids: &[Pid]) { self.refresh_pids_specifics( pids, ProcessRefreshKind::new() .with_memory() .with_cpu() .with_disk_usage() .with_exe(UpdateKind::OnlyIfNotSet), ); } /// Gets specified processes and updates the specified information. /// /// ⚠️ 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::{Pid, ProcessRefreshKind, System}; /// /// let mut s = System::new_all(); /// s.refresh_pids_specifics(&[Pid::from(1), Pid::from(2)], ProcessRefreshKind::new()); /// ``` pub fn refresh_pids_specifics(&mut self, pids: &[Pid], refresh_kind: ProcessRefreshKind) { if pids.is_empty() { return; } self.inner .refresh_processes_specifics(Some(pids), refresh_kind) } /// Refreshes *only* the process corresponding to `pid`. Returns `false` if the process doesn't /// exist (it will **NOT** be removed from the processes if it doesn't exist anymore). If it /// isn't listed yet, it'll be added. /// /// It is the same as calling: /// /// ```no_run /// # use sysinfo::{Pid, ProcessRefreshKind, System, UpdateKind}; /// # let mut system = System::new(); /// # let pid = Pid::from(0); /// system.refresh_process_specifics( /// pid, /// ProcessRefreshKind::new() /// .with_memory() /// .with_cpu() /// .with_disk_usage() /// .with_exe(UpdateKind::OnlyIfNotSet), /// ); /// ``` /// /// ⚠️ 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]. /// /// Example: /// /// ```no_run /// use sysinfo::{Pid, System}; /// /// let mut s = System::new_all(); /// s.refresh_process(Pid::from(1337)); /// ``` pub fn refresh_process(&mut self, pid: Pid) -> bool { self.refresh_process_specifics( pid, ProcessRefreshKind::new() .with_memory() .with_cpu() .with_disk_usage() .with_exe(UpdateKind::OnlyIfNotSet), ) } /// Refreshes *only* the process corresponding to `pid`. Returns `false` if the process doesn't /// exist (it will **NOT** be removed from the processes if it doesn't exist anymore). If it /// isn't listed yet, it'll be added. /// /// ⚠️ 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::{Pid, ProcessRefreshKind, System}; /// /// let mut s = System::new_all(); /// s.refresh_process_specifics(Pid::from(1337), ProcessRefreshKind::new()); /// ``` pub fn refresh_process_specifics( &mut self, pid: Pid, refresh_kind: ProcessRefreshKind, ) -> bool { self.inner.refresh_process_specifics(pid, refresh_kind) } /// 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") { /// println!("{} {}", process.pid(), process.name()); /// } /// ``` pub fn processes_by_name<'a: 'b, 'b>( &'a self, name: &'b str, ) -> impl Iterator + 'b { self.processes() .values() .filter(move |val: &&Process| val.name().contains(name)) } /// 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") { /// println!("{} {}", process.pid(), process.name()); /// } /// ``` pub fn processes_by_exact_name<'a: 'b, 'b>( &'a self, name: &'b str, ) -> impl Iterator + 'b { self.processes() .values() .filter(move |val: &&Process| val.name() == name) } /// Returns "global" CPUs information (aka the addition of all the CPUs). /// /// To have up-to-date information, you need to call [`System::refresh_cpu`] or /// [`System::refresh_specifics`] with `cpu` enabled. /// /// **⚠️ Important ⚠️** /// /// Information like [`Cpu::brand`], [`Cpu::vendor_id`] or [`Cpu::frequency`] /// are not set on the "global" CPU. /// /// ```no_run /// use sysinfo::{CpuRefreshKind, RefreshKind, System}; /// /// let s = System::new_with_specifics( /// RefreshKind::new().with_cpu(CpuRefreshKind::everything()), /// ); /// println!("{}%", s.global_cpu_info().cpu_usage()); /// ``` /// /// [`Cpu::brand`]: crate::Cpu::brand /// [`Cpu::vendor_id`]: crate::Cpu::vendor_id /// [`Cpu::frequency`]: crate::Cpu::frequency pub fn global_cpu_info(&self) -> &Cpu { self.inner.global_cpu_info() } /// Returns the list of the CPUs. /// /// By default, the list of CPUs is empty until you call [`System::refresh_cpu`] or /// [`System::refresh_specifics`] with `cpu` enabled. /// /// ```no_run /// use sysinfo::{CpuRefreshKind, RefreshKind, System}; /// /// let s = System::new_with_specifics( /// RefreshKind::new().with_cpu(CpuRefreshKind::everything()), /// ); /// for cpu in s.cpus() { /// println!("{}%", cpu.cpu_usage()); /// } /// ``` pub fn cpus(&self) -> &[Cpu] { self.inner.cpus() } /// 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!("{:?}", s.physical_core_count()); /// ``` pub fn physical_core_count(&self) -> Option { self.inner.physical_core_count() } /// 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. /// /// **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. /// /// **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 11.1 rather than the kernel /// version). /// /// **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 (e.g "MacOS 11.2 BigSur"). /// /// **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 /// - /// - /// /// **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 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() -> Option { SystemInner::cpu_arch() } } /// 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). /// /// If you want to send 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 if the signal was sent /// successfully. /// /// If you just want to kill the process, use [`Process::kill`] directly. /// /// 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) } /// 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) -> &str { self.inner.name() } /// Returns the command line. /// /// ```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) -> &[String] { 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) -> &[String] { 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, System}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}%", process.cpu_usage()); /// } /// ``` pub fn cpu_usage(&self) -> f32 { self.inner.cpu_usage() } /// Returns number of bytes read and written to disk. /// /// ⚠️ On Windows, this method actually returns **ALL** I/O read and /// written bytes. /// /// ```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() } /// Wait for process termination. /// /// ```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"); /// process.wait(); /// println!("Pid 1337 exited"); /// } /// ``` pub fn wait(&self) { self.inner.wait() } /// 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::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::cfg_if! { if #[cfg(all( any(target_os = "linux", target_os = "android"), not(feature = "unknown-ci") ))] { self.inner.thread_kind() } else { None } } } } 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::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); } } 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), "::new(); assert_eq!(r.", stringify!($name), "(), false); 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), "::new(); assert_eq!(r.", stringify!($name), "(), false); 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), "::new(); 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), "::new(); 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), "::new(); 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), "::new(); 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 } }; } /// 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::{ProcessRefreshKind, System, UpdateKind}; /// /// let mut system = System::new(); /// system.refresh_processes_specifics( /// ProcessRefreshKind::new().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(), } } } /// 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. /// /// ``` /// use sysinfo::{ProcessRefreshKind, System}; /// /// let mut system = System::new(); /// /// // We don't want to update the CPU information. /// system.refresh_processes_specifics(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, Default, PartialEq, Eq)] pub struct ProcessRefreshKind { cpu: bool, disk_usage: bool, memory: bool, user: UpdateKind, cwd: UpdateKind, root: UpdateKind, environ: UpdateKind, cmd: UpdateKind, exe: UpdateKind, } impl ProcessRefreshKind { /// Creates a new `ProcessRefreshKind` with every refresh set to `false`. /// /// ``` /// use sysinfo::{ProcessRefreshKind, UpdateKind}; /// /// let r = ProcessRefreshKind::new(); /// /// assert_eq!(r.cpu(), false); /// assert_eq!(r.user(), UpdateKind::Never); /// ``` pub fn new() -> 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, } } impl_get_set!(ProcessRefreshKind, cpu, with_cpu, without_cpu); 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); } /// 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::new(); /// /// assert_eq!(r.frequency(), false); /// assert_eq!(r.cpu_usage(), false); /// ``` pub fn new() -> 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::new().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::new(); /// /// assert_eq!(r.ram(), false); /// assert_eq!(r.swap(), false); /// ``` pub fn new() -> 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::new(); /// /// assert_eq!(r.processes().is_some(), false); /// assert_eq!(r.memory().is_some(), false); /// assert_eq!(r.cpu().is_some(), false); /// ``` pub fn new() -> 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); } /// 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_list(); /// 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 disk list /// loaded. It is a combination of [`Networks::new`] and /// [`Networks::refresh_list`]. /// /// ```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_list(); 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 list. /// /// ```no_run /// use sysinfo::Networks; /// /// let mut networks = Networks::new(); /// networks.refresh_list(); /// ``` pub fn refresh_list(&mut self) { self.inner.refresh_list() } /// Refreshes the network interfaces' content. If you didn't run [`Networks::refresh_list`] /// before, calling this method won't do anything as no interfaces are present. /// /// ⚠️ If a network interface is added or removed, this method won't take it into account. Use /// [`Networks::refresh_list`] instead. /// /// ⚠️ If you didn't call [`Networks::refresh_list`] beforehand, this method will do nothing /// as the network list will be empty. /// /// ```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(); /// ``` pub fn refresh(&mut self) { self.inner.refresh() } } 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(); /// /// 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(); /// /// 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(); /// /// 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(); /// /// 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(); /// /// 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(); /// /// 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() } } /// 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() } /// Updates the disk' information. /// /// ```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.inner.refresh() } } /// 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_list(); /// 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. /// It is a combination of [`Disks::new`] and [`Disks::refresh_list`]. /// /// ```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 { let mut disks = Self::new(); disks.refresh_list(); 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. /// /// ⚠️ If a disk is added or removed, this method won't take it into account. Use /// [`Disks::refresh_list`] instead. /// /// ⚠️ If you didn't call [`Disks::refresh_list`] beforehand, this method will do nothing as /// the disk list will be empty. /// /// ```no_run /// use sysinfo::Disks; /// /// let mut disks = Disks::new_with_refreshed_list(); /// // We wait some time...? /// disks.refresh(); /// ``` pub fn refresh(&mut self) { for disk in self.list_mut() { disk.refresh(); } } /// The disk list will be emptied then completely recomputed. /// /// ## Linux /// /// ⚠️ On Linux, the [NFS](https://en.wikipedia.org/wiki/Network_File_System) file /// systems are ignored and the information of a mounted NFS **cannot** be obtained /// via [`Disks::refresh_list`]. This is due to the fact that I/O function /// `statvfs` used by [`Disks::refresh_list`] is blocking and /// [may hang](https://github.com/GuillaumeGomez/sysinfo/pull/876) in some cases, /// requiring to call `systemctl stop` to terminate the NFS service from the remote /// server in some cases. /// /// ```no_run /// use sysinfo::Disks; /// /// let mut disks = Disks::new(); /// disks.refresh_list(); /// ``` pub fn refresh_list(&mut self) { self.inner.refresh_list(); } } 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, PartialEq, Eq, Clone, Copy)] 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", }) } } /// 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_list(); /// 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. /// It is a combination of [`Users::new`] and [`Users::refresh_list`]. /// /// ```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_list(); 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_list(); /// ``` pub fn refresh_list(&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: /// /// ```no_run /// 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_list(); /// for group in groups.list() { /// println!("{group:?}"); /// } /// ``` pub fn new() -> Self { Self { groups: Vec::new() } } /// Creates a new [`Groups`][crate::Groups] type with the user list loaded. /// It is a combination of [`Groups::new`] and [`Groups::refresh_list`]. /// /// ```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_list(); groups } /// Returns the users 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::Users; /// /// let mut users = Users::new(); /// users.refresh_list(); /// ``` pub fn refresh_list(&mut self) { crate::sys::get_groups(&mut self.groups); } } /// 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(Clone, Copy, PartialEq, Eq, PartialOrd, Debug)] 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, } /// 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, } macro_rules! xid { ($(#[$outer:meta])+ $name:ident, $type:ty $(, $trait:ty)?) => { $(#[$outer])+ #[repr(transparent)] #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct $name(pub(crate) $type); impl std::ops::Deref for $name { type Target = $type; fn deref(&self) -> &Self::Target { &self.0 } } $( impl TryFrom for $name { type Error = <$type as TryFrom>::Error; fn try_from(t: usize) -> Result>::Error> { Ok(Self(<$type>::try_from(t)?)) } } impl $trait for $name { type Err = <$type as FromStr>::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, FromStr ); }; } cfg_if::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, FromStr); gid!(libc::gid_t); } else if #[cfg(windows)] { uid!(crate::windows::Sid); gid!(u32); // Manual implementation outside of the macro... impl FromStr for Uid { type Err = ::Err; fn from_str(t: &str) -> Result { Ok(Self(t.parse()?)) } } } else { uid!(u32, FromStr); gid!(u32); } } /// 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() } } /// Type containing read and written bytes. /// /// It is returned by [`Process::disk_usage`][crate::Process::disk_usage]. /// /// ```no_run /// use sysinfo::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, /// ); /// } /// ``` #[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, } /// Enum describing the different status of a process. #[derive(Clone, Copy, Debug, PartialEq, Eq)] 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)] pub enum ThreadKind { /// Kernel thread. Kernel, /// User thread. Userland, } /// 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::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() } /// MAC address for network interface. /// /// It is returned by [`NetworkData::mac_address`][crate::NetworkData::mac_address]. #[derive(PartialEq, Eq, Copy, Clone, Debug)] 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], ) } } /// 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_list(); /// for component in &components { /// println!("{component:?}"); /// } /// ``` pub fn new() -> Self { Self { inner: ComponentsInner::new(), } } /// Creates a new [`Components`][crate::Components] type with the user list /// loaded. It is a combination of [`Components::new`] and /// [`Components::refresh_list`]. /// /// ```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_list(); 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 listed components' information. /// /// ⚠️ If a component is added or removed, this method won't take it into account. Use /// [`Components::refresh_list`] instead. /// /// ⚠️ If you didn't call [`Components::refresh_list`] beforehand, this method will do /// nothing as the component list will be empty. /// /// ```no_run /// use sysinfo::Components; /// /// let mut components = Components::new_with_refreshed_list(); /// // We wait some time...? /// components.refresh(); /// ``` pub fn refresh(&mut self) { #[cfg(all( feature = "multithread", not(feature = "unknown-ci"), not(all(target_os = "macos", feature = "apple-sandbox")), ))] use rayon::iter::ParallelIterator; into_iter_mut(self.list_mut()).for_each(|component| component.refresh()); } /// The component list will be emptied then completely recomputed. /// /// ```no_run /// use sysinfo::Components; /// /// let mut components = Components::new(); /// components.refresh_list(); /// ``` pub fn refresh_list(&mut self) { self.inner.refresh_list() } } /// Getting a component temperature information. /// /// ```no_run /// use sysinfo::Components; /// /// let components = Components::new_with_refreshed_list(); /// for component in &components { /// println!("{} {}°C", component.label(), component.temperature()); /// } /// ``` 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 { /// println!("{}°C", component.temperature()); /// } /// ``` pub fn temperature(&self) -> f32 { 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 { /// println!("{}°C", component.max()); /// } /// ``` pub fn max(&self) -> f32 { 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 { /// println!("{:?}°C", component.critical()); /// } /// ``` 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} temp{id}"` | /// | ✓ | ✓ | ✗ | ✓ | `"{name} {label} {id}"` | /// | ✓ | ✗ | ✓ | ✓ | `"{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() } /// 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() } } /// 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::new().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. /// s.refresh_cpu(); /// /// 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::new().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. /// s.refresh_cpu(); /// /// 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::new().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::new().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::new().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::new().with_cpu(CpuRefreshKind::everything()), /// ); /// for cpu in s.cpus() { /// println!("{}", cpu.frequency()); /// } /// ``` pub fn frequency(&self) -> u64 { self.inner.frequency() } } sysinfo-0.30.13/src/debug.rs000064400000000000000000000130301046102023000137020ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ Component, Components, Cpu, Disk, Disks, NetworkData, Networks, Process, System, User, Users, }; use std::fmt; impl fmt::Debug for Cpu { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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() } } impl fmt::Debug for System { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("System") .field("global CPU usage", &self.global_cpu_info().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() } } impl fmt::Debug for Disk { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!( fmt, "Disk({:?})[FS: {:?}][Type: {:?}][removable: {}] mounted on {:?}: {}/{} B", self.name(), self.file_system(), self.kind(), if self.is_removable() { "yes" } else { "no" }, self.mount_point(), self.available_space(), self.total_space(), ) } } impl fmt::Debug for Process { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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("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() } } impl fmt::Debug for Components { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Components {{ {} }}", self.iter() .map(|x| format!("{x:?}")) .collect::>() .join(", ") ) } } impl fmt::Debug for Component { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(critical) = self.critical() { write!( f, "{}: {}°C (max: {}°C / critical: {}°C)", self.label(), self.temperature(), self.max(), critical ) } else { write!( f, "{}: {}°C (max: {}°C)", self.label(), self.temperature(), self.max() ) } } } impl fmt::Debug for Networks { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Networks {{ {} }}", self.iter() .map(|x| format!("{x:?}")) .collect::>() .join(", ") ) } } impl fmt::Debug for NetworkData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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()) .finish() } } impl fmt::Debug for Disks { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Disks {{ {} }}", self.iter() .map(|x| format!("{x:?}")) .collect::>() .join(", ") ) } } impl fmt::Debug for Users { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Users {{ {} }}", self.iter() .map(|x| format!("{x:?}")) .collect::>() .join(", ") ) } } impl fmt::Debug for User { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("User") .field("uid", &self.id()) .field("gid", &self.group_id()) .field("name", &self.name()) .finish() } } sysinfo-0.30.13/src/lib.rs000064400000000000000000000446151046102023000133770ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![doc = include_str!("../README.md")] #![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::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; mod network; use crate::unix::sys as sys; 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; use crate::windows::network_helper; mod network; #[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; } } pub use crate::common::{ get_current_pid, CGroupLimits, Component, Components, Cpu, CpuRefreshKind, Disk, DiskKind, DiskUsage, Disks, Gid, Group, Groups, LoadAvg, MacAddr, MemoryRefreshKind, NetworkData, Networks, Pid, Process, ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, System, ThreadKind, Uid, UpdateKind, User, Users, }; pub(crate) use crate::common::GroupInner; pub(crate) use crate::sys::{ ComponentInner, ComponentsInner, CpuInner, DiskInner, DisksInner, NetworkDataInner, NetworksInner, ProcessInner, SystemInner, UserInner, }; pub use crate::sys::{IS_SUPPORTED_SYSTEM, MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS}; #[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; /// This function is only used on Linux targets, on the other platforms 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. /// /// ```no_run /// 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: isize) -> bool { cfg_if::cfg_if! { if #[cfg(all(not(feature = "unknown-ci"), any(target_os = "linux", target_os = "android")))] { use crate::sys::system::REMAINING_FILES; use std::sync::atomic::Ordering; if _new_limit < 0 { _new_limit = 0; } 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 diff = max.saturating_sub(remaining); Some(_new_limit.saturating_sub(diff)) }).unwrap(); true } else { false } } } // FIXME: Can be removed once negative trait bounds are supported. #[cfg(doctest)] mod doctest { /// 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 {} /// 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 {} } #[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) {} fn check_supported_signals(_: &'static [Signal]) {} fn check_minimum_cpu_update_interval(_: std::time::Duration) {} check_is_supported(IS_SUPPORTED_SYSTEM); check_supported_signals(SUPPORTED_SIGNALS); check_minimum_cpu_update_interval(MINIMUM_CPU_UPDATE_INTERVAL); } #[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(); // 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(); 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_list() { let mut users = Users::new(); assert!(users.list().is_empty()); users.refresh_list(); assert!(users.list().len() >= MIN_USERS); } #[test] fn check_uid_gid() { let mut users = Users::new(); assert!(users.list().is_empty()); users.refresh_list(); 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); } // And now check that our `get_user_by_id` method works. let s = System::new_with_specifics( RefreshKind::new() .with_processes(ProcessRefreshKind::new().with_user(UpdateKind::Always)), ); assert!(s .processes() .iter() .filter_map(|(_, p)| p.user_id()) .any(|uid| users.get_user_by_id(uid).is_some())); } } #[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::new() .with_processes(ProcessRefreshKind::new().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 {:?} found", uid ); } } } } #[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!(s.refresh_process(_pid)); // Then check that it still returns true if the process is already in our process list. assert!(s.refresh_process(_pid)); } } } #[test] fn ensure_is_supported_is_set_correctly() { if MIN_USERS > 0 { assert!(IS_SUPPORTED_SYSTEM); } else { assert!(!IS_SUPPORTED_SYSTEM); } } #[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 = s .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 = s .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!(s.physical_core_count(), None); } assert!(s.physical_core_count().unwrap_or(0) <= s.cpus().len()); } #[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!" ); } } // 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(); 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); } } } // 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(); 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_eq!(System::cpu_arch().is_some(), IS_SUPPORTED_SYSTEM); } // 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); } // 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()); } // 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()); 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] fn check_groups() { if !crate::IS_SUPPORTED_SYSTEM { return; } assert!(!Groups::new_with_refreshed_list().is_empty()); } } sysinfo-0.30.13/src/macros.rs000064400000000000000000000043631046102023000141110ustar 00000000000000// 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)*) => {{}}; } 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")))] macro_rules! retry_eintr { (set_to_0 => $($t:tt)+) => {{ let errno = crate::unix::libc_errno(); if !errno.is_null() { *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; } }}; } sysinfo-0.30.13/src/network.rs000064400000000000000000000013061046102023000143100ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::HashMap; use crate::network_helper::get_interface_address; use crate::NetworkData; /// Interface addresses are OS-independent pub(crate) fn refresh_networks_addresses(interfaces: &mut HashMap) { match 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); } } } sysinfo-0.30.13/src/serde.rs000064400000000000000000000341061046102023000137250ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use serde::{ser::SerializeStruct, Serialize, Serializer}; 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() } } impl Serialize for crate::Gid { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_newtype_struct("Gid", &self.to_string()) } } impl Serialize for crate::Uid { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_newtype_struct("Uid", &self.to_string()) } } impl Serialize for crate::Pid { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_newtype_struct("Pid", &self.to_string()) } } 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())?; 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("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() } } 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() } } 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_info", &self.global_cpu_info())?; 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() } } 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() } } impl Serialize for crate::Networks { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_seq(self.iter()) } } impl Serialize for crate::Disks { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_seq(self.iter()) } } impl Serialize for crate::Components { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_seq(self.iter()) } } impl Serialize for crate::Users { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_seq(self.iter()) } } 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) } } 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) } } 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() } } impl Serialize for crate::NetworkData { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // `13` corresponds to the number of fields. let mut state = serializer.serialize_struct("NetworkData", 13)?; 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.end() } } 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() } } 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() } } 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() } } 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) } } } 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) } } } 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() } } impl Serialize for crate::MacAddr { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_newtype_struct("MacAddr", &self.0) } } sysinfo-0.30.13/src/sysinfo.h000064400000000000000000000054561046102023000141260ustar 00000000000000// 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); void sysinfo_disks_refresh_list(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_list(CNetworks networks); 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); uint32_t sysinfo_cpu_physical_cores(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(); void sysinfo_rstring_free(RString str); sysinfo-0.30.13/src/unix/apple/app_store/component.rs000064400000000000000000000021101046102023000206730ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::Component; pub(crate) struct ComponentInner; impl ComponentInner { pub(crate) fn temperature(&self) -> f32 { 0.0 } pub(crate) fn max(&self) -> f32 { 0.0 } pub(crate) fn critical(&self) -> Option { None } pub(crate) fn label(&self) -> &str { "" } pub(crate) fn refresh(&mut self) {} } pub(crate) struct ComponentsInner { 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_list(&mut self) { // Doesn't do anything. } } sysinfo-0.30.13/src/unix/apple/app_store/mod.rs000064400000000000000000000001651046102023000174600ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod component; pub mod process; sysinfo-0.30.13/src/unix/apple/app_store/process.rs000064400000000000000000000033031046102023000203540ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::path::Path; 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) -> &str { "" } pub(crate) fn cmd(&self) -> &[String] { &[] } pub(crate) fn exe(&self) -> Option<&Path> { None } pub(crate) fn pid(&self) -> Pid { Pid(0) } pub(crate) fn environ(&self) -> &[String] { &[] } 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 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) {} pub(crate) fn session_id(&self) -> Option { None } } sysinfo-0.30.13/src/unix/apple/component.rs000064400000000000000000000002011046102023000166760ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub(crate) use crate::sys::inner::component::*; sysinfo-0.30.13/src/unix/apple/cpu.rs000064400000000000000000000273261046102023000155040ustar 00000000000000// 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}; use libc::{c_char, c_void, host_processor_info, mach_port_t, mach_task_self}; use std::mem; use std::ops::Deref; use std::sync::Arc; use std::time::Instant; pub(crate) struct CpusWrapper { pub(crate) global_cpu: Cpu, 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: Cpu { inner: CpuInner::new( "0".to_owned(), Arc::new(CpuData::new(std::ptr::null_mut(), 0)), 0, String::new(), String::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 .map(|last_update| last_update.elapsed() > crate::MINIMUM_CPU_UPDATE_INTERVAL) .unwrap_or(false); 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() }; 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( mach_task_self(), self.cpu_info.0 as _, prev_cpu_info_size as _, ); } self.cpu_info.0 = std::ptr::null_mut(); } } } pub(crate) struct CpuInner { name: String, cpu_usage: f32, cpu_data: Arc, frequency: u64, vendor_id: String, brand: String, } impl CpuInner { pub(crate) fn new( name: String, cpu_data: Arc, frequency: u64, vendor_id: String, brand: String, ) -> Self { Self { name, cpu_usage: 0f32, cpu_data, frequency, vendor_id, brand, } } pub(crate) fn set_cpu_usage(&mut self, cpu_usage: f32) { self.cpu_usage = cpu_usage; } pub(crate) fn update(&mut self, cpu_usage: f32, cpu_data: Arc) { self.cpu_usage = cpu_usage; self.cpu_data = cpu_data; } pub(crate) fn data(&self) -> Arc { Arc::clone(&self.cpu_data) } pub(crate) fn set_frequency(&mut self, frequency: u64) { self.frequency = 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 { &self.brand } } pub(crate) unsafe fn get_cpu_frequency() -> u64 { let mut speed: u64 = 0; let mut len = std::mem::size_of::(); if libc::sysctlbyname( b"hw.cpufrequency\0".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"))] { return 0; } #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] { crate::sys::inner::cpu::get_cpu_frequency() } } 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 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 Cpu, 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.inner.set_cpu_usage(total_cpu_usage); } } pub(crate) fn init_cpus( port: libc::mach_port_t, cpus: &mut Vec, global_cpu: &mut Cpu, 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() } } 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()) }); } 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(); } (vendor, get_sysctl_str(b"machdep.cpu.brand_string\0")) } #[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::new().with_cpu(CpuRefreshKind::new().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()); } } } sysinfo-0.30.13/src/unix/apple/disk.rs000064400000000000000000000320661046102023000156440ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::{ ffi, utils::{self, CFReleaser}, }; use crate::{Disk, DiskKind}; use core_foundation_sys::array::CFArrayCreate; use core_foundation_sys::base::kCFAllocatorDefault; use core_foundation_sys::dictionary::{CFDictionaryGetValueIfPresent, CFDictionaryRef}; use core_foundation_sys::number::{kCFBooleanTrue, CFBooleanRef, CFNumberGetValue}; use core_foundation_sys::string::{self as cfs, CFStringRef}; 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, pub(crate) file_system: OsString, pub(crate) mount_point: PathBuf, volume_url: RetainedCFURL, pub(crate) total_space: u64, pub(crate) available_space: u64, pub(crate) is_removable: 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.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 refresh(&mut self) -> bool { unsafe { if let Some(requested_properties) = build_requested_properties(&[ ffi::kCFURLVolumeAvailableCapacityKey, ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey, ]) { match get_disk_properties(&self.volume_url, &requested_properties) { Some(disk_props) => { self.available_space = get_available_volume_space(&disk_props); true } None => { sysinfo_debug!("Failed to get disk properties"); false } } } else { sysinfo_debug!("failed to create volume key list, skipping refresh"); false } } } } impl crate::DisksInner { pub(crate) fn new() -> Self { Self { disks: Vec::with_capacity(2), } } pub(crate) fn refresh_list(&mut self) { unsafe { get_list(&mut self.disks); } } 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) { container.clear(); 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 }; // Create a list of properties about the disk that we want to fetch. let requested_properties = match build_requested_properties(&[ ffi::kCFURLVolumeIsEjectableKey, ffi::kCFURLVolumeIsRemovableKey, ffi::kCFURLVolumeIsInternalKey, ffi::kCFURLVolumeTotalCapacityKey, ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey, ffi::kCFURLVolumeAvailableCapacityKey, ffi::kCFURLVolumeNameKey, ffi::kCFURLVolumeIsBrowsableKey, ffi::kCFURLVolumeIsLocalKey, ]) { Some(properties) => properties, None => { sysinfo_debug!("failed to create volume key list"); return; } }; for c_disk in raw_disks { let volume_url = match CFReleaser::new( core_foundation_sys::url::CFURLCreateFromFileSystemRepresentation( 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 emumerator. let browsable = get_bool_value( prop_dict.inner(), DictKey::Extern(ffi::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.inner(), DictKey::Extern(ffi::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(), )); if let Some(disk) = new_disk(mount_point, volume_url, c_disk, &prop_dict) { container.push(disk); } } } type RetainedCFArray = CFReleaser; type RetainedCFDictionary = CFReleaser; type RetainedCFURL = CFReleaser; unsafe fn build_requested_properties(properties: &[CFStringRef]) -> Option { CFReleaser::new(CFArrayCreate( ptr::null_mut(), properties.as_ptr() as *const *const c_void, properties.len() as _, &core_foundation_sys::array::kCFTypeArrayCallBacks, )) } fn get_disk_properties( volume_url: &RetainedCFURL, requested_properties: &RetainedCFArray, ) -> Option { CFReleaser::new(unsafe { ffi::CFURLCopyResourcePropertiesForKeys( volume_url.inner(), requested_properties.inner(), ptr::null_mut(), ) }) } fn get_available_volume_space(disk_props: &RetainedCFDictionary) -> u64 { // 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.inner(), DictKey::Extern(ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey), ) .filter(|bytes| *bytes != 0) .or_else(|| { get_int_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeAvailableCapacityKey), ) }) } .unwrap_or_default() as u64 } pub(super) enum DictKey { Extern(CFStringRef), #[cfg(target_os = "macos")] Defined(&'static str), } unsafe fn get_dict_value Option>( dict: CFDictionaryRef, key: DictKey, callback: F, ) -> Option { #[cfg(target_os = "macos")] let _defined; let key = match key { DictKey::Extern(val) => val, #[cfg(target_os = "macos")] DictKey::Defined(val) => { _defined = CFReleaser::new(cfs::CFStringCreateWithBytesNoCopy( kCFAllocatorDefault, val.as_ptr(), val.len() as _, cfs::kCFStringEncodingUTF8, false as _, core_foundation_sys::base::kCFAllocatorNull, ))?; _defined.inner() } }; let mut value = std::ptr::null(); if CFDictionaryGetValueIfPresent(dict, key.cast(), &mut value) != 0 { callback(value) } else { None } } pub(super) unsafe fn get_str_value(dict: CFDictionaryRef, key: DictKey) -> Option { get_dict_value(dict, key, |v| { let v = v as cfs::CFStringRef; let len_utf16 = cfs::CFStringGetLength(v) as usize; let len_bytes = len_utf16 * 2; // Two bytes per UTF-16 codepoint. let v_ptr = cfs::CFStringGetCStringPtr(v, cfs::kCFStringEncodingUTF8); if v_ptr.is_null() { // Fallback on CFStringGetString to read the underlying bytes from the CFString. let mut buf = vec![0; len_bytes]; let success = cfs::CFStringGetCString( v, buf.as_mut_ptr(), len_bytes as _, cfs::kCFStringEncodingUTF8, ); if success != 0 { utils::vec_to_rust(buf) } else { None } } else { crate::unix::utils::cstr_to_rust_with_size(v_ptr, Some(len_bytes)) } }) } unsafe fn get_bool_value(dict: CFDictionaryRef, key: DictKey) -> Option { get_dict_value(dict, key, |v| Some(v as CFBooleanRef == kCFBooleanTrue)) } unsafe fn get_int_value(dict: CFDictionaryRef, key: DictKey) -> Option { get_dict_value(dict, key, |v| { let mut val: i64 = 0; if CFNumberGetValue( v.cast(), core_foundation_sys::number::kCFNumberSInt64Type, &mut val as *mut i64 as *mut c_void, ) { Some(val) } else { None } }) } unsafe fn new_disk( mount_point: PathBuf, volume_url: RetainedCFURL, c_disk: libc::statfs, disk_props: &RetainedCFDictionary, ) -> Option { // 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. All iOS devices use flash-based storage // so we just assume the disk type is an SSD until Rust has a way to conditionally link to // IOKit in more recent deployment versions. #[cfg(target_os = "macos")] let type_ = crate::sys::inner::disk::get_disk_type(&c_disk).unwrap_or(DiskKind::Unknown(-1)); #[cfg(not(target_os = "macos"))] let type_ = DiskKind::SSD; // Note: Since we requested these properties from the system, we don't expect // these property retrievals to fail. let name = get_str_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeNameKey), ) .map(OsString::from)?; let is_removable = { let ejectable = get_bool_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeIsEjectableKey), ) .unwrap_or_default(); let removable = get_bool_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeIsRemovableKey), ) .unwrap_or_default(); let is_removable = ejectable || removable; if is_removable { is_removable } else { // If neither `ejectable` or `removable` return `true`, fallback to checking // if the disk is attached to the internal system. let internal = get_bool_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeIsInternalKey), ) .unwrap_or_default(); !internal } }; let total_space = get_int_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeTotalCapacityKey), )? as u64; let available_space = get_available_volume_space(disk_props); 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(), ) }; Some(Disk { inner: DiskInner { type_, name, file_system, mount_point, volume_url, total_space, available_space, is_removable, }, }) } sysinfo-0.30.13/src/unix/apple/ffi.rs000064400000000000000000000025531046102023000154540ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use core_foundation_sys::{ array::CFArrayRef, dictionary::CFDictionaryRef, error::CFErrorRef, string::CFStringRef, url::CFURLRef, }; // Reexport items defined in either macos or ios ffi module. #[cfg(not(target_os = "ios"))] pub use crate::sys::inner::ffi::*; #[link(name = "CoreFoundation", kind = "framework")] extern "C" { pub fn CFURLCopyResourcePropertiesForKeys( url: CFURLRef, keys: CFArrayRef, error: *mut CFErrorRef, ) -> CFDictionaryRef; pub static kCFURLVolumeIsEjectableKey: CFStringRef; pub static kCFURLVolumeIsRemovableKey: CFStringRef; pub static kCFURLVolumeAvailableCapacityKey: CFStringRef; pub static kCFURLVolumeAvailableCapacityForImportantUsageKey: CFStringRef; pub static kCFURLVolumeTotalCapacityKey: CFStringRef; pub static kCFURLVolumeNameKey: CFStringRef; pub static kCFURLVolumeIsLocalKey: CFStringRef; pub static kCFURLVolumeIsInternalKey: CFStringRef; pub static kCFURLVolumeIsBrowsableKey: CFStringRef; } #[cfg_attr(feature = "debug", derive(Eq, Hash, PartialEq))] #[allow(unused)] #[allow(non_camel_case_types)] #[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 } sysinfo-0.30.13/src/unix/apple/groups.rs000064400000000000000000000022031046102023000162170ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ common::{Gid, GroupInner}, Group, }; use libc::{endgrent, getgrent, setgrent}; use std::collections::HashMap; pub(crate) fn get_groups(groups: &mut Vec) { groups.clear(); let mut groups_map = HashMap::with_capacity(10); unsafe { setgrent(); loop { let gr = 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)); } } endgrent(); } for (name, gid) in groups_map { groups.push(Group { inner: GroupInner::new(gid, name), }); } } sysinfo-0.30.13/src/unix/apple/ios.rs000064400000000000000000000002621046102023000154750ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod ffi {} pub use crate::sys::app_store::component; pub use crate::sys::app_store::process; sysinfo-0.30.13/src/unix/apple/macos/component/arm.rs000064400000000000000000000130431046102023000205670ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::ffi::CStr; use core_foundation_sys::array::{CFArrayGetCount, CFArrayGetValueAtIndex}; use core_foundation_sys::base::kCFAllocatorDefault; use core_foundation_sys::string::{ kCFStringEncodingUTF8, CFStringCreateWithBytes, CFStringGetCStringPtr, }; use crate::sys::inner::ffi::{ kHIDPage_AppleVendor, kHIDUsage_AppleVendor_TemperatureSensor, kIOHIDEventTypeTemperature, matching, IOHIDEventFieldBase, IOHIDEventGetFloatValue, IOHIDEventSystemClientCopyServices, IOHIDEventSystemClientCreate, IOHIDEventSystemClientSetMatching, IOHIDServiceClientCopyEvent, IOHIDServiceClientCopyProperty, __IOHIDEventSystemClient, __IOHIDServiceClient, HID_DEVICE_PROPERTY_PRODUCT, }; use crate::sys::utils::CFReleaser; use crate::Component; pub(crate) struct ComponentsInner { components: Vec, client: Option>, } 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 } pub(crate) fn refresh_list(&mut self) { self.components.clear(); unsafe { let matches = match CFReleaser::new(matching( kHIDPage_AppleVendor, kHIDUsage_AppleVendor_TemperatureSensor, )) { Some(m) => m, None => return, }; if self.client.is_none() { let client = match CFReleaser::new(IOHIDEventSystemClientCreate(kCFAllocatorDefault)) { Some(c) => c, None => return, }; self.client = Some(client); } let client = self.client.as_ref().unwrap(); let _ = IOHIDEventSystemClientSetMatching(client.inner(), matches.inner()); let services = match CFReleaser::new(IOHIDEventSystemClientCopyServices(client.inner())) { Some(s) => s, None => return, }; let key_ref = match CFReleaser::new(CFStringCreateWithBytes( kCFAllocatorDefault, HID_DEVICE_PROPERTY_PRODUCT.as_ptr(), HID_DEVICE_PROPERTY_PRODUCT.len() as _, kCFStringEncodingUTF8, false as _, )) { Some(r) => r, None => return, }; let count = CFArrayGetCount(services.inner()); for i in 0..count { // The 'service' should never be freed since it is returned by a 'Get' call. // See issue https://github.com/GuillaumeGomez/sysinfo/issues/1279 let service = CFArrayGetValueAtIndex(services.inner(), i); if service.is_null() { continue; } let name = match CFReleaser::new(IOHIDServiceClientCopyProperty( service as *const _, key_ref.inner(), )) { Some(n) => n, None => continue, }; let name_ptr = CFStringGetCStringPtr(name.inner() as *const _, kCFStringEncodingUTF8); if name_ptr.is_null() { continue; } let name_str = CStr::from_ptr(name_ptr).to_string_lossy().to_string(); let mut component = ComponentInner::new(name_str, None, None, service as *mut _); component.refresh(); self.components.push(Component { inner: component }); } } } } pub(crate) struct ComponentInner { service: *mut __IOHIDServiceClient, temperature: f32, label: String, max: f32, critical: Option, } unsafe impl Send for ComponentInner {} unsafe impl Sync for ComponentInner {} impl ComponentInner { pub(crate) fn new( label: String, max: Option, critical: Option, service: *mut __IOHIDServiceClient, ) -> Self { Self { service, label, max: max.unwrap_or(0.), critical, temperature: 0., } } pub(crate) fn temperature(&self) -> f32 { self.temperature } pub(crate) fn max(&self) -> f32 { self.max } pub(crate) fn critical(&self) -> Option { self.critical } pub(crate) fn label(&self) -> &str { &self.label } pub(crate) fn refresh(&mut self) { unsafe { let event = match CFReleaser::new(IOHIDServiceClientCopyEvent( self.service as *const _, kIOHIDEventTypeTemperature, 0, 0, )) { Some(e) => e, None => return, }; self.temperature = IOHIDEventGetFloatValue( event.inner(), IOHIDEventFieldBase(kIOHIDEventTypeTemperature), ) as _; if self.temperature > self.max { self.max = self.temperature; } } } } sysinfo-0.30.13/src/unix/apple/macos/component/mod.rs000064400000000000000000000005501046102023000205660ustar 00000000000000// 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::*; sysinfo-0.30.13/src/unix/apple/macos/component/x86.rs000064400000000000000000000236651046102023000204500ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::{ffi, macos::utils::IOReleaser}; use crate::Component; use libc::{c_char, c_int, c_void}; use std::mem; const COMPONENTS_TEMPERATURE_IDS: &[(&str, &[i8])] = &[ ("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'C' as i8]), // PECI CPU "TCXC" ("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'c' as i8]), // PECI CPU "TCXc" ( "CPU Proximity", &['T' as i8, 'C' as i8, '0' as i8, 'P' as i8], ), // CPU Proximity (heat spreader) "TC0P" ("GPU", &['T' as i8, 'G' as i8, '0' as i8, 'P' as i8]), // GPU "TG0P" ("Battery", &['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: ffi::io_connect_t, } impl ComponentFFI { fn new(key: &[i8], connection: ffi::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 { 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_list(&mut self) { if let Some(ref connection) = self.connection { let connection = connection.inner(); self.components.clear(); // getting CPU critical temperature let critical_temp = get_temperature(connection, &['T' as i8, 'C' as i8, '0' as i8, 'D' as i8, 0]); for (id, v) in COMPONENTS_TEMPERATURE_IDS.iter() { if let Some(c) = ComponentInner::new((*id).to_owned(), None, critical_temp, v, connection) { self.components.push(Component { inner: c }); } } } } } pub(crate) struct ComponentInner { temperature: f32, max: f32, critical: Option, label: String, ffi_part: ComponentFFI, } impl ComponentInner { /// Creates a new `ComponentInner` with the given information. pub(crate) fn new( label: String, max: Option, critical: Option, key: &[i8], connection: ffi::io_connect_t, ) -> Option { let ffi_part = ComponentFFI::new(key, connection)?; ffi_part.temperature().map(|temperature| Self { temperature, label, max: max.unwrap_or(temperature), critical, ffi_part, }) } pub(crate) fn temperature(&self) -> f32 { self.temperature } pub(crate) fn max(&self) -> f32 { self.max } pub(crate) fn critical(&self) -> Option { self.critical } pub(crate) fn label(&self) -> &str { &self.label } pub(crate) fn refresh(&mut self) { if let Some(temp) = self.ffi_part.temperature() { self.temperature = temp; if self.temperature > self.max { self.max = self.temperature; } } } } unsafe fn perform_call( conn: ffi::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::(); ffi::IOConnectCallStructMethod( conn, index as u32, input_structure, mem::size_of::(), output_structure, &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) { *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: ffi::io_connect_t, key: &[i8], ) -> Result<(ffi::KeyData_t, ffi::Val_t), i32> { 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 != ffi::KIO_RETURN_SUCCESS { 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: ffi::io_connect_t, input_structure: &ffi::KeyData_t, mut val: ffi::Val_t, ) -> Result { let mut output_structure: ffi::KeyData_t = mem::zeroed::(); match perform_call( con, ffi::KERNEL_INDEX_SMC, input_structure, &mut output_structure, ) { ffi::KIO_RETURN_SUCCESS => { 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: ffi::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(), b"sp78\0".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: ffi::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(ffi::io_connect_t); impl IoService { fn new(obj: ffi::io_connect_t) -> Option { if obj == 0 { None } else { Some(Self(obj)) } } pub(crate) fn inner(&self) -> ffi::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: ffi::io_iterator_t = 0; unsafe { let matching_dictionary = ffi::IOServiceMatching(b"AppleSMC\0".as_ptr() as *const i8); if matching_dictionary.is_null() { sysinfo_debug!("IOServiceMatching call failed, `AppleSMC` not found"); return None; } let result = ffi::IOServiceGetMatchingServices( ffi::kIOMasterPortDefault, matching_dictionary, &mut iterator, ); if result != ffi::KIO_RETURN_SUCCESS { 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(ffi::IOIteratorNext(iterator.inner())) { Some(d) => d, None => { sysinfo_debug!("Error: no SMC found"); return None; } }; let mut conn = 0; let result = ffi::IOServiceOpen(device.inner(), libc::mach_task_self(), 0, &mut conn); if result != ffi::KIO_RETURN_SUCCESS { 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 { ffi::IOServiceClose(self.0); } } } sysinfo-0.30.13/src/unix/apple/macos/cpu.rs000064400000000000000000000063361046102023000166040ustar 00000000000000// 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() -> u64 { 0 } #[cfg(not(feature = "apple-sandbox"))] pub(crate) unsafe fn get_cpu_frequency() -> u64 { use crate::sys::ffi; use crate::sys::macos::utils::IOReleaser; use crate::sys::utils::CFReleaser; let matching = ffi::IOServiceMatching(b"AppleARMIODevice\0".as_ptr() as *const _); if matching.is_null() { sysinfo_debug!("IOServiceMatching call failed, `AppleARMIODevice` not found"); return 0; } // 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: ffi::io_iterator_t = 0; let result = ffi::IOServiceGetMatchingServices(ffi::kIOMasterPortDefault, matching, &mut iterator); if result != ffi::KIO_RETURN_SUCCESS { 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: ffi::io_name = std::mem::zeroed(); let entry = loop { let entry = match IOReleaser::new(ffi::IOIteratorNext(iterator.inner())) { Some(d) => d, None => { sysinfo_debug!("`pmgr` entry was not found in AppleARMIODevice service"); return 0; } }; let status = ffi::IORegistryEntryGetName(entry.inner(), name.as_mut_ptr()); if status != libc::KERN_SUCCESS { continue; } else if libc::strcmp(name.as_ptr(), b"pmgr\0".as_ptr() as *const _) == 0 { break entry; } }; let node_name = match CFReleaser::new(ffi::CFStringCreateWithCStringNoCopy( std::ptr::null(), b"voltage-states5-sram\0".as_ptr() as *const _, core_foundation_sys::string::kCFStringEncodingUTF8, core_foundation_sys::base::kCFAllocatorNull as *mut _, )) { Some(n) => n, None => { sysinfo_debug!("CFStringCreateWithCStringNoCopy failed"); return 0; } }; let core_ref = match CFReleaser::new(ffi::IORegistryEntryCreateCFProperty( entry.inner(), node_name.inner(), core_foundation_sys::base::kCFAllocatorDefault, 0, )) { Some(c) => c, None => { sysinfo_debug!("`voltage-states5-sram` property not found"); return 0; } }; let core_length = core_foundation_sys::data::CFDataGetLength(core_ref.inner() as *const _); 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_foundation_sys::data::CFDataGetBytes( core_ref.inner() as *const _, core_foundation_sys::base::CFRange::init(core_length - 8, 4), &mut max as *mut _ as *mut _, ); max / 1_000_000 } sysinfo-0.30.13/src/unix/apple/macos/disk.rs000064400000000000000000000110111046102023000167310ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::ffi; use crate::sys::{ disk::{get_str_value, DictKey}, macos::utils::IOReleaser, utils::CFReleaser, }; use crate::DiskKind; use core_foundation_sys::base::{kCFAllocatorDefault, kCFAllocatorNull}; use core_foundation_sys::string as cfs; use std::ffi::CStr; pub(crate) fn get_disk_type(disk: &libc::statfs) -> Option { let characteristics_string = unsafe { CFReleaser::new(cfs::CFStringCreateWithBytesNoCopy( kCFAllocatorDefault, ffi::kIOPropertyDeviceCharacteristicsKey.as_ptr(), ffi::kIOPropertyDeviceCharacteristicsKey.len() as _, cfs::kCFStringEncodingUTF8, false as _, kCFAllocatorNull, ))? }; // Removes `/dev/` from the value. let bsd_name = unsafe { CStr::from_ptr(disk.f_mntfromname.as_ptr()) .to_bytes() .strip_prefix(b"/dev/") .or_else(|| { sysinfo_debug!("unknown disk mount path format"); None })? }; // We don't need to wrap this in an auto-releaser because the following call to `IOServiceGetMatchingServices` // will take ownership of one retain reference. let matching = unsafe { ffi::IOBSDNameMatching(ffi::kIOMasterPortDefault, 0, bsd_name.as_ptr().cast()) }; if matching.is_null() { return None; } let mut service_iterator: ffi::io_iterator_t = 0; if unsafe { ffi::IOServiceGetMatchingServices( ffi::kIOMasterPortDefault, matching.cast(), &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: ffi::io_registry_entry_t = 0; while let Some(mut current_service_entry) = IOReleaser::new(unsafe { ffi::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 { ffi::IORegistryEntryGetParentEntry( current_service_entry.inner(), ffi::kIOServicePlane.as_ptr().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 { CFReleaser::new(ffi::IORegistryEntryCreateCFProperty( current_service_entry.inner(), characteristics_string.inner(), kCFAllocatorDefault, 0, )) }; if let Some(device_properties) = properties_result { let disk_type = unsafe { super::disk::get_str_value( device_properties.inner(), DictKey::Defined(ffi::kIOPropertyMediumTypeKey), ) }; if let Some(disk_type) = disk_type.and_then(|medium| match medium.as_str() { _ if medium == ffi::kIOPropertyMediumTypeSolidStateKey => Some(DiskKind::SSD), _ if medium == ffi::kIOPropertyMediumTypeRotationalKey => Some(DiskKind::HDD), _ => None, }) { return Some(disk_type); } else { // Many external drive vendors do not advertise their device's storage medium. // // In these cases, assuming that there were _any_ properties about them registered, we fallback // to `HDD` when no storage medium is provided by the device instead of `Unknown`. return Some(DiskKind::HDD); } } } } None } sysinfo-0.30.13/src/unix/apple/macos/ffi.rs000064400000000000000000000235021046102023000165530ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use core_foundation_sys::base::{mach_port_t, CFAllocatorRef}; use core_foundation_sys::dictionary::{CFDictionaryRef, CFMutableDictionaryRef}; use core_foundation_sys::string::CFStringRef; use libc::{c_char, kern_return_t}; // Note: IOKit is only available on MacOS up until very recent iOS versions: https://developer.apple.com/documentation/iokit #[allow(non_camel_case_types)] pub type io_object_t = mach_port_t; #[allow(non_camel_case_types)] pub type io_iterator_t = io_object_t; #[allow(non_camel_case_types)] pub type io_registry_entry_t = io_object_t; // This is a hack, `io_name_t` should normally be `[c_char; 128]` but Rust makes it very annoying // to deal with that so we go around it a bit. #[allow(non_camel_case_types, dead_code)] pub type io_name = [c_char; 128]; #[allow(non_camel_case_types)] pub type io_name_t = *const c_char; pub type IOOptionBits = u32; #[allow(non_upper_case_globals)] pub const kIOServicePlane: &[u8] = b"IOService\0"; #[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"; // Based on https://github.com/libusb/libusb/blob/bed8d3034eac74a6e1ba123b5c270ea63cb6cf1a/libusb/os/darwin_usb.c#L54-L55, // we can simply set it to 0 (and is the same value as its replacement `kIOMainPortDefault`). #[allow(non_upper_case_globals)] pub const kIOMasterPortDefault: mach_port_t = 0; // Note: Obtaining information about disks using IOKIt is allowed inside the default macOS App Sandbox. #[link(name = "IOKit", kind = "framework")] extern "C" { pub fn IOServiceGetMatchingServices( mainPort: mach_port_t, matching: CFMutableDictionaryRef, existing: *mut io_iterator_t, ) -> kern_return_t; #[allow(dead_code)] pub fn IOServiceMatching(a: *const c_char) -> CFMutableDictionaryRef; pub fn IOIteratorNext(iterator: io_iterator_t) -> io_object_t; pub fn IOObjectRelease(obj: io_object_t) -> kern_return_t; pub fn IORegistryEntryCreateCFProperty( entry: io_registry_entry_t, key: CFStringRef, allocator: CFAllocatorRef, options: IOOptionBits, ) -> CFDictionaryRef; pub fn IORegistryEntryGetParentEntry( entry: io_registry_entry_t, plane: io_name_t, parent: *mut io_registry_entry_t, ) -> kern_return_t; #[allow(dead_code)] pub fn IORegistryEntryGetName(entry: io_registry_entry_t, name: io_name_t) -> kern_return_t; pub fn IOBSDNameMatching( mainPort: mach_port_t, options: u32, bsdName: *const c_char, ) -> CFMutableDictionaryRef; } #[allow(dead_code)] pub const KIO_RETURN_SUCCESS: i32 = 0; extern "C" { // FIXME: to be removed once higher version than core_foundation_sys 0.8.4 is released. #[allow(dead_code)] pub fn CFStringCreateWithCStringNoCopy( alloc: CFAllocatorRef, cStr: *const c_char, encoding: core_foundation_sys::string::CFStringEncoding, contentsDeallocator: CFAllocatorRef, ) -> CFStringRef; } #[cfg(all( not(feature = "apple-sandbox"), any(target_arch = "x86", target_arch = "x86_64") ))] mod io_service { use super::{io_object_t, mach_port_t}; use libc::{kern_return_t, size_t, task_t}; #[allow(non_camel_case_types)] pub type io_connect_t = io_object_t; #[allow(non_camel_case_types)] pub type io_service_t = io_object_t; #[allow(non_camel_case_types)] pub type task_port_t = task_t; extern "C" { pub fn IOServiceOpen( device: io_service_t, owning_task: task_port_t, type_: u32, connect: *mut io_connect_t, ) -> kern_return_t; pub fn IOServiceClose(a: io_connect_t) -> kern_return_t; #[allow(dead_code)] pub fn IOConnectCallStructMethod( connection: mach_port_t, selector: u32, inputStruct: *const KeyData_t, inputStructCnt: size_t, outputStruct: *mut KeyData_t, outputStructCnt: *mut size_t, ) -> kern_return_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(feature = "apple-sandbox")] mod io_service {} #[cfg(all( not(feature = "apple-sandbox"), any(target_arch = "x86", target_arch = "x86_64") ))] pub use io_service::*; #[cfg(all(not(feature = "apple-sandbox"), target_arch = "aarch64"))] mod io_service { use std::ptr::null; use super::CFStringCreateWithCStringNoCopy; use core_foundation_sys::array::CFArrayRef; use core_foundation_sys::base::{CFAllocatorRef, CFRelease}; use core_foundation_sys::dictionary::{ kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks, CFDictionaryCreate, CFDictionaryRef, }; use core_foundation_sys::number::{kCFNumberSInt32Type, CFNumberCreate}; use core_foundation_sys::string::CFStringRef; #[repr(C)] pub struct __IOHIDServiceClient(libc::c_void); pub type IOHIDServiceClientRef = *const __IOHIDServiceClient; #[repr(C)] pub struct __IOHIDEventSystemClient(libc::c_void); pub type IOHIDEventSystemClientRef = *const __IOHIDEventSystemClient; #[repr(C)] pub struct __IOHIDEvent(libc::c_void); pub type IOHIDEventRef = *const __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"))] extern "C" { pub fn IOHIDEventSystemClientCreate(allocator: CFAllocatorRef) -> IOHIDEventSystemClientRef; pub fn IOHIDEventSystemClientSetMatching( client: IOHIDEventSystemClientRef, matches: CFDictionaryRef, ) -> i32; pub fn IOHIDEventSystemClientCopyServices(client: IOHIDEventSystemClientRef) -> CFArrayRef; pub fn IOHIDServiceClientCopyProperty( service: IOHIDServiceClientRef, key: CFStringRef, ) -> CFStringRef; pub fn IOHIDServiceClientCopyEvent( service: IOHIDServiceClientRef, v0: i64, v1: i32, v2: i64, ) -> IOHIDEventRef; pub fn IOHIDEventGetFloatValue(event: IOHIDEventRef, field: i64) -> f64; } pub(crate) const HID_DEVICE_PROPERTY_PRODUCT: &[u8] = b"Product\0"; pub(crate) const HID_DEVICE_PROPERTY_PRIMARY_USAGE: &[u8] = b"PrimaryUsage\0"; pub(crate) const HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE: &[u8] = b"PrimaryUsagePage\0"; #[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; pub(crate) fn matching(page: i32, usage: i32) -> CFDictionaryRef { unsafe { let keys = [ CFStringCreateWithCStringNoCopy( null() as *const _, HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE.as_ptr() as *const _, core_foundation_sys::string::kCFStringEncodingUTF8, core_foundation_sys::base::kCFAllocatorNull as *mut _, ), CFStringCreateWithCStringNoCopy( null() as *const _, HID_DEVICE_PROPERTY_PRIMARY_USAGE.as_ptr() as *const _, core_foundation_sys::string::kCFStringEncodingUTF8, core_foundation_sys::base::kCFAllocatorNull as *mut _, ), ]; let nums = [ CFNumberCreate(null(), kCFNumberSInt32Type, &page as *const _ as *const _), CFNumberCreate(null(), kCFNumberSInt32Type, &usage as *const _ as *const _), ]; let dict = CFDictionaryCreate( null(), &keys as *const _ as *const _, &nums as *const _ as *const _, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks, ); for key in keys { CFRelease(key as _); } for num in nums { CFRelease(num as _); } dict } } } #[cfg(all(not(feature = "apple-sandbox"), target_arch = "aarch64"))] pub use io_service::*; sysinfo-0.30.13/src/unix/apple/macos/mod.rs000064400000000000000000000010021046102023000165550ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod disk; pub mod ffi; pub(crate) mod utils; #[cfg(not(feature = "apple-sandbox"))] pub(crate) mod cpu; #[cfg(not(feature = "apple-sandbox"))] pub mod system; #[cfg(not(feature = "apple-sandbox"))] pub mod component; #[cfg(not(feature = "apple-sandbox"))] pub mod process; #[cfg(feature = "apple-sandbox")] pub use crate::sys::app_store::component; #[cfg(feature = "apple-sandbox")] pub use crate::sys::app_store::process; sysinfo-0.30.13/src/unix/apple/macos/process.rs000064400000000000000000000544301046102023000174710ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![allow(clippy::assigning_clones)] use std::ffi::OsStr; use std::mem::{self, MaybeUninit}; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; 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: String, 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, } impl ProcessInner { pub(crate) fn new_empty(pid: Pid) -> Self { Self { name: String::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, } } pub(crate) fn new(pid: Pid, parent: Option, start_time: u64, run_time: u64) -> Self { Self { name: String::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, } } pub(crate) fn kill_with(&self, signal: Signal) -> Option { let c_signal = crate::sys::convert_signal(signal)?; unsafe { Some(kill(self.pid.0, c_signal) == 0) } } pub(crate) fn name(&self) -> &str { &self.name } pub(crate) fn cmd(&self) -> &[String] { &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) -> &[String] { &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 { if 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 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) { let mut status = 0; // attempt waiting unsafe { if retry_eintr!(libc::waitpid(self.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 kill(self.pid.0, 0) == 0 { std::thread::sleep(duration); } } } } 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)) } } } } #[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); let mut updated_cpu_usage = false; 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; updated_cpu_usage = true; } } if !updated_cpu_usage { p.cpu_usage = 0.; } 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 { 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 { 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 _ { 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, ) -> Result, ()> { let info = match info { Some(info) => info, None => { let mut p = ProcessInner::new_empty(pid); if get_exe_and_name_backup(&mut p, refresh_kind) { 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); if !get_process_infos(&mut p, refresh_kind) && !get_exe_and_name_backup(&mut p, refresh_kind) { // 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.memory() { let task_info = get_task_info(pid); 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, ) -> bool { let exe_needs_update = refresh_kind.exe().needs_update(|| process.exe.is_none()); if !process.name.is_empty() && !exe_needs_update { return false; } let mut buffer: Vec = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _); 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 = String::from_utf8_lossy(&buffer).to_string(); let exe = PathBuf::from(tmp); if process.name.is_empty() { process.name = exe .file_name() .and_then(|x| x.to_str()) .unwrap_or("") .to_owned(); } 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(node.vip_path.len() * node.vip_path[0].len()), ) .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; } 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; // 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() { process.name = exe .file_name() .and_then(|x| x.to_str()) .unwrap_or("") .to_owned(); } if refresh_kind.exe().needs_update(|| process.exe.is_none()) { process.exe = Some(exe); } 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]) -> (PathBuf, &[u8]) { let pos = data.iter().position(|c| *c == 0).unwrap_or(data.len()); ( Path::new(OsStr::from_bytes(&data[..pos])).to_path_buf(), &data[pos..], ) } 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 = String::from_utf8_lossy(&data[..pos]); if !arg.is_empty() && refresh_cmd { cmd.push(arg.to_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 = String::from_utf8_lossy(&data[..pos]); if arg.is_empty() { return; } environ.push(arg.to_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, ) -> Result, ()> { unsafe { if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) { let p = &mut p.inner; if let Some(info) = get_bsd_info(pid) { if info.pbi_start_tvsec != p.start_time { // We don't it to be removed, just replaced. p.updated = true; // The owner of this PID changed. return create_new_process(pid, now, refresh_kind, Some(info)); } let parent = get_parent(&info); // Update the parent if it changed. if p.parent != parent { p.parent = parent; } } if !get_process_infos(p, refresh_kind) { get_exe_and_name_backup(p, refresh_kind); } 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); } 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)) } } } 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) } } } sysinfo-0.30.13/src/unix/apple/macos/system.rs000064400000000000000000000126061046102023000173360ustar 00000000000000// 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::{ host_processor_info, mach_port_t, munmap, natural_t, processor_cpu_load_info, processor_cpu_load_info_t, sysconf, vm_page_size, PROCESSOR_CPU_LOAD_INFO, _SC_CLK_TCK, }; use std::ptr::null_mut; 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, clock_per_sec: f64, old_cpu_info: ProcessorCpuLoadInfo, } 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.; sysinfo_debug!(""); Some(Self { timebase_to_ns: info.numer as f64 / info.denom as f64, clock_per_sec: nano_per_seconds / clock_ticks_per_sec as f64, old_cpu_info, }) } } pub fn get_time_interval(&mut self, port: mach_port_t) -> f64 { 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; // 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; if base_interval < smallest { smallest } else { base_interval / self.timebase_to_ns } } } } #[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; } 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 ); } } sysinfo-0.30.13/src/unix/apple/macos/utils.rs000064400000000000000000000012721046102023000171470ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::num::NonZeroU32; type IoObject = NonZeroU32; pub(crate) struct IOReleaser(IoObject); impl IOReleaser { pub(crate) fn new(obj: u32) -> Option { IoObject::new(obj).map(Self) } pub(crate) unsafe fn new_unchecked(obj: u32) -> Self { // Chance at catching in-development mistakes debug_assert_ne!(obj, 0); Self(IoObject::new_unchecked(obj)) } #[inline] pub(crate) fn inner(&self) -> u32 { self.0.get() } } impl Drop for IOReleaser { fn drop(&mut self) { unsafe { super::ffi::IOObjectRelease(self.0.get() as _) }; } } sysinfo-0.30.13/src/unix/apple/mod.rs000064400000000000000000000054731046102023000154730ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(target_os = "macos")] pub(crate) mod macos; #[cfg(target_os = "macos")] pub(crate) use self::macos as inner; #[cfg(target_os = "ios")] pub(crate) mod ios; #[cfg(target_os = "ios")] pub(crate) use self::ios as inner; #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] pub(crate) mod app_store; pub mod component; pub mod cpu; pub mod disk; mod ffi; pub mod groups; pub mod network; pub mod process; pub mod system; pub mod users; mod utils; pub(crate) use self::component::{ComponentInner, ComponentsInner}; pub(crate) use self::cpu::CpuInner; pub(crate) use self::disk::DiskInner; pub(crate) use self::network::{NetworkDataInner, NetworksInner}; pub(crate) use self::process::ProcessInner; pub(crate) use self::system::SystemInner; pub(crate) use crate::unix::groups::get_groups; pub(crate) use crate::unix::users::{get_users, UserInner}; pub(crate) use crate::unix::DisksInner; use std::time::Duration; #[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/is_supported.md")] pub const IS_SUPPORTED_SYSTEM: bool = true; #[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); sysinfo-0.30.13/src/unix/apple/network.rs000064400000000000000000000210231046102023000163720ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use libc::{self, c_char, if_msghdr2, CTL_NET, NET_RT_IFLIST2, PF_ROUTE, RTM_IFINFO2}; use std::collections::{hash_map, HashMap}; use std::ptr::null_mut; use crate::common::MacAddr; use crate::network::refresh_networks_addresses; use crate::NetworkData; 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_list(&mut self) { for (_, data) in self.interfaces.iter_mut() { data.inner.updated = false; } self.update_networks(true); self.interfaces.retain(|_, data| data.inner.updated); refresh_networks_addresses(&mut self.interfaces); } pub(crate) fn refresh(&mut self) { self.update_networks(false); } #[allow(clippy::cast_ptr_alignment)] #[allow(clippy::uninit_vec)] fn update_networks(&mut self, insert: bool) { let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0]; let mut len = 0; unsafe { if libc::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 libc::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); 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, current_out, old_out, (*if2m).ifm_data.ifi_obytes ); old_and_new!( interface, current_in, old_in, (*if2m).ifm_data.ifi_ibytes ); old_and_new!( interface, packets_in, old_packets_in, (*if2m).ifm_data.ifi_ipackets ); old_and_new!( interface, packets_out, old_packets_out, (*if2m).ifm_data.ifi_opackets ); old_and_new!( interface, errors_in, old_errors_in, (*if2m).ifm_data.ifi_ierrors ); old_and_new!( interface, errors_out, old_errors_out, (*if2m).ifm_data.ifi_oerrors ); interface.updated = true; } hash_map::Entry::Vacant(e) => { if !insert { continue; } let current_in = (*if2m).ifm_data.ifi_ibytes; let current_out = (*if2m).ifm_data.ifi_obytes; let packets_in = (*if2m).ifm_data.ifi_ipackets; let packets_out = (*if2m).ifm_data.ifi_opackets; let errors_in = (*if2m).ifm_data.ifi_ierrors; let errors_out = (*if2m).ifm_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, }, }); } } } } } } } #[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, } 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 } } sysinfo-0.30.13/src/unix/apple/process.rs000064400000000000000000000045771046102023000163760ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::fmt; pub(crate) use crate::sys::inner::process::*; use crate::ProcessStatus; #[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), } } } sysinfo-0.30.13/src/unix/apple/system.rs000064400000000000000000000400611046102023000162300ustar 00000000000000// 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}; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] use std::cell::UnsafeCell; use std::collections::HashMap; use std::ffi::CStr; use std::mem; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] use std::time::SystemTime; use libc::{ c_int, c_void, host_statistics64, mach_port_t, sysconf, sysctl, timeval, vm_statistics64, _SC_PAGESIZE, }; 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<'a> Send for Wrap<'a> {} #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] unsafe impl<'a> Sync for Wrap<'a> {} 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 { 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); } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] pub(crate) fn refresh_processes_specifics( &mut self, _filter: Option<&[Pid]>, _refresh_kind: ProcessRefreshKind, ) { } #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] pub(crate) fn refresh_processes_specifics( &mut self, filter: Option<&[Pid]>, refresh_kind: ProcessRefreshKind, ) { use crate::utils::into_iter; unsafe { let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); if count < 1 { return; } } 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), ) = if let Some(filter) = filter { (filter, &real_filter) } else { (&[], &empty_filter) }; let now = get_now(); let port = self.port; let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); 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; } update_process(wrap, pid, time_interval, now, refresh_kind, false) .unwrap_or_default() }) .collect() }; entries.into_iter().for_each(|entry| { self.process_list.insert(entry.pid(), entry); }); self.process_list .retain(|_, proc_| std::mem::replace(&mut proc_.inner.updated, false)); } } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] pub(crate) fn refresh_process_specifics( &mut self, _pid: Pid, _refresh_kind: ProcessRefreshKind, ) -> bool { false } #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] pub(crate) fn refresh_process_specifics( &mut self, pid: Pid, refresh_kind: ProcessRefreshKind, ) -> bool { let mut time_interval = None; let now = get_now(); if refresh_kind.cpu() { let port = self.port; time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); } let wrap = Wrap(UnsafeCell::new(&mut self.process_list)); match update_process(&wrap, pid, time_interval, now, refresh_kind, true) { Ok(Some(p)) => { self.process_list.insert(p.pid(), p); true } Ok(_) => true, Err(_) => false, } } // 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 process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } pub(crate) fn global_cpu_info(&self) -> &Cpu { &self.cpus.global_cpu } pub(crate) fn cpus(&self) -> &[Cpu] { &self.cpus.cpus } pub(crate) fn physical_core_count(&self) -> Option { physical_core_count() } 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 friendly_name = match Self::os_version().unwrap_or_default() { f_n if f_n.starts_with("14.0") => "Sonoma", f_n if f_n.starts_with("10.16") | f_n.starts_with("11.0") | f_n.starts_with("11.1") | f_n.starts_with("11.2") => { "Big Sur" } f_n if f_n.starts_with("10.15") => "Catalina", f_n if f_n.starts_with("10.14") => "Mojave", f_n if f_n.starts_with("10.13") => "High Sierra", f_n if f_n.starts_with("10.12") => "Sierra", f_n if f_n.starts_with("10.11") => "El Capitan", f_n if f_n.starts_with("10.10") => "Yosemite", f_n if f_n.starts_with("10.9") => "Mavericks", f_n if f_n.starts_with("10.8") => "Mountain Lion", f_n if f_n.starts_with("10.7") => "Lion", f_n if f_n.starts_with("10.6") => "Snow Leopard", f_n if f_n.starts_with("10.5") => "Leopard", f_n if f_n.starts_with("10.4") => "Tiger", f_n if f_n.starts_with("10.3") => "Panther", f_n if f_n.starts_with("10.2") => "Jaguar", f_n if f_n.starts_with("10.1") => "Puma", f_n if f_n.starts_with("10.0") => "Cheetah", _ => "", }; #[cfg(target_os = "macos")] let long_name = Some(format!( "MacOS {} {}", Self::os_version().unwrap_or_default(), friendly_name )); #[cfg(target_os = "ios")] let long_name = Some(format!("iOS {}", Self::os_version().unwrap_or_default())); 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 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 } } } } 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() } } } } sysinfo-0.30.13/src/unix/apple/users.rs000064400000000000000000000111551046102023000160470ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ common::{Gid, Uid}, User, UserInner, }; use libc::{c_char, endpwent, getpwent, setpwent, strlen}; use std::collections::HashMap; fn endswith(s1: *const c_char, s2: &[u8]) -> bool { if s1.is_null() { return false; } unsafe { let mut len = 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 _ { i -= 1; len -= 1; } i == -1 } } pub(crate) fn get_users(users: &mut Vec) { fn filter(shell: *const c_char, uid: u32) -> bool { !endswith(shell, b"/false") && !endswith(shell, b"/uucico") && uid < 65536 } users.clear(); let mut users_map = 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), }); } } // 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() // } sysinfo-0.30.13/src/unix/apple/utils.rs000064400000000000000000000040151046102023000160430ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use core_foundation_sys::base::CFRelease; use libc::{c_void, sysctl, sysctlbyname}; use std::ptr::NonNull; // A helper using to auto release the resource got from CoreFoundation. // More information about the ownership policy for CoreFoundation pelease refer the link below: // https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-CJBEJBHH #[repr(transparent)] pub(crate) struct CFReleaser(NonNull); impl CFReleaser { pub(crate) fn new(ptr: *const T) -> Option { // This cast is OK because `NonNull` is a transparent wrapper // over a `*const T`. Additionally, mutability doesn't matter with // pointers here. NonNull::new(ptr as *mut T).map(Self) } pub(crate) fn inner(&self) -> *const T { self.0.as_ptr().cast() } } impl Drop for CFReleaser { fn drop(&mut self) { unsafe { CFRelease(self.0.as_ptr().cast()) } } } // Safety: These are safe to implement because we only wrap non-mutable // CoreFoundation types, which are generally threadsafe unless noted // otherwise. unsafe impl Send for CFReleaser {} unsafe impl Sync for CFReleaser {} pub(crate) fn vec_to_rust(buf: Vec) -> Option { String::from_utf8( buf.into_iter() .flat_map(|b| if b > 0 { Some(b as u8) } else { None }) .collect(), ) .ok() } pub(crate) unsafe fn get_sys_value(mut len: usize, value: *mut c_void, mib: &mut [i32]) -> bool { sysctl( mib.as_mut_ptr(), mib.len() as _, value, &mut len as *mut _, std::ptr::null_mut(), 0, ) == 0 } pub(crate) unsafe fn get_sys_value_by_name( name: &[u8], len: &mut usize, value: *mut c_void, ) -> bool { sysctlbyname( name.as_ptr() as *const _, value, len, std::ptr::null_mut(), 0, ) == 0 } sysinfo-0.30.13/src/unix/freebsd/component.rs000064400000000000000000000050551046102023000172230ustar 00000000000000// 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, label: String, temperature: f32, max: f32, } impl ComponentInner { pub(crate) fn temperature(&self) -> f32 { self.temperature } pub(crate) fn max(&self) -> f32 { self.max } pub(crate) fn critical(&self) -> Option { None } pub(crate) fn label(&self) -> &str { &self.label } pub(crate) fn refresh(&mut self) { unsafe { if let Some(temperature) = refresh_component(&self.id) { self.temperature = temperature; if self.temperature > self.max { self.max = self.temperature; } } } } } unsafe fn refresh_component(id: &[u8]) -> Option { let mut temperature: libc::c_int = 0; if !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, components: Vec, } impl ComponentsInner { pub(crate) fn new() -> Self { let nb_cpus = unsafe { super::cpu::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::cpu::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_list(&mut self) { self.components.clear(); 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 { id, label: format!("CPU {}", core + 1), temperature, max: temperature, }, }); } } } } } sysinfo-0.30.13/src/unix/freebsd/cpu.rs000064400000000000000000000136541046102023000160140ustar 00000000000000// 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, VecSwitcher, }; use crate::{Cpu, CpuRefreshKind}; use libc::{c_int, c_ulong}; pub(crate) unsafe fn get_nb_cpus() -> usize { let mut smp: c_int = 0; let mut nb_cpus: c_int = 1; 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 } pub(crate) struct CpusWrapper { pub(crate) global_cpu: Cpu, 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 = 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: Cpu { inner: CpuInner::new(String::new(), String::new(), 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 fill_cpu(proc_: &mut Cpu, new_cp_time: &[c_ulong], old_cp_time: &[c_ulong]) { 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 { proc_.inner.cpu_usage = 0.; } else { proc_.inner.cpu_usage = cp_diff as f32 / total_diff as f32 * 100.; } } fill_cpu( &mut self.global_cpu, 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, proc_) in self.cpus.iter_mut().enumerate() { let index = pos * libc::CPUSTATES as usize; fill_cpu(proc_, &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. if !get_sys_value_by_name( format!("dev.cpu.{cpu_nb}.freq\0").as_bytes(), &mut frequency, ) { frequency = 0; } frequency as _ } sysinfo-0.30.13/src/unix/freebsd/disk.rs000064400000000000000000000110231046102023000161430ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Disk, DiskKind}; use std::ffi::{OsStr, OsString}; use std::os::unix::ffi::OsStringExt; use std::path::{Path, PathBuf}; use super::utils::c_buf_to_str; pub(crate) struct DiskInner { name: OsString, c_mount_point: Vec, mount_point: PathBuf, total_space: u64, available_space: u64, file_system: OsString, is_removable: bool, } impl DiskInner { pub(crate) fn kind(&self) -> DiskKind { 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 refresh(&mut self) -> bool { unsafe { let mut vfs: libc::statvfs = std::mem::zeroed(); refresh_disk(self, &mut vfs) } } } impl crate::DisksInner { pub(crate) fn new() -> Self { Self { disks: Vec::with_capacity(2), } } pub(crate) fn refresh_list(&mut self) { unsafe { get_all_list(&mut self.disks) } } pub(crate) fn list(&self) -> &[Disk] { &self.disks } pub(crate) fn list_mut(&mut self) -> &mut [Disk] { &mut self.disks } } // FIXME: if you want to get disk I/O usage: // statfs.[f_syncwrites, f_asyncwrites, f_syncreads, f_asyncreads] unsafe fn refresh_disk(disk: &mut DiskInner, vfs: &mut libc::statvfs) -> bool { if libc::statvfs(disk.c_mount_point.as_ptr() as *const _, vfs) < 0 { return false; } let f_frsize: u64 = vfs.f_frsize as _; disk.total_space = vfs.f_blocks.saturating_mul(f_frsize); disk.available_space = vfs.f_favail.saturating_mul(f_frsize); true } pub unsafe fn get_all_list(container: &mut Vec) { container.clear(); let mut fs_infos: *mut libc::statfs = std::ptr::null_mut(); let count = libc::getmntinfo(&mut fs_infos, libc::MNT_WAIT); if count < 1 { return; } let mut vfs: libc::statvfs = std::mem::zeroed(); let fs_infos: &[libc::statfs] = 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_str(&fs_info.f_fstypename).unwrap(), ); continue; } _ => {} } if libc::statvfs(fs_info.f_mntonname.as_ptr(), &mut vfs) != 0 { continue; } let mount_point = match c_buf_to_str(&fs_info.f_mntonname) { Some(m) => m, None => { sysinfo_debug!("Cannot get disk mount point, ignoring it."); continue; } }; let name = if mount_point == "/" { OsString::from("root") } else { OsString::from(mount_point) }; // USB keys and CDs are removable. let is_removable = [b"USB", b"usb"].iter().any(|b| *b == &fs_type[..]) || fs_type.starts_with(b"/dev/cd"); let f_frsize: u64 = vfs.f_frsize as _; container.push(Disk { inner: DiskInner { name, c_mount_point: fs_info.f_mntonname.to_vec(), mount_point: PathBuf::from(mount_point), total_space: vfs.f_blocks.saturating_mul(f_frsize), available_space: vfs.f_favail.saturating_mul(f_frsize), file_system: OsString::from_vec(fs_type), is_removable, }, }); } } sysinfo-0.30.13/src/unix/freebsd/mod.rs000064400000000000000000000042211046102023000157720ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod component; pub mod cpu; pub mod disk; pub mod network; pub mod process; pub mod system; mod utils; pub(crate) use self::component::{ComponentInner, ComponentsInner}; pub(crate) use self::cpu::CpuInner; pub(crate) use self::disk::DiskInner; pub(crate) use self::network::{NetworkDataInner, NetworksInner}; pub(crate) use self::process::ProcessInner; pub(crate) use self::system::SystemInner; pub(crate) use crate::unix::groups::get_groups; pub(crate) use crate::unix::users::{get_users, UserInner}; pub(crate) use crate::unix::DisksInner; use libc::c_int; use std::time::Duration; 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/is_supported.md")] pub const IS_SUPPORTED_SYSTEM: bool = true; #[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); sysinfo-0.30.13/src/unix/freebsd/network.rs000064400000000000000000000151411046102023000167070ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::{hash_map, HashMap}; use std::mem::MaybeUninit; use super::utils; use crate::common::MacAddr; use crate::network::refresh_networks_addresses; use crate::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_list(&mut self) { unsafe { self.refresh_interfaces(true); } // Remove interfaces which are gone. self.interfaces.retain(|_, n| n.inner.updated); refresh_networks_addresses(&mut self.interfaces); } pub(crate) fn refresh(&mut self) { unsafe { self.refresh_interfaces(false); } } unsafe fn refresh_interfaces(&mut self, refresh_all: bool) { let mut nb_interfaces: libc::c_int = 0; if !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 = 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 !utils::get_sys_value(&mib, &mut data) { continue; } if let Some(name) = utils::c_buf_to_string(&data.ifmd_name) { let data = &data.ifmd_data; 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); 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, }, }); } } } } } } 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, } 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 } } sysinfo-0.30.13/src/unix/freebsd/process.rs000064400000000000000000000217741046102023000167050ustar 00000000000000// 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::fmt; use std::path::{Path, PathBuf}; use libc::kill; use super::utils::{get_sys_value_str, WrapMap}; #[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: String, 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, } impl ProcessInner { pub(crate) fn kill_with(&self, signal: Signal) -> Option { let c_signal = crate::sys::convert_signal(signal)?; unsafe { Some(libc::kill(self.pid.0, c_signal) == 0) } } pub(crate) fn name(&self) -> &str { &self.name } pub(crate) fn cmd(&self) -> &[String] { &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) -> &[String] { &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 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) { let mut status = 0; // attempt waiting unsafe { if retry_eintr!(libc::waitpid(self.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 kill(self.pid.0, 0) == 0 { std::thread::sleep(duration); } } } } 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) 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() { (100 * kproc.ki_pctcpu) as f32 / fscale } else { 0. }; // 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_) = (*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 { 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 _; } 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, virtual_memory, memory, // procstat_getfiles cwd: None, exe: None, // kvm_getargv isn't thread-safe so we get it in the main thread. name: String::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, updated: false, }, })) } 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]; *exe = get_sys_value_str( &[ libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PATHNAME, pid.0, ], &mut buffer, ) .map(PathBuf::from); } } sysinfo-0.30.13/src/unix/freebsd/system.rs000064400000000000000000000531551046102023000165510ustar 00000000000000// 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, }; 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 crate::sys::cpu::{physical_core_count, CpusWrapper}; use crate::sys::process::get_exe; use crate::sys::utils::{ self, boot_time, c_buf_to_string, from_cstr_array, get_sys_value, get_sys_value_by_name, get_system_info, init_mib, }; use libc::c_int; 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_processes_specifics( &mut self, filter: Option<&[Pid]>, refresh_kind: ProcessRefreshKind, ) { unsafe { self.refresh_procs(filter, refresh_kind) } } pub(crate) fn refresh_process_specifics( &mut self, pid: Pid, refresh_kind: ProcessRefreshKind, ) -> bool { unsafe { let kd = self.system_info.kd.as_ptr(); let mut count = 0; let procs = libc::kvm_getprocs(kd, libc::KERN_PROC_PROC, 0, &mut count); if count < 1 { sysinfo_debug!("kvm_getprocs returned nothing..."); return false; } let now = super::utils::get_now(); let fscale = self.system_info.fscale; let page_size = self.system_info.page_size as isize; let proc_list = utils::WrapMap(UnsafeCell::new(&mut self.process_list)); let procs: &mut [utils::KInfoProc] = std::slice::from_raw_parts_mut(procs as _, count as _); #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; macro_rules! multi_iter { ($name:ident, $($iter:tt)+) => { $name = crate::utils::into_iter(procs).$($iter)+; } } let ret; #[cfg(not(feature = "multithread"))] multi_iter!(ret, find(|kproc| kproc.ki_pid == pid.0)); #[cfg(feature = "multithread")] multi_iter!(ret, find_any(|kproc| kproc.ki_pid == pid.0)); let kproc = if let Some(kproc) = ret { kproc } else { return false; }; match super::process::get_process_data( kproc, &proc_list, page_size, fscale, now, refresh_kind, ) { Ok(Some(process)) => { self.process_list.insert(process.inner.pid, process); } Ok(None) => {} Err(_) => return false, } let process = self.process_list.get_mut(&Pid(kproc.ki_pid)).unwrap(); add_missing_proc_info(&mut self.system_info, kproc, process, refresh_kind); true } } // 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 process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } pub(crate) fn global_cpu_info(&self) -> &Cpu { &self.cpus.global_cpu } pub(crate) fn cpus(&self) -> &[Cpu] { &self.cpus.cpus } pub(crate) fn physical_core_count(&self) -> Option { physical_core_count() } 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.osrelease\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 { let mut kern_version: [c_int; 2] = [0; 2]; unsafe { init_mib(b"kern.version\0", &mut kern_version); get_system_info(&kern_version, None) } } pub(crate) fn distribution_id() -> String { std::env::consts::OS.to_owned() } 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 } } } } impl SystemInner { unsafe fn refresh_procs(&mut self, filter: Option<&[Pid]>, refresh_kind: ProcessRefreshKind) { let mut count = 0; let kvm_procs = libc::kvm_getprocs( self.system_info.kd.as_ptr(), libc::KERN_PROC_PROC, 0, &mut count, ); if count < 1 { sysinfo_debug!("kvm_getprocs returned nothing..."); return; } #[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), ) = if let Some(filter) = filter { (filter, &real_filter) } else { (&[], &empty_filter) }; let new_processes = { #[cfg(feature = "multithread")] use rayon::iter::{ParallelIterator, ParallelIterator as IterTrait}; #[cfg(not(feature = "multithread"))] use std::iter::Iterator as IterTrait; 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 = super::utils::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; } super::process::get_process_data( kproc, &proc_list, page_size, fscale, now, refresh_kind, ) .ok()? }) .collect::>() }; // We remove all processes that don't exist anymore. self.process_list .retain(|_, v| std::mem::replace(&mut v.inner.updated, false)); for process in new_processes { self.process_list.insert(process.inner.pid, process); } 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); } } } } #[allow(clippy::assigning_clones)] 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 = 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().and_then(|s| s.to_str()) { proc_inner.name = name.to_owned(); } if cmd_needs_update { proc_inner.cmd = cmd; } } } 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_string(&kproc.ki_comm).unwrap_or_default(); } if refresh_kind .environ() .needs_update(|| proc_inner.environ.is_empty()) { proc_inner.environ = 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(), b"/dev/null\0".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() { self.procstat = libc::procstat_open_sysctl(); if self.procstat.is_null() { sysinfo_debug!("procstat_open_sysctl failed"); return; } } 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); } } } } sysinfo-0.30.13/src/unix/freebsd/utils.rs000064400000000000000000000157661046102023000163730ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Pid, Process}; use libc::{c_char, c_int, timeval}; use std::cell::UnsafeCell; use std::collections::HashMap; use std::ffi::CStr; use std::mem; use std::time::SystemTime; /// 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 } } } #[inline] pub unsafe fn init_mib(name: &[u8], mib: &mut [c_int]) { let mut len = mib.len(); libc::sysctlnametomib(name.as_ptr() as _, mib.as_mut_ptr(), &mut len); } pub(crate) 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 libc::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 _ } } } pub(crate) unsafe fn get_sys_value(mib: &[c_int], value: &mut T) -> bool { let mut len = mem::size_of::() as libc::size_t; libc::sysctl( mib.as_ptr(), mib.len() as _, value as *mut _ as *mut _, &mut len, std::ptr::null_mut(), 0, ) == 0 } pub(crate) unsafe fn get_sys_value_array(mib: &[c_int], value: &mut [T]) -> bool { let mut len = mem::size_of_val(value) as libc::size_t; libc::sysctl( mib.as_ptr(), mib.len() as _, value.as_mut_ptr() as *mut _, &mut len as *mut _, std::ptr::null_mut(), 0, ) == 0 } pub(crate) fn c_buf_to_str(buf: &[libc::c_char]) -> Option<&str> { unsafe { let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()); if let Some(pos) = buf.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes std::str::from_utf8(&buf[..pos]).ok() } else { std::str::from_utf8(buf).ok() } } } pub(crate) fn c_buf_to_string(buf: &[libc::c_char]) -> Option { c_buf_to_str(buf).map(|s| s.to_owned()) } pub(crate) unsafe fn get_sys_value_str(mib: &[c_int], buf: &mut [libc::c_char]) -> Option { let mut len = mem::size_of_val(buf) as libc::size_t; 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; } c_buf_to_string(&buf[..len / mem::size_of::()]) } pub(crate) unsafe fn get_sys_value_by_name(name: &[u8], value: &mut T) -> bool { let mut len = mem::size_of::() as libc::size_t; let original = len; libc::sysctlbyname( name.as_ptr() as *const c_char, value as *mut _ as *mut _, &mut len, std::ptr::null_mut(), 0, ) == 0 && original == len } 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 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 c_char, buf.as_mut_ptr() as *mut _, &mut size, std::ptr::null_mut(), 0, ) == 0 && size > 0 { c_buf_to_string(&buf) } else { // getting the system value failed None } } else { None } } } pub(crate) 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_string(&buf) } } } } pub(crate) unsafe fn from_cstr_array(ptr: *const *const c_char) -> Vec { if ptr.is_null() { return Vec::new(); } let mut max = 0; loop { 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 { let p = ptr.add(pos); if let Ok(s) = CStr::from_ptr(*p).to_str() { ret.push(s.to_owned()); } } ret } pub(crate) fn get_now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .map(|n| n.as_secs()) .unwrap_or(0) } // All this is needed because `kinfo_proc` doesn't implement `Send` (because it contains pointers). pub(crate) struct WrapMap<'a>(pub UnsafeCell<&'a mut HashMap>); unsafe impl<'a> Send for WrapMap<'a> {} unsafe impl<'a> Sync for WrapMap<'a> {} #[repr(transparent)] pub(crate) struct KInfoProc(libc::kinfo_proc); unsafe impl Send for KInfoProc {} unsafe impl Sync for KInfoProc {} impl std::ops::Deref for KInfoProc { type Target = libc::kinfo_proc; fn deref(&self) -> &Self::Target { &self.0 } } sysinfo-0.30.13/src/unix/groups.rs000064400000000000000000000026101046102023000151200ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(not(any(target_os = "macos", target_os = "ios")))] use crate::Group; impl crate::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 } } // Not used by mac. #[cfg(not(any(target_os = "macos", target_os = "ios")))] pub(crate) fn get_groups(groups: &mut Vec) { use crate::common::{Gid, GroupInner}; use std::fs::File; use std::io::Read; #[inline] fn parse_id(id: &str) -> Option { id.parse::().ok() } groups.clear(); let mut s = String::new(); let _ = File::open("/etc/group").and_then(|mut f| f.read_to_string(&mut s)); for line in s.lines() { let mut parts = line.split(':'); if let Some(name) = parts.next() { let mut parts = parts.skip(1); // Skip the user if the uid cannot be parsed correctly if let Some(gid) = parts.next().and_then(parse_id) { groups.push(Group { inner: GroupInner::new(Gid(gid), name.to_owned()), }); } } } } #[cfg(any(target_os = "macos", target_os = "ios"))] pub(crate) use crate::unix::apple::groups::get_groups; sysinfo-0.30.13/src/unix/linux/component.rs000064400000000000000000000330601046102023000167450ustar 00000000000000// 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::fs::{read_dir, File}; use std::io::Read; use std::path::{Path, PathBuf}; #[derive(Default)] pub(crate) struct ComponentInner { /// Optional associated device of a `Component`. device_model: 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 /// /// For formatting detail see `Component::label` function docstring. /// /// ## 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, // TODO: not used now. // 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, } // 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() { let Ok(file_type) = entry.file_type() else { continue; }; if file_type.is_dir() { continue; } let entry = entry.path(); let filename = entry.file_name().and_then(|x| x.to_str()).unwrap_or(""); if !filename.starts_with("temp") { continue; } let (id, item) = filename.split_once('_')?; let id = id.get(4..)?.parse::().ok()?; 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); component.name = name.unwrap_or_default(); let device_model = get_file_line(&folder.join("device/model"), 16); component.device_model = device_model; fill_component(component, item, folder, filename); } let compo = matchings .into_iter() .map(|(id, mut c)| { // sysinfo expose a generic interface with a `label`. // Problem: a lot of sensors don't have a label or a device model! ¯\_(ツ)_/¯ // So let's pretend we have a unique label! // See the table in `Component::label` documentation for the table detail. c.inner.label = c.inner.format_label("temp", id); c }) // Remove components without `tempN_input` file thermal. `Component` doesn't support // this kind of sensors yet .filter(|c| c.inner.input_file.is_some()); components.extend(compo); 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} {class}{id}") } (true, None) => format!("{name} {label}"), (false, Some(device_model)) => format!("{name} {device_model}"), (false, None) => format!("{name} {class}{id}"), } } pub(crate) fn temperature(&self) -> f32 { self.temperature.unwrap_or(f32::NAN) } pub(crate) fn max(&self) -> f32 { self.max.unwrap_or(f32::NAN) } pub(crate) fn critical(&self) -> Option { self.threshold_critical } pub(crate) fn label(&self) -> &str { &self.label } 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; } } pub(crate) struct ComponentsInner { 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_list(&mut self) { self.components.clear(); if let Ok(dir) = read_dir(Path::new("/sys/class/hwmon/")) { for entry in dir.flatten() { let Ok(file_type) = entry.file_type() else { continue; }; let entry = entry.path(); if !file_type.is_file() && entry .file_name() .and_then(|x| x.to_str()) .unwrap_or("") .starts_with("hwmon") { ComponentInner::from_hwmon(&mut self.components, &entry); } } } } } sysinfo-0.30.13/src/unix/linux/cpu.rs000064400000000000000000000660671046102023000155470ustar 00000000000000// 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: Cpu, pub(crate) cpus: Vec, /// Field set to `false` in `update_cpus` and to `true` in `refresh_processes_specifics`. /// /// The reason behind this is to avoid calling the `update_cpus` more than necessary. /// For example when running `refresh_all` or `refresh_specifics`. need_cpus_update: bool, 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: Cpu { inner: CpuInner::new_with_values( "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, String::new(), String::new(), ), }, cpus: Vec::with_capacity(4), need_cpus_update: true, got_cpu_frequency: false, last_update: None, } } pub(crate) fn refresh_if_needed( &mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind, ) { if self.need_cpus_update { self.refresh(only_update_global_cpu, refresh_kind); } } #[allow(clippy::assigning_clones)] 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); self.need_cpus_update = false; 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[..4] != b"cpu " { return; } let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); if first { self.global_cpu.inner.name = to_str!(parts.next().unwrap_or(&[])).to_owned(); } else { parts.next(); } self.global_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), ); } if first || !only_update_global_cpu { while let Some(Ok(line)) = it.next() { if &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 self.cpus[i].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), ); } i += 1; } } } } 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<'a>(val: &'a mut Vec) -> std::slice::IterMut<'a, 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.inner.total_time, self.global_cpu.inner.old_total_time, ) } pub(crate) fn len(&self) -> usize { self.cpus.len() } pub(crate) fn is_empty(&self) -> bool { self.cpus.is_empty() } pub(crate) fn set_need_cpus_update(&mut self) { self.need_cpus_update = true; } } /// Struct containing values to compute a CPU usage. #[derive(Clone, Copy, Debug)] 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 { /// Creates a new instance of `CpuValues` with everything set to `0`. pub fn new() -> CpuValues { CpuValues { user: 0, nice: 0, system: 0, idle: 0, iowait: 0, irq: 0, softirq: 0, steal: 0, guest: 0, guest_nice: 0, } } /// 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; } /// Returns work time. pub fn work_time(&self) -> u64 { self.user .saturating_add(self.nice) .saturating_add(self.system) .saturating_add(self.irq) .saturating_add(self.softirq) } /// Returns total time. pub fn total_time(&self) -> u64 { self.work_time() .saturating_add(self.idle) .saturating_add(self.iowait) // `steal`, `guest` and `guest_nice` are only used if we want to account the "guest" // into the computation. .saturating_add(self.guest) .saturating_add(self.guest_nice) .saturating_add(self.steal) } } pub(crate) struct CpuInner { old_values: CpuValues, new_values: CpuValues, pub(crate) name: String, cpu_usage: f32, total_time: u64, old_total_time: u64, 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 { let mut new_values = CpuValues::new(); new_values.set( user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, ); Self { name: name.to_owned(), old_values: CpuValues::new(), new_values, cpu_usage: 0f32, total_time: 0, old_total_time: 0, 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, ) { 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(); self.cpu_usage = min!(self.new_values.work_time(), self.old_values.work_time(), 0.) / min!(self.total_time, self.old_total_time, 1.) * 100.; if self.cpu_usage > 100. { self.cpu_usage = 100.; // to prevent the percentage to go above 100% } } pub(crate) fn cpu_usage(&self) -> f32 { self.cpu_usage } 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 { if 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| { line.starts_with("cpu MHz\t") || line.starts_with("BogoMIPS") || line.starts_with("clock\t") || line.starts_with("bogomips per cpu") }); find_cpu_mhz .and_then(|line| line.split(':').last()) .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/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L34 /// /// 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, 0xd20) => "Cortex-M23", (0x41, 0xd21) => "Cortex-M33", (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", // 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(); } fn get_value(s: &str) -> String { s.split(':') .last() .map(|x| x.trim().to_owned()) .unwrap_or_default() } fn get_hex_value(s: &str) -> u32 { s.split(':') .last() .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 = s.split('\n'); 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.next() { if line.starts_with("vendor_id\t") { info.vendor_id = Some(get_value(line)); } else if line.starts_with("model name\t") { info.brand = Some(get_value(line)); } else if line.starts_with("CPU implementer\t") { info.implementer = Some(get_hex_value(line)); } else if line.starts_with("CPU part\t") { info.part = Some(get_hex_value(line)); } else if info.has_all_info() || is_new_processor(line) { break; } } let (index, vendor_id, brand) = info.convert(); cpus.insert(index, (vendor_id, brand)); } } cpus } sysinfo-0.30.13/src/unix/linux/disk.rs000064400000000000000000000263301046102023000156770ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::{get_all_data, to_cpath}; use crate::{Disk, DiskKind}; use libc::statvfs; use std::ffi::{OsStr, OsString}; use std::fs; use std::mem; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; macro_rules! cast { ($x:expr) => { u64::from($x) }; } pub(crate) struct DiskInner { type_: DiskKind, device_name: OsString, file_system: OsString, mount_point: PathBuf, total_space: u64, available_space: u64, is_removable: 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 refresh(&mut self) -> bool { unsafe { let mut stat: statvfs = mem::zeroed(); let mount_point_cpath = to_cpath(&self.mount_point); if retry_eintr!(statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat)) == 0 { let tmp = cast!(stat.f_bsize).saturating_mul(cast!(stat.f_bavail)); self.available_space = cast!(tmp); true } else { false } } } } impl crate::DisksInner { pub(crate) fn new() -> Self { Self { disks: Vec::with_capacity(2), } } pub(crate) fn refresh_list(&mut self) { get_all_list( &mut self.disks, &get_all_data("/proc/mounts", 16_385).unwrap_or_default(), ) } pub(crate) fn list(&self) -> &[Disk] { &self.disks } pub(crate) fn list_mut(&mut self) -> &mut [Disk] { &mut self.disks } } fn new_disk( device_name: &OsStr, mount_point: &Path, file_system: &OsStr, removable_entries: &[PathBuf], ) -> Option { let mount_point_cpath = to_cpath(mount_point); let type_ = find_type_for_device_name(device_name); let mut total = 0; let mut available = 0; unsafe { let mut stat: statvfs = mem::zeroed(); if retry_eintr!(statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat)) == 0 { let bsize = cast!(stat.f_bsize); let blocks = cast!(stat.f_blocks); let bavail = cast!(stat.f_bavail); total = bsize.saturating_mul(blocks); available = bsize.saturating_mul(bavail); } if total == 0 { return None; } let mount_point = mount_point.to_owned(); let is_removable = removable_entries .iter() .any(|e| e.as_os_str() == device_name); Some(Disk { inner: DiskInner { type_, device_name: device_name.to_owned(), file_system: file_system.to_owned(), mount_point, total_space: cast!(total), available_space: cast!(available), is_removable, }, }) } } #[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_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) { container.clear(); // 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(), }; for disk 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 => true, "tmpfs" => !cfg!(feature = "linux-tmpfs"), // calling statvfs on a mounted CIFS or NFS may hang, when they are mounted with option: hard "cifs" | "nfs" | "nfs4" => !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")) }) .filter_map(|(fs_spec, fs_file, fs_vfstype)| { new_disk( fs_spec.as_ref(), Path::new(&fs_file), fs_vfstype.as_ref(), &removable_entries, ) }) { container.push(disk); } } // #[test] // fn check_all_list() { // let disks = get_all_disks_inner( // r#"tmpfs /proc tmpfs rw,seclabel,relatime 0 0 // proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 // systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=17771 0 0 // tmpfs /sys tmpfs rw,seclabel,relatime 0 0 // sysfs /sys sysfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0 // securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0 // cgroup2 /sys/fs/cgroup cgroup2 rw,seclabel,nosuid,nodev,noexec,relatime,nsdelegate 0 0 // pstore /sys/fs/pstore pstore rw,seclabel,nosuid,nodev,noexec,relatime 0 0 // none /sys/fs/bpf bpf rw,nosuid,nodev,noexec,relatime,mode=700 0 0 // configfs /sys/kernel/config configfs rw,nosuid,nodev,noexec,relatime 0 0 // selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0 // debugfs /sys/kernel/debug debugfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0 // tmpfs /dev/shm tmpfs rw,seclabel,relatime 0 0 // devpts /dev/pts devpts rw,seclabel,relatime,gid=5,mode=620,ptmxmode=666 0 0 // tmpfs /sys/fs/selinux tmpfs rw,seclabel,relatime 0 0 // /dev/vda2 /proc/filesystems xfs rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 // "#, // ); // assert_eq!(disks.len(), 1); // assert_eq!( // disks[0], // Disk { // type_: DiskType::Unknown(-1), // name: OsString::from("devpts"), // file_system: vec![100, 101, 118, 112, 116, 115], // mount_point: PathBuf::from("/dev/pts"), // total_space: 0, // available_space: 0, // } // ); // } sysinfo-0.30.13/src/unix/linux/mod.rs000064400000000000000000000043101046102023000155160ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod component; pub mod cpu; pub mod disk; pub mod network; pub mod process; pub mod system; pub(crate) mod utils; pub(crate) use self::component::{ComponentInner, ComponentsInner}; pub(crate) use self::cpu::CpuInner; pub(crate) use self::disk::DiskInner; pub(crate) use self::network::{NetworkDataInner, NetworksInner}; pub(crate) use self::process::ProcessInner; pub(crate) use self::system::SystemInner; pub(crate) use crate::unix::groups::get_groups; pub(crate) use crate::unix::users::{get_users, UserInner}; pub(crate) use crate::unix::DisksInner; use std::time::Duration; 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/is_supported.md")] pub const IS_SUPPORTED_SYSTEM: bool = true; #[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); sysinfo-0.30.13/src/unix/linux/network.rs000064400000000000000000000250661046102023000164430ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::{hash_map, HashMap}; use std::fs::File; use std::io::Read; use std::path::Path; use crate::common::MacAddr; use crate::network::refresh_networks_addresses; use crate::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)) { if 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, 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 = 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); 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); 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, // rx_compressed, // old_rx_compressed: rx_compressed, // tx_compressed, // old_tx_compressed: tx_compressed, updated: true, }, }); } }; } // Remove interfaces which are gone. interfaces.retain(|_, d| d.inner.updated); } } 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) { let mut v = vec![0; 30]; for (interface_name, data) in self.interfaces.iter_mut() { data.inner.update(interface_name, &mut v); } } pub(crate) fn refresh_list(&mut self) { refresh_networks_list_from_sysfs(&mut self.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, // /// 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 { fn update(&mut self, path: &str, data: &mut Vec) { let path = &Path::new("/sys/class/net/").join(path).join("statistics"); old_and_new!(self, rx_bytes, old_rx_bytes, read(path, "rx_bytes", data)); old_and_new!(self, tx_bytes, old_tx_bytes, read(path, "tx_bytes", data)); old_and_new!( self, rx_packets, old_rx_packets, read(path, "rx_packets", data) ); old_and_new!( self, tx_packets, old_tx_packets, read(path, "tx_packets", data) ); old_and_new!( self, rx_errors, old_rx_errors, read(path, "rx_errors", data) ); old_and_new!( self, tx_errors, old_tx_errors, read(path, "tx_errors", data) ); // old_and_new!( // self, // rx_compressed, // old_rx_compressed, // read(path, "rx_compressed", data) // ); // old_and_new!( // self, // tx_compressed, // old_tx_compressed, // read(path, "tx_compressed", data) // ); } 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 } } #[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, 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, 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, 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, sys_net_dir.path()); assert_eq!(interfaces.keys().collect::>(), ["itf2"]); } } sysinfo-0.30.13/src/unix/linux/process.rs000064400000000000000000000633571046102023000164350ustar 00000000000000// 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; use std::fmt; use std::fs::{self, DirEntry, File}; use std::io::Read; use std::path::{Path, PathBuf}; use std::str::FromStr; use libc::{c_ulong, gid_t, kill, uid_t}; use crate::sys::system::SystemInfo; use crate::sys::utils::{ get_all_data, get_all_data_from_file, realpath, FileCounter, PathHandler, PathPush, }; use crate::{ DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Signal, ThreadKind, Uid, }; #[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, ShortExe, 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: String, 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, 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>, pub(crate) stat_file: Option, old_read_bytes: u64, old_written_bytes: u64, read_bytes: u64, written_bytes: u64, thread_kind: Option, proc_path: PathBuf, } impl ProcessInner { pub(crate) fn new(pid: Pid, proc_path: PathBuf) -> Self { Self { name: String::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, 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, } } pub(crate) fn kill_with(&self, signal: Signal) -> Option { let c_signal = crate::sys::convert_signal(signal)?; unsafe { Some(kill(self.pid.0, c_signal) == 0) } } pub(crate) fn name(&self) -> &str { &self.name } pub(crate) fn cmd(&self) -> &[String] { &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) -> &[String] { &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 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) { let mut status = 0; // attempt waiting unsafe { if retry_eintr!(libc::waitpid(self.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 kill(self.pid.0, 0) == 0 { std::thread::sleep(duration); } } } } 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 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 unset_updated(p: &mut ProcessInner) { p.updated = false; } 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; p.updated = true; } pub(crate) fn update_process_disk_activity(p: &mut ProcessInner, path: &mut PathHandler) { let data = match get_all_data(path.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<'a, T> Send for Wrap<'a, T> {} unsafe impl<'a, T> Sync for Wrap<'a, T> {} #[inline(always)] fn compute_start_time_without_boot_time(parts: &[&str], info: &SystemInfo) -> u64 { // To be noted that the start time is invalid here, it still needs to be converted into // "real" time. u64::from_str(parts[ProcIndex::StartTime as usize]).unwrap_or(0) / info.clock_cycle } fn _get_stat_data(path: &Path, stat_file: &mut Option) -> Result { let mut file = File::open(path.join("stat")).map_err(|_| ())?; let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; *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.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, parts: &[&str], uptime: u64, info: &SystemInfo, ) { update_parent_pid(p, parent_pid, parts); get_status(p, 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.join("exe")); } if refresh_kind.cmd().needs_update(|| p.cmd.is_empty()) { p.cmd = copy_from_file(proc_path.join("cmdline")); } if refresh_kind.environ().needs_update(|| p.environ.is_empty()) { p.environ = copy_from_file(proc_path.join("environ")); } if refresh_kind.cwd().needs_update(|| p.cwd.is_none()) { p.cwd = realpath(proc_path.join("cwd")); } if refresh_kind.root().needs_update(|| p.root.is_none()) { p.root = realpath(proc_path.join("root")); } update_time_and_memory(proc_path, p, parts, uptime, info, refresh_kind); if refresh_kind.disk_usage() { update_process_disk_activity(p, proc_path); } } 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: &[&str], 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[ProcIndex::ShortExe as usize]; p.start_time_without_boot_time = compute_start_time_without_boot_time(parts, info); p.start_time = p .start_time_without_boot_time .saturating_add(info.boot_time); p.name = name.into(); if c_ulong::from_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, uptime, info, ); Process { inner: p } } pub(crate) fn _get_process_data( path: &Path, proc_list: &mut HashMap, pid: Pid, parent_pid: Option, uptime: u64, info: &SystemInfo, refresh_kind: ProcessRefreshKind, ) -> Result<(Option, Pid), ()> { let data; let parts = if let Some(ref mut entry) = proc_list.get_mut(&pid) { let entry = &mut entry.inner; 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(path, &mut entry.stat_file)? }; let parts = parse_stat_file(&data).ok_or(())?; let start_time_without_boot_time = compute_start_time_without_boot_time(&parts, info); // 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_without_boot_time == entry.start_time_without_boot_time { let mut proc_path = PathHandler::new(path); update_proc_info( entry, parent_pid, refresh_kind, &mut proc_path, &parts, uptime, info, ); refresh_user_group_ids(entry, &mut proc_path, refresh_kind); return Ok((None, pid)); } parts } else { let mut stat_file = None; let data = _get_stat_data(path, &mut stat_file)?; let parts = parse_stat_file(&data).ok_or(())?; let mut p = retrieve_all_new_process_info( pid, parent_pid, &parts, path, info, refresh_kind, uptime, ); p.inner.stat_file = stat_file; return Ok((Some(p), pid)); }; // If we're here, it means that the PID still exists but it's a different process. let p = retrieve_all_new_process_info(pid, parent_pid, &parts, path, info, refresh_kind, uptime); match proc_list.get_mut(&pid) { Some(ref mut entry) => **entry = p, // If it ever enters this case, it means that the process was removed from the HashMap // in-between with the usage of dark magic. None => unreachable!(), } // Since this PID is already in the HashMap, no need to add it again. Ok((None, pid)) } fn old_get_memory(entry: &mut ProcessInner, parts: &[&str], info: &SystemInfo) { // rss entry.memory = u64::from_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(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, 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.join("statm"), entry, info) { old_get_memory(entry, parts, info); } } set_time( entry, u64::from_str(parts[ProcIndex::UserTime as usize]).unwrap_or(0), u64::from_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>, } fn get_all_pid_entries( parent: Option<&OsStr>, parent_pid: Option, entry: DirEntry, data: &mut Vec, ) -> Option { let Ok(file_type) = entry.file_type() else { return None; }; if !file_type.is_dir() { return None; } let entry = entry.path(); let name = entry.file_name(); if name == parent { // Needed because tasks have their own PID listed in the "task" folder. return None; } let name = name?; let pid = Pid::from(usize::from_str(&name.to_string_lossy()).ok()?); let tasks_dir = Path::join(&entry, "task"); let tasks = if let Ok(entries) = fs::read_dir(tasks_dir) { let mut tasks = HashSet::new(); for task in entries .into_iter() .filter_map(|entry| get_all_pid_entries(Some(name), Some(pid), entry.ok()?, data)) { tasks.insert(task); } Some(tasks) } else { None }; data.push(ProcAndTasks { pid, parent_pid, path: entry, tasks, }); Some(pid) } #[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 } pub(crate) fn refresh_procs( proc_list: &mut HashMap, path: &Path, uptime: u64, info: &SystemInfo, filter: Option<&[Pid]>, refresh_kind: ProcessRefreshKind, ) -> bool { #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; #[inline(always)] fn real_filter(e: &ProcAndTasks, filter: &[Pid]) -> bool { filter.contains(&e.pid) } #[inline(always)] fn empty_filter(_e: &ProcAndTasks, _filter: &[Pid]) -> bool { true } #[allow(clippy::type_complexity)] let (filter, filter_callback): ( &[Pid], &(dyn Fn(&ProcAndTasks, &[Pid]) -> bool + Sync + Send), ) = if let Some(filter) = filter { (filter, &real_filter) } else { (&[], &empty_filter) }; // FIXME: To prevent retrieving a task more than once (it can be listed in `/proc/[PID]/task` // subfolder and directly in `/proc` at the same time), might be interesting to use a `HashSet`. let procs = { let d = match fs::read_dir(path) { Ok(d) => d, Err(_) => return false, }; let proc_list = Wrap(UnsafeCell::new(proc_list)); iter(d) .map(|entry| { let Ok(entry) = entry else { return Vec::new() }; let mut entries = Vec::new(); get_all_pid_entries(None, None, entry, &mut entries); entries }) .flatten() .filter(|e| filter_callback(e, filter)) .filter_map(|e| { let (mut p, _) = _get_process_data( e.path.as_path(), proc_list.get(), e.pid, e.parent_pid, uptime, info, refresh_kind, ) .ok()?; if let Some(ref mut p) = p { p.inner.tasks = e.tasks; } p }) .collect::>() }; for proc_ in procs { proc_list.insert(proc_.pid(), proc_); } true } 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 { let mut out = Vec::with_capacity(10); let mut data = data.as_slice(); while let Some(pos) = data.iter().position(|c| *c == 0) { match std::str::from_utf8(&data[..pos]).map(|s| s.trim()) { Ok(s) if !s.is_empty() => out.push(s.to_string()), _ => {} } data = &data[pos + 1..]; } out } } 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_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 parse_stat_file(data: &str) -> 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 parts = Vec::with_capacity(52); let mut data_it = data.splitn(2, ' '); parts.push(data_it.next()?); let mut data_it = data_it.next()?.rsplitn(2, ')'); let data = data_it.next()?; parts.push(data_it.next()?); parts.extend(data.split_whitespace()); // Remove command name '(' if let Some(name) = parts[ProcIndex::ShortExe as usize].strip_prefix('(') { parts[ProcIndex::ShortExe as usize] = name; } Some(parts) } sysinfo-0.30.13/src/unix/linux/system.rs000064400000000000000000000555651046102023000163050ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::cpu::{get_physical_core_count, CpusWrapper}; use crate::sys::process::{_get_process_data, compute_cpu_usage, refresh_procs, unset_updated}; use crate::sys::utils::{get_all_data, to_u64}; use crate::{Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind}; use libc::{self, c_char, sysconf, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE}; use std::cmp::min; use std::collections::HashMap; use std::ffi::CStr; use std::fs::File; use std::io::Read; use std::path::Path; use std::str::FromStr; use std::sync::atomic::AtomicIsize; // 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) static REMAINING_FILES: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { 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. 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 _) } }); pub(crate) fn get_max_nb_fds() -> isize { 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 isize / 2 } } } 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 clear_procs(&mut self, refresh_kind: ProcessRefreshKind) { let (total_time, compute_cpu, max_value) = if refresh_kind.cpu() { self.cpus .refresh_if_needed(true, CpuRefreshKind::new().with_cpu_usage()); if self.cpus.is_empty() { sysinfo_debug!("cannot compute processes CPU usage: no CPU found..."); (0., false, 0.) } else { let (new, old) = self.cpus.get_global_raw_times(); let total_time = if old > new { 1 } else { new - old }; ( total_time as f32 / self.cpus.len() as f32, true, self.get_max_process_cpu_usage(), ) } } else { (0., false, 0.) }; self.process_list.retain(|_, proc_| { let proc_ = &mut proc_.inner; if !proc_.updated { return false; } if compute_cpu { compute_cpu_usage(proc_, total_time, max_value); } unset_updated(proc_); true }); } 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, filter: Option<&[Pid]>, refresh_kind: ProcessRefreshKind, ) { let uptime = Self::uptime(); refresh_procs( &mut self.process_list, Path::new("/proc"), uptime, &self.info, filter, refresh_kind, ); self.clear_procs(refresh_kind); self.cpus.set_need_cpus_update(); } pub(crate) fn refresh_process_specifics( &mut self, pid: Pid, refresh_kind: ProcessRefreshKind, ) -> bool { let uptime = Self::uptime(); match _get_process_data( &Path::new("/proc/").join(pid.to_string()), &mut self.process_list, pid, None, uptime, &self.info, refresh_kind, ) { Ok((Some(p), pid)) => { self.process_list.insert(pid, p); } Ok(_) => {} Err(_e) => { sysinfo_debug!("Cannot get information for PID {:?}: {:?}", pid, _e); return false; } }; if refresh_kind.cpu() { self.refresh_cpus(true, CpuRefreshKind::new().with_cpu_usage()); if self.cpus.is_empty() { eprintln!("Cannot compute process CPU usage: no cpus found..."); return true; } let (new, old) = self.cpus.get_global_raw_times(); let total_time = (if old >= new { 1 } else { new - old }) as f32; let total_time = total_time / self.cpus.len() as f32; let max_cpu_usage = self.get_max_process_cpu_usage(); if let Some(p) = self.process_list.get_mut(&pid) { let p = &mut p.inner; compute_cpu_usage(p, total_time, max_cpu_usage); unset_updated(p); } } else if let Some(p) = self.process_list.get_mut(&pid) { unset_updated(&mut p.inner); } true } // 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 process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } pub(crate) fn global_cpu_info(&self) -> &Cpu { &self.cpus.global_cpu } pub(crate) fn cpus(&self) -> &[Cpu] { &self.cpus.cpus } pub(crate) fn physical_core_count(&self) -> Option { get_physical_core_count() } 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 { let content = get_all_data("/proc/uptime", 50).unwrap_or_default(); content .split('.') .next() .and_then(|t| t.parse().ok()) .unwrap_or_default() } 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) } pub(crate) fn long_os_version() -> Option { #[cfg(target_os = "android")] let system_name = "Android"; #[cfg(not(target_os = "android"))] let system_name = "Linux"; Some(format!( "{} {} {}", system_name, Self::os_version().unwrap_or_default(), Self::name().unwrap_or_default() )) } 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 = std::mem::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()) } pub(crate) fn cpu_arch() -> Option { let mut raw = std::mem::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, }) } } } fn read_u64(filename: &str) -> Option { get_all_data(filename, 16_635) .ok() .and_then(|d| u64::from_str(d.trim()).ok()) } fn read_table(filename: &str, colsep: char, mut f: F) where F: FnMut(&str, u64), { if let Ok(content) = get_all_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)); } } 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)) = ( read_u64("/sys/fs/cgroup/memory.current"), read_u64("/sys/fs/cgroup/memory.max"), ) { // cgroups v2 let mut limits = Self { total_memory: sys.mem_total, free_memory: sys.mem_free, free_swap: sys.swap_free, }; 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)) = ( // cgroups v1 read_u64("/sys/fs/cgroup/memory/memory.usage_in_bytes"), read_u64("/sys/fs/cgroup/memory/memory.limit_in_bytes"), ) { let mut limits = Self { total_memory: sys.mem_total, free_memory: sys.mem_free, free_swap: sys.swap_free, }; 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, } #[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=", }; 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; } }; for line in buf.lines() { if let Some(stripped) = line.strip_prefix(info_str) { return Some(stripped.replace('"', "")); } } None } #[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; } }; 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 { #[cfg(target_os = "android")] use super::get_system_info_android; #[cfg(not(target_os = "android"))] use super::get_system_info_linux; use super::InfoType; #[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()); } #[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()) ); // 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 ); } } sysinfo-0.30.13/src/unix/linux/utils.rs000064400000000000000000000063671046102023000161150ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::fs::File; use std::io::{self, Read, Seek}; use std::path::{Path, PathBuf}; use std::sync::atomic::Ordering; use crate::sys::system::REMAINING_FILES; pub(crate) fn get_all_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) } pub(crate) fn get_all_data>(file_path: P, size: usize) -> io::Result { let mut file = File::open(file_path.as_ref())?; get_all_data_from_file(&mut file, size) } #[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 } } } /// Type used to correctly handle the `REMAINING_FILES` global. pub(crate) struct FileCounter(File); impl FileCounter { pub(crate) 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); } } /// 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`. pub(crate) struct PathHandler(PathBuf); 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::join` call will first call `pop` internally. Self(path.join("a")) } } pub(crate) trait PathPush { fn join(&mut self, p: &str) -> &Path; } impl PathPush for PathHandler { fn join(&mut self, p: &str) -> &Path { self.0.pop(); self.0.push(p); self.0.as_path() } } // This implementation allows to skip one allocation that is done in `PathHandler`. impl PathPush for PathBuf { fn join(&mut self, p: &str) -> &Path { self.push(p); self.as_path() } } 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. 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 } sysinfo-0.30.13/src/unix/mod.rs000064400000000000000000000022641046102023000143650ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. cfg_if::cfg_if! { if #[cfg(any(target_os = "macos", target_os = "ios"))] { pub(crate) mod apple; pub(crate) use apple as sys; 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")] pub(crate) use libc::__errno_location as libc_errno; #[cfg(target_os = "android")] pub(crate) use libc::__errno as libc_errno; } else if #[cfg(target_os = "freebsd")] { pub(crate) mod freebsd; pub(crate) use freebsd as sys; pub(crate) use libc::__error as libc_errno; } else { compile_error!("Invalid cfg!"); } } pub(crate) mod groups; pub(crate) mod network_helper; pub(crate) mod users; pub(crate) mod utils; 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 } } sysinfo-0.30.13/src/unix/network_helper.rs000064400000000000000000000073411046102023000166370ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::common::MacAddr; use std::ptr::null_mut; /// 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 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: *const libc::ifaddrs) -> Option { let sock_addr = (*ifap).ifa_addr; if sock_addr.is_null() { return None; } 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: *const libc::ifaddrs) -> Option { use libc::sockaddr_ll; let sock_addr = (*ifap).ifa_addr; if sock_addr.is_null() { return None; } 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) fn get_interface_address() -> Result { let mut ifap = null_mut(); unsafe { if retry_eintr!(libc::getifaddrs(&mut ifap)) == 0 && !ifap.is_null() { Ok(InterfaceAddressIterator { ifap, buf: ifap }) } else { Err("failed to call getifaddrs()".to_string()) } } } sysinfo-0.30.13/src/unix/users.rs000064400000000000000000000101311046102023000147370ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ common::{Gid, Uid}, Group, }; #[cfg(not(any(target_os = "macos", target_os = "ios")))] use crate::User; use libc::{getgrgid_r, getgrouplist}; 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; 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 _ { // 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 { 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(); } } // Not used by mac. #[cfg(not(any(target_os = "macos", target_os = "ios")))] pub(crate) fn get_users(users: &mut Vec) { use std::fs::File; use std::io::Read; #[inline] fn parse_id(id: &str) -> Option { id.parse::().ok() } users.clear(); let mut s = String::new(); let _ = File::open("/etc/passwd").and_then(|mut f| f.read_to_string(&mut s)); for line in s.lines() { let mut parts = line.split(':'); if let Some(username) = parts.next() { let mut parts = parts.skip(1); // Skip the user if the uid cannot be parsed correctly if let Some(uid) = parts.next().and_then(parse_id) { if let Some(group_id) = parts.next().and_then(parse_id) { users.push(User { inner: UserInner::new(Uid(uid), Gid(group_id), username.to_owned()), }); } } } } } #[cfg(any(target_os = "macos", target_os = "ios"))] pub(crate) use crate::unix::apple::users::get_users; sysinfo-0.30.13/src/unix/utils.rs000064400000000000000000000014551046102023000147470ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use libc::c_char; pub(crate) fn cstr_to_rust(c: *const c_char) -> Option { cstr_to_rust_with_size(c, None) } pub(crate) fn cstr_to_rust_with_size(c: *const 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() } } sysinfo-0.30.13/src/unknown/component.rs000064400000000000000000000021101046102023000163120ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::Component; pub(crate) struct ComponentInner; impl ComponentInner { pub(crate) fn temperature(&self) -> f32 { 0.0 } pub(crate) fn max(&self) -> f32 { 0.0 } pub(crate) fn critical(&self) -> Option { None } pub(crate) fn label(&self) -> &str { "" } pub(crate) fn refresh(&mut self) {} } pub(crate) struct ComponentsInner { 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_list(&mut self) { // Doesn't do anything. } } sysinfo-0.30.13/src/unknown/cpu.rs000064400000000000000000000007451046102023000151130ustar 00000000000000// 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 new() -> Self { Self } 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 { "" } } sysinfo-0.30.13/src/unknown/disk.rs000064400000000000000000000024511046102023000152520ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Disk, DiskKind}; use std::{ffi::OsStr, path::Path}; pub(crate) struct DiskInner; impl DiskInner { pub(crate) fn kind(&self) -> DiskKind { unreachable!() } pub(crate) fn name(&self) -> &OsStr { unreachable!() } 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 refresh(&mut self) -> bool { true } } 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_list(&mut self) { // Does nothing. } pub(crate) fn list(&self) -> &[Disk] { &self.disks } pub(crate) fn list_mut(&mut self) -> &mut [Disk] { &mut self.disks } } sysinfo-0.30.13/src/unknown/groups.rs000064400000000000000000000004731046102023000156410ustar 00000000000000// 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) {} sysinfo-0.30.13/src/unknown/mod.rs000064400000000000000000000020341046102023000150740ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod component; pub mod cpu; pub mod disk; pub mod groups; pub mod network; pub mod process; pub mod system; pub mod users; pub(crate) use self::component::{ComponentInner, ComponentsInner}; pub(crate) use self::cpu::CpuInner; pub(crate) use self::disk::{DiskInner, DisksInner}; pub(crate) use self::groups::get_groups; pub(crate) use self::network::{NetworkDataInner, NetworksInner}; pub(crate) use self::process::ProcessInner; pub(crate) use self::system::SystemInner; pub(crate) use self::users::{get_users, UserInner}; use std::time::Duration; declare_signals! { (), _ => None, } #[doc = include_str!("../../md_doc/is_supported.md")] pub const IS_SUPPORTED_SYSTEM: bool = false; #[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); sysinfo-0.30.13/src/unknown/network.rs000064400000000000000000000030371046102023000160120ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::common::MacAddr; use crate::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_list(&mut self) {} pub(crate) fn refresh(&mut self) {} } 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 } } sysinfo-0.30.13/src/unknown/process.rs000064400000000000000000000036151046102023000160010ustar 00000000000000// 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::fmt; use std::path::Path; 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) -> &str { "" } pub(crate) fn cmd(&self) -> &[String] { &[] } pub(crate) fn exe(&self) -> Option<&Path> { None } pub(crate) fn pid(&self) -> Pid { self.pid } pub(crate) fn environ(&self) -> &[String] { &[] } 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 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) {} pub(crate) fn session_id(&self) -> Option { None } } sysinfo-0.30.13/src/unknown/system.rs000064400000000000000000000054561046102023000156540ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ Cpu, CpuInner, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind, }; use std::collections::HashMap; pub(crate) struct SystemInner { processes_list: HashMap, global_cpu: Cpu, } impl SystemInner { pub(crate) fn new() -> Self { Self { processes_list: Default::default(), global_cpu: Cpu { inner: CpuInner::new(), }, } } 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_processes_specifics( &mut self, _filter: Option<&[Pid]>, _refresh_kind: ProcessRefreshKind, ) { } pub(crate) fn refresh_process_specifics( &mut self, _pid: Pid, _refresh_kind: ProcessRefreshKind, ) -> bool { false } // COMMON PART // // Need to be moved into a "common" file to avoid duplication. pub(crate) fn processes(&self) -> &HashMap { &self.processes_list } pub(crate) fn process(&self, _pid: Pid) -> Option<&Process> { None } pub(crate) fn global_cpu_info(&self) -> &Cpu { &self.global_cpu } pub(crate) fn cpus(&self) -> &[Cpu] { &[] } pub(crate) fn physical_core_count(&self) -> Option { None } 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 host_name() -> Option { None } pub(crate) fn cpu_arch() -> Option { None } } sysinfo-0.30.13/src/unknown/users.rs000064400000000000000000000007271046102023000154650ustar 00000000000000// 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) {} sysinfo-0.30.13/src/utils.rs000064400000000000000000000040431046102023000137600ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. /// 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 sequential iterator if the `multithread` feature is disabled. /// Uses the `std::iter::IntoIterator` trait. #[cfg(any( not(feature = "multithread"), feature = "unknown-ci", all(target_os = "macos", feature = "apple-sandbox") ))] #[allow(dead_code)] pub(crate) fn into_iter(val: T) -> T::IntoIter where T: IntoIterator, { val.into_iter() } /// Converts the value into a parallel mutable iterator if the `multithread` feature is enabled. /// Uses the `rayon::iter::IntoParallelRefMutIterator` trait. #[cfg(all( feature = "multithread", not(feature = "unknown-ci"), not(all(target_os = "macos", feature = "apple-sandbox")), ))] pub(crate) fn into_iter_mut<'a, T>( val: &'a mut T, ) -> >::Iter where T: rayon::iter::IntoParallelRefMutIterator<'a> + ?Sized, { val.par_iter_mut() } // 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(any( not(feature = "multithread"), feature = "unknown-ci", all(target_os = "macos", feature = "apple-sandbox") ))] pub(crate) fn into_iter_mut(val: T) -> T::IntoIter where T: IntoIterator, { val.into_iter() } sysinfo-0.30.13/src/windows/component.rs000064400000000000000000000213771046102023000163250ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::Component; use windows::core::w; use windows::Win32::Foundation::{SysAllocString, SysFreeString}; use windows::Win32::Security::PSECURITY_DESCRIPTOR; use windows::Win32::System::Com::{ CoCreateInstance, CoInitializeEx, CoInitializeSecurity, CoSetProxyBlanket, CoUninitialize, CLSCTX_INPROC_SERVER, 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::{VariantClear, VARIANT}; use windows::Win32::System::Wmi::{ IEnumWbemClassObject, IWbemLocator, IWbemServices, WbemLocator, WBEM_FLAG_FORWARD_ONLY, WBEM_FLAG_NONSYSTEM_ONLY, WBEM_FLAG_RETURN_IMMEDIATELY, WBEM_INFINITE, }; pub(crate) struct ComponentInner { temperature: f32, max: f32, critical: Option, label: String, connection: Option, } impl ComponentInner { /// Creates a new `ComponentInner` with the given information. fn new() -> Option { let mut c = Connection::new() .and_then(|x| x.initialize_security()) .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), }) } pub(crate) fn temperature(&self) -> f32 { self.temperature } pub(crate) fn max(&self) -> f32 { self.max } pub(crate) fn critical(&self) -> Option { self.critical } pub(crate) fn label(&self) -> &str { &self.label } pub(crate) fn refresh(&mut self) { if self.connection.is_none() { self.connection = Connection::new() .and_then(|x| x.initialize_security()) .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 { if let Some((temperature, _)) = connection.temperature(false) { self.temperature = temperature; if self.temperature > self.max { self.max = self.temperature; } } } } } pub(crate) struct ComponentsInner { 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_list(&mut self) { self.components = match ComponentInner::new() { Some(c) => vec![Component { inner: c }], None => Vec::new(), }; } } macro_rules! bstr { ($x:literal) => {{ SysAllocString(w!($x)) }}; } struct Connection { instance: Option, server_connection: Option, enumerator: Option, initialized: bool, } #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for Connection {} unsafe impl Sync for Connection {} impl Connection { #[allow(clippy::unnecessary_wraps)] fn new() -> Option { let val = unsafe { CoInitializeEx(None, Default::default()) }; Some(Connection { instance: None, server_connection: None, enumerator: None, initialized: val.is_ok(), }) } fn initialize_security(self) -> Option { unsafe { CoInitializeSecurity( PSECURITY_DESCRIPTOR::default(), -1, None, None, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, None, EOAC_NONE, None, ) } .map_or(None, |_| Some(self)) } 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(); if self.initialized { unsafe { CoUninitialize() }; } } } sysinfo-0.30.13/src/windows/cpu.rs000064400000000000000000000460541046102023000151110ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::tools::KeyHandler; 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; use windows::core::{s, PCSTR, PCWSTR}; use windows::Win32::Foundation::{ CloseHandle, BOOLEAN, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS, FALSE, HANDLE, }; use windows::Win32::System::Performance::{ PdhAddEnglishCounterA, PdhAddEnglishCounterW, PdhCloseQuery, PdhCollectQueryData, PdhCollectQueryDataEx, PdhGetFormattedCounterValue, PdhOpenQueryA, PdhRemoveCounter, PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE, }; use windows::Win32::System::Power::{ CallNtPowerInformation, ProcessorInformation, PROCESSOR_POWER_INFORMATION, }; 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, RegisterWaitForSingleObject, INFINITE, WT_EXECUTEDEFAULT, }; // 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? static LOAD_AVG: once_cell::sync::Lazy>> = once_cell::sync::Lazy::new(|| unsafe { init_load_avg() }); pub(crate) fn get_load_average() -> LoadAvg { if let Ok(avg) = LOAD_AVG.lock() { if let Some(avg) = &*avg { return avg.clone(); } } LoadAvg::default() } unsafe extern "system" fn load_avg_callback(counter: *mut c_void, _: BOOLEAN) { let mut display_value = mem::MaybeUninit::::uninit(); if PdhGetFormattedCounterValue( counter as _, 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() { if 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 = 0; if PdhOpenQueryA(PCSTR::null(), 0, &mut query) != ERROR_SUCCESS.0 { sysinfo_debug!("init_load_avg: PdhOpenQueryA failed"); return Mutex::new(None); } let mut counter = 0; if PdhAddEnglishCounterA(query, s!("\\System\\Cpu Queue Length"), 0, &mut counter) != 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(counter); PdhCloseQuery(query); sysinfo_debug!("init_load_avg: RegisterWaitForSingleObject failed"); Mutex::new(None) } } struct InternalQuery { query: HANDLE, 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.0); } if !self.event.is_invalid() { let _err = CloseHandle(self.event); } if !self.query.is_invalid() { PdhCloseQuery(self.query.0); } } } } pub(crate) struct Query { internal: InternalQuery, } impl Query { pub fn new() -> Option { let mut query = 0; unsafe { if PdhOpenQueryA(PCSTR::null(), 0, &mut query) == ERROR_SUCCESS.0 { let q = InternalQuery { query: HANDLE(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.0, 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 = 0; let ret = PdhAddEnglishCounterW( self.internal.query.0, PCWSTR::from_raw(getter.as_ptr()), 0, &mut counter, ); if ret == ERROR_SUCCESS.0 { self.internal.data.insert(name.clone(), HANDLE(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.0) != ERROR_SUCCESS.0 { sysinfo_debug!("failed to refresh CPU data"); } } } } pub(crate) struct CpusWrapper { global: Cpu, cpus: Vec, got_cpu_frequency: bool, } impl CpusWrapper { pub fn new() -> Self { Self { global: Cpu { inner: CpuInner::new_with_values( "Total CPU".to_owned(), String::new(), String::new(), 0, ), }, cpus: Vec::new(), got_cpu_frequency: false, } } pub fn global_cpu(&self) -> &Cpu { &self.global } pub fn global_cpu_mut(&mut self) -> &mut Cpu { &mut self.global } 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); self.got_cpu_frequency = refresh_kind.frequency(); } } pub fn len(&mut self) -> usize { self.init_if_needed(CpuRefreshKind::new()); 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) { if self.got_cpu_frequency { return; } let frequencies = get_frequencies(self.cpus.len()); for (cpu, frequency) in self.cpus.iter_mut().zip(frequencies) { cpu.inner.set_frequency(frequency); } self.global .inner .set_frequency(self.cpus.first().map(|cpu| cpu.frequency()).unwrap_or(0)); self.got_cpu_frequency = true; } } pub(crate) struct CpuInner { name: String, cpu_usage: f32, key_used: Option, vendor_id: String, brand: String, frequency: u64, } impl CpuInner { 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 { &self.brand } pub(crate) fn new_with_values( name: String, vendor_id: String, brand: String, frequency: u64, ) -> Self { Self { name, cpu_usage: 0f32, key_used: None, vendor_id, brand, frequency, } } pub(crate) fn set_cpu_usage(&mut self, value: f32) { self.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; 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()) } pub(crate) fn get_key_used(p: &mut Cpu) -> &mut Option { &mut p.inner.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 } } sysinfo-0.30.13/src/windows/disk.rs000064400000000000000000000261071046102023000152510ustar 00000000000000// 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}; use std::ffi::{c_void, OsStr, OsString}; use std::mem::size_of; use std::os::windows::ffi::OsStringExt; use std::path::Path; use windows::core::{Error, HRESULT, PCWSTR}; use windows::Win32::Foundation::MAX_PATH; use windows::Win32::Storage::FileSystem::{ FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDiskFreeSpaceExW, GetDriveTypeW, GetVolumeInformationW, GetVolumePathNamesForVolumeNameW, }; use windows::Win32::System::Ioctl::{ PropertyStandardQuery, StorageDeviceSeekPenaltyProperty, DEVICE_SEEK_PENALTY_DESCRIPTOR, IOCTL_STORAGE_QUERY_PROPERTY, STORAGE_PROPERTY_QUERY, }; use windows::Win32::System::WindowsProgramming::{DRIVE_FIXED, DRIVE_REMOVABLE}; use windows::Win32::System::IO::DeviceIoControl; /// 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 = 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, } 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 refresh(&mut self) -> bool { if self.total_space != 0 { unsafe { let mut tmp = 0; let lpdirectoryname = PCWSTR::from_raw(self.mount_point.as_ptr()); if GetDiskFreeSpaceExW(lpdirectoryname, None, None, Some(&mut tmp)).is_ok() { self.available_space = tmp; return true; } } } false } } 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_list(&mut self) { unsafe { self.disks = get_list(); } } 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 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() -> Vec { #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; crate::utils::into_iter(get_volume_guid_paths()) .flat_map(|volume_name| { let raw_volume_name = PCWSTR::from_raw(volume_name.as_ptr()); let drive_type = GetDriveTypeW(raw_volume_name); let is_removable = drive_type == DRIVE_REMOVABLE; if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE { return Vec::new(); } let mut name = [0u16; MAX_PATH as usize + 1]; let mut file_system = [0u16; 32]; let volume_info_res = GetVolumeInformationW( raw_volume_name, Some(&mut name), None, None, None, Some(&mut file_system), ) .is_ok(); if !volume_info_res { sysinfo_debug!( "Error: GetVolumeInformationW = {:?}", Error::from_win32().code() ); return Vec::new(); } let mount_paths = get_volume_path_names_for_volume_name(&volume_name[..]); if mount_paths.is_empty() { return Vec::new(); } // 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 Some(handle) = HandleWrapper::new_from_file(&device_path[..], Default::default()) else { return Vec::new(); }; let Some((total_space, available_space)) = get_drive_size(&mount_paths[0][..]) else { return Vec::new(); }; if total_space == 0 { sysinfo_debug!("total_space == 0"); return Vec::new(); } let spq_trim = STORAGE_PROPERTY_QUERY { PropertyId: StorageDeviceSeekPenaltyProperty, QueryType: PropertyStandardQuery, AdditionalParameters: [0], }; let mut result: DEVICE_SEEK_PENALTY_DESCRIPTOR = std::mem::zeroed(); let mut dw_size = 0; let device_io_control = DeviceIoControl( handle.0, IOCTL_STORAGE_QUERY_PROPERTY, Some(&spq_trim as *const STORAGE_PROPERTY_QUERY as *const c_void), size_of::() as u32, Some(&mut result as *mut DEVICE_SEEK_PENALTY_DESCRIPTOR as *mut c_void), size_of::() as u32, Some(&mut dw_size), None, ) .is_ok(); let type_ = if !device_io_control || dw_size != size_of::() as u32 { DiskKind::Unknown(-1) } else { let is_hdd = result.IncursSeekPenalty.as_bool(); if is_hdd { DiskKind::HDD } else { DiskKind::SSD } }; let name = os_string_from_zero_terminated(&name); let file_system = os_string_from_zero_terminated(&file_system); mount_paths .into_iter() .map(move |mount_path| Disk { inner: DiskInner { type_, 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, available_space, is_removable, }, }) .collect::>() }) .collect::>() } 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]) } sysinfo-0.30.13/src/windows/groups.rs000064400000000000000000000073421046102023000156360ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::to_str; use crate::{ common::{Gid, GroupInner}, windows::sid::Sid, Group, }; use std::ptr::null_mut; use windows::core::PCWSTR; use windows::Win32::Foundation::ERROR_MORE_DATA; use windows::Win32::NetworkManagement::NetManagement::{ NERR_Success, NetApiBufferFree, NetGroupEnum, NetGroupGetInfo, GROUP_INFO_0, GROUP_INFO_3, MAX_PREFERRED_LENGTH, }; 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 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: &mut *(self.inner_mut() as *mut *mut T as *mut *mut u8) } } pub(crate) fn get_groups(groups: &mut Vec) { groups.clear(); let mut resume_handle: usize = 0; unsafe { loop { let mut buffer: NetApiBuffer = Default::default(); let mut nb_read = 0; let mut total = 0; let status = NetGroupEnum( PCWSTR::null(), 0, 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.grpi0_name.is_null() { continue; } let mut group: NetApiBuffer = Default::default(); if NetGroupGetInfo( PCWSTR::null(), PCWSTR::from_raw(entry.grpi0_name.as_ptr()), 3, group.inner_mut_as_bytes(), ) == NERR_Success { if let Some(_sid) = Sid::from_psid((*group.0).grpi3_group_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 = to_str(entry.grpi0_name); groups.push(Group { inner: GroupInner::new(Gid(0), name), }); } } } } else { sysinfo_debug!( "NetGroupEnum 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; } } } } sysinfo-0.30.13/src/windows/mod.rs000064400000000000000000000022051046102023000150670ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. mod component; mod cpu; mod disk; mod groups; mod network; pub(crate) mod network_helper; mod process; mod sid; mod system; mod tools; mod users; mod utils; pub(crate) use self::component::{ComponentInner, ComponentsInner}; pub(crate) use self::cpu::CpuInner; pub(crate) use self::disk::{DiskInner, DisksInner}; pub(crate) use self::groups::get_groups; pub(crate) use self::network::{NetworkDataInner, NetworksInner}; pub(crate) use self::process::ProcessInner; pub use self::sid::Sid; pub(crate) use self::system::SystemInner; pub(crate) use self::users::get_users; pub(crate) use self::users::UserInner; use std::time::Duration; declare_signals! { (), Signal::Kill => (), _ => None, } #[doc = include_str!("../../md_doc/is_supported.md")] pub const IS_SUPPORTED_SYSTEM: bool = true; #[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); sysinfo-0.30.13/src/windows/network.rs000064400000000000000000000221651046102023000160100ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::common::MacAddr; use crate::network::refresh_networks_addresses; use crate::NetworkData; use std::collections::{hash_map, HashMap}; use windows::Win32::NetworkManagement::IpHelper::{ FreeMibTable, GetIfEntry2, GetIfTable2, MIB_IF_ROW2, MIB_IF_TABLE2, }; use windows::Win32::NetworkManagement::Ndis::{MediaConnectStateDisconnected, NET_LUID_LH}; 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_list(&mut self) { 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, }; 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); 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 { id: ptr.InterfaceLuid, 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, updated: true, }, }); } } } let _err = FreeMibTable(table as _); } // Remove interfaces which are gone. self.interfaces.retain(|_, d| d.inner.updated); // Refresh all interfaces' addresses. refresh_networks_addresses(&mut self.interfaces); } pub(crate) fn refresh(&mut self) { let entry = std::mem::MaybeUninit::::zeroed(); unsafe { let mut entry = entry.assume_init(); for (_, interface) in self.interfaces.iter_mut() { let interface = &mut interface.inner; entry.InterfaceLuid = interface.id; entry.InterfaceIndex = 0; // to prevent the function to pick this one as index if GetIfEntry2(&mut entry).is_err() { continue; } old_and_new!(interface, current_out, old_out, entry.OutOctets); old_and_new!(interface, current_in, old_in, entry.InOctets); old_and_new!( interface, packets_in, old_packets_in, entry.InUcastPkts.saturating_add(entry.InNUcastPkts) ); old_and_new!( interface, packets_out, old_packets_out, entry.OutUcastPkts.saturating_add(entry.OutNUcastPkts) ); old_and_new!(interface, errors_in, old_errors_in, entry.InErrors); old_and_new!(interface, errors_out, old_errors_out, entry.OutErrors); } } } } pub(crate) struct NetworkDataInner { id: NET_LUID_LH, 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, } 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 } } sysinfo-0.30.13/src/windows/network_helper.rs000064400000000000000000000074071046102023000173510ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::ptr::null_mut; use windows::Win32::Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS}; use windows::Win32::NetworkManagement::IpHelper::{ GetAdaptersAddresses, GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_MULTICAST, IP_ADAPTER_ADDRESSES_LH, }; use windows::Win32::Networking::WinSock::AF_UNSPEC; use crate::common::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 = 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 Drop for InterfaceAddressIterator { fn drop(&mut self) { unsafe { libc::free(self.buf as _); } } } pub(crate) fn get_interface_address() -> Result { unsafe { // 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 { 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}")) } } sysinfo-0.30.13/src/windows/process.rs000064400000000000000000001053601046102023000157740ustar 00000000000000// 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::OsString; use std::fmt; #[cfg(feature = "debug")] use std::io; use std::mem::{size_of, zeroed, MaybeUninit}; use std::os::windows::ffi::OsStringExt; use std::os::windows::process::CommandExt; use std::path::{Path, PathBuf}; use std::process; use std::ptr::null_mut; use std::str; use std::sync::Arc; use libc::c_void; use ntapi::ntexapi::{SystemProcessIdInformation, SYSTEM_PROCESS_ID_INFORMATION}; use ntapi::ntrtl::RTL_USER_PROCESS_PARAMETERS; use ntapi::ntwow64::{PEB32, RTL_USER_PROCESS_PARAMETERS32}; use once_cell::sync::Lazy; use windows::core::PCWSTR; use windows::Wdk::System::SystemInformation::{NtQuerySystemInformation, SYSTEM_INFORMATION_CLASS}; use windows::Wdk::System::SystemServices::RtlGetVersion; use windows::Wdk::System::Threading::{ NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation, ProcessWow64Information, PROCESSINFOCLASS, }; use windows::Win32::Foundation::{ LocalFree, ERROR_INSUFFICIENT_BUFFER, FILETIME, HANDLE, HINSTANCE, HLOCAL, MAX_PATH, STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH, UNICODE_STRING, }; use windows::Win32::Security::{GetTokenInformation, TokenUser, TOKEN_QUERY, TOKEN_USER}; use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory; use windows::Win32::System::Memory::{ GetProcessHeap, HeapAlloc, HeapFree, LocalAlloc, VirtualQueryEx, HEAP_ZERO_MEMORY, LMEM_FIXED, LMEM_ZEROINIT, MEMORY_BASIC_INFORMATION, }; 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::{ GetProcessIoCounters, GetProcessTimes, GetSystemTimes, OpenProcess, OpenProcessToken, CREATE_NO_WINDOW, IO_COUNTERS, PEB, PROCESS_BASIC_INFORMATION, PROCESS_QUERY_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ, }; use windows::Win32::UI::Shell::CommandLineToArgvW; 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 { 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(); 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) { if 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 overwritting 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: String, 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, } struct CPUsageCalculationValues { old_process_sys_cpu: u64, old_process_user_cpu: u64, old_system_sys_cpu: u64, old_system_user_cpu: u64, } 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, } } } static WINDOWS_8_1_OR_NEWER: Lazy = Lazy::new(|| 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 }); #[cfg(feature = "debug")] unsafe fn display_ntstatus_error(ntstatus: windows::core::HRESULT) { let code = ntstatus.0; let message = ntstatus.message(); sysinfo_debug!( "Couldn't get process infos: NtQuerySystemInformation returned {}: {}", code, message ); } // Take a look at https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm // for explanations. unsafe fn get_process_name(pid: Pid) -> Option { let mut info = SYSTEM_PROCESS_ID_INFORMATION { ProcessId: pid.0 as _, ImageName: MaybeUninit::zeroed().assume_init(), }; // `MaximumLength` MUST BE a power of 2: here 32768 because the the returned name may be a full // UNC path (up to 32767). info.ImageName.MaximumLength = 1 << 15; for i in 0.. { let local_alloc = LocalAlloc( LMEM_FIXED | LMEM_ZEROINIT, info.ImageName.MaximumLength as _, ); match local_alloc { Ok(buf) if !buf.0.is_null() => info.ImageName.Buffer = buf.0.cast(), _ => { sysinfo_debug!("Couldn't get process infos: LocalAlloc failed"); return None; } } match NtQuerySystemInformation( SYSTEM_INFORMATION_CLASS(SystemProcessIdInformation as _), &mut info as *mut _ as *mut _, size_of::() as _, null_mut(), ) .ok() { Ok(()) => break, Err(err) if err.code() == STATUS_INFO_LENGTH_MISMATCH.to_hresult() => { if !info.ImageName.Buffer.is_null() { let _err = LocalFree(HLOCAL(info.ImageName.Buffer.cast())); } if i > 2 { // Too many iterations, we should have the correct length at this point // normally, aborting name retrieval. sysinfo_debug!( "NtQuerySystemInformation returned `STATUS_INFO_LENGTH_MISMATCH` too many times" ); return None; } // New length has been set into `MaximumLength` so we just continue the loop. } Err(_err) => { if !info.ImageName.Buffer.is_null() { let _err = LocalFree(HLOCAL(info.ImageName.Buffer.cast())); } #[cfg(feature = "debug")] { display_ntstatus_error(_err.code()); } return None; } } } if info.ImageName.Buffer.is_null() { return None; } let s = std::slice::from_raw_parts( info.ImageName.Buffer, // The length is in bytes, not the length of string info.ImageName.Length as usize / std::mem::size_of::(), ); let os_str = OsString::from_wide(s); let name = Path::new(&os_str) .file_name() .map(|s| s.to_string_lossy().to_string()); let _err = LocalFree(HLOCAL(info.ImageName.Buffer.cast())); name } unsafe fn get_exe(process_handler: &HandleWrapper) -> Option { let mut exe_buf = [0u16; MAX_PATH as usize + 1]; GetModuleFileNameExW( **process_handler, HINSTANCE::default(), exe_buf.as_mut_slice(), ); Some(PathBuf::from(null_terminated_wchar_to_string(&exe_buf))) } impl ProcessInner { pub(crate) fn new_from_pid(pid: Pid, now: u64) -> Option { unsafe { let process_handler = get_process_handler(pid)?; let name = get_process_name(pid).unwrap_or_default(); let (start_time, run_time) = get_start_and_run_time(*process_handler, now); Some(Self { handle: Some(Arc::new(process_handler)), name, pid, parent: None, 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, }) } } pub(crate) fn new_full( pid: Pid, parent: Option, memory: u64, virtual_memory: u64, name: String, now: u64, ) -> 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, user_id: None, parent, cmd: Vec::new(), environ: Vec::new(), exe: None, cwd: None, root: None, status: ProcessStatus::Run, memory, virtual_memory, 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, } } 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() { update_memory(self); } 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 get_handle(&self) -> Option { self.handle.as_ref().map(|h| ***h) } pub(crate) fn get_start_time(&self) -> Option { self.handle.as_ref().map(|handle| get_start_time(***handle)) } pub(crate) fn kill_with(&self, signal: Signal) -> Option { crate::sys::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) -> &str { &self.name } pub(crate) fn cmd(&self) -> &[String] { &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) -> &[String] { &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 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) { 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! return; } std::thread::sleep(std::time::Duration::from_millis(10)); } } else { // In this case, we can't do anything so we just return. sysinfo_debug!("can't wait on this process so returning"); } } 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 } } } #[inline] unsafe fn get_process_times(handle: HANDLE) -> u64 { 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, ); super::utils::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(); if let Err(err) = NtQueryInformationProcess( process_handle, process_information_class as _, null_mut(), 0, return_length.as_mut_ptr() as *mut _, ) .ok() { if ![ 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(); 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(String::from_utf16_lossy(arg.as_wide())); } let _err = LocalFree(HLOCAL(argv_p as _)); res } unsafe fn get_region_size(handle: HANDLE, ptr: *const c_void) -> Result { let mut meminfo = MaybeUninit::::uninit(); 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; 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(); 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(); if pwow32info.is_null() { // target is a 64 bit process 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; } 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]) -> String { match slice.iter().position(|&x| x == 0) { Some(pos) => OsString::from_wide(&slice[..pos]) .to_string_lossy() .into_owned(), None => OsString::from_wide(slice).to_string_lossy().into_owned(), } } 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].iter().any(|&c| c == equals) { environ.push( OsString::from_wide(&raw_env[begin..end]) .to_string_lossy() .into_owned(), ); 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) { 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); } // FIXME: should these values be stored in one place to make use of // `MINIMUM_CPU_UPDATE_INTERVAL`? let _err = GetSystemTimes( Some(&mut fglobal_idle_time), Some(&mut fglobal_kernel_time), Some(&mut fglobal_user_time), ); 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); 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; } } } } pub(crate) fn update_memory(p: &mut ProcessInner) { if let Some(handle) = p.get_handle() { unsafe { let mut pmc: PROCESS_MEMORY_COUNTERS_EX = zeroed(); if GetProcessMemoryInfo( handle, (&mut pmc as *mut PROCESS_MEMORY_COUNTERS_EX).cast(), size_of::() as _, ) .is_ok() { p.memory = pmc.WorkingSetSize as _; p.virtual_memory = pmc.PrivateUsage as _; } } } } #[inline(always)] const fn filetime_to_u64(ft: FILETIME) -> u64 { ((ft.dwHighDateTime as u64) << 32) + ft.dwLowDateTime as u64 } sysinfo-0.30.13/src/windows/sid.rs000064400000000000000000000125031046102023000150710ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::{fmt::Display, str::FromStr}; use windows::core::{PCWSTR, PWSTR}; use windows::Win32::Foundation::{LocalFree, ERROR_INSUFFICIENT_BUFFER, HLOCAL, PSID}; use windows::Win32::Security::Authorization::{ConvertSidToStringSidW, ConvertStringSidToSidW}; use windows::Win32::Security::{ CopySid, GetLengthSid, IsValidSid, LookupAccountSidW, SidTypeUnknown, }; use crate::sys::utils::to_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 !IsValidSid(psid).as_bool() { return None; } let length = GetLengthSid(psid); let mut sid = vec![0; length as usize]; if 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. 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, PWSTR::null(), &mut name_len, PWSTR::null(), &mut domain_len, &mut name_use, ) { if 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, PWSTR::from_raw(name.as_mut_ptr()), &mut name_len, PWSTR::null(), &mut domain_len, &mut name_use, ) .is_err() { sysinfo_debug!( "LookupAccountSidW failed: {:?}", std::io::Error::last_os_error() ); return None; } Some(to_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(); if let Err(_err) = ConvertSidToStringSidW(sid, &mut string_sid) { sysinfo_debug!("ConvertSidToStringSidW failed: {:?}", _err); return None; } let result = to_str(string_sid); let _err = LocalFree(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(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()) } } } sysinfo-0.30.13/src/windows/system.rs000064400000000000000000000514221046102023000156410ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, ProcessRefreshKind}; use crate::sys::cpu::*; use crate::sys::process::get_start_time; use crate::sys::tools::*; use crate::sys::utils::{get_now, get_reg_string_value, get_reg_value_u32}; use crate::{Process, ProcessInner}; use crate::utils::into_iter; use std::cell::UnsafeCell; use std::collections::HashMap; use std::mem::{size_of, zeroed}; use std::ptr; use std::time::SystemTime; use ntapi::ntexapi::SYSTEM_PROCESS_INFORMATION; use windows::core::PWSTR; use windows::Wdk::System::SystemInformation::{NtQuerySystemInformation, SystemProcessInformation}; use windows::Win32::Foundation::{HANDLE, STATUS_INFO_LENGTH_MISMATCH, STILL_ACTIVE}; use windows::Win32::System::ProcessStatus::{K32GetPerformanceInfo, PERFORMANCE_INFORMATION}; use windows::Win32::System::Registry::HKEY_LOCAL_MACHINE; use windows::Win32::System::SystemInformation; use windows::Win32::System::SystemInformation::{ ComputerNamePhysicalDnsHostname, GetComputerNameExW, GetSystemInfo, GetTickCount64, GlobalMemoryStatusEx, MEMORYSTATUSEX, SYSTEM_INFO, }; use windows::Win32::System::Threading::GetExitCodeProcess; 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) } } // Useful for parallel iterations. struct Wrap(T); #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for Wrap {} unsafe impl Sync for Wrap {} unsafe fn boot_time() -> u64 { match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(n) => n.as_secs().saturating_sub(GetTickCount64() / 1_000), 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, } } pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { if self.query.is_none() { self.query = Query::new(); if let Some(ref mut query) = self.query { add_english_counter( r"\Processor(_Total)\% Idle Time".to_string(), query, get_key_used(self.cpus.global_cpu_mut()), "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"), ); } } } if let Some(ref mut query) = self.query { query.refresh(); let mut total_idle_time = None; if let Some(ref key_used) = *get_key_used(self.cpus.global_cpu_mut()) { 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_cpu_mut() .inner .set_cpu_usage(100.0 - total_idle_time); } for p in self.cpus.iter_mut(refresh_kind) { let mut idle_time = None; if let Some(ref key_used) = *get_key_used(p) { idle_time = Some( query .get(&key_used.unique_id) .expect("key_used disappeared"), ); } if let Some(idle_time) = idle_time { p.inner.set_cpu_usage(100.0 - idle_time); } } if refresh_kind.frequency() { self.cpus.get_frequencies(); } } } 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::map_entry)] pub(crate) fn refresh_process_specifics( &mut self, pid: Pid, refresh_kind: ProcessRefreshKind, ) -> bool { let now = get_now(); let nb_cpus = self.cpus.len() as u64; if let Some(proc_) = self.process_list.get_mut(&pid) { if let Some(ret) = refresh_existing_process(proc_, nb_cpus, now, refresh_kind) { return ret; } // We need to re-make the process because the PID owner changed. } if let Some(mut p) = ProcessInner::new_from_pid(pid, now) { p.update(refresh_kind, nb_cpus, now, true); p.updated = false; self.process_list.insert(pid, Process { inner: p }); true } else { false } } #[allow(clippy::cast_ptr_alignment)] pub(crate) fn refresh_processes_specifics( &mut self, filter: Option<&[Pid]>, refresh_kind: ProcessRefreshKind, ) { // Windows 10 notebook requires at least 512KiB of memory to make it in one go let mut buffer_size = 512 * 1024; let mut process_information: Vec = Vec::with_capacity(buffer_size); unsafe { loop { let mut cb_needed = 0; // reserve(n) ensures the Vec has capacity for n elements on top of len // so we should reserve buffer_size - len. len will always be zero at this point // this is a no-op on the first call as buffer_size == capacity process_information.reserve(buffer_size); match NtQuerySystemInformation( SystemProcessInformation, process_information.as_mut_ptr() as *mut _, buffer_size as _, &mut cb_needed, ) .ok() { Ok(()) => break, Err(err) if err.code() == STATUS_INFO_LENGTH_MISMATCH.to_hresult() => { // GetNewBufferSize if cb_needed == 0 { buffer_size *= 2; continue; } // allocating a few more kilo bytes just in case there are some new process // kicked in since new call to NtQuerySystemInformation buffer_size = (cb_needed + (1024 * 10)) as usize; continue; } Err(_err) => { sysinfo_debug!( "Couldn't get process infos: NtQuerySystemInformation returned {}", _err, ); return; } } } #[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), ) = if let Some(filter) = filter { (filter, &real_filter) } else { (&[], &empty_filter) }; // If we reach this point NtQuerySystemInformation succeeded // and the buffer contents are initialized process_information.set_len(buffer_size); // Parse the data block to get process information let mut process_ids = Vec::with_capacity(500); let mut process_information_offset = 0; loop { let p = process_information .as_ptr() .offset(process_information_offset) as *const SYSTEM_PROCESS_INFORMATION; // read_unaligned is necessary to avoid // misaligned pointer dereference: address must be a multiple of 0x8 but is 0x... // under x86_64 wine (and possibly other systems) let pi = ptr::read_unaligned(p); if filter_callback(Pid(pi.UniqueProcessId as _), filter) { process_ids.push(Wrap(p)); } if pi.NextEntryOffset == 0 { break; } process_information_offset += pi.NextEntryOffset as isize; } let process_list = Wrap(UnsafeCell::new(&mut self.process_list)); let nb_cpus = if refresh_kind.cpu() { self.cpus.len() as u64 } else { 0 }; let now = get_now(); #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; // TODO: instead of using parallel iterator only here, would be better to be // able to run it over `process_information` directly! let processes = into_iter(process_ids) .filter_map(|pi| { // as above, read_unaligned is necessary let pi = ptr::read_unaligned(pi.0); let pid = Pid(pi.UniqueProcessId as _); let ppid: usize = pi.InheritedFromUniqueProcessId as _; let parent = if ppid != 0 { Some(Pid(pi.InheritedFromUniqueProcessId as _)) } else { None }; if let Some(proc_) = (*process_list.0.get()).get_mut(&pid) { let proc_ = &mut proc_.inner; if proc_ .get_start_time() .map(|start| start == proc_.start_time()) .unwrap_or(true) { if refresh_kind.memory() { proc_.memory = pi.WorkingSetSize as _; proc_.virtual_memory = pi.VirtualSize as _; } proc_.update(refresh_kind, nb_cpus, now, false); // Update the parent in case it changed. proc_.parent = parent; return None; } // If the PID owner changed, we need to recompute the whole process. sysinfo_debug!("owner changed for PID {}", proc_.pid()); } let name = get_process_name(&pi, pid); let (memory, virtual_memory) = if refresh_kind.memory() { (pi.WorkingSetSize as _, pi.VirtualSize as _) } else { (0, 0) }; let mut p = ProcessInner::new_full(pid, parent, memory, virtual_memory, name, now); p.update(refresh_kind.without_memory(), nb_cpus, now, false); Some(Process { inner: p }) }) .collect::>(); for p in processes.into_iter() { self.process_list.insert(p.pid(), p); } self.process_list.retain(|_, v| { let x = v.inner.updated; v.inner.updated = false; x }); } } pub(crate) fn processes(&self) -> &HashMap { &self.process_list } pub(crate) fn process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } pub(crate) fn global_cpu_info(&self) -> &Cpu { self.cpus.global_cpu() } pub(crate) fn cpus(&self) -> &[Cpu] { self.cpus.cpus() } pub(crate) fn physical_core_count(&self) -> Option { get_physical_core_count() } 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, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "ProductName", ) .map(|product_name| product_name.replace("Windows 10 ", "Windows 11 ")); } get_reg_string_value( HKEY_LOCAL_MACHINE, "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, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "CurrentBuildNumber", ) } pub(crate) fn os_version() -> Option { let build_number = get_reg_string_value( HKEY_LOCAL_MACHINE, "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, "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 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 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 } /// If it returns `None`, it means that the PID owner changed and that the `Process` must be /// completely recomputed. fn refresh_existing_process( proc_: &mut Process, nb_cpus: u64, now: u64, refresh_kind: ProcessRefreshKind, ) -> Option { let proc_ = &mut proc_.inner; if let Some(handle) = proc_.get_handle() { if get_start_time(handle) != proc_.start_time() { sysinfo_debug!("owner changed for PID {}", proc_.pid()); // PID owner changed! return None; } if !is_proc_running(handle) { return Some(false); } } else { return Some(false); } proc_.update(refresh_kind, nb_cpus, now, false); proc_.updated = false; Some(true) } #[allow(clippy::size_of_in_element_count)] //^ needed for "name.Length as usize / std::mem::size_of::()" pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: Pid) -> String { let name = &process.ImageName; if name.Buffer.is_null() { match process_id.0 { 0 => "Idle".to_owned(), 4 => "System".to_owned(), _ => format!(" Process {process_id}"), } } else { unsafe { let slice = std::slice::from_raw_parts( name.Buffer, // The length is in bytes, not the length of string name.Length as usize / std::mem::size_of::(), ); String::from_utf16_lossy(slice) } } } 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, PWSTR::null(), &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, 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 } sysinfo-0.30.13/src/windows/tools.rs000064400000000000000000000011431046102023000154500ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::cpu::Query; pub(crate) struct KeyHandler { pub unique_id: String, } impl KeyHandler { pub fn new(unique_id: String) -> KeyHandler { KeyHandler { unique_id } } } pub(crate) fn add_english_counter( s: String, query: &mut 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)); } } sysinfo-0.30.13/src/windows/users.rs000064400000000000000000000207271046102023000154620ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::to_str; use crate::{ common::{Gid, Uid}, windows::sid::Sid, Group, GroupInner, User, }; use std::ptr::null_mut; use windows::core::{w, PCWSTR}; use windows::Win32::Foundation::{ERROR_MORE_DATA, LUID}; use windows::Win32::NetworkManagement::NetManagement::{ NERR_Success, NetApiBufferFree, NetUserEnum, NetUserGetInfo, NetUserGetLocalGroups, FILTER_NORMAL_ACCOUNT, LG_INCLUDE_INDIRECT, LOCALGROUP_USERS_INFO_0, MAX_PREFERRED_LENGTH, USER_INFO_0, USER_INFO_23, }; use windows::Win32::Security::Authentication::Identity::{ LsaEnumerateLogonSessions, LsaFreeReturnBuffer, LsaGetLogonSessionData, SECURITY_LOGON_SESSION_DATA, SECURITY_LOGON_TYPE, }; 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) { unsafe { get_groups_for_user(PCWSTR(c_user_name.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: &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 } } 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; let status = NetUserGetLocalGroups( w!(""), 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() { 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_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 { if 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_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), }); } } } } } sysinfo-0.30.13/src/windows/utils.rs000064400000000000000000000111651046102023000154550ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use windows::core::{PCWSTR, PWSTR}; use windows::Win32::Foundation::{self, CloseHandle, FILETIME, HANDLE}; use windows::Win32::Storage::FileSystem::{ CreateFileW, FILE_ACCESS_RIGHTS, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, }; use windows::Win32::System::Registry::{ RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY, KEY_READ, REG_NONE, }; use std::ffi::OsStr; use std::ops::Deref; use std::os::windows::ffi::OsStrExt; use std::time::SystemTime; #[inline] pub(crate) fn filetime_to_u64(f: FILETIME) -> u64 { (f.dwHighDateTime as u64) << 32 | (f.dwLowDateTime as u64) } #[inline] pub(crate) fn get_now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .map(|n| n.as_secs()) .unwrap_or(0) } pub(crate) unsafe fn to_str(p: PWSTR) -> String { if p.is_null() { return String::new(); } p.to_string().unwrap_or_else(|_e| { sysinfo_debug!("Failed to convert to UTF-16 string: {}", _e); String::new() }) } 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 RegOpenKeyExW( hkey, PCWSTR::from_raw(path.as_ptr()), 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; RegQueryValueExW( self.0, PCWSTR::from_raw(field_name.as_ptr()), None, Some(&mut buf_type), Some(buf.as_mut_ptr()), Some(buf_len), ) } } 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() } } pub(crate) struct HandleWrapper(pub(crate) HANDLE); impl HandleWrapper { pub(crate) fn new(handle: HANDLE) -> Option { if handle.is_invalid() { None } else { Some(Self(handle)) } } pub(crate) unsafe fn new_from_file( drive_name: &[u16], open_rights: FILE_ACCESS_RIGHTS, ) -> Option { let lpfilename = PCWSTR::from_raw(drive_name.as_ptr()); let handle = CreateFileW( lpfilename, open_rights.0, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, Default::default(), HANDLE::default(), ) .ok()?; 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) }; } } sysinfo-0.30.13/test_bin/main.rs000064400000000000000000000013171046102023000145650ustar 00000000000000// 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)); } } sysinfo-0.30.13/tests/code_checkers/docs.rs000064400000000000000000000100371046102023000167040ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::{show_error, TestResult}; 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) { if 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` folder or if we are in a `ffi.rs` file. if p.parent().unwrap().file_name().unwrap() == OsStr::new("src") || p.file_name().unwrap() == OsStr::new("ffi.rs") { 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 } sysinfo-0.30.13/tests/code_checkers/headers.rs000064400000000000000000000034051046102023000173700ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::{show_error, TestResult}; 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, } } } } sysinfo-0.30.13/tests/code_checkers/mod.rs000064400000000000000000000022641046102023000165360ustar 00000000000000// 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); } } sysinfo-0.30.13/tests/code_checkers/signals.rs000064400000000000000000000041241046102023000174140ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::{show_error, TestResult}; 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(") { if 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 } sysinfo-0.30.13/tests/code_checkers/utils.rs000064400000000000000000000032221046102023000171120ustar 00000000000000// 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()); } sysinfo-0.30.13/tests/components.rs000064400000000000000000000006431046102023000153620ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[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_list(); assert!(!c.is_empty()); } sysinfo-0.30.13/tests/cpu.rs000064400000000000000000000032501046102023000137610ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. // 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(); 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 s = sysinfo::System::new(); let count = s.physical_core_count(); assert_ne!(count, None); assert!(count.unwrap() > 0); } } #[test] fn test_global_cpu_info_not_set() { let mut s = sysinfo::System::new(); assert_eq!(s.global_cpu_info().vendor_id(), ""); assert_eq!(s.global_cpu_info().brand(), ""); assert_eq!(s.global_cpu_info().frequency(), 0); s.refresh_cpu(); assert_eq!(s.global_cpu_info().vendor_id(), ""); assert_eq!(s.global_cpu_info().brand(), ""); assert_eq!(s.global_cpu_info().frequency(), 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(); s.refresh_cpu(); assert!(s.cpus().iter().any(|c| !c.cpu_usage().is_nan())); } sysinfo-0.30.13/tests/disk_list.rs000064400000000000000000000010401046102023000151520ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[test] fn test_disks() { if sysinfo::IS_SUPPORTED_SYSTEM { let s = sysinfo::System::new_all(); // If we don't have any physical core present, it's very likely that we're inside a VM... if s.physical_core_count().unwrap_or_default() > 0 { let mut disks = sysinfo::Disks::new(); assert!(disks.list().is_empty()); disks.refresh_list(); assert!(!disks.list().is_empty()); } } } sysinfo-0.30.13/tests/extras.rs000064400000000000000000000001441046102023000144770ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. mod code_checkers; sysinfo-0.30.13/tests/network.rs000064400000000000000000000007221046102023000146640ustar 00000000000000// 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. #[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(); assert_eq!(n.iter().count(), 0); n.refresh_list(); assert!(n.iter().count() > 0); } } sysinfo-0.30.13/tests/process.rs000064400000000000000000000631451046102023000146610ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use sysinfo::{Pid, ProcessRefreshKind, System, UpdateKind}; #[test] fn test_cwd() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("3") .arg("CwdSignal") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("3") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; 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(ProcessRefreshKind::new().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 = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("3") .arg("CmdSignal") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("3") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; std::thread::sleep(std::time::Duration::from_millis(500)); let mut s = System::new(); assert!(s.processes().is_empty()); s.refresh_processes_specifics(ProcessRefreshKind::new().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].contains("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] 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_process_specifics(pid, sysinfo::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(ProcessRefreshKind::new().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_process(sysinfo::get_current_pid().expect("failed to get current pid")); 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(); 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(); 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_process(pid); 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 = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("3") .arg("ProcessTimes") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("3") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; 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(); 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(); 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 = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("300") .arg("RefreshProcesses") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("300") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; 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(); 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)); s.refresh_processes(); // Checks that the process isn't listed anymore. assert!(s.process(pid).is_none()); } // 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() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let task_name = "task_1_second"; std::thread::Builder::new() .name(task_name.into()) .spawn(|| { std::thread::sleep(std::time::Duration::from_secs(1)); }) .unwrap(); let pid = Pid::from_u32(std::process::id() as _); // Checks that the task is listed as it should. let mut s = System::new(); s.refresh_processes(); assert!(s .process(pid) .unwrap() .tasks() .map(|tasks| tasks.iter().any(|task_pid| s .process(*task_pid) .map(|task| task.name() == task_name) .unwrap_or(false))) .unwrap_or(false)); assert!(s.processes_by_exact_name(task_name).next().is_some()); // Let's give some time to the system to clean up... std::thread::sleep(std::time::Duration::from_secs(2)); s.refresh_processes(); assert!(!s .process(pid) .unwrap() .tasks() .map(|tasks| tasks.iter().any(|task_pid| s .process(*task_pid) .map(|task| task.name() == task_name) .unwrap_or(false))) .unwrap_or(false)); assert!(s.processes_by_exact_name(task_name).next().is_none()); } // Checks that `refresh_process` is NOT removing dead processes. #[test] fn test_refresh_process() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let mut p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("300") .arg("RefreshProcess") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("300") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; 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_process(pid); 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!(!s.refresh_process(pid)); // Checks that the process is still listed. assert!(s.process(pid).is_some()); } #[test] fn test_wait_child() { if !sysinfo::IS_SUPPORTED_SYSTEM || cfg!(feature = "apple-sandbox") { return; } let p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("300") .arg("WaitChild") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("300") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; let before = std::time::Instant::now(); let pid = Pid::from_u32(p.id() as _); let mut s = System::new(); s.refresh_process(pid); 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!(!s.refresh_process(pid)); 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_process(pid); let process = s.process(pid).expect("Process not found!"); // Wait for a non child process. process.wait(); // Child process should not be present. assert!(!s.refresh_process(pid)); // 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( sysinfo::RefreshKind::new().with_processes(sysinfo::ProcessRefreshKind::new()), ); 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).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("").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() { use sysinfo::{DiskUsage, ProcessRefreshKind}; 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 lazyness in testing them... assert_eq!(p.disk_usage(), 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_process_specifics(pid, ProcessRefreshKind::new()); { 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_process_specifics(pid, ProcessRefreshKind::new().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_process_specifics(pid, ProcessRefreshKind::new()); { 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_process_specifics(pid, ProcessRefreshKind::new()); { let p = s.process(pid).unwrap(); assert_eq!( p.$name()$($extra)+, concat!("failed 0 check check for ", stringify!($name)), ); } s.refresh_process_specifics(pid, ProcessRefreshKind::new().$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_process_specifics(pid, ProcessRefreshKind::new()); { let p = s.process(pid).unwrap(); assert_ne!( p.$name()$($extra)+, concat!("failed non-0 check (number 2) check for ", stringify!($name)),); } } } s.refresh_process_specifics(pid, ProcessRefreshKind::new()); check_empty(&s, pid); s.refresh_process_specifics(pid, ProcessRefreshKind::new()); 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 = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("3") .arg("RefreshPids") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("3") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; 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_pids(pids); 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_process(current_pid); let run_time = s.process(current_pid).expect("no process found").run_time(); std::thread::sleep(std::time::Duration::from_secs(2)); s.refresh_process(current_pid); let new_run_time = s.process(current_pid).expect("no process found").run_time(); assert!( new_run_time > run_time, "{} not superior to {}", new_run_time, 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(); 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(); // 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(); } sysinfo-0.30.13/tests/send_sync.rs000064400000000000000000000004371046102023000151630ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[test] #[allow(clippy::extra_unused_type_parameters)] fn test_send_sync() { fn is_send() {} fn is_sync() {} is_send::(); is_sync::(); } sysinfo-0.30.13/tests/system.rs000064400000000000000000000154451046102023000145270ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![allow(clippy::assertions_on_constants)] use sysinfo::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!( sys.refresh_process(sysinfo::get_current_pid().expect("failed to get current pid")), "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(); 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(); 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] #[ignore] // This test MUST be run on its own to prevent wrong CPU usage measurements. fn test_consecutive_cpu_usage_update() { use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; 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(ProcessRefreshKind::new().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() .iter() .map(|(pid, _)| *pid) .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_process_specifics(*pid, ProcessRefreshKind::new().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::new().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 futher than this point. return; } s.refresh_memory_specifics(sysinfo::MemoryRefreshKind::new().with_swap()); 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(); assert_ne!(s.total_swap(), 0); assert_ne!(s.free_swap(), 0); assert_ne!(s.total_memory(), 0); assert_ne!(s.free_memory(), 0); }