diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
commit | d1b2d29528b7794b41e66fc2136e395a02f8529b (patch) | |
tree | a4a17504b260206dec3cf55b2dca82929a348ac2 /vendor/sysinfo-0.26.7 | |
parent | Releasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip |
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/sysinfo-0.26.7')
97 files changed, 19782 insertions, 0 deletions
diff --git a/vendor/sysinfo-0.26.7/.cargo-checksum.json b/vendor/sysinfo-0.26.7/.cargo-checksum.json new file mode 100644 index 000000000..b6ddd19f3 --- /dev/null +++ b/vendor/sysinfo-0.26.7/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"ADDING_NEW_PLATFORMS.md":"43f54e5bd825d402cad4197e215c3a5fdeea81a39bec83505695557494953ae4","CHANGELOG.md":"1e0192d6d8427582507fcbecf84094717103ebcdd76414961eda42698ec83174","Cargo.lock":"cb71bc6523035a64eec65b923cc1f531ddde9b6e3b3c89e45e2a5c4e60183c1d","Cargo.toml":"71de8d3afb51b04d1186e0bc356fbfd80415ed639e9b56b8d5191f6e71628f83","LICENSE":"1f14f9ce70a5fdedab449bb32cd7d3f20e6e9d18c2a2b74221fc203db8684d43","Makefile":"c997ed3d89a46b42e79fc5f73c67fa9f21419779442b98dfb7d51fe4a49e31a2","README.md":"b60bb99658f3ad9347922a8d9851427009b8a068ddd15946d05bf4d2bd6d9090","benches/basic.rs":"994cc27b68f62ccd1df54f85bbffd0a8af887bc43efead7ff02b0e85cc2137ea","examples/simple.c":"cb18ac15e7303c5b465a28d116fa1f17e2d3478c501ab323a3d609bdaa866bd2","examples/simple.rs":"ffb83a3fb7e5cf4497cf5ae92a80d1cfd22c57964009583f4186d002afc177dd","md_doc/component.md":"98d4bd1493c3812e5b56203b6a5533359816fa8650b8dbf6fb2fe42e38473238","md_doc/cpu.md":"9bb6f7b7dade0b8acf078438bd8697d7807bb03d89c3c14c16cbaefcda2d02a7","md_doc/disk.md":"a7f63b95f8eed225484b34d8656ae7ec9dd7c619e2801d5f822f14a68bed6aba","md_doc/network_data.md":"a91aef3810f680cc3426379971d08babde03bb00c636b7c9fa9253bf32aabd4d","md_doc/networks.md":"ec192e4a13cdbb0d6af59c1b67076d09f34b42527412fdf244a40252f8fb9a40","md_doc/pid.md":"077dd679a758e317ef0cf911d6d0299ddfe4d6fa642a18460661d6da97eb5fde","md_doc/process.md":"b354fad715566d0b1922dc7eb408dfaecc3dc34c02850b7e62d709c737a8464f","md_doc/system.md":"2ee835f40091174f143cb734490c2dd3aab16a29594d42031d3d9ee7ed75e371","src/apple/app_store/component.rs":"c64f2b554f0f03bb001c58b109b0f48bfb63a3371dfc45116bae6f1d4ffea528","src/apple/app_store/mod.rs":"4558616af6978f9db2cab0704fada3404cb2346e462a51b09c355ad0bf5209b2","src/apple/app_store/process.rs":"603c9df6526cea8d3cf81c1c5b72c183109e9117903766fccb3e67669033e4ac","src/apple/component.rs":"2811bf6f53776c00903ab023de18098ed63eaa914016e2c17f37697b634768b1","src/apple/cpu.rs":"1a6d29b42d2e05506efb253845ceba25e42e8750042c90e29a7e047dc24d5b92","src/apple/disk.rs":"1a469917d2029f8842d59d36fe6294f1d7216ccd5fa42366c1004740b7d723a2","src/apple/ffi.rs":"8d19c70830ad91e67704cafc7489933d2bd32f0fe7e5186fe3b09d25e64ddffa","src/apple/ios.rs":"7c9ac153d7a49af1cf0e5a37754ba388813a80fb77619fca510505e4e4e278c0","src/apple/macos/component/arm.rs":"b3a5fb2addbe24503556293c704c4d090ad87ca866d90c3c5312eef79962dcd0","src/apple/macos/component/mod.rs":"0daab53e82a10f013b486b077177f4746c043b3571d23de36bb35c87b1ec9dd9","src/apple/macos/component/x86.rs":"8d7de10f37275301d09b3a8a072b6c5101b61e6142d3c9a93d428753ab229b2a","src/apple/macos/disk.rs":"20bb15824ed8a591b94c138fa1278c583e2d488ac9291a3a365514ab98e5b846","src/apple/macos/ffi.rs":"5449bcc8c8aa3e156f3238b264743805a1a939d6767c5f3a0576cd85083a0876","src/apple/macos/mod.rs":"32e0717737d172c073e479f68042f865b2f69421b6674d60aef880a87667b02b","src/apple/macos/process.rs":"ccee06976403384791ae70dceecdcbe0a6efcbcf539acba3cdaa6320d426ca7a","src/apple/macos/system.rs":"131186910327a2287307501993a8d0ac2d771e9ae2ac924c153786d3256bd62a","src/apple/macos/utils.rs":"c8a3809cce3311b5ac94855444d87495c269fe5ecf1d9cd7c373e40edbb61135","src/apple/mod.rs":"f764ca71f945cfc85592954db6404e9bd025b835f923078f3bdb0f1b0ebf3ea3","src/apple/network.rs":"c1c05f85167c652ae60d9b1e1f668a7aabd8ce6a89c93ef732a1d87540e7236b","src/apple/process.rs":"d9c10439f7ed98825bc439976c71010260da78640155a11a5532ded87a1946e9","src/apple/system.rs":"209423d0602ea71c87d5eeb09e843e3b6a4dbde8576d06c08024104e1ac9f45e","src/apple/users.rs":"610bf4dbe028fdb65e67235f779a11a888353a95d2862faf5fb794f065ad4e87","src/apple/utils.rs":"d4ea7f759b69e8a149246b2e4dab924b22d179d6961eb9d2a3c467d59b22cb1a","src/c_interface.rs":"82806dc47ba4eb19fb70fe4db8ca9bfab01d696b9a1917dcbcec7a58ad6f5a93","src/common.rs":"01fba002141d77172e879fc23dbff812c8993f0b056067a14c3257023d251614","src/debug.rs":"250e09a2a9ff17f9312b713c7f7937a40967ead53bd8722b56a007a110f7afaa","src/freebsd/component.rs":"882e129128bcac6be2e19b35017fa1f993fe97c974a1f73cdc0e02761cf64100","src/freebsd/cpu.rs":"173068c665d6b9ab2b9bee4c8a7ba9b260919613336fcce7897832ff1b2ee6af","src/freebsd/disk.rs":"e159b29beb7972633a10f051cc7becd42f34a91393cfbeb42d7c11955e5807fc","src/freebsd/mod.rs":"fdccfd037a3e9ac339ec803cf671904ef9ea3a902df205765dc30f234c2b6b56","src/freebsd/network.rs":"f980a7bedb9397b2fd11ba9e9196756023dc7e6848e88c7172af6b363186799c","src/freebsd/process.rs":"aea7028d65f24af5e486c937fedb6181374230e795d6718cb3e665ff41865d7c","src/freebsd/system.rs":"47e397525689f6d3591b27fe933e4bb31d62a118bccc5f5dc3367ba0a0143521","src/freebsd/utils.rs":"a4616be6c12a85369eac4840a2dc485ddfd7dc2ef6a02c2e2ef9099749f4ba69","src/lib.rs":"7185cea7d4fa84fe994b262597ae721eaae7e6701d821f746d985e66323f2016","src/linux/component.rs":"8bc34dd380dd4262e8a0835e67b7ae3dd54b68ee2d919a4384f733cc2af09c59","src/linux/cpu.rs":"abed341d370fff574b5c325b99f6fb33701947f59c44589bb01b97d8ce5c00e9","src/linux/disk.rs":"65c07161c88f4531e46645c1cb667ae1c0a1a78de38578120f8c8b37062b9e3f","src/linux/mod.rs":"3060ebbaf1a0b0f6ae8a380af6e7c304190f4c5384e70bfe3523e898a6af98f7","src/linux/network.rs":"1a57d734f5dc61c7985816e963f9a2721fa8c318a384909711dc443dd2bad74e","src/linux/process.rs":"e910c2c3ca58ff488ec06501ca82569103d273576d207592bf3f4dd65edfc56d","src/linux/system.rs":"d0ac2163100f0e051eeb0dff2a4b40ba799cfb3e1d99e4eeb4dfff2cf98b9ea9","src/linux/utils.rs":"e4d01b869e9b4b96dcbf03b92a8cdb53bbcd751d3aea6ad8ce3451ac5b5b1f04","src/macros.rs":"0c5d348d32efe019568596268d4b5eed44288554bfad54d3c9e9ab29d74706f6","src/sysinfo.h":"2d0a581507f809163d0f7eb2de2b2b23adadc480c94894ec0a790c18cdf5a5b2","src/system.rs":"1f750c5912ce8ccfbe1052281ef4e6aaa0765108e6d2d979a286fa74e2571277","src/traits.rs":"21501c06a6002d2335e030cb3f6c57ede424c18e0bce0a3939f460471962f88f","src/unknown/component.rs":"0c6c36533bffed04de4719c7b664c5f0af99f81a4284923475d48c1248d960ab","src/unknown/cpu.rs":"bd4ff7e3d1ab2da6830e5bbc635187323d67a7062b973034d57b439d0555f9b3","src/unknown/disk.rs":"8c2d2b1bfd55bd67040910b726387f9dded4dc10a1e1b27c5a6f88182d73d4dd","src/unknown/mod.rs":"0f6b02841a32d51aeb6a58fd228fb7b2dbbd6b32679c3033b8a33e6a58e35c16","src/unknown/network.rs":"f1ab8998756a4a4607ad17a0596dea0957b752ea0511556f11c5a3575339477d","src/unknown/process.rs":"db462276bfb50ef8f13cf959e20f46fbd4425816318e5715384f83dce68d6187","src/unknown/system.rs":"ad6ec949c0e02ef4d7145435b12854d38d7b601c71ff474e7d1d830d8c5d0771","src/users.rs":"2b0a629c41bd7873a0c11c3cc639aa5215d8801f2a0db1e45575c0169d29804e","src/utils.rs":"37738900055dd3334f36fac9b8f50b3eef2435fbfacc7dbab05bbcdb86199750","src/windows/component.rs":"e4deb1db32c954ed8d165ff234230d72ad63d6cb210b776cf660dd11200ca23f","src/windows/cpu.rs":"9acd73b7a23c6ae36be4dc4a2b7f6bb10a4b2f9d5589764da1ea8dbfccd9f17b","src/windows/disk.rs":"87e7f638576a3f7fc7a792837187cb6b315d7dbf86d79bbbadd04f552da6b64c","src/windows/mod.rs":"8299250e5a013686dba74324361b4a45c65a2e4282da4133b1b97e094732433f","src/windows/network.rs":"d53eff35b6d153d138082dd5043d8a4475e3be6a6b035e29fe79b1b4269dc364","src/windows/process.rs":"5ff561ab05bfb225ddb34b58b136e07a3a844357027300038a32eca2dfa9e9e5","src/windows/system.rs":"e0ec4ed3f82979f16a1bf3cec4b34ca9606ed703f86cb663fdaeb6ef428a9861","src/windows/tools.rs":"93d375c78843bea9911a4f9dc3c536682269059a1fa03e7d242c59dcdd28b672","src/windows/users.rs":"315d891a94de89b50cb24dbe1d3be7dd636185bafa8dd8d625714fc04a1f17d2","src/windows/utils.rs":"4ae34f6ae6da63df7f2762800d1b6f3eba5ba3d0a7838c9220d0a25b73aa38cb","tests/code_checkers/docs.rs":"d9aaaa2b8ce224319c8812fc127ec4d6e355911724d52ee295b4e53859ea383e","tests/code_checkers/headers.rs":"80d82d0290e5e1f50cffb264018dc975fe30927b16d761db5cf9a71292b51750","tests/code_checkers/mod.rs":"171356a68bd2d9c6d5e22c2344df5902276184fa0edd63a6ebf4e5e82fb48ae0","tests/code_checkers/signals.rs":"72e9a0315a793dce0ed87083380a6bd998ffc70d71c8e4c2782f81d30c29dc9d","tests/code_checkers/utils.rs":"8acd064ce01b64905921719c17db12f9444788073c1095335dcb1454097705fa","tests/cpu.rs":"342ab66e912732d5b0edcd4b4c770546290909a56ca62b84338d560054e7baab","tests/disk_list.rs":"fe586252caa1f65c617744ce754718f1ecc1632644282dec2d98fdd058dfd098","tests/extras.rs":"adce56aa1d9ddcd33d19900e07a3e23df8800a8d55cc2d11c348daf5065aa2ff","tests/network.rs":"71975421c17a594813f0a70f36ea5262979f5e85dafc391efecbef62cd249975","tests/process.rs":"63ac38afb22a6c813343afb50f91a4d44f96177dbbec2f7298a422ea41d3b7c5","tests/send_sync.rs":"c247878d496823abcef531d312acdcc9759fe334641fffe00398e7a65d7caff9","tests/uptime.rs":"9f01e9c747b51587c3e609c98e621494491850016e4e3705faf142627e77ecb8"},"package":"c375d5fd899e32847b8566e10598d6e9f1d9b55ec6de3cdf9e7da4bdc51371bc"}
\ No newline at end of file diff --git a/vendor/sysinfo-0.26.7/ADDING_NEW_PLATFORMS.md b/vendor/sysinfo-0.26.7/ADDING_NEW_PLATFORMS.md new file mode 100644 index 000000000..4d9330a07 --- /dev/null +++ b/vendor/sysinfo-0.26.7/ADDING_NEW_PLATFORMS.md @@ -0,0 +1,8 @@ +To get the `target_os` etc: +``` +rustc --print cfg +``` + +In `src/lib.rs` add the matching based on the `cfg` data. + +Create `src/<new_platform>`. diff --git a/vendor/sysinfo-0.26.7/CHANGELOG.md b/vendor/sysinfo-0.26.7/CHANGELOG.md new file mode 100644 index 000000000..5e3402112 --- /dev/null +++ b/vendor/sysinfo-0.26.7/CHANGELOG.md @@ -0,0 +1,511 @@ +# 0.26.7 + + * Apple: Greatly improve disk retrieval (I recommend reading the pull request first comment for more information here: <https://github.com/GuillaumeGomez/sysinfo/pull/855>). + * Remove build script. + +# 0.26.6 + + * Add `Process::wait`. + * Add "Good pratice" 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/<pid>/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 priviledges. + +# 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<Pid>` 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 removeable 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<usize>`. + * 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`. diff --git a/vendor/sysinfo-0.26.7/Cargo.lock b/vendor/sysinfo-0.26.7/Cargo.lock new file mode 100644 index 000000000..1b08f4cd0 --- /dev/null +++ b/vendor/sysinfo-0.26.7/Cargo.lock @@ -0,0 +1,249 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "crossbeam-channel" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ntapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +dependencies = [ + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sysinfo" +version = "0.26.7" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "tempfile", + "winapi", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[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" diff --git a/vendor/sysinfo-0.26.7/Cargo.toml b/vendor/sysinfo-0.26.7/Cargo.toml new file mode 100644 index 000000000..f8719a4dd --- /dev/null +++ b/vendor/sysinfo-0.26.7/Cargo.toml @@ -0,0 +1,100 @@ +# 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.59" +name = "sysinfo" +version = "0.26.7" +authors = ["Guillaume Gomez <guillaume1.gomez@gmail.com>"] +exclude = ["/test-unknown"] +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" + +[lib] +name = "sysinfo" +crate_type = [ + "rlib", + "cdylib", +] + +[dependencies.cfg-if] +version = "1.0" + +[dependencies.rayon] +version = "^1.5.1" +optional = true + +[features] +apple-app-store = ["apple-sandbox"] +apple-sandbox = [] +c-interface = [] +debug = ["libc/extra_traits"] +default = ["multithread"] +multithread = ["rayon"] +unknown-ci = [] + +[target."cfg(all(target_os = \"linux\", not(target_os = \"android\")))".dev-dependencies.tempfile] +version = "3.2" + +[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.0" + +[target."cfg(not(any(target_os = \"unknown\", target_arch = \"wasm32\")))".dependencies.libc] +version = "^0.2.112" + +[target."cfg(windows)".dependencies.ntapi] +version = "0.4" + +[target."cfg(windows)".dependencies.winapi] +version = "0.3.9" +features = [ + "errhandlingapi", + "fileapi", + "handleapi", + "heapapi", + "ifdef", + "ioapiset", + "minwindef", + "pdh", + "psapi", + "synchapi", + "sysinfoapi", + "winbase", + "winerror", + "winioctl", + "winnt", + "oleauto", + "wbemcli", + "rpcdce", + "combaseapi", + "objidl", + "powerbase", + "netioapi", + "lmcons", + "lmaccess", + "lmapibuf", + "memoryapi", + "ntlsa", + "securitybaseapi", + "shellapi", + "std", +] diff --git a/vendor/sysinfo-0.26.7/LICENSE b/vendor/sysinfo-0.26.7/LICENSE new file mode 100644 index 000000000..04d6c8eaa --- /dev/null +++ b/vendor/sysinfo-0.26.7/LICENSE @@ -0,0 +1,22 @@ +The 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. + diff --git a/vendor/sysinfo-0.26.7/Makefile b/vendor/sysinfo-0.26.7/Makefile new file mode 100644 index 000000000..0dc114ce8 --- /dev/null +++ b/vendor/sysinfo-0.26.7/Makefile @@ -0,0 +1,45 @@ +# +# 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 build --features=c-interface + gcc -o $@ $^ $(CFLAGS) -L$(LDIR) $(LIBS) + +release: $(OBJ) + @echo "Compiling in release mode" + cargo build --features=c-interface --release + 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)/*~ diff --git a/vendor/sysinfo-0.26.7/README.md b/vendor/sysinfo-0.26.7/README.md new file mode 100644 index 000000000..e44a731f4 --- /dev/null +++ b/vendor/sysinfo-0.26.7/README.md @@ -0,0 +1,214 @@ +# sysinfo ![img_github_ci] [![][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 +[`SystemExt::IS_SUPPORTED`] constant. + +The minimum-supported version of `rustc` is **1.59**. + +## Usage + +⚠️ 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::{NetworkExt, NetworksExt, ProcessExt, System, SystemExt}; + +// 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(); + +// We display all disks' information: +println!("=> disks:"); +for disk in sys.disks() { + println!("{:?}", disk); +} + +// Network interfaces name, data received and data transmitted: +println!("=> networks:"); +for (interface_name, data) in sys.networks() { + println!("{}: {}/{} B", interface_name, data.received(), data.transmitted()); +} + +// Components temperature: +println!("=> components:"); +for component in sys.components() { + println!("{:?}", component); +} + +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: {:?}", sys.name()); +println!("System kernel version: {:?}", sys.kernel_version()); +println!("System OS version: {:?}", sys.os_version()); +println!("System host name: {:?}", sys.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()); +} +``` + +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::{CpuExt, System, SystemExt}; + +let mut sys = System::new(); + +loop { + sys.refresh_cpu(); // Refreshing CPU information. + for cpu in sys.cpus() { + print!("{}% ", cpu.cpu_usage()); + } + // Sleeping for 500 ms to let time for the system to run for long + // enough to have useful information. + std::thread::sleep(std::time::Duration::from_millis(500)); +} +``` + +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/release/ ./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_github_ci]: https://github.com/GuillaumeGomez/sysinfo/workflows/CI/badge.svg +[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/ diff --git a/vendor/sysinfo-0.26.7/benches/basic.rs b/vendor/sysinfo-0.26.7/benches/basic.rs new file mode 100644 index 000000000..a9771e740 --- /dev/null +++ b/vendor/sysinfo-0.26.7/benches/basic.rs @@ -0,0 +1,161 @@ +#![feature(test)] + +extern crate test; + +use sysinfo::get_current_pid; +use sysinfo::{DiskExt, SystemExt}; + +#[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_system(b: &mut test::Bencher) { + let mut s = sysinfo::System::new_all(); + + s.refresh_system(); + b.iter(move || { + s.refresh_system(); + }); +} + +#[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 s = sysinfo::System::new_all(); + + let disks = s.disks_mut(); + let disk = &mut disks[0]; + b.iter(move || { + disk.refresh(); + }); +} + +#[bench] +fn bench_refresh_disks(b: &mut test::Bencher) { + let mut s = sysinfo::System::new_all(); + + b.iter(move || { + s.refresh_disks(); + }); +} + +#[bench] +fn bench_refresh_disks_list(b: &mut test::Bencher) { + let mut s = sysinfo::System::new(); + + b.iter(move || { + s.refresh_disks_list(); + }); +} + +#[bench] +fn bench_refresh_networks(b: &mut test::Bencher) { + let mut s = sysinfo::System::new_all(); + + b.iter(move || { + s.refresh_networks(); + }); +} + +#[bench] +fn bench_refresh_networks_list(b: &mut test::Bencher) { + let mut s = sysinfo::System::new(); + + b.iter(move || { + s.refresh_networks_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 s = sysinfo::System::new_all(); + + b.iter(move || { + s.refresh_components(); + }); +} + +#[bench] +fn bench_refresh_components_list(b: &mut test::Bencher) { + let mut s = sysinfo::System::new_all(); + + b.iter(move || { + s.refresh_components_list(); + }); +} + +#[bench] +fn bench_refresh_users_list(b: &mut test::Bencher) { + let mut s = sysinfo::System::new_all(); + + b.iter(move || { + s.refresh_users_list(); + }); +} diff --git a/vendor/sysinfo-0.26.7/examples/simple.c b/vendor/sysinfo-0.26.7/examples/simple.c new file mode 100644 index 000000000..28a23beef --- /dev/null +++ b/vendor/sysinfo-0.26.7/examples/simple.c @@ -0,0 +1,86 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include <pthread.h> +#include "sysinfo.h" + +void print_process(CProcess process) { + RString exe = sysinfo_process_get_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_get_pid(process), + sysinfo_process_get_parent_pid(process), + sysinfo_process_get_cpu_usage(process), + sysinfo_process_get_memory(process), + sysinfo_process_get_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_system(system); + CProcess process = sysinfo_get_process_by_pid(system, getpid()); + printf("\n== Task(s) for current process: ==\n"); + print_process(process); + printf("Got %ld task(s)\n", sysinfo_process_get_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(); + sysinfo_refresh_all(system); + printf("total memory: %ld\n", sysinfo_get_total_memory(system)); + printf("free memory: %ld\n", sysinfo_get_free_memory(system)); + printf("used memory: %ld\n", sysinfo_get_used_memory(system)); + printf("total swap: %ld\n", sysinfo_get_total_swap(system)); + printf("free swap: %ld\n", sysinfo_get_free_swap(system)); + printf("used swap: %ld\n", sysinfo_get_used_swap(system)); + printf("networks received: %ld\n", sysinfo_get_networks_received(system)); + printf("networks transmitted: %ld\n", sysinfo_get_networks_transmitted(system)); + unsigned int len = 0, i = 0; + float *procs = NULL; + sysinfo_get_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_get_processes(system, process_loop, &i)); + check_tasks(system); + // we can now free the CSystem object. + sysinfo_destroy(system); + return 0; +} diff --git a/vendor/sysinfo-0.26.7/examples/simple.rs b/vendor/sysinfo-0.26.7/examples/simple.rs new file mode 100644 index 000000000..926cbca79 --- /dev/null +++ b/vendor/sysinfo-0.26.7/examples/simple.rs @@ -0,0 +1,448 @@ +// 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)] + +extern crate sysinfo; + +use std::io::{self, BufRead, Write}; +use std::str::FromStr; +use sysinfo::Signal::*; +use sysinfo::{ + CpuExt, NetworkExt, NetworksExt, Pid, ProcessExt, Signal, System, SystemExt, UserExt, +}; + +const signals: &[Signal] = &[ + Hangup, + Interrupt, + Quit, + Illegal, + Trap, + Abort, + Bus, + FloatingPointException, + Kill, + User1, + Segv, + User2, + Pipe, + Alarm, + Term, + Child, + Continue, + Stop, + TSTP, + TTIN, + TTOU, + Urgent, + XCPU, + XFSZ, + VirtualAlarm, + Profiling, + Winch, + IO, + Power, + 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_disks : reloads only disks' information" + ); + writeln!( + &mut io::stdout(), + "refresh_users : reloads only users' 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"); + 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) -> bool { + match input.trim() { + "help" => print_help(), + "refresh_disks" => { + writeln!(&mut io::stdout(), "Refreshing disk list..."); + sys.refresh_disks_list(); + writeln!(&mut io::stdout(), "Done."); + } + "refresh_users" => { + writeln!(&mut io::stdout(), "Refreshing user list..."); + sys.refresh_users_list(); + 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 process usage: {}%", + sys.global_cpu_info().cpu_usage() + ); + for proc_ in sys.cpus() { + writeln!(&mut io::stdout(), "{proc_:?}"); + } + } + "memory" => { + writeln!( + &mut io::stdout(), + "total memory: {} KB", + sys.total_memory() / 1_000 + ); + writeln!( + &mut io::stdout(), + "used memory : {} KB", + sys.used_memory() / 1_000 + ); + writeln!( + &mut io::stdout(), + "total swap : {} KB", + sys.total_swap() / 1_000 + ); + writeln!( + &mut io::stdout(), + "used swap : {} 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" => { + writeln!( + &mut io::stdout(), + "{} MHz", + sys.global_cpu_info().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 = sys.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 sys.components() { + writeln!(&mut io::stdout(), "{component:?}"); + } + } + "network" => { + for (interface_name, data) in sys.networks().iter() { + writeln!( + &mut io::stdout(), + "{}:\n input data (new / total): {} / {} B\n output data (new / total): {} / {} B", + interface_name, + 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 sys.disks() { + writeln!(&mut io::stdout(), "{disk:?}"); + } + } + "users" => { + for user in sys.users() { + writeln!(&mut io::stdout(), "{:?}", user.name()); + } + } + "boot_time" => { + writeln!(&mut io::stdout(), "{} seconds", sys.boot_time()); + } + "uptime" => { + let up = sys.uptime(); + let mut uptime = sys.uptime(); + let days = uptime / 86400; + uptime -= days * 86400; + let hours = uptime / 3600; + uptime -= hours * 3600; + let minutes = uptime / 60; + writeln!( + &mut io::stdout(), + "{} days {} hours {} minutes ({} seconds in total)", + days, + hours, + minutes, + up, + ); + } + 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 `{}` couldn't be updated...", + pid + ); + } + } else { + writeln!(&mut io::stdout(), "Invalid [pid] received..."); + } + } else { + writeln!( + &mut io::stdout(), + "\"{}\": Unknown command. Enter 'help' if you want to get the commands' \ + list.", + x + ); + } + } + "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: {}", + sys.name().unwrap_or_else(|| "<unknown>".to_owned()), + sys.kernel_version() + .unwrap_or_else(|| "<unknown>".to_owned()), + sys.os_version().unwrap_or_else(|| "<unknown>".to_owned()), + sys.long_os_version() + .unwrap_or_else(|| "<unknown>".to_owned()), + sys.host_name().unwrap_or_else(|| "<unknown>".to_owned()), + ); + } + e => { + writeln!( + &mut io::stdout(), + "\"{}\": Unknown command. Enter 'help' if you want to get the commands' \ + list.", + e + ); + } + } + false +} + +fn main() { + println!("Getting processes' information..."); + let mut t = System::new_all(); + 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 t); + } +} diff --git a/vendor/sysinfo-0.26.7/md_doc/component.md b/vendor/sysinfo-0.26.7/md_doc/component.md new file mode 100644 index 000000000..618cace2f --- /dev/null +++ b/vendor/sysinfo-0.26.7/md_doc/component.md @@ -0,0 +1,16 @@ +Struct containing a component information (temperature and name for the moment). + +## Linux + +More information can be found at [kernel.org][k]. + +Note: these may not be present on virtual Linux systems, such as **Docker** +or **Windows Subsystem for Linux**. These hosts do not expose this information +and therefore `Component` elements may be missing or not as expected. + +[k]: https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface + +## Windows + +Please note that on Windows, you need to have Administrator priviledges to get this +information. diff --git a/vendor/sysinfo-0.26.7/md_doc/cpu.md b/vendor/sysinfo-0.26.7/md_doc/cpu.md new file mode 100644 index 000000000..6281e0980 --- /dev/null +++ b/vendor/sysinfo-0.26.7/md_doc/cpu.md @@ -0,0 +1 @@ +Struct containing information of a CPU. diff --git a/vendor/sysinfo-0.26.7/md_doc/disk.md b/vendor/sysinfo-0.26.7/md_doc/disk.md new file mode 100644 index 000000000..f94b7c1b8 --- /dev/null +++ b/vendor/sysinfo-0.26.7/md_doc/disk.md @@ -0,0 +1 @@ +Struct containing a disk information. diff --git a/vendor/sysinfo-0.26.7/md_doc/network_data.md b/vendor/sysinfo-0.26.7/md_doc/network_data.md new file mode 100644 index 000000000..070794948 --- /dev/null +++ b/vendor/sysinfo-0.26.7/md_doc/network_data.md @@ -0,0 +1 @@ +Contains network interface information. diff --git a/vendor/sysinfo-0.26.7/md_doc/networks.md b/vendor/sysinfo-0.26.7/md_doc/networks.md new file mode 100644 index 000000000..d8b6cc632 --- /dev/null +++ b/vendor/sysinfo-0.26.7/md_doc/networks.md @@ -0,0 +1,8 @@ +Networks interfaces. + +```no_run +use sysinfo::{NetworksExt, System, SystemExt}; + +let s = System::new_all(); +let networks = s.networks(); +``` diff --git a/vendor/sysinfo-0.26.7/md_doc/pid.md b/vendor/sysinfo-0.26.7/md_doc/pid.md new file mode 100644 index 000000000..d80e1ab9f --- /dev/null +++ b/vendor/sysinfo-0.26.7/md_doc/pid.md @@ -0,0 +1,20 @@ +Process id + +Can be used as an integer type by simple casting. For example: + +``` +use sysinfo::{PidExt, 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`. diff --git a/vendor/sysinfo-0.26.7/md_doc/process.md b/vendor/sysinfo-0.26.7/md_doc/process.md new file mode 100644 index 000000000..0b7f9f7f7 --- /dev/null +++ b/vendor/sysinfo-0.26.7/md_doc/process.md @@ -0,0 +1,10 @@ +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. diff --git a/vendor/sysinfo-0.26.7/md_doc/system.md b/vendor/sysinfo-0.26.7/md_doc/system.md new file mode 100644 index 000000000..faf50b915 --- /dev/null +++ b/vendor/sysinfo-0.26.7/md_doc/system.md @@ -0,0 +1 @@ +Structs containing system's information. diff --git a/vendor/sysinfo-0.26.7/src/apple/app_store/component.rs b/vendor/sysinfo-0.26.7/src/apple/app_store/component.rs new file mode 100644 index 000000000..914fc9406 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/app_store/component.rs @@ -0,0 +1,26 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::ComponentExt; + +#[doc = include_str!("../../../md_doc/component.md")] +pub struct Component {} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + 0.0 + } + + fn max(&self) -> f32 { + 0.0 + } + + fn critical(&self) -> Option<f32> { + None + } + + fn label(&self) -> &str { + "" + } + + fn refresh(&mut self) {} +} diff --git a/vendor/sysinfo-0.26.7/src/apple/app_store/mod.rs b/vendor/sysinfo-0.26.7/src/apple/app_store/mod.rs new file mode 100644 index 000000000..0df24cabf --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/app_store/mod.rs @@ -0,0 +1,4 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub mod component; +pub mod process; diff --git a/vendor/sysinfo-0.26.7/src/apple/app_store/process.rs b/vendor/sysinfo-0.26.7/src/apple/app_store/process.rs new file mode 100644 index 000000000..7c43697a6 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/app_store/process.rs @@ -0,0 +1,84 @@ +// 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, ProcessExt, ProcessStatus, Signal, Uid}; + +#[doc = include_str!("../../../md_doc/process.md")] +pub struct Process; + +impl ProcessExt for Process { + fn kill_with(&self, _signal: Signal) -> Option<bool> { + None + } + + fn name(&self) -> &str { + "" + } + + fn cmd(&self) -> &[String] { + &[] + } + + fn exe(&self) -> &Path { + Path::new("/") + } + + fn pid(&self) -> Pid { + Pid(0) + } + + fn environ(&self) -> &[String] { + &[] + } + + fn cwd(&self) -> &Path { + Path::new("/") + } + + fn root(&self) -> &Path { + Path::new("/") + } + + fn memory(&self) -> u64 { + 0 + } + + fn virtual_memory(&self) -> u64 { + 0 + } + + fn parent(&self) -> Option<Pid> { + None + } + + fn status(&self) -> ProcessStatus { + ProcessStatus::Unknown(0) + } + + fn start_time(&self) -> u64 { + 0 + } + + fn run_time(&self) -> u64 { + 0 + } + + fn cpu_usage(&self) -> f32 { + 0.0 + } + + fn disk_usage(&self) -> DiskUsage { + DiskUsage::default() + } + + fn user_id(&self) -> Option<&Uid> { + None + } + + fn group_id(&self) -> Option<Gid> { + None + } + + fn wait(&self) {} +} diff --git a/vendor/sysinfo-0.26.7/src/apple/component.rs b/vendor/sysinfo-0.26.7/src/apple/component.rs new file mode 100644 index 000000000..7c4196e72 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/component.rs @@ -0,0 +1,3 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub use crate::sys::inner::component::*; diff --git a/vendor/sysinfo-0.26.7/src/apple/cpu.rs b/vendor/sysinfo-0.26.7/src/apple/cpu.rs new file mode 100644 index 000000000..e613bdd2c --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/cpu.rs @@ -0,0 +1,331 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::system::get_sys_value; + +use crate::{CpuExt, CpuRefreshKind}; + +use libc::{c_char, host_processor_info, mach_task_self}; +use std::mem; +use std::ops::Deref; +use std::sync::Arc; + +pub(crate) struct UnsafePtr<T>(*mut T); + +unsafe impl<T> Send for UnsafePtr<T> {} +unsafe impl<T> Sync for UnsafePtr<T> {} + +impl<T> Deref for UnsafePtr<T> { + type Target = *mut T; + + fn deref(&self) -> &*mut T { + &self.0 + } +} + +pub(crate) struct CpuData { + pub cpu_info: UnsafePtr<i32>, + 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::<i32>() 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(); + } + } +} + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + name: String, + cpu_usage: f32, + cpu_data: Arc<CpuData>, + frequency: u64, + vendor_id: String, + brand: String, +} + +impl Cpu { + pub(crate) fn new( + name: String, + cpu_data: Arc<CpuData>, + frequency: u64, + vendor_id: String, + brand: String, + ) -> Cpu { + Cpu { + 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<CpuData>) { + self.cpu_usage = cpu_usage; + self.cpu_data = cpu_data; + } + + pub(crate) fn data(&self) -> Arc<CpuData> { + Arc::clone(&self.cpu_data) + } + + pub(crate) fn set_frequency(&mut self, frequency: u64) { + self.frequency = frequency; + } +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + /// Returns the CPU frequency in MHz. + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + fn brand(&self) -> &str { + &self.brand + } +} + +pub(crate) fn get_cpu_frequency() -> u64 { + let mut speed: u64 = 0; + let mut len = std::mem::size_of::<u64>(); + unsafe { + libc::sysctlbyname( + b"hw.cpufrequency\0".as_ptr() as *const c_char, + &mut speed as *mut _ as _, + &mut len, + std::ptr::null_mut(), + 0, + ); + speed / 1_000_000 + } +} + +#[inline] +fn get_in_use(cpu_info: *mut i32, offset: isize) -> i32 { + unsafe { + *cpu_info.offset(offset + libc::CPU_STATE_USER as isize) + + *cpu_info.offset(offset + libc::CPU_STATE_SYSTEM as isize) + + *cpu_info.offset(offset + libc::CPU_STATE_NICE as isize) + } +} + +#[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_.data().cpu_info.0; + let in_use; + let total; + + // 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); + total = in_use + get_idle(cpu_info, offset); + } else { + in_use = get_in_use(cpu_info, offset) - get_in_use(old_cpu_info, offset); + total = in_use + (get_idle(cpu_info, offset) - get_idle(old_cpu_info, offset)); + } + in_use as f32 / total as f32 * 100. +} + +pub(crate) fn update_cpu_usage<F: FnOnce(Arc<CpuData>, *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.set_cpu_usage(total_cpu_usage); + } +} + +pub(crate) fn init_cpus( + port: libc::mach_port_t, + cpus: &mut Vec<Cpu>, + global_cpu: &mut Cpu, + refresh_kind: CpuRefreshKind, +) { + let mut num_cpu = 0; + let mut mib = [0, 0]; + + let (vendor_id, brand) = get_vendor_id_and_brand(); + let frequency = if refresh_kind.frequency() { + get_cpu_frequency() + } else { + 0 + }; + + unsafe { + if !get_sys_value( + libc::CTL_HW as _, + libc::HW_NCPU as _, + mem::size_of::<u32>(), + &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 p = Cpu::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(&p, cpu_info, offset); + p.set_cpu_usage(cpu_usage); + percentage += p.cpu_usage(); + } + cpus.push(p); + + offset += libc::CPU_STATE_MAX as isize; + } + (percentage, cpus.len()) + }); + + // We didn't set them above to avoid cloning them unnecessarily. + global_cpu.brand = brand; + global_cpu.vendor_id = vendor_id; + global_cpu.frequency = frequency; +} + +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()); + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/disk.rs b/vendor/sysinfo-0.26.7/src/apple/disk.rs new file mode 100644 index 000000000..d866d5bba --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/disk.rs @@ -0,0 +1,395 @@ +// 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::{DiskExt, DiskType}; + +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; +use std::path::{Path, PathBuf}; +use std::ptr; + +#[doc = include_str!("../../md_doc/disk.md")] +pub struct Disk { + pub(crate) type_: DiskType, + pub(crate) name: OsString, + pub(crate) file_system: Vec<u8>, + pub(crate) mount_point: PathBuf, + volume_url: RetainedCFURL, + pub(crate) total_space: u64, + pub(crate) available_space: u64, + pub(crate) is_removable: bool, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + self.type_ + } + + fn name(&self) -> &OsStr { + &self.name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + &self.mount_point + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + 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 => false, + } + } else { + sysinfo_debug!("failed to create volume key list, skipping refresh"); + false + } + } + } +} + +pub(super) unsafe fn get_disks() -> Vec<Disk> { + let raw_disks = { + let count = libc::getfsstat(ptr::null_mut(), 0, libc::MNT_NOWAIT); + if count < 1 { + return Vec::new(); + } + let bufsize = count * std::mem::size_of::<libc::statfs>() 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 Vec::new(); + } + + 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 Vec::new(); + } + }; + + let mut disks = Vec::with_capacity(raw_disks.len()); + 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(), + )); + + disks.extend(new_disk(mount_point, volume_url, c_disk, &prop_dict)) + } + + disks +} + +type RetainedCFArray = CFReleaser<core_foundation_sys::array::__CFArray>; +type RetainedCFDictionary = CFReleaser<core_foundation_sys::dictionary::__CFDictionary>; +type RetainedCFURL = CFReleaser<core_foundation_sys::url::__CFURL>; + +unsafe fn build_requested_properties(properties: &[CFStringRef]) -> Option<RetainedCFArray> { + 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<RetainedCFDictionary> { + 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<T, F: FnOnce(*const c_void) -> Option<T>>( + dict: CFDictionaryRef, + key: DictKey, + callback: F, +) -> Option<T> { + #[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<String> { + 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 { + utils::cstr_to_rust_with_size(v_ptr, Some(len_bytes)) + } + }) +} + +unsafe fn get_bool_value(dict: CFDictionaryRef, key: DictKey) -> Option<bool> { + get_dict_value(dict, key, |v| Some(v as CFBooleanRef == kCFBooleanTrue)) +} + +unsafe fn get_int_value(dict: CFDictionaryRef, key: DictKey) -> Option<i64> { + 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<Disk> { + // IOKit is not available on any but the most recent (16+) iOS and iPadOS versions. + // Due to this, we can't query the medium type. 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(DiskType::Unknown(-1)); + #[cfg(not(target_os = "macos"))] + let type_ = DiskType::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 = IntoIterator::into_iter(c_disk.f_fstypename) + .filter_map(|b| if b != 0 { Some(b as u8) } else { None }) + .collect(); + + Some(Disk { + type_, + name, + file_system, + mount_point, + volume_url, + total_space, + available_space, + is_removable, + }) +} diff --git a/vendor/sysinfo-0.26.7/src/apple/ffi.rs b/vendor/sysinfo-0.26.7/src/apple/ffi.rs new file mode 100644 index 000000000..72822202f --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/ffi.rs @@ -0,0 +1,38 @@ +// 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. +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))] +#[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 +} diff --git a/vendor/sysinfo-0.26.7/src/apple/ios.rs b/vendor/sysinfo-0.26.7/src/apple/ios.rs new file mode 100644 index 000000000..0393c5ec6 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/ios.rs @@ -0,0 +1,5 @@ +// 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; diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/component/arm.rs b/vendor/sysinfo-0.26.7/src/apple/macos/component/arm.rs new file mode 100644 index 000000000..328ffebfa --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/component/arm.rs @@ -0,0 +1,179 @@ +// 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, CFRetain}; +use core_foundation_sys::string::{ + kCFStringEncodingUTF8, CFStringCreateWithBytes, CFStringGetCStringPtr, +}; + +use crate::apple::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::ComponentExt; + +pub(crate) struct Components { + pub inner: Vec<Component>, + client: Option<CFReleaser<__IOHIDEventSystemClient>>, +} + +impl Components { + pub(crate) fn new() -> Self { + Self { + inner: vec![], + client: None, + } + } + + pub(crate) fn refresh(&mut self) { + self.inner.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, + }; + // Without this call, client is freed during the execution of the program. It must be kept! + CFRetain(client.inner() as _); + 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 { + let service = match CFReleaser::new( + CFArrayGetValueAtIndex(services.inner(), i) as *const _ + ) { + Some(s) => s, + None => continue, + }; + + let name = match CFReleaser::new(IOHIDServiceClientCopyProperty( + service.inner(), + key_ref.inner(), + )) { + Some(n) => n, + None => continue, + }; + + let name_ptr = + CFStringGetCStringPtr(name.inner() as *const _, kCFStringEncodingUTF8); + let name_str = CStr::from_ptr(name_ptr).to_string_lossy().to_string(); + + let mut component = Component::new(name_str, None, None, service); + component.refresh(); + + self.inner.push(component); + } + } + } +} + +unsafe impl Send for Components {} +unsafe impl Sync for Components {} + +#[doc = include_str!("../../../../md_doc/component.md")] +pub struct Component { + service: CFReleaser<__IOHIDServiceClient>, + temperature: f32, + label: String, + max: f32, + critical: Option<f32>, +} + +impl Component { + pub(crate) fn new( + label: String, + max: Option<f32>, + critical: Option<f32>, + service: CFReleaser<__IOHIDServiceClient>, + ) -> Self { + Self { + service, + label, + max: max.unwrap_or(0.), + critical, + temperature: 0., + } + } +} + +unsafe impl Send for Component {} +unsafe impl Sync for Component {} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + self.critical + } + + fn label(&self) -> &str { + &self.label + } + + fn refresh(&mut self) { + unsafe { + let event = match CFReleaser::new(IOHIDServiceClientCopyEvent( + self.service.inner() 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; + } + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/component/mod.rs b/vendor/sysinfo-0.26.7/src/apple/macos/component/mod.rs new file mode 100644 index 000000000..50b359e61 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/component/mod.rs @@ -0,0 +1,13 @@ +// 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 use self::x86::*; + +#[cfg(target_arch = "aarch64")] +pub(crate) mod arm; + +#[cfg(target_arch = "aarch64")] +pub use self::arm::*; diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/component/x86.rs b/vendor/sysinfo-0.26.7/src/apple/macos/component/x86.rs new file mode 100644 index 000000000..415f90455 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/component/x86.rs @@ -0,0 +1,326 @@ +// 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::ComponentExt; + +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 `ComponentExt::refresh`. + connection: ffi::io_connect_t, +} + +impl ComponentFFI { + fn new(key: &[i8], connection: ffi::io_connect_t) -> Option<ComponentFFI> { + unsafe { + get_key_size(connection, key) + .ok() + .map(|(input_structure, val)| ComponentFFI { + input_structure, + val, + connection, + }) + } + } + + fn temperature(&self) -> Option<f32> { + 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 Components { + pub inner: Vec<Component>, + connection: Option<IoService>, +} + +impl Components { + pub(crate) fn new() -> Self { + Self { + inner: Vec::with_capacity(2), + connection: IoService::new_connection(), + } + } + + pub(crate) fn refresh(&mut self) { + if let Some(ref connection) = self.connection { + let connection = connection.inner(); + self.inner.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) = + Component::new((*id).to_owned(), None, critical_temp, v, connection) + { + self.inner.push(c); + } + } + } + } +} + +#[doc = include_str!("../../../../md_doc/component.md")] +pub struct Component { + temperature: f32, + max: f32, + critical: Option<f32>, + label: String, + ffi_part: ComponentFFI, +} + +impl Component { + /// Creates a new `Component` with the given information. + pub(crate) fn new( + label: String, + max: Option<f32>, + critical: Option<f32>, + key: &[i8], + connection: ffi::io_connect_t, + ) -> Option<Component> { + let ffi_part = ComponentFFI::new(key, connection)?; + ffi_part.temperature().map(|temperature| Component { + temperature, + label, + max: max.unwrap_or(temperature), + critical, + ffi_part, + }) + } +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + self.critical + } + + fn label(&self) -> &str { + &self.label + } + + 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::KeyData_t>(); + + ffi::IOConnectCallStructMethod( + conn, + index as u32, + input_structure, + mem::size_of::<ffi::KeyData_t>(), + 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::<ffi::KeyData_t>(); + let mut output_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); + let mut val: ffi::Val_t = mem::zeroed::<ffi::Val_t>(); + + 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<ffi::Val_t, i32> { + let mut output_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); + + 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<f32> { + 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<f32> { + 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<Self> { + 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<Self> { + let mut iterator: ffi::io_iterator_t = 0; + + unsafe { + let matching_dictionary = ffi::IOServiceMatching(b"AppleSMC\0".as_ptr() as *const i8); + 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); + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/disk.rs b/vendor/sysinfo-0.26.7/src/apple/macos/disk.rs new file mode 100644 index 000000000..3a4372a2f --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/disk.rs @@ -0,0 +1,126 @@ +// 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::DiskType; + +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<DiskType> { + 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(DiskType::SSD), + _ if medium == ffi::kIOPropertyMediumTypeRotationalKey => Some(DiskType::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(DiskType::HDD); + } + } + } + } + + None +} diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/ffi.rs b/vendor/sysinfo-0.26.7/src/apple/macos/ffi.rs new file mode 100644 index 000000000..0b9c82cfa --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/ffi.rs @@ -0,0 +1,291 @@ +// 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; +#[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: &str = "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"; + +// 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; + + 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; + + pub fn IOBSDNameMatching( + mainPort: mach_port_t, + options: u32, + bsdName: *const c_char, + ) -> CFMutableDictionaryRef; + + // This is deprecated as of macOS 12.0, but Rust doesn't have a good way to only use the replacement on 12+. + pub static kIOMasterPortDefault: mach_port_t; +} + +#[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 core_foundation_sys::dictionary::CFMutableDictionaryRef; + use libc::{c_char, 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 IOServiceMatching(a: *const c_char) -> CFMutableDictionaryRef; + + 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; + + pub const KIO_RETURN_SUCCESS: i32 = 0; +} + +#[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 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::{CFStringCreateWithCString, 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 = [ + CFStringCreateWithCString( + null() as *const _, + HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE.as_ptr() as *const _, + 0, + ), + CFStringCreateWithCString( + null() as *const _, + HID_DEVICE_PROPERTY_PRIMARY_USAGE.as_ptr() as *const _, + 0, + ), + ]; + + 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::*; diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/mod.rs b/vendor/sysinfo-0.26.7/src/apple/macos/mod.rs new file mode 100644 index 000000000..856c5931d --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/mod.rs @@ -0,0 +1,20 @@ +// 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 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; diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/process.rs b/vendor/sysinfo-0.26.7/src/apple/macos/process.rs new file mode 100644 index 000000000..fff9c1f71 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/process.rs @@ -0,0 +1,688 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::ffi::CStr; +use std::mem::{self, MaybeUninit}; +use std::ops::Deref; +use std::path::{Path, PathBuf}; + +use std::borrow::Borrow; + +use libc::{c_int, c_void, kill, size_t}; + +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; + +use crate::sys::process::ThreadStatus; +use crate::sys::system::Wrap; + +#[doc = include_str!("../../../md_doc/process.md")] +pub struct Process { + pub(crate) name: String, + pub(crate) cmd: Vec<String>, + pub(crate) exe: PathBuf, + pid: Pid, + parent: Option<Pid>, + pub(crate) environ: Vec<String>, + cwd: PathBuf, + pub(crate) root: PathBuf, + 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<Uid>, + group_id: Option<Gid>, + 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 status: Option<ThreadStatus>, + pub(crate) old_read_bytes: u64, + pub(crate) old_written_bytes: u64, + pub(crate) read_bytes: u64, + pub(crate) written_bytes: u64, +} + +impl Process { + pub(crate) fn new_empty(pid: Pid, exe: PathBuf, name: String, cwd: PathBuf) -> Process { + Process { + name, + pid, + parent: None, + cmd: Vec::new(), + environ: Vec::new(), + exe, + cwd, + root: PathBuf::new(), + 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, + 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<Pid>, start_time: u64, run_time: u64) -> Process { + Process { + name: String::new(), + pid, + parent, + cmd: Vec::new(), + environ: Vec::new(), + exe: PathBuf::new(), + cwd: PathBuf::new(), + root: PathBuf::new(), + memory: 0, + virtual_memory: 0, + cpu_usage: 0., + old_utime: 0, + old_stime: 0, + updated: true, + start_time, + run_time, + user_id: None, + group_id: None, + process_status: ProcessStatus::Unknown(0), + status: None, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + } + } +} + +impl ProcessExt for Process { + fn kill_with(&self, signal: Signal) -> Option<bool> { + let c_signal = crate::sys::system::convert_signal(signal)?; + unsafe { Some(kill(self.pid.0, c_signal) == 0) } + } + + fn name(&self) -> &str { + &self.name + } + + fn cmd(&self) -> &[String] { + &self.cmd + } + + fn exe(&self) -> &Path { + self.exe.as_path() + } + + fn pid(&self) -> Pid { + self.pid + } + + fn environ(&self) -> &[String] { + &self.environ + } + + fn cwd(&self) -> &Path { + self.cwd.as_path() + } + + fn root(&self) -> &Path { + self.root.as_path() + } + + fn memory(&self) -> u64 { + self.memory + } + + fn virtual_memory(&self) -> u64 { + self.virtual_memory + } + + fn parent(&self) -> Option<Pid> { + self.parent + } + + fn status(&self) -> ProcessStatus { + self.process_status + } + + fn start_time(&self) -> u64 { + self.start_time + } + + fn run_time(&self) -> u64 { + self.run_time + } + + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn disk_usage(&self) -> DiskUsage { + DiskUsage { + read_bytes: self.read_bytes - self.old_read_bytes, + total_read_bytes: self.read_bytes, + written_bytes: self.written_bytes - self.old_written_bytes, + total_written_bytes: self.written_bytes, + } + } + + fn user_id(&self) -> Option<&Uid> { + self.user_id.as_ref() + } + + fn group_id(&self) -> Option<Gid> { + self.group_id + } + + fn wait(&self) { + let mut status = 0; + // attempt waiting + unsafe { + if 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); + } + } + } + } +} + +#[allow(deprecated)] // Because of libc::mach_absolute_time. +pub(crate) fn compute_cpu_usage( + p: &mut Process, + task_info: libc::proc_taskinfo, + system_time: u64, + user_time: u64, + time_interval: Option<f64>, +) { + if let Some(time_interval) = time_interval { + let total_existing_time = p.old_stime.saturating_add(p.old_utime); + if time_interval > 0.000001 && total_existing_time > 0 { + let total_current_time = task_info + .pti_total_system + .saturating_add(task_info.pti_total_user); + + let total_time_diff = total_current_time.saturating_sub(total_existing_time); + if total_time_diff > 0 { + p.cpu_usage = (total_time_diff as f64 / time_interval * 100.) as f32; + } + } else { + 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 + }; + } + } +} + +/*pub fn set_time(p: &mut Process, utime: u64, stime: u64) { + p.old_utime = p.utime; + p.old_stime = p.stime; + p.utime = utime; + p.stime = stime; + p.updated = true; +}*/ + +unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo { + let mut task_info = mem::zeroed::<libc::proc_taskinfo>(); + // 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::<libc::proc_taskinfo>() 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 = libc::__error(); + // If errno is equal to ESCHR, it means the process is dead. + !errno.is_null() && *errno != libc::ESRCH + } +} + +#[inline] +fn do_not_get_env_path(_: &str, _: &mut PathBuf, _: &mut bool) {} + +#[inline] +fn do_get_env_path(env: &str, root: &mut PathBuf, check: &mut bool) { + if *check && env.starts_with("PATH=") { + *check = false; + *root = Path::new(&env[5..]).to_path_buf(); + } +} + +unsafe fn get_bsd_info(pid: Pid) -> Option<libc::proc_bsdinfo> { + let mut info = mem::zeroed::<libc::proc_bsdinfo>(); + + if libc::proc_pidinfo( + pid.0, + libc::PROC_PIDTBSDINFO, + 0, + &mut info as *mut _ as *mut _, + mem::size_of::<libc::proc_bsdinfo>() as _, + ) != mem::size_of::<libc::proc_bsdinfo>() as _ + { + None + } else { + Some(info) + } +} + +unsafe fn create_new_process( + pid: Pid, + mut size: size_t, + now: u64, + refresh_kind: ProcessRefreshKind, + info: Option<libc::proc_bsdinfo>, +) -> Result<Option<Process>, ()> { + let mut vnodepathinfo = mem::zeroed::<libc::proc_vnodepathinfo>(); + let result = libc::proc_pidinfo( + pid.0, + libc::PROC_PIDVNODEPATHINFO, + 0, + &mut vnodepathinfo as *mut _ as *mut _, + mem::size_of::<libc::proc_vnodepathinfo>() as _, + ); + let cwd = if result > 0 { + let buffer = vnodepathinfo.pvi_cdir.vip_path; + let buffer = CStr::from_ptr(buffer.as_ptr() as _); + buffer + .to_str() + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::new()) + } else { + PathBuf::new() + }; + + let info = match info { + Some(info) => info, + None => { + let mut buffer: Vec<u8> = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _); + match libc::proc_pidpath( + 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_unchecked(buffer); + let exe = PathBuf::from(tmp); + let name = exe + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .to_owned(); + return Ok(Some(Process::new_empty(pid, exe, name, cwd))); + } + _ => {} + } + return Err(()); + } + }; + let parent = match info.pbi_ppid as i32 { + 0 => None, + p => Some(Pid(p)), + }; + + let mut proc_args = Vec::with_capacity(size as _); + let ptr: *mut u8 = proc_args.as_mut_slice().as_mut_ptr(); + let mut mib = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid.0 as _]; + /* + * /---------------\ 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 + */ + if libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + ptr as *mut c_void, + &mut size, + std::ptr::null_mut(), + 0, + ) == -1 + { + return Err(()); // not enough rights I assume? + } + let mut n_args: c_int = 0; + libc::memcpy( + (&mut n_args) as *mut c_int as *mut c_void, + ptr as *const c_void, + mem::size_of::<c_int>(), + ); + + let mut cp = ptr.add(mem::size_of::<c_int>()); + let mut start = cp; + + let start_time = info.pbi_start_tvsec; + let run_time = now.saturating_sub(start_time); + + let mut p = if cp < ptr.add(size) { + while cp < ptr.add(size) && *cp != 0 { + cp = cp.offset(1); + } + let exe = Path::new(get_unchecked_str(cp, start).as_str()).to_path_buf(); + let name = exe + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .to_owned(); + while cp < ptr.add(size) && *cp == 0 { + cp = cp.offset(1); + } + start = cp; + let mut c = 0; + let mut cmd = Vec::with_capacity(n_args as usize); + while c < n_args && cp < ptr.add(size) { + if *cp == 0 { + c += 1; + cmd.push(get_unchecked_str(cp, start)); + start = cp.offset(1); + } + cp = cp.offset(1); + } + + #[inline] + unsafe fn get_environ<F: Fn(&str, &mut PathBuf, &mut bool)>( + ptr: *mut u8, + mut cp: *mut u8, + size: size_t, + mut root: PathBuf, + callback: F, + ) -> (Vec<String>, PathBuf) { + let mut environ = Vec::with_capacity(10); + let mut start = cp; + let mut check = true; + while cp < ptr.add(size) { + if *cp == 0 { + if cp == start { + break; + } + let e = get_unchecked_str(cp, start); + callback(&e, &mut root, &mut check); + environ.push(e); + start = cp.offset(1); + } + cp = cp.offset(1); + } + (environ, root) + } + + let (environ, root) = if exe.is_absolute() { + if let Some(parent_path) = exe.parent() { + get_environ( + ptr, + cp, + size, + parent_path.to_path_buf(), + do_not_get_env_path, + ) + } else { + get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) + } + } else { + get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) + }; + let mut p = Process::new(pid, parent, start_time, run_time); + + p.exe = exe; + p.name = name; + p.cwd = cwd; + p.cmd = parse_command_line(&cmd); + p.environ = environ; + p.root = root; + p + } else { + Process::new(pid, parent, start_time, run_time) + }; + + 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_uid)); + p.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(p)) +} + +pub(crate) fn update_process( + wrap: &Wrap, + pid: Pid, + size: size_t, + time_interval: Option<f64>, + now: u64, + refresh_kind: ProcessRefreshKind, + check_if_alive: bool, +) -> Result<Option<Process>, ()> { + unsafe { + if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) { + if p.memory == 0 { + // We don't have access to this process' information. + return if check_if_pid_is_alive(pid, check_if_alive) { + p.updated = true; + Ok(None) + } else { + Err(()) + }; + } + 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, size, now, refresh_kind, Some(info)); + } + } + let task_info = get_task_info(pid); + let mut thread_info = mem::zeroed::<libc::proc_threadinfo>(); + 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::<libc::proc_threadinfo>() 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; + + if refresh_kind.cpu() { + compute_cpu_usage(p, task_info, system_time, user_time, time_interval); + } + + p.memory = task_info.pti_resident_size; + p.virtual_memory = task_info.pti_virtual_size; + if refresh_kind.disk_usage() { + update_proc_disk_activity(p); + } + p.updated = true; + return Ok(None); + } + create_new_process(pid, size, now, refresh_kind, get_bsd_info(pid)) + } +} + +fn update_proc_disk_activity(p: &mut Process) { + p.old_read_bytes = p.read_bytes; + p.old_written_bytes = p.written_bytes; + + let mut pidrusage = MaybeUninit::<libc::rusage_info_v2>::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(unknown_lints)] +#[allow(clippy::uninit_vec)] +pub(crate) fn get_proc_list() -> Option<Vec<Pid>> { + unsafe { + let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); + if count < 1 { + return None; + } + let mut pids: Vec<Pid> = Vec::with_capacity(count as usize); + pids.set_len(count as usize); + let count = count * mem::size_of::<Pid>() 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) + } + } +} + +unsafe fn get_unchecked_str(cp: *mut u8, start: *mut u8) -> String { + let len = cp as usize - start as usize; + let part = Vec::from_raw_parts(start, len, len); + let tmp = String::from_utf8_unchecked(part.clone()); + mem::forget(part); + tmp +} + +fn parse_command_line<T: Deref<Target = str> + Borrow<str>>(cmd: &[T]) -> Vec<String> { + let mut x = 0; + let mut command = Vec::with_capacity(cmd.len()); + while x < cmd.len() { + let mut y = x; + if cmd[y].starts_with('\'') || cmd[y].starts_with('"') { + let c = if cmd[y].starts_with('\'') { '\'' } else { '"' }; + while y < cmd.len() && !cmd[y].ends_with(c) { + y += 1; + } + command.push(cmd[x..y].join(" ")); + x = y; + } else { + command.push(cmd[x].to_owned()); + } + x += 1; + } + command +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_get_path() { + let mut path = PathBuf::new(); + let mut check = true; + + do_get_env_path("PATH=tadam", &mut path, &mut check); + + assert!(!check); + assert_eq!(path, PathBuf::from("tadam")); + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/system.rs b/vendor/sysinfo-0.26.7/src/apple/macos/system.rs new file mode 100644 index 000000000..949532234 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/system.rs @@ -0,0 +1,136 @@ +// 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; + +unsafe fn free_cpu_load_info(cpu_load: &mut processor_cpu_load_info_t) { + if !cpu_load.is_null() { + munmap(*cpu_load as _, vm_page_size); + *cpu_load = null_mut(); + } +} + +pub(crate) struct SystemTimeInfo { + timebase_to_ns: f64, + clock_per_sec: f64, + old_cpu_load: processor_cpu_load_info_t, + old_cpu_count: natural_t, +} + +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<Self> { + 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 mut old_cpu_load = null_mut(); + let old_cpu_count = match Self::update_ticks(port, &mut old_cpu_load) { + Some(c) => c, + 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_load, + old_cpu_count, + }) + } + } + + fn update_ticks( + port: mach_port_t, + cpu_load: &mut processor_cpu_load_info_t, + ) -> Option<natural_t> { + let mut info_size = std::mem::size_of::<processor_cpu_load_info_t>() as _; + let mut cpu_count = 0; + + unsafe { + free_cpu_load_info(cpu_load); + + if host_processor_info( + port, + PROCESSOR_CPU_LOAD_INFO, + &mut cpu_count, + 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(cpu_count) + } + } + } + + pub fn get_time_interval(&mut self, port: mach_port_t) -> f64 { + let mut total = 0; + let mut new_cpu_load = null_mut(); + + let new_cpu_count = match Self::update_ticks(port, &mut new_cpu_load) { + Some(c) => c, + None => return 0., + }; + let cpu_count = std::cmp::min(self.old_cpu_count, new_cpu_count); + unsafe { + for i in 0..cpu_count { + let new_load: &processor_cpu_load_info = &*new_cpu_load.offset(i as _); + let old_load: &processor_cpu_load_info = &*self.old_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 - old; + } + } + } + + free_cpu_load_info(&mut self.old_cpu_load); + self.old_cpu_load = new_cpu_load; + self.old_cpu_count = new_cpu_count; + + // Now we convert the ticks to nanoseconds: + total as f64 / self.timebase_to_ns * self.clock_per_sec / cpu_count as f64 + } + } +} + +impl Drop for SystemTimeInfo { + fn drop(&mut self) { + unsafe { + free_cpu_load_info(&mut self.old_cpu_load); + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/utils.rs b/vendor/sysinfo-0.26.7/src/apple/macos/utils.rs new file mode 100644 index 000000000..ff870db55 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/utils.rs @@ -0,0 +1,30 @@ +// 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<Self> { + 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 _) }; + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/mod.rs b/vendor/sysinfo-0.26.7/src/apple/mod.rs new file mode 100644 index 000000000..fd6fdec2c --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/mod.rs @@ -0,0 +1,32 @@ +// 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 network; +pub mod process; +pub mod system; +pub mod users; +mod utils; + +pub use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo-0.26.7/src/apple/network.rs b/vendor/sysinfo-0.26.7/src/apple/network.rs new file mode 100644 index 000000000..3c4918155 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/network.rs @@ -0,0 +1,240 @@ +// 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::{NetworkExt, NetworksExt, NetworksIter}; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $new_val; + }}; +} + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Self { + Networks { + interfaces: HashMap::new(), + } + } + + #[allow(unknown_lints)] + #[allow(clippy::cast_ptr_alignment)] + #[allow(clippy::uninit_vec)] + fn update_networks(&mut self) { + let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0]; + let 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 mut interface = e.get_mut(); + 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) => { + 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 { + 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, + }); + } + } + } + } + } + } +} + +impl NetworksExt for Networks { + #[allow(clippy::needless_lifetimes)] + fn iter<'a>(&'a self) -> NetworksIter<'a> { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + for (_, data) in self.interfaces.iter_mut() { + data.updated = false; + } + self.update_networks(); + self.interfaces.retain(|_, data| data.updated); + } + + fn refresh(&mut self) { + self.update_networks(); + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +#[derive(PartialEq, Eq)] +pub struct NetworkData { + 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, +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.current_in.saturating_sub(self.old_in) + } + + fn total_received(&self) -> u64 { + self.current_in + } + + fn transmitted(&self) -> u64 { + self.current_out.saturating_sub(self.old_out) + } + + fn total_transmitted(&self) -> u64 { + self.current_out + } + + fn packets_received(&self) -> u64 { + self.packets_in.saturating_sub(self.old_packets_in) + } + + fn total_packets_received(&self) -> u64 { + self.packets_in + } + + fn packets_transmitted(&self) -> u64 { + self.packets_out.saturating_sub(self.old_packets_out) + } + + fn total_packets_transmitted(&self) -> u64 { + self.packets_out + } + + fn errors_on_received(&self) -> u64 { + self.errors_in.saturating_sub(self.old_errors_in) + } + + fn total_errors_on_received(&self) -> u64 { + self.errors_in + } + + fn errors_on_transmitted(&self) -> u64 { + self.errors_out.saturating_sub(self.old_errors_out) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.errors_out + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/process.rs b/vendor/sysinfo-0.26.7/src/apple/process.rs new file mode 100644 index 000000000..e0f005bdc --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/process.rs @@ -0,0 +1,83 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::fmt; + +pub use crate::sys::inner::process::*; +use crate::ProcessStatus; + +#[doc(hidden)] +impl From<u32> for ProcessStatus { + fn from(status: u32) -> ProcessStatus { + match status { + 1 => ProcessStatus::Idle, + 2 => ProcessStatus::Run, + 3 => ProcessStatus::Sleep, + 4 => ProcessStatus::Stop, + 5 => ProcessStatus::Zombie, + x => ProcessStatus::Unknown(x), + } + } +} + +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, Debug, PartialEq, Eq)] +pub 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<i32> for ThreadStatus { + fn from(status: i32) -> ThreadStatus { + match status { + 1 => ThreadStatus::Running, + 2 => ThreadStatus::Stopped, + 3 => ThreadStatus::Waiting, + 4 => ThreadStatus::Uninterruptible, + 5 => ThreadStatus::Halted, + x => ThreadStatus::Unknown(x), + } + } +} + +impl ThreadStatus { + /// Used to display `ThreadStatus`. + pub fn to_string(&self) -> &str { + match *self { + ThreadStatus::Running => "Running", + ThreadStatus::Stopped => "Stopped", + ThreadStatus::Waiting => "Waiting", + ThreadStatus::Uninterruptible => "Uninterruptible", + ThreadStatus::Halted => "Halted", + ThreadStatus::Unknown(_) => "Unknown", + } + } +} + +impl fmt::Display for ThreadStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/system.rs b/vendor/sysinfo-0.26.7/src/apple/system.rs new file mode 100644 index 000000000..12abbd23b --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/system.rs @@ -0,0 +1,691 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::component::Component; +use crate::sys::cpu::*; +use crate::sys::disk::*; +use crate::sys::network::Networks; +use crate::sys::process::*; + +use crate::{ + CpuExt, CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, +}; + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +use crate::ProcessExt; + +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::mem; +use std::sync::Arc; +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +use std::time::SystemTime; + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +use libc::size_t; + +use libc::{ + c_char, c_int, c_void, host_statistics64, mach_port_t, sysconf, sysctl, sysctlbyname, timeval, + vm_statistics64, _SC_PAGESIZE, +}; + +#[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] +use super::inner::component::Components; + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +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, + // 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! { + c_int, + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: HashMap<Pid, Process>, + mem_total: u64, + mem_free: u64, + mem_available: u64, + swap_total: u64, + swap_free: u64, + global_cpu: Cpu, + cpus: Vec<Cpu>, + page_size_kb: u64, + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] + components: Components, + disks: Vec<Disk>, + networks: Networks, + port: mach_port_t, + users: Vec<User>, + boot_time: u64, + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + clock_info: Option<crate::sys::macos::system::SystemTimeInfo>, + got_cpu_frequency: bool, +} + +pub(crate) struct Wrap<'a>(pub UnsafeCell<&'a mut HashMap<Pid, Process>>); + +unsafe impl<'a> Send for Wrap<'a> {} +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::<timeval>(); + 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 SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + fn new_with_specifics(refreshes: RefreshKind) -> System { + unsafe { + let port = libc::mach_host_self(); + + let mut s = System { + process_list: HashMap::with_capacity(200), + mem_total: 0, + mem_free: 0, + mem_available: 0, + swap_total: 0, + swap_free: 0, + global_cpu: Cpu::new( + "0".to_owned(), + Arc::new(CpuData::new(std::ptr::null_mut(), 0)), + 0, + String::new(), + String::new(), + ), + cpus: Vec::new(), + page_size_kb: sysconf(_SC_PAGESIZE) as _, + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] + components: Components::new(), + disks: Vec::with_capacity(1), + networks: Networks::new(), + port, + users: Vec::new(), + boot_time: boot_time(), + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + clock_info: crate::sys::macos::system::SystemTimeInfo::new(port), + got_cpu_frequency: false, + }; + s.refresh_specifics(refreshes); + s + } + } + + fn refresh_memory(&mut self) { + let mut mib = [0, 0]; + + unsafe { + // get system values + // get swap info + let mut xs: libc::xsw_usage = mem::zeroed::<libc::xsw_usage>(); + if get_sys_value( + libc::CTL_VM as _, + libc::VM_SWAPUSAGE as _, + mem::size_of::<libc::xsw_usage>(), + &mut xs as *mut _ as *mut c_void, + &mut mib, + ) { + self.swap_total = xs.xsu_total; + self.swap_free = xs.xsu_avail; + } + // get ram info + if self.mem_total < 1 { + get_sys_value( + libc::CTL_HW as _, + libc::HW_MEMSIZE as _, + mem::size_of::<u64>(), + &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::<vm_statistics64>(); + 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 = self.mem_total.saturating_sub( + u64::from(stat.active_count) + .saturating_add(u64::from(stat.inactive_count)) + .saturating_add(u64::from(stat.wire_count)) + .saturating_add(u64::from(stat.speculative_count)) + .saturating_sub(u64::from(stat.purgeable_count)) + .saturating_mul(self.page_size_kb), + ); + self.mem_free = u64::from(stat.free_count).saturating_mul(self.page_size_kb); + } + } + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn refresh_components_list(&mut self) {} + + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] + fn refresh_components_list(&mut self) { + self.components.refresh(); + } + + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { + let cpus = &mut self.cpus; + if cpus.is_empty() { + init_cpus(self.port, cpus, &mut self.global_cpu, refresh_kind); + self.got_cpu_frequency = refresh_kind.frequency(); + return; + } + if refresh_kind.frequency() && !self.got_cpu_frequency { + let frequency = get_cpu_frequency(); + for proc_ in cpus.iter_mut() { + proc_.set_frequency(frequency); + } + self.got_cpu_frequency = true; + } + if refresh_kind.cpu_usage() { + update_cpu_usage(self.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_.update(cpu_usage, Arc::clone(&proc_data)); + percentage += proc_.cpu_usage(); + + offset += libc::CPU_STATE_MAX as isize; + } + (percentage, cpus.len()) + }); + } + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn refresh_processes_specifics(&mut self, _refresh_kind: ProcessRefreshKind) {} + + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + fn refresh_processes_specifics(&mut self, 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() { + let now = get_now(); + let arg_max = get_arg_max(); + let port = self.port; + let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); + let entries: Vec<Process> = { + let wrap = &Wrap(UnsafeCell::new(&mut self.process_list)); + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + into_iter(pids) + .flat_map(|pid| { + match update_process( + wrap, + pid, + arg_max as size_t, + time_interval, + now, + refresh_kind, + false, + ) { + Ok(x) => x, + _ => None, + } + }) + .collect() + }; + entries.into_iter().for_each(|entry| { + self.process_list.insert(entry.pid(), entry); + }); + self.process_list + .retain(|_, proc_| std::mem::replace(&mut proc_.updated, false)); + } + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn refresh_process_specifics(&mut self, _pid: Pid, _refresh_kind: ProcessRefreshKind) -> bool { + false + } + + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + let now = get_now(); + let arg_max = get_arg_max(); + let port = self.port; + let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); + match { + let wrap = Wrap(UnsafeCell::new(&mut self.process_list)); + update_process( + &wrap, + pid, + arg_max as size_t, + time_interval, + now, + refresh_kind, + true, + ) + } { + Ok(Some(p)) => { + self.process_list.insert(p.pid(), p); + true + } + Ok(_) => true, + Err(_) => false, + } + } + + fn refresh_disks_list(&mut self) { + self.disks = unsafe { get_disks() }; + } + + fn refresh_users_list(&mut self) { + self.users = crate::apple::users::get_users_list(); + } + + // COMMON PART + // + // Need to be moved into a "common" file to avoid duplication. + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.get(&pid) + } + + fn global_cpu_info(&self) -> &Cpu { + &self.global_cpu + } + + fn cpus(&self) -> &[Cpu] { + &self.cpus + } + + fn physical_core_count(&self) -> Option<usize> { + let mut physical_core_count = 0; + + unsafe { + if get_sys_value_by_name( + b"hw.physicalcpu\0", + &mut mem::size_of::<u32>(), + &mut physical_core_count as *mut usize as *mut c_void, + ) { + Some(physical_core_count) + } else { + None + } + } + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + self.mem_free + } + + fn available_memory(&self) -> u64 { + self.mem_available + } + + fn used_memory(&self) -> u64 { + self.mem_total - self.mem_free + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_free + } + + // TODO: need to be checked + fn used_swap(&self) -> u64 { + self.swap_total - self.swap_free + } + + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] + fn components(&self) -> &[Component] { + &self.components.inner + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn components(&self) -> &[Component] { + &[] + } + + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] + fn components_mut(&mut self) -> &mut [Component] { + &mut self.components.inner + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn components_mut(&mut self) -> &mut [Component] { + &mut [] + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks + } + + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + self.disks.sort_unstable_by(compare); + } + + fn uptime(&self) -> u64 { + unsafe { + let csec = libc::time(::std::ptr::null_mut()); + + libc::difftime(csec, self.boot_time as _) as u64 + } + } + + fn load_average(&self) -> 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], + } + } + } + + fn users(&self) -> &[User] { + &self.users + } + + fn boot_time(&self) -> u64 { + self.boot_time + } + + fn name(&self) -> Option<String> { + get_system_info(libc::KERN_OSTYPE, Some("Darwin")) + } + + fn long_os_version(&self) -> Option<String> { + #[cfg(target_os = "macos")] + let friendly_name = match self.os_version().unwrap_or_default() { + 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 + } + + fn host_name(&self) -> Option<String> { + get_system_info(libc::KERN_HOSTNAME, None) + } + + fn kernel_version(&self) -> Option<String> { + get_system_info(libc::KERN_OSRELEASE, None) + } + + fn os_version(&self) -> Option<String> { + 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 + } + } + } + + fn distribution_id(&self) -> String { + std::env::consts::OS.to_owned() + } +} + +impl Default for System { + fn default() -> System { + System::new() + } +} + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +fn get_arg_max() -> usize { + let mut mib = [libc::CTL_KERN, libc::KERN_ARGMAX]; + let mut arg_max = 0i32; + let mut size = mem::size_of::<c_int>(); + unsafe { + if sysctl( + mib.as_mut_ptr(), + mib.len() as _, + (&mut arg_max) as *mut i32 as *mut c_void, + &mut size, + std::ptr::null_mut(), + 0, + ) == -1 + { + 4096 // We default to this value + } else { + arg_max as usize + } + } +} + +pub(crate) unsafe fn get_sys_value( + high: u32, + low: u32, + mut len: usize, + value: *mut c_void, + mib: &mut [i32; 2], +) -> bool { + mib[0] = high as i32; + mib[1] = low as i32; + sysctl( + mib.as_mut_ptr(), + mib.len() as _, + value, + &mut len as *mut usize, + std::ptr::null_mut(), + 0, + ) == 0 +} + +unsafe fn get_sys_value_by_name(name: &[u8], len: &mut usize, value: *mut c_void) -> bool { + sysctlbyname( + name.as_ptr() as *const c_char, + value, + len, + std::ptr::null_mut(), + 0, + ) == 0 +} + +fn get_system_info(value: c_int, default: Option<&str>) -> Option<String> { + 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() + } + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/users.rs b/vendor/sysinfo-0.26.7/src/apple/users.rs new file mode 100644 index 000000000..690aceee1 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/users.rs @@ -0,0 +1,178 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + common::{Gid, Uid}, + User, +}; + +use crate::sys::utils; +use libc::{c_char, endpwent, getgrgid, getgrouplist, getpwent, gid_t, setpwent, strlen}; + +fn get_user_groups(name: *const c_char, group_id: gid_t) -> Vec<String> { + let mut add = 0; + + loop { + let mut nb_groups = 256 + add; + let mut groups = Vec::with_capacity(nb_groups as _); + unsafe { + if getgrouplist(name, group_id as _, groups.as_mut_ptr(), &mut nb_groups) == -1 { + add += 100; + continue; + } + groups.set_len(nb_groups as _); + return groups + .into_iter() + .filter_map(|g| { + let group = getgrgid(g as _); + if group.is_null() { + return None; + } + utils::cstr_to_rust((*group).gr_name) + }) + .collect(); + } + } +} + +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 + } +} + +fn users_list<F>(filter: F) -> Vec<User> +where + F: Fn(*const c_char, u32) -> bool, +{ + let mut users = Vec::new(); + + unsafe { + setpwent(); + loop { + let pw = getpwent(); + if pw.is_null() { + break; + } + + if !filter((*pw).pw_shell, (*pw).pw_uid) { + // This is not a "real" or "local" user. + continue; + } + + let groups = get_user_groups((*pw).pw_name, (*pw).pw_gid); + let uid = (*pw).pw_uid; + let gid = (*pw).pw_gid; + if let Some(name) = utils::cstr_to_rust((*pw).pw_name) { + users.push(User { + uid: Uid(uid), + gid: Gid(gid), + name, + groups, + }); + } + } + endpwent(); + } + users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap()); + users.dedup_by(|a, b| a.name == b.name); + users +} + +pub(crate) fn get_users_list() -> Vec<User> { + users_list(|shell, uid| { + !endswith(shell, b"/false") && !endswith(shell, b"/uucico") && uid < 65536 + }) +} + +// This was the OSX-based solution. It provides enough information, but what a mess! +// pub fn get_users_list() -> Vec<User> { +// 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, +// }; +// let groups = get_user_groups(&name); +// 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<String> { +// 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<u16> = Vec::new(); +// let mut x = 0; +// loop { +// let letter = *ptr.offset(x); +// if letter == 0 { +// break; +// } +// v.push(letter); +// x += 1; +// } +// v +// }.ok() +// } diff --git a/vendor/sysinfo-0.26.7/src/apple/utils.rs b/vendor/sysinfo-0.26.7/src/apple/utils.rs new file mode 100644 index 000000000..408c02c31 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/utils.rs @@ -0,0 +1,71 @@ +// 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_char; +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<T>(NonNull<T>); + +impl<T> CFReleaser<T> { + pub(crate) fn new(ptr: *const T) -> Option<Self> { + // 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<T> Drop for CFReleaser<T> { + 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<T> Send for CFReleaser<T> {} +unsafe impl<T> Sync for CFReleaser<T> {} + +pub(crate) fn cstr_to_rust(c: *const c_char) -> Option<String> { + cstr_to_rust_with_size(c, None) +} + +pub(crate) fn cstr_to_rust_with_size(c: *const c_char, size: Option<usize>) -> Option<String> { + if c.is_null() { + return None; + } + let mut s = match size { + Some(len) => Vec::with_capacity(len), + None => Vec::new(), + }; + let mut i = 0; + unsafe { + loop { + let value = *c.offset(i) as u8; + if value == 0 { + break; + } + s.push(value); + i += 1; + } + String::from_utf8(s).ok() + } +} + +pub(crate) fn vec_to_rust(buf: Vec<i8>) -> Option<String> { + String::from_utf8( + buf.into_iter() + .flat_map(|b| if b > 0 { Some(b as u8) } else { None }) + .collect(), + ) + .ok() +} diff --git a/vendor/sysinfo-0.26.7/src/c_interface.rs b/vendor/sysinfo-0.26.7/src/c_interface.rs new file mode 100644 index 000000000..0914a8135 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/c_interface.rs @@ -0,0 +1,468 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{CpuExt, NetworkExt, NetworksExt, Pid, Process, ProcessExt, System, SystemExt}; +use libc::{self, c_char, c_float, c_uint, c_void, pid_t, size_t}; +use std::borrow::BorrowMut; +use std::ffi::CString; + +/// 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_t, process: CProcess, data: *mut c_void) -> bool; + +/// 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 { + Box::from_raw(system as *mut System); + } +} + +/// Equivalent of [`System::refresh_system()`][crate::System#method.refresh_system]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_system(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_system(); + } + Box::into_raw(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<System> = 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()`][crate::System#method.refresh_cpu]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_cpu(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_cpu(); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::refresh_components()`][crate::System#method.refresh_temperatures]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_components(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_components(); + } + 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<System> = 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<System> = 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]. +#[cfg(target_os = "linux")] +#[no_mangle] +pub extern "C" fn sysinfo_refresh_process(system: CSystem, pid: pid_t) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = 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 [`System::refresh_disks()`][crate::System#method.refresh_disks]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_disks(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_disks(); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::refresh_disks_list()`][crate::System#method.refresh_disks_list]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_disks_list(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_disks_list(); + } + Box::into_raw(system); + } +} + +/// 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<System> = 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<System> = 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<System> = 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<System> = 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<System> = 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<System> = Box::from_raw(system as *mut System); + let ret = system.used_swap() as size_t; + Box::into_raw(system); + ret + } +} + +/// Equivalent of +/// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.received() as size_t)`. +#[no_mangle] +pub extern "C" fn sysinfo_networks_received(system: CSystem) -> size_t { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let ret = system.networks().iter().fold(0, |acc: size_t, (_, data)| { + acc.saturating_add(data.received() as size_t) + }); + Box::into_raw(system); + 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(system: CSystem) -> size_t { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let ret = system.networks().iter().fold(0, |acc: size_t, (_, data)| { + acc.saturating_add(data.transmitted() as size_t) + }); + Box::into_raw(system); + 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<System> = Box::from_raw(system as *mut System); + { + let cpus = system.cpus(); + if (*procs).is_null() { + (*procs) = + libc::malloc(::std::mem::size_of::<c_float>() * 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<ProcessLoop>, + data: *mut c_void, +) -> size_t { + assert!(!system.is_null()); + if let Some(fn_pointer) = fn_pointer { + unsafe { + let system: Box<System> = 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_t) -> CProcess { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = 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! +#[cfg(target_os = "linux")] +#[no_mangle] +pub extern "C" fn sysinfo_process_tasks( + process: CProcess, + fn_pointer: Option<ProcessLoop>, + data: *mut c_void, +) -> size_t { + assert!(!process.is_null()); + if let Some(fn_pointer) = fn_pointer { + unsafe { + let process = process as *const Process; + for (pid, process) in (*process).tasks.iter() { + if !fn_pointer(pid.0, process as *const Process as CProcess, data) { + break; + } + } + (*process).tasks.len() as size_t + } + } else { + 0 + } +} + +/// Equivalent of [`Process::pid()`][crate::Process#method.pid]. +#[no_mangle] +pub extern "C" fn sysinfo_process_pid(process: CProcess) -> pid_t { + 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_t { + 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().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().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().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 _); + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/common.rs b/vendor/sysinfo-0.26.7/src/common.rs new file mode 100644 index 000000000..2710204f5 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/common.rs @@ -0,0 +1,965 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{NetworkData, Networks, NetworksExt, UserExt}; + +use std::convert::From; +use std::fmt; +use std::str::FromStr; + +/// Trait to have a common conversions for the [`Pid`][crate::Pid] type. +/// +/// ``` +/// use sysinfo::{Pid, PidExt}; +/// +/// let p = Pid::from_u32(0); +/// let value: u32 = p.as_u32(); +/// ``` +pub trait PidExt<T>: Copy + From<T> + FromStr + fmt::Display { + /// Allows to convert [`Pid`][crate::Pid] into [`u32`]. + /// + /// ``` + /// use sysinfo::{Pid, PidExt}; + /// + /// let p = Pid::from_u32(0); + /// let value: u32 = p.as_u32(); + /// ``` + fn as_u32(self) -> u32; + /// Allows to convert a [`u32`] into [`Pid`][crate::Pid]. + /// + /// ``` + /// use sysinfo::{Pid, PidExt}; + /// + /// let p = Pid::from_u32(0); + /// ``` + fn from_u32(v: u32) -> Self; +} + +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<$typ> for Pid { + fn from(v: $typ) -> Self { + Self(v) + } + } + impl From<Pid> for $typ { + fn from(v: Pid) -> Self { + v.0 + } + } + impl PidExt<$typ> for Pid { + fn as_u32(self) -> u32 { + self.0 as u32 + } + fn from_u32(v: u32) -> Self { + Self(v as _) + } + } + impl FromStr for Pid { + type Err = <$typ as FromStr>::Err; + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(Self(<$typ>::from_str(s)?)) + } + } + impl fmt::Display for Pid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } + } + }; +} + +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 + } + }; + + ($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 `true`. + +``` +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 `false`. + +``` +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 + } + }; +} + +/// Used to determine what you want to refresh specifically on the [`Process`] 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::{ProcessExt, ProcessRefreshKind, System, SystemExt}; +/// +/// 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, + user: bool, +} + +impl ProcessRefreshKind { + /// Creates a new `ProcessRefreshKind` with every refresh set to `false`. + /// + /// ``` + /// use sysinfo::ProcessRefreshKind; + /// + /// let r = ProcessRefreshKind::new(); + /// + /// assert_eq!(r.cpu(), false); + /// assert_eq!(r.disk_usage(), false); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Creates a new `ProcessRefreshKind` with every refresh set to `true`. + /// + /// ``` + /// use sysinfo::ProcessRefreshKind; + /// + /// let r = ProcessRefreshKind::everything(); + /// + /// assert_eq!(r.cpu(), true); + /// assert_eq!(r.disk_usage(), true); + /// ``` + pub fn everything() -> Self { + Self { + cpu: true, + disk_usage: true, + user: true, + } + } + + 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, + r#"This refresh is about `user_id` and `group_id`. Please note that it has an effect mostly +on Windows as other platforms get this information alongside the Process information directly."#, + ); +} + +/// 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::{CpuExt, CpuRefreshKind, System, SystemExt}; +/// +/// 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); + /// ``` + 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); + /// ``` + 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 what you want to refresh specifically on the [`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, SystemExt}; +/// +/// // We want everything except disks. +/// let mut system = System::new_with_specifics(RefreshKind::everything().without_disks_list()); +/// +/// assert_eq!(system.disks().len(), 0); +/// # if System::IS_SUPPORTED && !cfg!(feature = "apple-sandbox") { +/// assert!(system.processes().len() > 0); +/// # } +/// ``` +/// +/// [`System`]: crate::System +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct RefreshKind { + networks: bool, + networks_list: bool, + processes: Option<ProcessRefreshKind>, + disks_list: bool, + disks: bool, + memory: bool, + cpu: Option<CpuRefreshKind>, + components: bool, + components_list: bool, + users_list: bool, +} + +impl RefreshKind { + /// Creates a new `RefreshKind` with every refresh set to `false`/`None`. + /// + /// ``` + /// use sysinfo::RefreshKind; + /// + /// let r = RefreshKind::new(); + /// + /// assert_eq!(r.networks(), false); + /// assert_eq!(r.networks_list(), false); + /// assert_eq!(r.processes().is_some(), false); + /// assert_eq!(r.disks_list(), false); + /// assert_eq!(r.disks(), false); + /// assert_eq!(r.memory(), false); + /// assert_eq!(r.cpu().is_some(), false); + /// assert_eq!(r.components(), false); + /// assert_eq!(r.components_list(), false); + /// assert_eq!(r.users_list(), 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.networks(), true); + /// assert_eq!(r.networks_list(), true); + /// assert_eq!(r.processes().is_some(), true); + /// assert_eq!(r.disks_list(), true); + /// assert_eq!(r.disks(), true); + /// assert_eq!(r.memory(), true); + /// assert_eq!(r.cpu().is_some(), true); + /// assert_eq!(r.components(), true); + /// assert_eq!(r.components_list(), true); + /// assert_eq!(r.users_list(), true); + /// ``` + pub fn everything() -> Self { + Self { + networks: true, + networks_list: true, + processes: Some(ProcessRefreshKind::everything()), + disks: true, + disks_list: true, + memory: true, + cpu: Some(CpuRefreshKind::everything()), + components: true, + components_list: true, + users_list: true, + } + } + + impl_get_set!( + RefreshKind, + processes, + with_processes, + without_processes, + ProcessRefreshKind + ); + impl_get_set!(RefreshKind, networks, with_networks, without_networks); + impl_get_set!( + RefreshKind, + networks_list, + with_networks_list, + without_networks_list + ); + impl_get_set!(RefreshKind, disks, with_disks, without_disks); + impl_get_set!(RefreshKind, disks_list, with_disks_list, without_disks_list); + impl_get_set!(RefreshKind, memory, with_memory, without_memory); + impl_get_set!(RefreshKind, cpu, with_cpu, without_cpu, CpuRefreshKind); + impl_get_set!(RefreshKind, components, with_components, without_components); + impl_get_set!( + RefreshKind, + components_list, + with_components_list, + without_components_list + ); + impl_get_set!(RefreshKind, users_list, with_users_list, without_users_list); +} + +/// Iterator over network interfaces. +/// +/// It is returned by [`Networks::iter`][crate::Networks#method.iter]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt, NetworksExt}; +/// +/// let system = System::new_all(); +/// let networks_iter = system.networks().iter(); +/// ``` +pub struct NetworksIter<'a> { + inner: std::collections::hash_map::Iter<'a, String, NetworkData>, +} + +impl<'a> NetworksIter<'a> { + pub(crate) fn new(v: std::collections::hash_map::Iter<'a, String, NetworkData>) -> Self { + NetworksIter { inner: v } + } +} + +impl<'a> Iterator for NetworksIter<'a> { + type Item = (&'a String, &'a NetworkData); + + fn next(&mut self) -> Option<Self::Item> { + self.inner.next() + } +} + +impl<'a> IntoIterator for &'a Networks { + type Item = (&'a String, &'a NetworkData); + type IntoIter = NetworksIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// Enum containing the different supported disks types. +/// +/// This type is returned by [`Disk::get_type`][crate::Disk#method.type]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt, DiskExt}; +/// +/// let system = System::new_all(); +/// for disk in system.disks() { +/// println!("{:?}: {:?}", disk.name(), disk.type_()); +/// } +/// ``` +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum DiskType { + /// HDD type. + HDD, + /// SSD type. + SSD, + /// Unknown type. + Unknown(isize), +} + +/// 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 +/// [`SystemExt::SUPPORTED_SIGNALS`][crate::SystemExt::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) + } +} + +/// A struct representing system load average value. +/// +/// It is returned by [`SystemExt::load_average`][crate::SystemExt::load_average]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt}; +/// +/// let s = System::new_all(); +/// let load_avg = s.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) => { + $(#[$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 + } + } + }; +} + +macro_rules! uid { + ($(#[$outer:meta])+ $type:ty) => { + xid!($(#[$outer])+ Uid, $type); + }; +} + +macro_rules! gid { + ($(#[$outer:meta])+ $type:ty) => { + xid!($(#[$outer])+ #[derive(Copy)] Gid, $type); + }; +} + +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!( + /// A user id wrapping a platform specific type. + libc::uid_t + ); + gid!( + /// A group id wrapping a platform specific type. + libc::gid_t + ); + } else if #[cfg(windows)] { + uid!( + /// A user id wrapping a platform specific type. + Box<str> + ); + gid!( + /// A group id wrapping a platform specific type. + u32 + ); + } else { + uid!( + /// A user id wrapping a platform specific type. + u32 + ); + gid!( + /// A group id wrapping a platform specific type. + u32 + ); + + } +} + +/// Type containing user information. +/// +/// It is returned by [`SystemExt::users`][crate::SystemExt::users]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt}; +/// +/// let s = System::new_all(); +/// println!("users: {:?}", s.users()); +/// ``` +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct User { + pub(crate) uid: Uid, + pub(crate) gid: Gid, + pub(crate) name: String, + pub(crate) groups: Vec<String>, +} + +impl UserExt for User { + fn id(&self) -> &Uid { + &self.uid + } + + fn group_id(&self) -> Gid { + self.gid + } + + fn name(&self) -> &str { + &self.name + } + + fn groups(&self) -> &[String] { + &self.groups + } +} + +/// Type containing read and written bytes. +/// +/// It is returned by [`ProcessExt::disk_usage`][crate::ProcessExt::disk_usage]. +/// +/// ```no_run +/// use sysinfo::{ProcessExt, System, SystemExt}; +/// +/// 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/FreeBSD + /// + /// Waiting in uninterruptible disk sleep. + /// + /// ## macOs + /// + /// Process being created by fork. + /// + /// ## Other OS + /// + /// Not available. + Idle, + /// Running. + Run, + /// ## Linux/FreeBSD + /// + /// Sleeping in an interruptible waiting. + /// + /// ## macOS + /// + /// Sleeping on an address. + /// + /// ## Other OS + /// + /// Not available. + Sleep, + /// ## Linux/FreeBSD + /// + /// Stopped (on a signal) or (before Linux 2.6.33) trace stopped. + /// + /// ## macOS + /// + /// 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/FreeBSD + /// + /// Dead/uninterruptible sleep (usually IO). + /// + /// ## 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). + /// + /// ## Other OS + /// + /// Not available. + Parked, + /// ## FreeBSD + /// + /// Blocked on a lock. + /// + /// ## Other OS + /// + /// Not available. + LockBlocked, + /// Unknown. + Unknown(u32), +} + +/// 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) => { +/// eprintln!("failed to get current pid: {}", e); +/// } +/// } +/// ``` +#[allow(clippy::unnecessary_wraps)] +pub fn get_current_pid() -> Result<Pid, &'static str> { + cfg_if::cfg_if! { + if #[cfg(feature = "unknown-ci")] { + fn inner() -> Result<Pid, &'static str> { + 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<Pid, &'static str> { + unsafe { Ok(Pid(libc::getpid())) } + } + } else if #[cfg(windows)] { + fn inner() -> Result<Pid, &'static str> { + use winapi::um::processthreadsapi::GetCurrentProcessId; + + unsafe { Ok(Pid(GetCurrentProcessId() as _)) } + } + } else { + fn inner() -> Result<Pid, &'static str> { + Err("Unknown platform") + } + } + } + inner() +} + +#[cfg(test)] +mod tests { + use super::ProcessStatus; + + // This test only exists to ensure that the `Display` trait is implemented on the + // `ProcessStatus` enum on all targets. + #[test] + fn check_display_impl_process_status() { + println!("{} {:?}", ProcessStatus::Parked, ProcessStatus::Idle); + } +} diff --git a/vendor/sysinfo-0.26.7/src/debug.rs b/vendor/sysinfo-0.26.7/src/debug.rs new file mode 100644 index 000000000..ef460eb49 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/debug.rs @@ -0,0 +1,132 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + Component, ComponentExt, Cpu, CpuExt, Disk, DiskExt, NetworkData, NetworkExt, Networks, + NetworksExt, Process, ProcessExt, System, SystemExt, +}; + +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 network interfaces", &self.networks().iter().count()) + .field("nb processes", &self.processes().len()) + .field("nb disks", &self.disks().len()) + .field("nb components", &self.components().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() + .iter() + .map(|c| *c as char) + .collect::<Vec<_>>(), + self.type_(), + 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()) + .finish() + } +} + +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::<Vec<_>>() + .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() + } +} diff --git a/vendor/sysinfo-0.26.7/src/freebsd/component.rs b/vendor/sysinfo-0.26.7/src/freebsd/component.rs new file mode 100644 index 000000000..c4e84fe63 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/freebsd/component.rs @@ -0,0 +1,69 @@ +// 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::ComponentExt; + +#[doc = include_str!("../../md_doc/component.md")] +pub struct Component { + id: Vec<u8>, + label: String, + temperature: f32, + max: f32, +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + None + } + + fn label(&self) -> &str { + &self.label + } + + 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<f32> { + 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 unsafe fn get_components(nb_cpus: usize) -> Vec<Component> { + // For now, we only have temperature for CPUs... + let mut components = Vec::with_capacity(nb_cpus); + + for core in 0..nb_cpus { + let id = format!("dev.cpu.{core}.temperature\0").as_bytes().to_vec(); + if let Some(temperature) = refresh_component(&id) { + components.push(Component { + id, + label: format!("CPU {}", core + 1), + temperature, + max: temperature, + }); + } + } + components +} diff --git a/vendor/sysinfo-0.26.7/src/freebsd/cpu.rs b/vendor/sysinfo-0.26.7/src/freebsd/cpu.rs new file mode 100644 index 000000000..9d6c8e506 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/freebsd/cpu.rs @@ -0,0 +1,44 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::CpuExt; + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + pub(crate) cpu_usage: f32, + name: String, + pub(crate) vendor_id: String, + pub(crate) frequency: u64, +} + +impl Cpu { + pub(crate) fn new(name: String, vendor_id: String, frequency: u64) -> Cpu { + Cpu { + cpu_usage: 0., + name, + vendor_id, + frequency, + } + } +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + fn brand(&self) -> &str { + "" + } +} diff --git a/vendor/sysinfo-0.26.7/src/freebsd/disk.rs b/vendor/sysinfo-0.26.7/src/freebsd/disk.rs new file mode 100644 index 000000000..5156f1215 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/freebsd/disk.rs @@ -0,0 +1,143 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskExt, DiskType}; + +use std::ffi::{OsStr, OsString}; +use std::path::{Path, PathBuf}; + +use super::utils::c_buf_to_str; + +#[doc = include_str!("../../md_doc/disk.md")] +pub struct Disk { + name: OsString, + c_mount_point: Vec<libc::c_char>, + mount_point: PathBuf, + total_space: u64, + available_space: u64, + file_system: Vec<u8>, + is_removable: bool, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + DiskType::Unknown(-1) + } + + fn name(&self) -> &OsStr { + &self.name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + &self.mount_point + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + fn refresh(&mut self) -> bool { + unsafe { + let mut vfs: libc::statvfs = std::mem::zeroed(); + refresh_disk(self, &mut vfs) + } + } +} + +// 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 Disk, 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_disks() -> Vec<Disk> { + 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 Vec::new(); + } + let mut vfs: libc::statvfs = std::mem::zeroed(); + let fs_infos: &[libc::statfs] = std::slice::from_raw_parts(fs_infos as _, count as _); + let mut disks = Vec::new(); + + 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: &[libc::c_char] = + if let Some(pos) = fs_info.f_fstypename.iter().position(|x| *x == 0) { + &fs_info.f_fstypename[..pos] + } else { + &fs_info.f_fstypename + }; + let fs_type: &[u8] = std::slice::from_raw_parts(fs_type.as_ptr() as _, fs_type.len()); + 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 _; + + disks.push(Disk { + 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: fs_type.to_vec(), + is_removable, + }); + } + disks +} diff --git a/vendor/sysinfo-0.26.7/src/freebsd/mod.rs b/vendor/sysinfo-0.26.7/src/freebsd/mod.rs new file mode 100644 index 000000000..8a8726812 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/freebsd/mod.rs @@ -0,0 +1,16 @@ +// 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 use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo-0.26.7/src/freebsd/network.rs b/vendor/sysinfo-0.26.7/src/freebsd/network.rs new file mode 100644 index 000000000..e58ad823a --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/freebsd/network.rs @@ -0,0 +1,199 @@ +// 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::{NetworkExt, NetworksExt, NetworksIter}; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $data:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $data.$name; + }}; +} + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Networks { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + fn iter(&self) -> NetworksIter { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + unsafe { + self.refresh_interfaces(true); + } + // Remove interfaces which are gone. + self.interfaces.retain(|_, n| n.updated); + } + + fn refresh(&mut self) { + unsafe { + self.refresh_interfaces(false); + } + } +} + +impl Networks { + 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.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 mut interface = e.get_mut(); + + 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 { + 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, + }); + } + } + } + } + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +pub struct NetworkData { + /// 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, +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.ifi_ibytes.saturating_sub(self.old_ifi_ibytes) + } + + fn total_received(&self) -> u64 { + self.ifi_ibytes + } + + fn transmitted(&self) -> u64 { + self.ifi_obytes.saturating_sub(self.old_ifi_obytes) + } + + fn total_transmitted(&self) -> u64 { + self.ifi_obytes + } + + fn packets_received(&self) -> u64 { + self.ifi_ipackets.saturating_sub(self.old_ifi_ipackets) + } + + fn total_packets_received(&self) -> u64 { + self.ifi_ipackets + } + + fn packets_transmitted(&self) -> u64 { + self.ifi_opackets.saturating_sub(self.old_ifi_opackets) + } + + fn total_packets_transmitted(&self) -> u64 { + self.ifi_opackets + } + + fn errors_on_received(&self) -> u64 { + self.ifi_ierrors.saturating_sub(self.old_ifi_ierrors) + } + + fn total_errors_on_received(&self) -> u64 { + self.ifi_ierrors + } + + fn errors_on_transmitted(&self) -> u64 { + self.ifi_oerrors.saturating_sub(self.old_ifi_oerrors) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.ifi_oerrors + } +} diff --git a/vendor/sysinfo-0.26.7/src/freebsd/process.rs b/vendor/sysinfo-0.26.7/src/freebsd/process.rs new file mode 100644 index 000000000..b0dda0f76 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/freebsd/process.rs @@ -0,0 +1,275 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskUsage, Gid, Pid, ProcessExt, 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<libc::c_char> 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", + }) + } +} + +#[doc = include_str!("../../md_doc/process.md")] +pub struct Process { + pub(crate) name: String, + pub(crate) cmd: Vec<String>, + pub(crate) exe: PathBuf, + pub(crate) pid: Pid, + parent: Option<Pid>, + pub(crate) environ: Vec<String>, + pub(crate) cwd: PathBuf, + pub(crate) root: PathBuf, + 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, + group_id: Gid, + read_bytes: u64, + old_read_bytes: u64, + written_bytes: u64, + old_written_bytes: u64, +} + +impl ProcessExt for Process { + fn kill_with(&self, signal: Signal) -> Option<bool> { + let c_signal = super::system::convert_signal(signal)?; + unsafe { Some(libc::kill(self.pid.0, c_signal) == 0) } + } + + fn name(&self) -> &str { + &self.name + } + + fn cmd(&self) -> &[String] { + &self.cmd + } + + fn exe(&self) -> &Path { + self.exe.as_path() + } + + fn pid(&self) -> Pid { + self.pid + } + + fn environ(&self) -> &[String] { + &self.environ + } + + fn cwd(&self) -> &Path { + self.cwd.as_path() + } + + fn root(&self) -> &Path { + self.root.as_path() + } + + fn memory(&self) -> u64 { + self.memory + } + + fn virtual_memory(&self) -> u64 { + self.virtual_memory + } + + fn parent(&self) -> Option<Pid> { + self.parent + } + + fn status(&self) -> ProcessStatus { + self.status + } + + fn start_time(&self) -> u64 { + self.start_time + } + + fn run_time(&self) -> u64 { + self.run_time + } + + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + 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, + } + } + + fn user_id(&self) -> Option<&Uid> { + Some(&self.user_id) + } + + fn group_id(&self) -> Option<Gid> { + Some(self.group_id) + } + + fn wait(&self) { + let mut status = 0; + // attempt waiting + unsafe { + if 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) unsafe fn get_process_data( + kproc: &libc::kinfo_proc, + wrap: &WrapMap, + page_size: isize, + fscale: f32, + now: u64, + refresh_kind: ProcessRefreshKind, +) -> Result<Option<Process>, ()> { + 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 = kproc.ki_size as _; + let memory = (kproc.ki_rssize as u64).saturating_mul(page_size as _); + // 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)) { + 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; + 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! + let mut buffer = [0; libc::PATH_MAX as usize + 1]; + + let exe = get_sys_value_str( + &[ + libc::CTL_KERN, + libc::KERN_PROC, + libc::KERN_PROC_PATHNAME, + kproc.ki_pid, + ], + &mut buffer, + ) + .unwrap_or_default(); + // 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 { + pid: Pid(kproc.ki_pid), + parent, + user_id: Uid(kproc.ki_ruid), + group_id: Gid(kproc.ki_rgid), + start_time, + run_time: now.saturating_sub(start_time), + cpu_usage, + virtual_memory, + memory, + // procstat_getfiles + cwd: PathBuf::new(), + exe: exe.into(), + // 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: PathBuf::new(), + // 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, + })) +} diff --git a/vendor/sysinfo-0.26.7/src/freebsd/system.rs b/vendor/sysinfo-0.26.7/src/freebsd/system.rs new file mode 100644 index 000000000..12298bbcf --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/freebsd/system.rs @@ -0,0 +1,808 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + sys::{component::Component, Cpu, Disk, Networks, Process}, + CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, +}; + +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 super::utils::{ + self, boot_time, c_buf_to_string, from_cstr_array, get_frequency_for_cpu, get_sys_value, + get_sys_value_array, get_sys_value_by_name, get_sys_value_str_by_name, get_system_info, + init_mib, +}; + +use libc::c_int; + +declare_signals! { + c_int, + Signal::Hangup => libc::SIGHUP, + Signal::Interrupt => libc::SIGINT, + Signal::Quit => libc::SIGQUIT, + Signal::Illegal => libc::SIGILL, + Signal::Trap => libc::SIGTRAP, + Signal::Abort => libc::SIGABRT, + Signal::IOT => libc::SIGIOT, + Signal::Bus => libc::SIGBUS, + Signal::FloatingPointException => libc::SIGFPE, + Signal::Kill => libc::SIGKILL, + Signal::User1 => libc::SIGUSR1, + Signal::Segv => libc::SIGSEGV, + Signal::User2 => libc::SIGUSR2, + Signal::Pipe => libc::SIGPIPE, + Signal::Alarm => libc::SIGALRM, + Signal::Term => libc::SIGTERM, + Signal::Child => libc::SIGCHLD, + Signal::Continue => libc::SIGCONT, + Signal::Stop => libc::SIGSTOP, + Signal::TSTP => libc::SIGTSTP, + Signal::TTIN => libc::SIGTTIN, + Signal::TTOU => libc::SIGTTOU, + Signal::Urgent => libc::SIGURG, + Signal::XCPU => libc::SIGXCPU, + Signal::XFSZ => libc::SIGXFSZ, + Signal::VirtualAlarm => libc::SIGVTALRM, + Signal::Profiling => libc::SIGPROF, + Signal::Winch => libc::SIGWINCH, + Signal::IO => libc::SIGIO, + Signal::Sys => libc::SIGSYS, + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: HashMap<Pid, Process>, + mem_total: u64, + mem_free: u64, + mem_used: u64, + swap_total: u64, + swap_used: u64, + global_cpu: Cpu, + cpus: Vec<Cpu>, + components: Vec<Component>, + disks: Vec<Disk>, + networks: Networks, + users: Vec<User>, + boot_time: u64, + system_info: SystemInfo, + got_cpu_frequency: bool, +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + fn new_with_specifics(refreshes: RefreshKind) -> System { + let system_info = SystemInfo::new(); + + let mut s = System { + process_list: HashMap::with_capacity(200), + mem_total: 0, + mem_free: 0, + mem_used: 0, + swap_total: 0, + swap_used: 0, + global_cpu: Cpu::new(String::new(), String::new(), 0), + cpus: Vec::with_capacity(system_info.nb_cpus as _), + components: Vec::with_capacity(2), + disks: Vec::with_capacity(1), + networks: Networks::new(), + users: Vec::new(), + boot_time: boot_time(), + system_info, + got_cpu_frequency: false, + }; + s.refresh_specifics(refreshes); + s + } + + fn refresh_memory(&mut self) { + 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(); + let (swap_used, swap_total) = self.system_info.get_swap_info(); + self.swap_total = swap_total; + self.swap_used = swap_used; + } + + fn refresh_cpu_specifics(&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(|| "<unknown>".to_owned()); + for pos in 0..self.system_info.nb_cpus { + if refresh_kind.frequency() { + unsafe { + frequency = get_frequency_for_cpu(pos); + } + } + self.cpus + .push(Cpu::new(format!("cpu {pos}"), vendor_id.clone(), frequency)); + } + self.global_cpu.vendor_id = vendor_id; + 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_.frequency = get_frequency_for_cpu(pos as _); + } + } + self.got_cpu_frequency = true; + } + if refresh_kind.cpu_usage() { + self.system_info + .get_cpu_usage(&mut self.global_cpu, &mut self.cpus); + } + } + + fn refresh_components_list(&mut self) { + if self.cpus.is_empty() { + self.refresh_cpu(); + } + self.components = unsafe { super::component::get_components(self.cpus.len()) }; + } + + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { + unsafe { self.refresh_procs(refresh_kind) } + } + + 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(proc_)) => { + self.add_missing_proc_info(self.system_info.kd.as_ptr(), kproc, proc_); + true + } + Ok(None) => true, + Err(_) => false, + } + } + } + + fn refresh_disks_list(&mut self) { + self.disks = unsafe { super::disk::get_all_disks() }; + } + + fn refresh_users_list(&mut self) { + self.users = crate::users::get_users_list(); + } + + // COMMON PART + // + // Need to be moved into a "common" file to avoid duplication. + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.get(&pid) + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn global_cpu_info(&self) -> &Cpu { + &self.global_cpu + } + + fn cpus(&self) -> &[Cpu] { + &self.cpus + } + + fn physical_core_count(&self) -> Option<usize> { + 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 + } + } + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + self.mem_free + } + + fn available_memory(&self) -> u64 { + self.mem_free + } + + fn used_memory(&self) -> u64 { + self.mem_used + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_total - self.swap_used + } + + // TODO: need to be checked + fn used_swap(&self) -> u64 { + self.swap_used + } + + fn components(&self) -> &[Component] { + &self.components + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut self.components + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks + } + + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + self.disks.sort_unstable_by(compare); + } + + fn uptime(&self) -> u64 { + unsafe { + let csec = libc::time(::std::ptr::null_mut()); + + libc::difftime(csec, self.boot_time as _) as u64 + } + } + + fn boot_time(&self) -> u64 { + self.boot_time + } + + fn load_average(&self) -> 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], + } + } + } + + fn users(&self) -> &[User] { + &self.users + } + + fn name(&self) -> Option<String> { + self.system_info.get_os_name() + } + + fn long_os_version(&self) -> Option<String> { + self.system_info.get_os_release_long() + } + + fn host_name(&self) -> Option<String> { + self.system_info.get_hostname() + } + + fn kernel_version(&self) -> Option<String> { + self.system_info.get_kernel_version() + } + + fn os_version(&self) -> Option<String> { + self.system_info.get_os_release() + } + + fn distribution_id(&self) -> String { + std::env::consts::OS.to_owned() + } +} + +impl Default for System { + fn default() -> Self { + Self::new() + } +} + +impl System { + unsafe fn refresh_procs(&mut self, refresh_kind: ProcessRefreshKind) { + let kd = self.system_info.kd.as_ptr(); + let procs = { + 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; + } + #[cfg(feature = "multithread")] + use rayon::iter::{ParallelIterator, ParallelIterator as IterTrait}; + #[cfg(not(feature = "multithread"))] + use std::iter::Iterator as IterTrait; + + 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)); + let procs: &mut [utils::KInfoProc] = + std::slice::from_raw_parts_mut(procs as _, count as _); + + IterTrait::filter_map(crate::utils::into_iter(procs), |kproc| { + super::process::get_process_data( + kproc, + &proc_list, + page_size, + fscale, + now, + refresh_kind, + ) + .ok() + .and_then(|p| p.map(|p| (kproc, p))) + }) + .collect::<Vec<_>>() + }; + + // We remove all processes that don't exist anymore. + self.process_list + .retain(|_, v| std::mem::replace(&mut v.updated, false)); + + for (kproc, proc_) in procs { + self.add_missing_proc_info(kd, kproc, proc_); + } + } + + unsafe fn add_missing_proc_info( + &mut self, + kd: *mut libc::kvm_t, + kproc: &libc::kinfo_proc, + mut proc_: Process, + ) { + proc_.cmd = from_cstr_array(libc::kvm_getargv(kd, kproc, 0) as _); + self.system_info.get_proc_missing_info(kproc, &mut proc_); + if !proc_.cmd.is_empty() { + // First, we try to retrieve the name from the command line. + let p = Path::new(&proc_.cmd[0]); + if let Some(name) = p.file_name().and_then(|s| s.to_str()) { + proc_.name = name.to_owned(); + } + if proc_.root.as_os_str().is_empty() { + if let Some(parent) = p.parent() { + proc_.root = parent.to_path_buf(); + } + } + } + if proc_.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_.name = c_buf_to_string(&kproc.ki_comm).unwrap_or_default(); + } + proc_.environ = from_cstr_array(libc::kvm_getenvv(kd, kproc, 0) as _); + self.process_list.insert(proc_.pid, proc_); + } +} + +#[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<u64> { + 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], + os_type: [c_int; 2], + os_release: [c_int; 2], + kern_version: [c_int; 2], + hostname: [c_int; 2], + buf_space: [c_int; 2], + nb_cpus: c_int, + kd: NonNull<libc::kvm_t>, + // For these two fields, we could use `kvm_getcptime` but the function isn't very efficient... + mib_cp_time: [c_int; 2], + mib_cp_times: [c_int; 2], + // For the global CPU usage. + cp_time: utils::VecSwitcher<libc::c_ulong>, + // For each CPU usage. + cp_times: utils::VecSwitcher<libc::c_ulong>, + /// 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 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; + } + } + + 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(), + os_type: Default::default(), + os_release: Default::default(), + kern_version: Default::default(), + hostname: Default::default(), + nb_cpus, + kd, + mib_cp_time: Default::default(), + mib_cp_times: Default::default(), + cp_time: utils::VecSwitcher::new(vec![0; libc::CPUSTATES as usize]), + cp_times: utils::VecSwitcher::new(vec![ + 0; + nb_cpus as usize * libc::CPUSTATES as usize + ]), + 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); + + init_mib(b"kern.ostype\0", &mut si.os_type); + init_mib(b"kern.osrelease\0", &mut si.os_release); + init_mib(b"kern.version\0", &mut si.kern_version); + init_mib(b"kern.hostname\0", &mut si.hostname); + + init_mib(b"kern.cp_time\0", &mut si.mib_cp_time); + init_mib(b"kern.cp_times\0", &mut si.mib_cp_times); + + si + } + } + + fn get_os_name(&self) -> Option<String> { + get_system_info(&[self.os_type[0], self.os_type[1]], Some("FreeBSD")) + } + + fn get_kernel_version(&self) -> Option<String> { + get_system_info(&[self.kern_version[0], self.kern_version[1]], None) + } + + fn get_os_release_long(&self) -> Option<String> { + get_system_info(&[self.os_release[0], self.os_release[1]], None) + } + + fn get_os_release(&self) -> Option<String> { + // It returns something like "13.0-RELEASE". We want to keep everything until the "-". + get_system_info(&[self.os_release[0], self.os_release[1]], None) + .and_then(|s| s.split('-').next().map(|s| s.to_owned())) + } + + fn get_hostname(&self) -> Option<String> { + get_system_info(&[self.hostname[0], self.hostname[1]], Some("")) + } + + /// 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 -= 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 _)) + } + } + + fn get_cpu_usage(&mut self, global: &mut Cpu, cpus: &mut [Cpu]) { + 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: &[libc::c_ulong], old_cp_time: &[libc::c_ulong]) { + let mut total_new: u64 = 0; + let mut total_old: u64 = 0; + let mut cp_diff: libc::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 += new_cp_time[i] - old_cp_time[i]; + } + let mut tmp: u64 = new_cp_time[i] as _; + total_new += tmp; + tmp = old_cp_time[i] as _; + total_old += tmp; + } + + let total_diff = total_new - total_old; + if total_diff < 1 { + proc_.cpu_usage = 0.; + } else { + proc_.cpu_usage = cp_diff as f32 / total_diff as f32 * 100.; + } + } + + fill_cpu(global, 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 cpus.iter_mut().enumerate() { + let index = pos * libc::CPUSTATES as usize; + + fill_cpu(proc_, &new_cp_times[index..], &old_cp_times[index..]); + } + } + + #[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 Process) { + if self.procstat.is_null() { + self.procstat = libc::procstat_open_sysctl(); + } + if self.procstat.is_null() { + 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; + let mut done = 0; + while !entry.is_null() && done < 2 { + { + let tmp = &*entry; + if tmp.fs_uflags & libc::PS_FST_UFLAG_CDIR != 0 { + if !tmp.fs_path.is_null() { + if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { + proc_.cwd = PathBuf::from(p); + done += 1; + } + } + } else if tmp.fs_uflags & libc::PS_FST_UFLAG_RDIR != 0 { + if !tmp.fs_path.is_null() { + if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { + proc_.root = 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); + } + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/freebsd/utils.rs b/vendor/sysinfo-0.26.7/src/freebsd/utils.rs new file mode 100644 index 000000000..5745a37f6 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/freebsd/utils.rs @@ -0,0 +1,296 @@ +// 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<T> { + v1: Vec<T>, + v2: Vec<T>, + first: bool, +} + +impl<T: Clone> VecSwitcher<T> { + pub fn new(v1: Vec<T>) -> 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::<timeval>(); + 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<T: Sized>(mib: &[c_int], value: &mut T) -> bool { + let mut len = mem::size_of::<T>() 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<T: Sized>(mib: &[c_int], value: &mut [T]) -> bool { + let mut len = (mem::size_of::<T>() * value.len()) 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<String> { + 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<String> { + let mut len = (mem::size_of::<libc::c_char>() * buf.len()) 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::<libc::c_char>()]) +} + +pub(crate) unsafe fn get_sys_value_by_name<T: Sized>(name: &[u8], value: &mut T) -> bool { + let mut len = mem::size_of::<T>() 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<String> { + 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<libc::c_char> = 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<String> { + 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<libc::c_char> = 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<String> { + 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<Pid, Process>>); + +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 + } +} + +pub(crate) unsafe fn get_frequency_for_cpu(cpu_nb: c_int) -> u64 { + let mut frequency = 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 _ +} diff --git a/vendor/sysinfo-0.26.7/src/lib.rs b/vendor/sysinfo-0.26.7/src/lib.rs new file mode 100644 index 000000000..0800d2562 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/lib.rs @@ -0,0 +1,491 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#![doc = include_str!("../README.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)] +#![allow(unknown_lints)] + +#[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 unknown as sys; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 0; + } else if #[cfg(any(target_os = "macos", target_os = "ios"))] { + mod apple; + use apple as sys; + extern crate core_foundation_sys; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 1; + } else if #[cfg(windows)] { + mod windows; + use windows as sys; + extern crate winapi; + extern crate ntapi; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 1; + } else if #[cfg(any(target_os = "linux", target_os = "android"))] { + mod linux; + use linux as sys; + pub(crate) mod users; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 1; + } else if #[cfg(target_os = "freebsd")] { + mod freebsd; + use freebsd as sys; + pub(crate) mod users; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 1; + } else { + mod unknown; + use unknown as sys; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 0; + } +} + +pub use common::{ + get_current_pid, CpuRefreshKind, DiskType, DiskUsage, Gid, LoadAvg, NetworksIter, Pid, PidExt, + ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, Uid, User, +}; +pub use sys::{Component, Cpu, Disk, NetworkData, Networks, Process, System}; +pub use traits::{ + ComponentExt, CpuExt, DiskExt, NetworkExt, NetworksExt, ProcessExt, SystemExt, UserExt, +}; + +#[cfg(feature = "c-interface")] +pub use c_interface::*; + +#[cfg(feature = "c-interface")] +mod c_interface; +mod common; +mod debug; +mod system; +mod traits; +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, SystemExt, 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")))] + { + if _new_limit < 0 { + _new_limit = 0; + } + let max = sys::system::get_max_nb_fds(); + if _new_limit > max { + _new_limit = max; + } + unsafe { + if let Ok(ref mut x) = sys::system::REMAINING_FILES.lock() { + // 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. + let diff = max.saturating_sub(**x); + **x = _new_limit.saturating_sub(diff); + true + } else { + false + } + } + } 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, SystemExt}; + /// + /// 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, SystemExt}; + /// + /// 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, SystemExt}; + /// + /// let s = System::new(); + /// ``` + /// + /// And now we check if it fails when we try to clone it: + /// + /// ```compile_fail + /// use sysinfo::{Process, System, SystemExt}; + /// + /// 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!(!System::IS_SUPPORTED); + } + + #[test] + fn check_process_memory_usage() { + let mut s = System::new(); + s.refresh_all(); + + if System::IS_SUPPORTED { + // 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_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 System::IS_SUPPORTED { + 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 !System::IS_SUPPORTED { + 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(std::time::Duration::from_millis(100)); + 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 !System::IS_SUPPORTED { + return; + } + let mut s = System::new(); + for _ in 0..10 { + s.refresh_cpu(); + // Wait a bit to update CPU usage values + std::thread::sleep(std::time::Duration::from_millis(100)); + if s.cpus().iter().any(|c| c.cpu_usage() > 0.0) { + // All good! + return; + } + } + panic!("CPU usage is always zero..."); + } + + #[test] + fn check_users() { + let mut s = System::new(); + assert!(s.users().is_empty()); + s.refresh_users_list(); + assert!(s.users().len() >= MIN_USERS); + + let mut s = System::new(); + assert!(s.users().is_empty()); + s.refresh_all(); + assert!(s.users().is_empty()); + + let s = System::new_all(); + assert!(s.users().len() >= MIN_USERS); + } + + #[test] + fn check_uid_gid() { + let mut s = System::new(); + assert!(s.users().is_empty()); + s.refresh_users_list(); + let users = s.users(); + assert!(users.len() >= MIN_USERS); + + if System::IS_SUPPORTED { + #[cfg(not(target_os = "windows"))] + { + let user = users + .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!(users.iter().filter(|u| **u.id() > 0).count() > 0); + } + + // And now check that our `get_user_by_id` method works. + s.refresh_processes(); + assert!(s + .processes() + .iter() + .filter_map(|(_, p)| p.user_id()) + .any(|uid| s.get_user_by_id(uid).is_some())); + } + } + + #[test] + fn check_system_info() { + let s = System::new(); + + // We don't want to test on unsupported systems. + if System::IS_SUPPORTED { + assert!(!s.name().expect("Failed to get system name").is_empty()); + + assert!(!s + .kernel_version() + .expect("Failed to get kernel version") + .is_empty()); + + assert!(!s.os_version().expect("Failed to get os version").is_empty()); + + assert!(!s + .long_os_version() + .expect("Failed to get long OS version") + .is_empty()); + } + + assert!(!s.distribution_id().is_empty()); + } + + #[test] + fn check_host_name() { + // We don't want to test on unsupported systems. + if System::IS_SUPPORTED { + let s = System::new(); + assert!(s.host_name().is_some()); + } + } + + #[test] + fn check_refresh_process_return_value() { + // We don't want to test on unsupported systems. + if System::IS_SUPPORTED { + 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!(System::IS_SUPPORTED); + } else { + assert!(!System::IS_SUPPORTED); + } + } + + #[test] + fn check_cpus_number() { + let mut s = System::new(); + + // This information isn't retrieved by default. + assert!(s.cpus().is_empty()); + if System::IS_SUPPORTED { + // 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(); + // 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] + fn check_nb_supported_signals() { + if System::IS_SUPPORTED { + assert!( + !System::SUPPORTED_SIGNALS.is_empty(), + "SUPPORTED_SIGNALS shoudn't be empty on supported systems!" + ); + } else { + assert!( + System::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 !System::IS_SUPPORTED { + return; + } + let mut s = System::new(); + s.refresh_processes(); + for proc_ in s.cpus() { + assert_eq!(proc_.frequency(), 0); + } + s.refresh_cpu(); + 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 !System::IS_SUPPORTED { + 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() + ); + } + + // We ensure that the `Process` cmd information is retrieved as expected. + #[test] + fn check_cmd_line() { + if !System::IS_SUPPORTED { + return; + } + let mut sys = System::new(); + sys.refresh_processes_specifics(ProcessRefreshKind::new()); + + assert!(sys + .processes() + .iter() + .any(|(_, process)| !process.cmd().is_empty())); + } +} diff --git a/vendor/sysinfo-0.26.7/src/linux/component.rs b/vendor/sysinfo-0.26.7/src/linux/component.rs new file mode 100644 index 000000000..7815103b4 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/linux/component.rs @@ -0,0 +1,352 @@ +// 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::ComponentExt; + +use std::collections::HashMap; +use std::fs::{read_dir, File}; +use std::io::Read; +use std::path::{Path, PathBuf}; + +#[doc = include_str!("../../md_doc/component.md")] +#[derive(Default)] +pub struct Component { + /// Optional associated device of a `Component`. + device_model: Option<String>, + /// 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<f32>, + /// Maximum value computed by sysinfo + max: Option<f32>, + /// Max threshold provided by the chip/kernel + /// - Read in:`temp[1-*]_max` + /// - Unit: read as millidegree Celsius converted to Celsius. + threshold_max: Option<f32>, + /// Min threshold provided by the chip/kernel. + /// - Read in:`temp[1-*]_min` + /// - Unit: read as millidegree Celsius converted to Celsius. + threshold_min: Option<f32>, + /// 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<f32>, + /// 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<TermalSensorType>, + /// Component Label + /// + /// For formating 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<PathBuf>, + /// `temp[1-*]_highest file` to read if disponnible highest value. + highest_file: Option<PathBuf>, +} + +// Read arbitrary data from sysfs. +fn get_file_line(file: &Path, capacity: usize) -> Option<String> { + 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<N>(file: &Path) -> Option<N> +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<f32> { + 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<i32>) -> Option<f32> { + temp.map(|n| (n as f32) / 1000f32) +} + +/// Information about thermal sensor. It may be unavailable as it's +/// kernel module and chip dependant. +enum TermalSensorType { + /// 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. + Unknown(u8), +} + +impl From<u8> for TermalSensorType { + 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 Component, item: &str, folder: &Path, file: &str) { + let hwmon_file = folder.join(file); + match item { + "type" => { + component.sensor_type = + read_number_from_file::<u8>(&hwmon_file).map(TermalSensorType::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 Component { + /// 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: hightest historic value in `tempN_hightest`. + /// - 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<Component>, folder: &Path) -> Option<()> { + let dir = read_dir(folder).ok()?; + let mut matchings: HashMap<u32, Component> = HashMap::with_capacity(10); + for entry in dir.flatten() { + let entry = entry.path(); + let filename = entry.file_name().and_then(|x| x.to_str()).unwrap_or(""); + if entry.is_dir() || !filename.starts_with("temp") { + continue; + } + + let (id, item) = filename.split_once('_')?; + let id = id.get(4..)?.parse::<u32>().ok()?; + + let component = matchings.entry(id).or_insert_with(Component::default); + 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.label = c.format_label("temp", id); + c + }) + // Remove components without `tempN_input` file termal. `Component` doesn't support this kind of sensors yet + .filter(|c| c.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 Component { + 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}"), + } + } +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature.unwrap_or(f32::NAN) + } + + fn max(&self) -> f32 { + self.max.unwrap_or(f32::NAN) + } + + fn critical(&self) -> Option<f32> { + self.threshold_critical + } + + fn label(&self) -> &str { + &self.label + } + + 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) fn get_components() -> Vec<Component> { + let mut components = Vec::with_capacity(10); + if let Ok(dir) = read_dir(Path::new("/sys/class/hwmon/")) { + for entry in dir.flatten() { + let entry = entry.path(); + if !entry.is_dir() + || !entry + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .starts_with("hwmon") + { + continue; + } + Component::from_hwmon(&mut components, &entry); + } + components.sort_by(|c1, c2| c1.label.to_lowercase().cmp(&c2.label.to_lowercase())); + } + components +} diff --git a/vendor/sysinfo-0.26.7/src/linux/cpu.rs b/vendor/sysinfo-0.26.7/src/linux/cpu.rs new file mode 100644 index 000000000..103f5362a --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/linux/cpu.rs @@ -0,0 +1,553 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#![allow(clippy::too_many_arguments)] + +use std::collections::HashSet; +use std::fs::File; +use std::io::{BufRead, BufReader, Read}; + +use crate::sys::utils::to_u64; +use crate::{CpuExt, 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<Cpu>, + /// 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, +} + +impl CpusWrapper { + pub(crate) fn new() -> Self { + Self { + global_cpu: Cpu::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, + } + } + + 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); + } + } + + pub(crate) fn refresh(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) { + 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 first = self.cpus.is_empty(); + let mut it = buf.split(b'\n'); + let (vendor_id, brand) = if first { + get_vendor_id_and_brand() + } else { + (String::new(), String::new()) + }; + + 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.name = to_str!(parts.next().unwrap_or(&[])).to_owned(); + } else { + parts.next(); + } + self.global_cpu.set( + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + ); + } + if first || !only_update_global_cpu { + while let Some(Ok(line)) = it.next() { + if &line[..3] != b"cpu" { + break; + } + + let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); + if first { + self.cpus.push(Cpu::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.clone(), + brand.clone(), + )); + } else { + parts.next(); // we don't want the name again + self.cpus[i].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<Cpu>) -> std::slice::IterMut<'a, Cpu> { + val.iter_mut() + } + + // `get_cpu_frequency` is very slow, so better run it in parallel. + self.global_cpu.frequency = iter_mut(&mut self.cpus) + .enumerate() + .map(|(pos, proc_)| { + proc_.frequency = get_cpu_frequency(pos); + proc_.frequency + }) + .max() + .unwrap_or(0); + + self.got_cpu_frequency = true; + } + + if first { + self.global_cpu.vendor_id = vendor_id; + self.global_cpu.brand = brand; + } + } + + pub(crate) fn get_global_raw_times(&self) -> (u64, u64) { + (self.global_cpu.total_time, self.global_cpu.old_total_time) + } + + pub(crate) fn len(&self) -> usize { + self.cpus.len() + } + + pub(crate) fn is_empty(&self) -> bool { + self.cpus.is_empty() + } + + 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)] +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, + } + } + + /// Creates a new instance of `CpuValues` with everything set to the corresponding argument. + pub fn new_with_values( + user: u64, + nice: u64, + system: u64, + idle: u64, + iowait: u64, + irq: u64, + softirq: u64, + steal: u64, + guest: u64, + guest_nice: u64, + ) -> CpuValues { + CpuValues { + user, + nice, + system, + idle, + iowait, + irq, + softirq, + steal, + _guest: guest, + _guest_nice: guest_nice, + } + } + + /*pub fn is_zero(&self) -> bool { + self.user == 0 && self.nice == 0 && self.system == 0 && self.idle == 0 && + self.iowait == 0 && self.irq == 0 && self.softirq == 0 && self.steal == 0 && + self.guest == 0 && self.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, + ) { + self.user = user; + self.nice = 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) + .saturating_add(self.steal) + } + + /// Returns total time. + pub fn total_time(&self) -> u64 { + // `guest` is already included in `user` + // `guest_nice` is already included in `nice` + self.work_time() + .saturating_add(self.idle) + .saturating_add(self.iowait) + } +} + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + 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 Cpu { + 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, + ) -> Cpu { + Cpu { + name: name.to_owned(), + old_values: CpuValues::new(), + new_values: CpuValues::new_with_values( + user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, + ), + 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) => { + if $a > $b { + ($a - $b) as f32 + } else { + 1. + } + }; + } + 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()) + / min!(self.total_time, self.old_total_time) + * 100.; + if self.cpu_usage > 100. { + self.cpu_usage = 100.; // to prevent the percentage to go above 100% + } + } +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + /// Returns the CPU frequency in MHz. + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + 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{}/cpufreq/scaling_cur_freq", + cpu_core_index + )) + .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::<u64>() { + 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::<f64>().ok()) + .map(|speed| speed as u64) + .unwrap_or_default() +} + +#[allow(unused_assignments)] +pub(crate) fn get_physical_core_count() -> Option<usize> { + 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<String> = 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()) +} + +/// 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() -> (String, String) { + let mut s = String::new(); + if File::open("/proc/cpuinfo") + .and_then(|mut f| f.read_to_string(&mut s)) + .is_err() + { + return (String::new(), String::new()); + } + + fn get_value(s: &str) -> String { + s.split(':') + .last() + .map(|x| x.trim().to_owned()) + .unwrap_or_default() + } + + let mut vendor_id = None; + let mut brand = None; + + for it in s.split('\n') { + if it.starts_with("vendor_id\t") { + vendor_id = Some(get_value(it)); + } else if it.starts_with("model name\t") { + brand = Some(get_value(it)); + } else { + continue; + } + if brand.is_some() && vendor_id.is_some() { + break; + } + } + (vendor_id.unwrap_or_default(), brand.unwrap_or_default()) +} diff --git a/vendor/sysinfo-0.26.7/src/linux/disk.rs b/vendor/sysinfo-0.26.7/src/linux/disk.rs new file mode 100644 index 000000000..6d7fc083c --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/linux/disk.rs @@ -0,0 +1,294 @@ +// 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::{DiskExt, DiskType}; + +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) + }; +} + +#[doc = include_str!("../../md_doc/disk.md")] +#[derive(PartialEq, Eq)] +pub struct Disk { + type_: DiskType, + device_name: OsString, + file_system: Vec<u8>, + mount_point: PathBuf, + total_space: u64, + available_space: u64, + is_removable: bool, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + self.type_ + } + + fn name(&self) -> &OsStr { + &self.device_name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + &self.mount_point + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + fn refresh(&mut self) -> bool { + unsafe { + let mut stat: statvfs = mem::zeroed(); + let mount_point_cpath = to_cpath(&self.mount_point); + if 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 + } + } + } +} + +fn new_disk( + device_name: &OsStr, + mount_point: &Path, + file_system: &[u8], + removable_entries: &[PathBuf], +) -> Option<Disk> { + 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 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 { + 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) -> DiskType { + // 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) => DiskType::HDD, + // The disk is marked as non-rotational so it's very likely a SSD. + Some(0) => DiskType::SSD, + // Normally it shouldn't happen but welcome to the wonderful world of IT! :D + Some(x) => DiskType::Unknown(x), + // The information isn't available... + None => DiskType::Unknown(-1), + } +} + +fn get_all_disks_inner(content: &str) -> Vec<Disk> { + // The goal of this array is to list all removable devices (the ones whose name starts with + // "usb-"). Then we check if + 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<PathBuf>>(), + _ => Vec::new(), + }; + + content + .lines() + .map(|line| { + let line = line.trim_start(); + // mounts format + // http://man7.org/linux/man-pages/man5/fstab.5.html + // fs_spec<tab>fs_file<tab>fs_vfstype<tab>other 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 = matches!( + *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 + "tmpfs" | + "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 + ); + + !(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_bytes(), + &removable_entries, + ) + }) + .collect() +} + +pub(crate) fn get_all_disks() -> Vec<Disk> { + get_all_disks_inner(&get_all_data("/proc/mounts", 16_385).unwrap_or_default()) +} + +// #[test] +// fn check_all_disks() { +// 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, +// } +// ); +// } diff --git a/vendor/sysinfo-0.26.7/src/linux/mod.rs b/vendor/sysinfo-0.26.7/src/linux/mod.rs new file mode 100644 index 000000000..9cff98907 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/linux/mod.rs @@ -0,0 +1,16 @@ +// 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 use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo-0.26.7/src/linux/network.rs b/vendor/sysinfo-0.26.7/src/linux/network.rs new file mode 100644 index 000000000..c8da2bcb3 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/linux/network.rs @@ -0,0 +1,314 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::fs::File; +use std::io::Read; +use std::path::Path; + +use crate::{NetworkExt, NetworksExt, NetworksIter}; +use std::collections::{hash_map, HashMap}; + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, 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<P: AsRef<Path>>(parent: P, path: &str, data: &mut Vec<u8>) -> 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 +} + +impl Networks { + pub(crate) fn new() -> Self { + Networks { + interfaces: HashMap::new(), + } + } +} + +fn refresh_networks_list_from_sysfs( + interfaces: &mut HashMap<String, NetworkData>, + 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.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 mut interface = e.get_mut(); + 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 { + 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, + // 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.updated); + } +} + +impl NetworksExt for Networks { + fn iter(&self) -> NetworksIter { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh(&mut self) { + let mut v = vec![0; 30]; + + for (interface_name, data) in self.interfaces.iter_mut() { + data.update(interface_name, &mut v); + } + } + + fn refresh_networks_list(&mut self) { + refresh_networks_list_from_sysfs(&mut self.interfaces, Path::new("/sys/class/net/")); + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +pub struct NetworkData { + /// 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, + // /// 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 NetworkData { + fn update(&mut self, path: &str, data: &mut Vec<u8>) { + 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) + // ); + } +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.rx_bytes.saturating_sub(self.old_rx_bytes) + } + + fn total_received(&self) -> u64 { + self.rx_bytes + } + + fn transmitted(&self) -> u64 { + self.tx_bytes.saturating_sub(self.old_tx_bytes) + } + + fn total_transmitted(&self) -> u64 { + self.tx_bytes + } + + fn packets_received(&self) -> u64 { + self.rx_packets.saturating_sub(self.old_rx_packets) + } + + fn total_packets_received(&self) -> u64 { + self.rx_packets + } + + fn packets_transmitted(&self) -> u64 { + self.tx_packets.saturating_sub(self.old_tx_packets) + } + + fn total_packets_transmitted(&self) -> u64 { + self.tx_packets + } + + fn errors_on_received(&self) -> u64 { + self.rx_errors.saturating_sub(self.old_rx_errors) + } + + fn total_errors_on_received(&self) -> u64 { + self.rx_errors + } + + fn errors_on_transmitted(&self) -> u64 { + self.tx_errors.saturating_sub(self.old_tx_errors) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.tx_errors + } +} + +#[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::<Vec<_>>(), ["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<String> = 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<String> = 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::<Vec<_>>(), ["itf2"]); + } +} diff --git a/vendor/sysinfo-0.26.7/src/linux/process.rs b/vendor/sysinfo-0.26.7/src/linux/process.rs new file mode 100644 index 000000000..d7d61b5bc --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/linux/process.rs @@ -0,0 +1,711 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::fmt; +use std::fs::{self, File}; +use std::io::Read; +use std::mem::MaybeUninit; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +use libc::{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::utils::into_iter; +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; + +#[doc(hidden)] +impl From<u32> for ProcessStatus { + fn from(status: u32) -> ProcessStatus { + match status { + 1 => ProcessStatus::Idle, + 2 => ProcessStatus::Run, + 3 => ProcessStatus::Sleep, + 4 => ProcessStatus::Stop, + 5 => ProcessStatus::Zombie, + x => ProcessStatus::Unknown(x), + } + } +} + +#[doc(hidden)] +impl From<char> for ProcessStatus { + fn from(status: char) -> ProcessStatus { + match status { + 'R' => ProcessStatus::Run, + 'S' => ProcessStatus::Sleep, + 'D' => ProcessStatus::Idle, + '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", + _ => "Unknown", + }) + } +} + +#[doc = include_str!("../../md_doc/process.md")] +pub struct Process { + pub(crate) name: String, + pub(crate) cmd: Vec<String>, + pub(crate) exe: PathBuf, + pub(crate) pid: Pid, + parent: Option<Pid>, + pub(crate) environ: Vec<String>, + pub(crate) cwd: PathBuf, + pub(crate) root: PathBuf, + 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<Uid>, + group_id: Option<Gid>, + pub(crate) status: ProcessStatus, + /// Tasks run by this process. + pub tasks: HashMap<Pid, Process>, + pub(crate) stat_file: Option<FileCounter>, + old_read_bytes: u64, + old_written_bytes: u64, + read_bytes: u64, + written_bytes: u64, +} + +impl Process { + pub(crate) fn new(pid: Pid) -> Process { + Process { + name: String::with_capacity(20), + pid, + parent: None, + cmd: Vec::with_capacity(2), + environ: Vec::with_capacity(10), + exe: PathBuf::new(), + cwd: PathBuf::new(), + root: PathBuf::new(), + 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, + group_id: None, + status: ProcessStatus::Unknown(0), + tasks: if pid.0 == 0 { + HashMap::with_capacity(1000) + } else { + HashMap::new() + }, + stat_file: None, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + } + } +} + +impl ProcessExt for Process { + fn kill_with(&self, signal: Signal) -> Option<bool> { + let c_signal = super::system::convert_signal(signal)?; + unsafe { Some(kill(self.pid.0, c_signal) == 0) } + } + + fn name(&self) -> &str { + &self.name + } + + fn cmd(&self) -> &[String] { + &self.cmd + } + + fn exe(&self) -> &Path { + self.exe.as_path() + } + + fn pid(&self) -> Pid { + self.pid + } + + fn environ(&self) -> &[String] { + &self.environ + } + + fn cwd(&self) -> &Path { + self.cwd.as_path() + } + + fn root(&self) -> &Path { + self.root.as_path() + } + + fn memory(&self) -> u64 { + self.memory + } + + fn virtual_memory(&self) -> u64 { + self.virtual_memory + } + + fn parent(&self) -> Option<Pid> { + self.parent + } + + fn status(&self) -> ProcessStatus { + self.status + } + + fn start_time(&self) -> u64 { + self.start_time + } + + fn run_time(&self) -> u64 { + self.run_time + } + + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + 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, + } + } + + fn user_id(&self) -> Option<&Uid> { + self.user_id.as_ref() + } + + fn group_id(&self) -> Option<Gid> { + self.group_id + } + + fn wait(&self) { + let mut status = 0; + // attempt waiting + unsafe { + if 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 compute_cpu_usage(p: &mut Process, 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) + p.stime.saturating_sub(p.old_stime)) + as f32 + / total_time + * 100.) + .min(max_value); +} + +pub(crate) fn set_time(p: &mut Process, 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 Process, path: &Path) { + 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::<u64>().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::<u64>().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[21]).unwrap_or(0) / info.clock_cycle +} + +fn _get_stat_data(path: &Path, stat_file: &mut Option<FileCounter>) -> Result<String, ()> { + 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 Process, part: &str) { + p.status = part + .chars() + .next() + .map(ProcessStatus::from) + .unwrap_or_else(|| ProcessStatus::Unknown(0)); +} + +fn refresh_user_group_ids<P: PathPush>(p: &mut Process, path: &mut P) { + if let Some((user_id, group_id)) = get_uid_and_gid(path.join("status")) { + p.user_id = Some(Uid(user_id)); + p.group_id = Some(Gid(group_id)); + } +} + +fn retrieve_all_new_process_info( + pid: Pid, + proc_list: &Process, + parts: &[&str], + path: &Path, + info: &SystemInfo, + refresh_kind: ProcessRefreshKind, + uptime: u64, +) -> Process { + let mut p = Process::new(pid); + let mut tmp = PathHandler::new(path); + let name = parts[1]; + + p.parent = if proc_list.pid.0 != 0 { + Some(proc_list.pid) + } else { + match Pid::from_str(parts[3]) { + Ok(p) if p.0 != 0 => Some(p), + _ => None, + } + }; + + 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); + + get_status(&mut p, parts[2]); + + if refresh_kind.user() { + refresh_user_group_ids(&mut p, &mut tmp); + } + + if proc_list.pid.0 != 0 { + // If we're getting information for a child, no need to get those info since we + // already have them... + p.cmd = proc_list.cmd.clone(); + p.name = proc_list.name.clone(); + p.environ = proc_list.environ.clone(); + p.exe = proc_list.exe.clone(); + p.cwd = proc_list.cwd.clone(); + p.root = proc_list.root.clone(); + } else { + p.name = name.into(); + + match tmp.join("exe").read_link() { + Ok(exe_path) => { + p.exe = exe_path; + } + Err(_) => { + // Do not use cmd[0] because it is not the same thing. + // See https://github.com/GuillaumeGomez/sysinfo/issues/697. + p.exe = PathBuf::new() + } + } + + p.cmd = copy_from_file(tmp.join("cmdline")); + p.environ = copy_from_file(tmp.join("environ")); + p.cwd = realpath(tmp.join("cwd")); + p.root = realpath(tmp.join("root")); + } + + update_time_and_memory( + path, + &mut p, + parts, + proc_list.memory, + proc_list.virtual_memory, + uptime, + info, + refresh_kind, + ); + if refresh_kind.disk_usage() { + update_process_disk_activity(&mut p, path); + } + p +} + +pub(crate) fn _get_process_data( + path: &Path, + proc_list: &mut Process, + pid: Pid, + uptime: u64, + info: &SystemInfo, + refresh_kind: ProcessRefreshKind, +) -> Result<(Option<Process>, Pid), ()> { + let pid = match path.file_name().and_then(|x| x.to_str()).map(Pid::from_str) { + Some(Ok(nb)) if nb != pid => nb, + _ => return Err(()), + }; + + let parent_memory = proc_list.memory; + let parent_virtual_memory = proc_list.virtual_memory; + + let data; + let parts = if let Some(ref mut entry) = proc_list.tasks.get_mut(&pid) { + 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(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 { + get_status(entry, parts[2]); + update_time_and_memory( + path, + entry, + &parts, + parent_memory, + parent_virtual_memory, + uptime, + info, + refresh_kind, + ); + if refresh_kind.disk_usage() { + update_process_disk_activity(entry, path); + } + if refresh_kind.user() && entry.user_id.is_none() { + refresh_user_group_ids(entry, &mut PathBuf::from(path)); + } + 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, proc_list, &parts, path, info, refresh_kind, uptime); + p.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, proc_list, &parts, path, info, refresh_kind, uptime); + match proc_list.tasks.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)) +} + +#[allow(clippy::too_many_arguments)] +fn update_time_and_memory( + path: &Path, + entry: &mut Process, + parts: &[&str], + parent_memory: u64, + parent_virtual_memory: u64, + uptime: u64, + info: &SystemInfo, + refresh_kind: ProcessRefreshKind, +) { + { + // rss + entry.memory = u64::from_str(parts[23]) + .unwrap_or(0) + .saturating_mul(info.page_size_kb); + if entry.memory >= parent_memory { + entry.memory -= parent_memory; + } + // 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[22]).unwrap_or(0); + if entry.virtual_memory >= parent_virtual_memory { + entry.virtual_memory -= parent_virtual_memory; + } + set_time( + entry, + u64::from_str(parts[13]).unwrap_or(0), + u64::from_str(parts[14]).unwrap_or(0), + ); + entry.run_time = uptime.saturating_sub(entry.start_time_without_boot_time); + } + refresh_procs( + entry, + &path.join("task"), + entry.pid, + uptime, + info, + refresh_kind, + ); +} + +pub(crate) fn refresh_procs( + proc_list: &mut Process, + path: &Path, + pid: Pid, + uptime: u64, + info: &SystemInfo, + refresh_kind: ProcessRefreshKind, +) -> bool { + let d = match fs::read_dir(path) { + Ok(d) => d, + Err(_) => return false, + }; + let folders = d + .filter_map(|entry| { + let entry = entry.ok()?; + let entry = entry.path(); + + if entry.is_dir() { + Some(entry) + } else { + None + } + }) + .collect::<Vec<_>>(); + if pid.0 == 0 { + let proc_list = Wrap(UnsafeCell::new(proc_list)); + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + into_iter(folders) + .filter_map(|e| { + let (p, _) = _get_process_data( + e.as_path(), + proc_list.get(), + pid, + uptime, + info, + refresh_kind, + ) + .ok()?; + p + }) + .collect::<Vec<_>>() + } else { + let mut updated_pids = Vec::with_capacity(folders.len()); + let new_tasks = folders + .iter() + .filter_map(|e| { + let (p, pid) = + _get_process_data(e.as_path(), proc_list, pid, uptime, info, refresh_kind) + .ok()?; + updated_pids.push(pid); + p + }) + .collect::<Vec<_>>(); + // Sub-tasks are not cleaned up outside so we do it here directly. + proc_list + .tasks + .retain(|&pid, _| updated_pids.iter().any(|&x| x == pid)); + new_tasks + } + .into_iter() + .for_each(|e| { + proc_list.tasks.insert(e.pid(), e); + }); + true +} + +fn copy_from_file(entry: &Path) -> Vec<String> { + match File::open(entry) { + Ok(mut f) => { + let mut data = vec![0; 16_384]; + + if let Ok(size) = f.read(&mut data) { + data.truncate(size); + let mut out = Vec::with_capacity(20); + let mut start = 0; + for (pos, x) in data.iter().enumerate() { + if *x == 0 { + if pos - start >= 1 { + if let Ok(s) = + std::str::from_utf8(&data[start..pos]).map(|x| x.trim().to_owned()) + { + out.push(s); + } + } + start = pos + 1; // to keeping prevent '\0' + } + } + out + } else { + Vec::new() + } + } + Err(_) => Vec::new(), + } +} + +fn get_uid_and_gid(file_path: &Path) -> Option<(uid_t, gid_t)> { + use std::os::unix::ffi::OsStrExt; + + unsafe { + let mut sstat: MaybeUninit<libc::stat> = MaybeUninit::uninit(); + + let mut file_path: Vec<u8> = file_path.as_os_str().as_bytes().to_vec(); + file_path.push(0); + if libc::stat(file_path.as_ptr() as *const _, sstat.as_mut_ptr()) == 0 { + let sstat = sstat.assume_init(); + + return Some((sstat.st_uid, sstat.st_gid)); + } + } + + 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 second entry to get + // the effective u/gid. + + let f = |h: &str, n: &str| -> Option<uid_t> { + if h.starts_with(n) { + h.split_whitespace().nth(2).unwrap_or("0").parse().ok() + } else { + None + } + }; + let mut uid = None; + let mut gid = None; + for line in status_data.lines() { + if let Some(u) = f(line, "Uid:") { + assert!(uid.is_none()); + uid = Some(u); + } else if let Some(g) = f(line, "Gid:") { + assert!(gid.is_none()); + gid = Some(g); + } else { + continue; + } + if uid.is_some() && gid.is_some() { + break; + } + } + match (uid, gid) { + (Some(u), Some(g)) => Some((u, g)), + _ => None, + } +} + +fn parse_stat_file(data: &str) -> Option<Vec<&str>> { + // 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[1].strip_prefix('(') { + parts[1] = name; + } + Some(parts) +} diff --git a/vendor/sysinfo-0.26.7/src/linux/system.rs b/vendor/sysinfo-0.26.7/src/linux/system.rs new file mode 100644 index 000000000..3c4fce345 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/linux/system.rs @@ -0,0 +1,750 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::component::{self, Component}; +use crate::sys::cpu::*; +use crate::sys::disk; +use crate::sys::process::*; +use crate::sys::utils::{get_all_data, to_u64}; +use crate::{ + CpuRefreshKind, Disk, LoadAvg, Networks, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, +}; + +use libc::{self, c_char, c_int, sysconf, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE}; +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufRead, BufReader, Read}; +use std::path::Path; +use std::str::FromStr; +use std::sync::{Arc, Mutex}; + +// 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. +#[allow(clippy::mutex_atomic)] +pub(crate) static mut REMAINING_FILES: once_cell::sync::Lazy<Arc<Mutex<isize>>> = + 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 Arc::new(Mutex::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. + Arc::new(Mutex::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(f) = File::open("/proc/stat") { + let buf = BufReader::new(f); + let line = buf + .split(b'\n') + .filter_map(|r| r.ok()) + .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... + let mut up = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + unsafe { + 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_kb: u64, + pub(crate) clock_cycle: u64, + pub(crate) boot_time: u64, +} + +impl SystemInfo { + fn new() -> Self { + unsafe { + Self { + page_size_kb: sysconf(_SC_PAGESIZE) as _, + clock_cycle: sysconf(_SC_CLK_TCK) as _, + boot_time: boot_time(), + } + } + } +} + +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::Poll => libc::SIGPOLL, + Signal::Power => libc::SIGPWR, + Signal::Sys => libc::SIGSYS, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: Process, + 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, + components: Vec<Component>, + disks: Vec<Disk>, + networks: Networks, + users: Vec<User>, + info: SystemInfo, + cpus: CpusWrapper, +} + +impl System { + /// 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.tasks.retain(|_, proc_| { + if !proc_.updated { + return false; + } + if compute_cpu { + compute_cpu_usage(proc_, total_time, max_value); + } + proc_.updated = false; + true + }); + } + + fn refresh_cpus(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) { + self.cpus.refresh(only_update_global_cpu, refresh_kind); + } +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + fn new_with_specifics(refreshes: RefreshKind) -> System { + let process_list = Process::new(Pid(0)); + let mut s = System { + process_list, + 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(), + components: Vec::new(), + disks: Vec::with_capacity(2), + networks: Networks::new(), + users: Vec::new(), + info: SystemInfo::new(), + }; + s.refresh_specifics(refreshes); + s + } + + fn refresh_components_list(&mut self) { + self.components = component::get_components(); + } + + fn refresh_memory(&mut self) { + if let Ok(data) = get_all_data("/proc/meminfo", 16_385) { + let mut mem_available_found = false; + + for line in data.split('\n') { + let mut iter = line.split(':'); + let field = match iter.next() { + Some("MemTotal") => &mut self.mem_total, + Some("MemFree") => &mut self.mem_free, + Some("MemAvailable") => { + mem_available_found = true; + &mut self.mem_available + } + Some("Buffers") => &mut self.mem_buffers, + Some("Cached") => &mut self.mem_page_cache, + Some("Shmem") => &mut self.mem_shmem, + Some("SReclaimable") => &mut self.mem_slab_reclaimable, + Some("SwapTotal") => &mut self.swap_total, + Some("SwapFree") => &mut self.swap_free, + _ => continue, + }; + if let Some(val_str) = iter.next().and_then(|s| s.trim_start().split(' ').next()) { + if let Ok(value) = u64::from_str(val_str) { + // /proc/meminfo reports KiB, though it says "kB". Convert it. + *field = value.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 + + self.mem_buffers + + self.mem_page_cache + + self.mem_slab_reclaimable + - self.mem_shmem; + } + } + } + + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { + self.refresh_cpus(false, refresh_kind); + } + + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { + let uptime = self.uptime(); + refresh_procs( + &mut self.process_list, + Path::new("/proc"), + Pid(0), + uptime, + &self.info, + refresh_kind, + ); + self.clear_procs(refresh_kind); + self.cpus.set_need_cpus_update(); + } + + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + let uptime = self.uptime(); + let found = match _get_process_data( + &Path::new("/proc/").join(pid.to_string()), + &mut self.process_list, + Pid(0), + uptime, + &self.info, + refresh_kind, + ) { + Ok((Some(p), pid)) => { + self.process_list.tasks.insert(pid, p); + true + } + Ok(_) => true, + Err(_) => false, + }; + if found { + if refresh_kind.cpu() { + self.refresh_cpus(true, CpuRefreshKind::new().with_cpu_usage()); + + if self.cpus.is_empty() { + sysinfo_debug!("Cannot compute process CPU usage: no cpus found..."); + return found; + } + let (new, old) = self.cpus.get_global_raw_times(); + let total_time = (if old >= new { 1 } else { new - old }) as f32; + + let max_cpu_usage = self.get_max_process_cpu_usage(); + if let Some(p) = self.process_list.tasks.get_mut(&pid) { + compute_cpu_usage(p, total_time / self.cpus.len() as f32, max_cpu_usage); + p.updated = false; + } + } else if let Some(p) = self.process_list.tasks.get_mut(&pid) { + p.updated = false; + } + } + found + } + + fn refresh_disks_list(&mut self) { + self.disks = disk::get_all_disks(); + } + + fn refresh_users_list(&mut self) { + self.users = crate::users::get_users_list(); + } + + // COMMON PART + // + // Need to be moved into a "common" file to avoid duplication. + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list.tasks + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.tasks.get(&pid) + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn global_cpu_info(&self) -> &Cpu { + &self.cpus.global_cpu + } + + fn cpus(&self) -> &[Cpu] { + &self.cpus.cpus + } + + fn physical_core_count(&self) -> Option<usize> { + get_physical_core_count() + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + self.mem_free + } + + fn available_memory(&self) -> u64 { + self.mem_available + } + + fn used_memory(&self) -> u64 { + self.mem_total - self.mem_available + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_free + } + + // need to be checked + fn used_swap(&self) -> u64 { + self.swap_total - self.swap_free + } + + fn components(&self) -> &[Component] { + &self.components + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut self.components + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks + } + + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + self.disks.sort_unstable_by(compare); + } + + fn uptime(&self) -> u64 { + let content = get_all_data("/proc/uptime", 50).unwrap_or_default(); + content + .split('.') + .next() + .and_then(|t| t.parse().ok()) + .unwrap_or_default() + } + + fn boot_time(&self) -> u64 { + self.info.boot_time + } + + fn load_average(&self) -> 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::<f64>().unwrap()) + .collect::<Vec<f64>>(); + LoadAvg { + one: loads[0], + five: loads[1], + fifteen: loads[2], + } + } + + fn users(&self) -> &[User] { + &self.users + } + + #[cfg(not(target_os = "android"))] + fn name(&self) -> Option<String> { + get_system_info_linux( + InfoType::Name, + Path::new("/etc/os-release"), + Path::new("/etc/lsb-release"), + ) + } + + #[cfg(target_os = "android")] + fn name(&self) -> Option<String> { + get_system_info_android(InfoType::Name) + } + + fn long_os_version(&self) -> Option<String> { + #[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() + )) + } + + fn host_name(&self) -> Option<String> { + 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 + } + } + } + + fn kernel_version(&self) -> Option<String> { + let mut raw = std::mem::MaybeUninit::<libc::utsname>::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::<String>(); + + Some(release) + } else { + None + } + } + } + + #[cfg(not(target_os = "android"))] + fn os_version(&self) -> Option<String> { + get_system_info_linux( + InfoType::OsVersion, + Path::new("/etc/os-release"), + Path::new("/etc/lsb-release"), + ) + } + + #[cfg(target_os = "android")] + fn os_version(&self) -> Option<String> { + get_system_info_android(InfoType::OsVersion) + } + + #[cfg(not(target_os = "android"))] + fn distribution_id(&self) -> 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")] + fn distribution_id(&self) -> 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()) + } +} + +impl Default for System { + fn default() -> System { + System::new() + } +} + +#[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<String> { + if let Ok(f) = File::open(path) { + let reader = BufReader::new(f); + + let info_str = match info { + InfoType::Name => "NAME=", + InfoType::OsVersion => "VERSION_ID=", + InfoType::DistributionID => "ID=", + }; + + for line in reader.lines().flatten() { + 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 reader = BufReader::new(File::open(fallback_path).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 reader.lines().flatten() { + 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<String> { + // 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 + ); + } +} diff --git a/vendor/sysinfo-0.26.7/src/linux/utils.rs b/vendor/sysinfo-0.26.7/src/linux/utils.rs new file mode 100644 index 000000000..60a9aa2b0 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/linux/utils.rs @@ -0,0 +1,123 @@ +// 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, SeekFrom}; +use std::path::{Path, PathBuf}; + +use crate::sys::system::REMAINING_FILES; + +pub(crate) fn get_all_data_from_file(file: &mut File, size: usize) -> io::Result<String> { + let mut buf = String::with_capacity(size); + file.seek(SeekFrom::Start(0))?; + file.read_to_string(&mut buf)?; + Ok(buf) +} + +pub(crate) fn get_all_data<P: AsRef<Path>>(file_path: P, size: usize) -> io::Result<String> { + 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) -> std::path::PathBuf { + match std::fs::read_link(path) { + Ok(f) => f, + Err(_e) => { + sysinfo_debug!("failed to get real path for {:?}: {:?}", path, _e); + PathBuf::new() + } + } +} + +/// Type used to correctly handle the `REMAINING_FILES` global. +pub(crate) struct FileCounter(File); + +impl FileCounter { + pub(crate) fn new(f: File) -> Option<Self> { + unsafe { + if let Ok(ref mut x) = REMAINING_FILES.lock() { + if **x > 0 { + **x -= 1; + return Some(Self(f)); + } + // All file descriptors we were allowed are being used. + } + } + None + } +} + +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) { + unsafe { + if let Ok(ref mut x) = crate::sys::system::REMAINING_FILES.lock() { + **x += 1; + } + } + } +} + +/// 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<u8>` suitable for use with C functions. +pub(crate) fn to_cpath(path: &std::path::Path) -> Vec<u8> { + 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 +} diff --git a/vendor/sysinfo-0.26.7/src/macros.rs b/vendor/sysinfo-0.26.7/src/macros.rs new file mode 100644 index 000000000..3bb4defa1 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/macros.rs @@ -0,0 +1,58 @@ +// 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),)* + } + } + ) +} diff --git a/vendor/sysinfo-0.26.7/src/sysinfo.h b/vendor/sysinfo-0.26.7/src/sysinfo.h new file mode 100644 index 000000000..13c039128 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/sysinfo.h @@ -0,0 +1,45 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#pragma once + +#include <sys/types.h> +#include <stdbool.h> + +typedef void* CSystem; +typedef const void* CProcess; +typedef const char* RString; + +CSystem *sysinfo_init(); +void sysinfo_destroy(CSystem system); +void sysinfo_refresh_system(CSystem system); +void sysinfo_refresh_all(CSystem system); +void sysinfo_refresh_processes(CSystem system); +#ifdef __linux__ +void sysinfo_refresh_process(CSystem system, pid_t pid); +#endif +void sysinfo_refresh_disks(CSystem system); +void sysinfo_refresh_disk_list(CSystem system); +size_t sysinfo_get_total_memory(CSystem system); +size_t sysinfo_get_free_memory(CSystem system); +size_t sysinfo_get_used_memory(CSystem system); +size_t sysinfo_get_total_swap(CSystem system); +size_t sysinfo_get_free_swap(CSystem system); +size_t sysinfo_get_used_swap(CSystem system); +size_t sysinfo_get_network_income(CSystem system); +size_t sysinfo_get_network_outcome(CSystem system); +void sysinfo_get_cpus_usage(CSystem system, unsigned int *length, float **cpus); +size_t sysinfo_get_processes(CSystem system, bool (*fn_pointer)(pid_t, CProcess, void*), + void *data); +#ifdef __linux__ +size_t sysinfo_process_get_tasks(CProcess process, bool (*fn_pointer)(pid_t, CProcess, void*), + void *data); +#endif +CProcess sysinfo_get_process_by_pid(CSystem system, pid_t pid); +pid_t sysinfo_process_get_pid(CProcess process); +pid_t sysinfo_process_get_parent_pid(CProcess process); +float sysinfo_process_get_cpu_usage(CProcess process); +size_t sysinfo_process_get_memory(CProcess process); +RString sysinfo_process_get_executable_path(CProcess process); +RString sysinfo_process_get_root_directory(CProcess process); +RString sysinfo_process_get_current_directory(CProcess process); +void sysinfo_rstring_free(RString str); diff --git a/vendor/sysinfo-0.26.7/src/system.rs b/vendor/sysinfo-0.26.7/src/system.rs new file mode 100644 index 000000000..63a70651f --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/system.rs @@ -0,0 +1,117 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +// Once https://github.com/rust-lang/rfcs/blob/master/text/1422-pub-restricted.md +// feature gets stabilized, we can move common parts in here. + +#[cfg(test)] +mod tests { + use crate::{ProcessExt, System, SystemExt}; + + #[test] + fn test_refresh_system() { + let mut sys = System::new(); + sys.refresh_system(); + // We don't want to test on unsupported systems. + if System::IS_SUPPORTED { + 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 System::IS_SUPPORTED { + assert!( + sys.refresh_process(crate::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(crate::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 crate::get_current_pid() { + Ok(pid) => pid, + _ => { + if !System::IS_SUPPORTED { + 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!(!System::IS_SUPPORTED); + } + } + + #[test] + fn check_if_send_and_sync() { + trait Foo { + fn foo(&self) {} + } + impl<T> Foo for T where T: Send {} + + trait Bar { + fn bar(&self) {} + } + + impl<T> Bar for T where T: Sync {} + + let mut sys = System::new(); + sys.refresh_processes(); + let current_pid = match crate::get_current_pid() { + Ok(pid) => pid, + _ => { + if !System::IS_SUPPORTED { + 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!(!System::IS_SUPPORTED); + } + } + + #[test] + fn check_hostname_has_no_nuls() { + let sys = System::new(); + + if let Some(hostname) = sys.host_name() { + assert!(!hostname.contains('\u{0}')) + } + } + + #[test] + fn check_uptime() { + let sys = System::new(); + let uptime = sys.uptime(); + if System::IS_SUPPORTED { + std::thread::sleep(std::time::Duration::from_millis(1000)); + let new_uptime = sys.uptime(); + assert!(uptime < new_uptime); + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/traits.rs b/vendor/sysinfo-0.26.7/src/traits.rs new file mode 100644 index 000000000..7b442990c --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/traits.rs @@ -0,0 +1,1678 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + common::{Gid, Uid}, + sys::{Component, Cpu, Disk, Networks, Process}, +}; +use crate::{ + CpuRefreshKind, DiskType, DiskUsage, LoadAvg, NetworksIter, Pid, ProcessRefreshKind, + ProcessStatus, RefreshKind, Signal, User, +}; + +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fmt::Debug; +use std::path::Path; + +/// Contains all the methods of the [`Disk`][crate::Disk] struct. +/// +/// ```no_run +/// use sysinfo::{DiskExt, System, SystemExt}; +/// +/// let s = System::new(); +/// for disk in s.disks() { +/// println!("{:?}: {:?}", disk.name(), disk.type_()); +/// } +/// ``` +pub trait DiskExt: Debug { + /// Returns the disk type. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{:?}", disk.type_()); + /// } + /// ``` + fn type_(&self) -> DiskType; + + /// Returns the disk name. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{:?}", disk.name()); + /// } + /// ``` + fn name(&self) -> &OsStr; + + /// Returns the file system used on this disk (so for example: `EXT4`, `NTFS`, etc...). + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{:?}", disk.file_system()); + /// } + /// ``` + fn file_system(&self) -> &[u8]; + + /// Returns the mount point of the disk (`/` for example). + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{:?}", disk.mount_point()); + /// } + /// ``` + fn mount_point(&self) -> &Path; + + /// Returns the total disk size, in bytes. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{}", disk.total_space()); + /// } + /// ``` + fn total_space(&self) -> u64; + + /// Returns the available disk size, in bytes. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{}", disk.available_space()); + /// } + /// ``` + fn available_space(&self) -> u64; + + /// Returns `true` if the disk is removable. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{}", disk.is_removable()); + /// } + /// ``` + fn is_removable(&self) -> bool; + + /// Updates the disk' information. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for disk in s.disks_mut() { + /// disk.refresh(); + /// } + /// ``` + fn refresh(&mut self) -> bool; +} + +/// Contains all the methods of the [`Process`][crate::Process] struct. +pub trait ProcessExt: Debug { + /// 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 [`ProcessExt::kill_with`]. + /// + /// To get the list of the supported signals on this system, use + /// [`SystemExt::SUPPORTED_SIGNALS`]. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// process.kill(); + /// } + /// ``` + 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 [`ProcessExt::kill`] directly. + /// + /// To get the list of the supported signals on this system, use + /// [`SystemExt::SUPPORTED_SIGNALS`]. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, Signal, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// if process.kill_with(Signal::Kill).is_none() { + /// eprintln!("This signal isn't supported on this platform"); + /// } + /// } + /// ``` + fn kill_with(&self, signal: Signal) -> Option<bool>; + + /// 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 [`ProcessExt::exe`] instead (which can be empty sometimes!). + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.name()); + /// } + /// ``` + fn name(&self) -> &str; + + /// Returns the command line. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{:?}", process.cmd()); + /// } + /// ``` + fn cmd(&self) -> &[String]; + + /// Returns the path to the process. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.exe().display()); + /// } + /// ``` + /// + /// ### Implementation notes + /// + /// On Linux, this method will return an empty path if there + /// was an error trying to read `/proc/<pid>/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. + fn exe(&self) -> &Path; + + /// Returns the pid of the process. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.pid()); + /// } + /// ``` + fn pid(&self) -> Pid; + + /// Returns the environment variables of the process. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{:?}", process.environ()); + /// } + /// ``` + fn environ(&self) -> &[String]; + + /// Returns the current working directory. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.cwd().display()); + /// } + /// ``` + fn cwd(&self) -> &Path; + + /// Returns the path of the root directory. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.root().display()); + /// } + /// ``` + fn root(&self) -> &Path; + + /// Returns the memory usage (in bytes). + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{} bytes", process.memory()); + /// } + /// ``` + fn memory(&self) -> u64; + + /// Returns the virtual memory usage (in bytes). + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{} bytes", process.virtual_memory()); + /// } + /// ``` + fn virtual_memory(&self) -> u64; + + /// Returns the parent pid. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{:?}", process.parent()); + /// } + /// ``` + fn parent(&self) -> Option<Pid>; + + /// Returns the status of the processus. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{:?}", process.status()); + /// } + /// ``` + fn status(&self) -> ProcessStatus; + + /// Returns the time where the process was started (in seconds) from epoch. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("Started at {} seconds", process.start_time()); + /// } + /// ``` + fn start_time(&self) -> u64; + + /// Returns for how much time the process has been running (in seconds). + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("Running since {} seconds", process.run_time()); + /// } + /// ``` + fn run_time(&self) -> u64; + + /// Returns the total CPU usage (in %). Notice that it might be bigger than 100 if run on a + /// multicore machine. + /// + /// If you want a value between 0% and 100%, divide the returned value by the number of CPU + /// CPUs. + /// + /// **Warning**: If you want accurate CPU usage number, better leave a bit of time + /// between two calls of this method (200 ms for example). + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}%", process.cpu_usage()); + /// } + /// ``` + fn cpu_usage(&self) -> f32; + + /// Returns number of bytes read and written to disk. + /// + /// ⚠️ On Windows and FreeBSD, this method actually returns **ALL** I/O read and written bytes. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// 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, + /// ); + /// } + /// ``` + fn disk_usage(&self) -> DiskUsage; + + /// 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 + /// [`SystemExt::get_user_by_id`]. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// + /// if let Some(process) = s.process(Pid::from(1337)) { + /// eprintln!("User id for process 1337: {:?}", process.user_id()); + /// } + /// ``` + fn user_id(&self) -> Option<&Uid>; + + /// Returns the process group ID of the process. + /// + /// ⚠️ It always returns `None` on Windows. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// + /// if let Some(process) = s.process(Pid::from(1337)) { + /// eprintln!("Group id for process 1337: {:?}", process.group_id()); + /// } + /// ``` + fn group_id(&self) -> Option<Gid>; + + /// Wait for process termination. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// + /// if let Some(process) = s.process(Pid::from(1337)) { + /// eprintln!("Waiting for pid 1337"); + /// process.wait(); + /// eprintln!("Pid 1337 exited"); + /// } + /// ``` + fn wait(&self); +} + +/// Contains all the methods of the [`Cpu`][crate::Cpu] struct. +pub trait CpuExt: Debug { + /// 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::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for cpu in s.cpus() { + /// println!("{}%", cpu.cpu_usage()); + /// } + /// ``` + fn cpu_usage(&self) -> f32; + + /// Returns this CPU's name. + /// + /// ```no_run + /// use sysinfo::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for cpu in s.cpus() { + /// println!("{}", cpu.name()); + /// } + /// ``` + fn name(&self) -> &str; + + /// Returns the CPU's vendor id. + /// + /// ```no_run + /// use sysinfo::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for cpu in s.cpus() { + /// println!("{}", cpu.vendor_id()); + /// } + /// ``` + fn vendor_id(&self) -> &str; + + /// Returns the CPU's brand. + /// + /// ```no_run + /// use sysinfo::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for cpu in s.cpus() { + /// println!("{}", cpu.brand()); + /// } + /// ``` + fn brand(&self) -> &str; + + /// Returns the CPU's frequency. + /// + /// ```no_run + /// use sysinfo::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for cpu in s.cpus() { + /// println!("{}", cpu.frequency()); + /// } + /// ``` + fn frequency(&self) -> u64; +} + +/// Contains all the methods of the [`System`][crate::System] type. +pub trait SystemExt: Sized + Debug + Default + Send + Sync { + /// Returns `true` if this OS is supported. Please refer to the + /// [crate-level documentation](index.html) to get the list of supported OSes. + /// + /// ``` + /// use sysinfo::{System, SystemExt}; + /// + /// if System::IS_SUPPORTED { + /// println!("This OS is supported!"); + /// } else { + /// println!("This OS isn't supported (yet?)."); + /// } + /// ``` + const IS_SUPPORTED: bool; + + /// Returns the list of the supported signals on this system (used by + /// [`ProcessExt::kill_with`]). + /// + /// ``` + /// use sysinfo::{System, SystemExt}; + /// + /// println!("supported signals: {:?}", System::SUPPORTED_SIGNALS); + /// ``` + const SUPPORTED_SIGNALS: &'static [Signal]; + + /// Creates a new [`System`] instance with nothing loaded except the cpus list. If you + /// want to load components, network interfaces or the disks, you'll have to use the + /// `refresh_*_list` methods. [`SystemExt::refresh_networks_list`] for example. + /// + /// Use the [`refresh_all`] method to update its internal information (or any of the `refresh_` + /// method). + /// + /// [`System`]: crate::System + /// [`refresh_all`]: #method.refresh_all + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// ``` + fn new() -> Self { + Self::new_with_specifics(RefreshKind::new()) + } + + /// Creates a new [`System`] instance with everything loaded. + /// + /// It is an equivalent of [`SystemExt::new_with_specifics`]`(`[`RefreshKind::everything`]`())`. + /// + /// [`System`]: crate::System + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// ``` + 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::{RefreshKind, System, SystemExt}; + /// + /// // We want everything except disks. + /// let mut system = System::new_with_specifics(RefreshKind::everything().without_disks_list()); + /// + /// assert_eq!(system.disks().len(), 0); + /// # if System::IS_SUPPORTED && !cfg!(feature = "apple-sandbox") { + /// assert!(system.processes().len() > 0); + /// # } + /// + /// // If you want the disks list afterwards, just call the corresponding + /// // "refresh_disks_list": + /// system.refresh_disks_list(); + /// let disks = system.disks(); + /// ``` + fn new_with_specifics(refreshes: RefreshKind) -> Self; + + /// Refreshes according to the given [`RefreshKind`]. It calls the corresponding + /// "refresh_" methods. + /// + /// ``` + /// use sysinfo::{ProcessRefreshKind, RefreshKind, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// + /// // Let's just update networks and processes: + /// s.refresh_specifics( + /// RefreshKind::new().with_networks().with_processes(ProcessRefreshKind::everything()), + /// ); + /// ``` + fn refresh_specifics(&mut self, refreshes: RefreshKind) { + if refreshes.memory() { + self.refresh_memory(); + } + if let Some(kind) = refreshes.cpu() { + self.refresh_cpu_specifics(kind); + } + if refreshes.components_list() { + self.refresh_components_list(); + } else if refreshes.components() { + self.refresh_components(); + } + if refreshes.networks_list() { + self.refresh_networks_list(); + } else if refreshes.networks() { + self.refresh_networks(); + } + if let Some(kind) = refreshes.processes() { + self.refresh_processes_specifics(kind); + } + if refreshes.disks_list() { + self.refresh_disks_list(); + } else if refreshes.disks() { + self.refresh_disks(); + } + if refreshes.users_list() { + self.refresh_users_list(); + } + } + + /// Refreshes all system, processes, disks and network interfaces information. + /// + /// Please note that it doesn't recompute disks list, components list, network interfaces + /// list nor users list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_all(); + /// ``` + fn refresh_all(&mut self) { + self.refresh_system(); + self.refresh_processes(); + self.refresh_disks(); + self.refresh_networks(); + } + + /// Refreshes system information (RAM, swap, CPU usage and components' temperature). + /// + /// If you want some more specific refreshes, you might be interested into looking at + /// [`refresh_memory`], [`refresh_cpu`] and [`refresh_components`]. + /// + /// [`refresh_memory`]: SystemExt::refresh_memory + /// [`refresh_cpu`]: SystemExt::refresh_memory + /// [`refresh_components`]: SystemExt::refresh_components + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_system(); + /// ``` + fn refresh_system(&mut self) { + self.refresh_memory(); + self.refresh_cpu(); + self.refresh_components(); + } + + /// Refreshes RAM and SWAP usage. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_memory(); + /// ``` + fn refresh_memory(&mut self); + + /// Refreshes 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 + /// 200ms) to get accurate values as it uses previous results to compute the next value. + /// + /// Calling this method is the same as calling + /// `refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage())`. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_cpu(); + /// ``` + fn refresh_cpu(&mut self) { + self.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()) + } + + /// Refreshes CPUs specific information. + /// + /// Please note that it doesn't recompute disks list, components list, network interfaces + /// list nor users list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt, CpuRefreshKind}; + /// + /// let mut s = System::new_all(); + /// s.refresh_cpu_specifics(CpuRefreshKind::everything()); + /// ``` + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind); + + /// Refreshes components' temperature. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_components(); + /// ``` + fn refresh_components(&mut self) { + for component in self.components_mut() { + component.refresh(); + } + } + + /// Refreshes components list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new(); + /// s.refresh_components_list(); + /// ``` + fn refresh_components_list(&mut self); + + /// Gets all processes and updates their information. + /// + /// It does the same as `system.refresh_processes_specifics(ProcessRefreshKind::everything())`. + /// + /// ⚠️ 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::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_processes(); + /// ``` + fn refresh_processes(&mut self) { + self.refresh_processes_specifics(ProcessRefreshKind::everything()); + } + + /// 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, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_processes_specifics(ProcessRefreshKind::new()); + /// ``` + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind); + + /// 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 + /// `sys.refresh_process_specifics(pid, ProcessRefreshKind::everything())`. + /// + /// ```no_run + /// use sysinfo::{Pid, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_process(Pid::from(1337)); + /// ``` + fn refresh_process(&mut self, pid: Pid) -> bool { + self.refresh_process_specifics(pid, ProcessRefreshKind::everything()) + } + + /// 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. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessRefreshKind, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_process_specifics(Pid::from(1337), ProcessRefreshKind::new()); + /// ``` + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool; + + /// Refreshes the listed disks' information. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_disks(); + /// ``` + fn refresh_disks(&mut self) { + for disk in self.disks_mut() { + disk.refresh(); + } + } + + /// The disk list will be emptied then completely recomputed. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_disks_list(); + /// ``` + fn refresh_disks_list(&mut self); + + /// Refreshes users list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_users_list(); + /// ``` + fn refresh_users_list(&mut self); + + /// Refreshes networks data. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_networks(); + /// ``` + /// + /// It is a shortcut for: + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.networks_mut(); + /// networks.refresh(); + /// ``` + fn refresh_networks(&mut self) { + self.networks_mut().refresh(); + } + + /// The network list will be updated: removing not existing anymore interfaces and adding new + /// ones. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_networks_list(); + /// ``` + /// + /// This is a shortcut for: + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.networks_mut(); + /// networks.refresh_networks_list(); + /// ``` + fn refresh_networks_list(&mut self) { + self.networks_mut().refresh_networks_list(); + } + + /// Returns the process list. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for (pid, process) in s.processes() { + /// println!("{} {}", pid, process.name()); + /// } + /// ``` + fn processes(&self) -> &HashMap<Pid, Process>; + + /// Returns the process corresponding to the given pid or `None` if no such process exists. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.name()); + /// } + /// ``` + fn process(&self, pid: Pid) -> Option<&Process>; + + /// Returns an iterator of process containing the given `name`. + /// + /// If you want only the processes with exactly the given `name`, take a look at + /// [`SystemExt::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::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for process in s.processes_by_name("htop") { + /// println!("{} {}", process.pid(), process.name()); + /// } + /// ``` + // FIXME: replace the returned type with `impl Iterator<Item = &Process>` when it's supported! + fn processes_by_name<'a>( + &'a self, + name: &'a str, + ) -> Box<dyn Iterator<Item = &'a Process> + 'a> { + Box::new( + 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 + /// [`SystemExt::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::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for process in s.processes_by_exact_name("htop") { + /// println!("{} {}", process.pid(), process.name()); + /// } + /// ``` + // FIXME: replace the returned type with `impl Iterator<Item = &Process>` when it's supported! + fn processes_by_exact_name<'a>( + &'a self, + name: &'a str, + ) -> Box<dyn Iterator<Item = &'a Process> + 'a> { + Box::new( + 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 [`SystemExt::refresh_cpu`] or + /// [`SystemExt::refresh_specifics`] with `cpu` enabled. + /// + /// ```no_run + /// use sysinfo::{CpuRefreshKind, CpuExt, RefreshKind, System, SystemExt}; + /// + /// let s = System::new_with_specifics( + /// RefreshKind::new().with_cpu(CpuRefreshKind::everything()), + /// ); + /// println!("{}%", s.global_cpu_info().cpu_usage()); + /// ``` + fn global_cpu_info(&self) -> &Cpu; + + /// Returns the list of the CPUs. + /// + /// By default, the list of cpus is empty until you call [`SystemExt::refresh_cpu`] or + /// [`SystemExt::refresh_specifics`] with `cpu` enabled. + /// + /// ```no_run + /// use sysinfo::{CpuRefreshKind, CpuExt, RefreshKind, System, SystemExt}; + /// + /// let s = System::new_with_specifics( + /// RefreshKind::new().with_cpu(CpuRefreshKind::everything()), + /// ); + /// for cpu in s.cpus() { + /// println!("{}%", cpu.cpu_usage()); + /// } + /// ``` + fn cpus(&self) -> &[Cpu]; + + /// 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::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// println!("{:?}", s.physical_core_count()); + /// ``` + fn physical_core_count(&self) -> Option<usize>; + + /// Returns the RAM size in bytes. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} bytes", s.total_memory()); + /// ``` + fn total_memory(&self) -> u64; + + /// 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 [`get_available_memory`](#tymethod.available_memory). + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} bytes", s.free_memory()); + /// ``` + fn free_memory(&self) -> u64; + + /// 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 [`SystemExt::free_memory`] + /// returns the same value as this method. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} bytes", s.available_memory()); + /// ``` + fn available_memory(&self) -> u64; + + /// Returns the amount of used RAM in bytes. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} bytes", s.used_memory()); + /// ``` + fn used_memory(&self) -> u64; + + /// Returns the SWAP size in bytes. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} bytes", s.total_swap()); + /// ``` + fn total_swap(&self) -> u64; + + /// Returns the amount of free SWAP in bytes. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} bytes", s.free_swap()); + /// ``` + fn free_swap(&self) -> u64; + + /// Returns the amount of used SWAP in bytes. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} bytes", s.used_swap()); + /// ``` + fn used_swap(&self) -> u64; + + /// Returns the components list. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.components() { + /// println!("{}: {}°C", component.label(), component.temperature()); + /// } + /// ``` + fn components(&self) -> &[Component]; + + /// Returns a mutable components list. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for component in s.components_mut() { + /// component.refresh(); + /// } + /// ``` + fn components_mut(&mut self) -> &mut [Component]; + + /// Returns the users list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt, UserExt}; + /// + /// let mut s = System::new_all(); + /// for user in s.users() { + /// println!("{} is in {} groups", user.name(), user.groups().len()); + /// } + /// ``` + fn users(&self) -> &[User]; + + /// Returns the disks list. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for disk in s.disks() { + /// println!("{:?}", disk.name()); + /// } + /// ``` + fn disks(&self) -> &[Disk]; + + /// Returns the disks list. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for disk in s.disks_mut() { + /// disk.refresh(); + /// } + /// ``` + fn disks_mut(&mut self) -> &mut [Disk]; + + /// Sort the disk list with the provided callback. + /// + /// Internally, it is using the [`slice::sort_unstable_by`] function, so please refer to it + /// for implementation details. + /// + /// ⚠️ If you use [`SystemExt::refresh_disks_list`], you need to use this method before using + /// [`SystemExt::disks`] or [`SystemExt::disks_mut`] if you want them to be sorted. + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering; + + /// Returns the network interfaces object. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, data) in networks { + /// println!( + /// "[{}] in: {}, out: {}", + /// interface_name, + /// data.received(), + /// data.transmitted(), + /// ); + /// } + /// ``` + fn networks(&self) -> &Networks; + + /// Returns a mutable access to network interfaces. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.networks_mut(); + /// networks.refresh_networks_list(); + /// ``` + fn networks_mut(&mut self) -> &mut Networks; + + /// Returns system uptime (in seconds). + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("System running since {} seconds", s.uptime()); + /// ``` + fn uptime(&self) -> u64; + + /// Returns the time (in seconds) when the system booted since UNIX epoch. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("System booted at {} seconds", s.boot_time()); + /// ``` + fn boot_time(&self) -> u64; + + /// Returns the system load average value. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// let load_avg = s.load_average(); + /// println!( + /// "one minute: {}%, five minutes: {}%, fifteen minutes: {}%", + /// load_avg.one, + /// load_avg.five, + /// load_avg.fifteen, + /// ); + /// ``` + fn load_average(&self) -> LoadAvg; + + /// Returns the system name. + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("OS: {:?}", s.name()); + /// ``` + fn name(&self) -> Option<String>; + + /// Returns the system's kernel version. + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("kernel version: {:?}", s.kernel_version()); + /// ``` + fn kernel_version(&self) -> Option<String>; + + /// 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, SystemExt}; + /// + /// let s = System::new(); + /// println!("OS version: {:?}", s.os_version()); + /// ``` + fn os_version(&self) -> Option<String>; + + /// 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, SystemExt}; + /// + /// let s = System::new(); + /// println!("Long OS Version: {:?}", s.long_os_version()); + /// ``` + fn long_os_version(&self) -> Option<String>; + + /// Returns the distribution id as defined by os-release, + /// or [`std::env::consts::OS`]. + /// + /// See also + /// - <https://www.freedesktop.org/software/systemd/man/os-release.html#ID=> + /// - <https://doc.rust-lang.org/std/env/consts/constant.OS.html> + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("Distribution ID: {:?}", s.distribution_id()); + /// ``` + fn distribution_id(&self) -> String; + + /// Returns the system hostname based off DNS + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("Hostname: {:?}", s.host_name()); + /// ``` + fn host_name(&self) -> Option<String>; + + /// 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 + /// let s = System::new_all(); + /// s.users().find(|user| user.id() == user_id); + /// ``` + /// + /// Full example: + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// + /// if let Some(process) = s.process(Pid::from(1337)) { + /// if let Some(user_id) = process.user_id() { + /// eprintln!("User for process 1337: {:?}", s.get_user_by_id(user_id)); + /// } + /// } + /// ``` + fn get_user_by_id(&self, user_id: &Uid) -> Option<&User> { + self.users().iter().find(|user| user.id() == user_id) + } +} + +/// Getting volume of received and transmitted data. +pub trait NetworkExt: Debug { + /// Returns the number of received bytes since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {} B", network.received()); + /// } + /// ``` + fn received(&self) -> u64; + + /// Returns the total number of received bytes. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {} B", network.total_received()); + /// } + /// ``` + fn total_received(&self) -> u64; + + /// Returns the number of transmitted bytes since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {} B", network.transmitted()); + /// } + /// ``` + fn transmitted(&self) -> u64; + + /// Returns the total number of transmitted bytes. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {} B", network.total_transmitted()); + /// } + /// ``` + fn total_transmitted(&self) -> u64; + + /// Returns the number of incoming packets since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.packets_received()); + /// } + /// ``` + fn packets_received(&self) -> u64; + + /// Returns the total number of incoming packets. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.total_packets_received()); + /// } + /// ``` + fn total_packets_received(&self) -> u64; + + /// Returns the number of outcoming packets since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.packets_transmitted()); + /// } + /// ``` + fn packets_transmitted(&self) -> u64; + + /// Returns the total number of outcoming packets. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.total_packets_transmitted()); + /// } + /// ``` + fn total_packets_transmitted(&self) -> u64; + + /// Returns the number of incoming errors since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.errors_on_received()); + /// } + /// ``` + fn errors_on_received(&self) -> u64; + + /// Returns the total number of incoming errors. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.total_errors_on_received()); + /// } + /// ``` + fn total_errors_on_received(&self) -> u64; + + /// Returns the number of outcoming errors since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.errors_on_transmitted()); + /// } + /// ``` + fn errors_on_transmitted(&self) -> u64; + + /// Returns the total number of outcoming errors. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.total_errors_on_transmitted()); + /// } + /// ``` + fn total_errors_on_transmitted(&self) -> u64; +} + +/// Interacting with network interfaces. +pub trait NetworksExt: Debug { + /// Returns an iterator over the network interfaces. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {} B", network.received()); + /// } + /// ``` + fn iter(&self) -> NetworksIter; + + /// Refreshes the network interfaces list. + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.networks_mut(); + /// networks.refresh_networks_list(); + /// ``` + fn refresh_networks_list(&mut self); + + /// Refreshes the network interfaces' content. + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.networks_mut(); + /// networks.refresh(); + /// ``` + fn refresh(&mut self); +} + +/// Getting a component temperature information. +pub trait ComponentExt: Debug { + /// Returns the temperature of the component (in celsius degree). + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.components() { + /// println!("{}°C", component.temperature()); + /// } + /// ``` + /// + /// ## Linux + /// + /// Returns `f32::NAN` if it failed to retrieve it. + fn temperature(&self) -> f32; + + /// 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. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.components() { + /// println!("{}°C", component.max()); + /// } + /// ``` + /// + /// ## Linux + /// + /// May be computed by sysinfo from kernel. + /// Returns `f32::NAN` if it failed to retrieve it. + fn max(&self) -> f32; + + /// Returns the highest temperature before the component halts (in celsius degree). + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.components() { + /// println!("{:?}°C", component.critical()); + /// } + /// ``` + /// + /// ## Linux + /// + /// Critical threshold defined by chip or kernel. + fn critical(&self) -> Option<f32>; + + /// Returns the label of the component. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.components() { + /// println!("{}", component.label()); + /// } + /// ``` + /// + /// ## Linux + /// + /// Since components informations are 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}"` | + fn label(&self) -> &str; + + /// Refreshes component. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for component in s.components_mut() { + /// component.refresh(); + /// } + /// ``` + fn refresh(&mut self); +} + +/// Getting information for a user. +/// +/// It is returned from [`SystemExt::users`]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt, UserExt}; +/// +/// let mut s = System::new_all(); +/// for user in s.users() { +/// println!("{} is in {} groups", user.name(), user.groups().len()); +/// } +/// ``` +pub trait UserExt: Debug { + /// Return the user id of the user. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt, UserExt}; + /// + /// let mut s = System::new_all(); + /// for user in s.users() { + /// println!("{:?}", *user.id()); + /// } + /// ``` + fn id(&self) -> &Uid; + + /// Return the group id of the user. + /// + /// *NOTE* - On Windows, this value defaults to 0. 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::{System, SystemExt, UserExt}; + /// + /// let mut s = System::new_all(); + /// for user in s.users() { + /// println!("{}", *user.group_id()); + /// } + /// ``` + fn group_id(&self) -> Gid; + + /// Returns the name of the user. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt, UserExt}; + /// + /// let mut s = System::new_all(); + /// for user in s.users() { + /// println!("{}", user.name()); + /// } + /// ``` + fn name(&self) -> &str; + + /// Returns the groups of the user. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt, UserExt}; + /// + /// let mut s = System::new_all(); + /// for user in s.users() { + /// println!("{} is in {:?}", user.name(), user.groups()); + /// } + /// ``` + fn groups(&self) -> &[String]; +} diff --git a/vendor/sysinfo-0.26.7/src/unknown/component.rs b/vendor/sysinfo-0.26.7/src/unknown/component.rs new file mode 100644 index 000000000..ec4a8e87a --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/unknown/component.rs @@ -0,0 +1,26 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::ComponentExt; + +#[doc = include_str!("../../md_doc/component.md")] +pub struct Component {} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + 0.0 + } + + fn max(&self) -> f32 { + 0.0 + } + + fn critical(&self) -> Option<f32> { + None + } + + fn label(&self) -> &str { + "" + } + + fn refresh(&mut self) {} +} diff --git a/vendor/sysinfo-0.26.7/src/unknown/cpu.rs b/vendor/sysinfo-0.26.7/src/unknown/cpu.rs new file mode 100644 index 000000000..72b9be447 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/unknown/cpu.rs @@ -0,0 +1,34 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::CpuExt; + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu {} + +impl Cpu { + pub(crate) fn new() -> Cpu { + Cpu {} + } +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + 0.0 + } + + fn name(&self) -> &str { + "" + } + + fn frequency(&self) -> u64 { + 0 + } + + fn vendor_id(&self) -> &str { + "" + } + + fn brand(&self) -> &str { + "" + } +} diff --git a/vendor/sysinfo-0.26.7/src/unknown/disk.rs b/vendor/sysinfo-0.26.7/src/unknown/disk.rs new file mode 100644 index 000000000..8956da9f5 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/unknown/disk.rs @@ -0,0 +1,42 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskExt, DiskType}; + +use std::{ffi::OsStr, path::Path}; + +#[doc = include_str!("../../md_doc/disk.md")] +pub struct Disk {} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + unreachable!() + } + + fn name(&self) -> &OsStr { + unreachable!() + } + + fn file_system(&self) -> &[u8] { + &[] + } + + fn mount_point(&self) -> &Path { + Path::new("") + } + + fn total_space(&self) -> u64 { + 0 + } + + fn available_space(&self) -> u64 { + 0 + } + + fn is_removable(&self) -> bool { + false + } + + fn refresh(&mut self) -> bool { + true + } +} diff --git a/vendor/sysinfo-0.26.7/src/unknown/mod.rs b/vendor/sysinfo-0.26.7/src/unknown/mod.rs new file mode 100644 index 000000000..e62712607 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/unknown/mod.rs @@ -0,0 +1,15 @@ +// 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 use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo-0.26.7/src/unknown/network.rs b/vendor/sysinfo-0.26.7/src/unknown/network.rs new file mode 100644 index 000000000..4a8ff4831 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/unknown/network.rs @@ -0,0 +1,81 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::collections::HashMap; + +use crate::{NetworkExt, NetworksExt, NetworksIter}; + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Networks { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + fn iter(&self) -> NetworksIter { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) {} + + fn refresh(&mut self) {} +} + +#[doc = include_str!("../../md_doc/network_data.md")] +pub struct NetworkData; + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + 0 + } + + fn total_received(&self) -> u64 { + 0 + } + + fn transmitted(&self) -> u64 { + 0 + } + + fn total_transmitted(&self) -> u64 { + 0 + } + + fn packets_received(&self) -> u64 { + 0 + } + + fn total_packets_received(&self) -> u64 { + 0 + } + + fn packets_transmitted(&self) -> u64 { + 0 + } + + fn total_packets_transmitted(&self) -> u64 { + 0 + } + + fn errors_on_received(&self) -> u64 { + 0 + } + + fn total_errors_on_received(&self) -> u64 { + 0 + } + + fn errors_on_transmitted(&self) -> u64 { + 0 + } + + fn total_errors_on_transmitted(&self) -> u64 { + 0 + } +} diff --git a/vendor/sysinfo-0.26.7/src/unknown/process.rs b/vendor/sysinfo-0.26.7/src/unknown/process.rs new file mode 100644 index 000000000..4a12ec829 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/unknown/process.rs @@ -0,0 +1,94 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskUsage, Gid, Pid, ProcessExt, 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") + } +} + +#[doc = include_str!("../../md_doc/process.md")] +pub struct Process { + pid: Pid, + parent: Option<Pid>, +} + +impl ProcessExt for Process { + fn kill_with(&self, _signal: Signal) -> Option<bool> { + None + } + + fn name(&self) -> &str { + "" + } + + fn cmd(&self) -> &[String] { + &[] + } + + fn exe(&self) -> &Path { + Path::new("") + } + + fn pid(&self) -> Pid { + self.pid + } + + fn environ(&self) -> &[String] { + &[] + } + + fn cwd(&self) -> &Path { + Path::new("") + } + + fn root(&self) -> &Path { + Path::new("") + } + + fn memory(&self) -> u64 { + 0 + } + + fn virtual_memory(&self) -> u64 { + 0 + } + + fn parent(&self) -> Option<Pid> { + self.parent + } + + fn status(&self) -> ProcessStatus { + ProcessStatus::Unknown(0) + } + + fn start_time(&self) -> u64 { + 0 + } + + fn run_time(&self) -> u64 { + 0 + } + + fn cpu_usage(&self) -> f32 { + 0.0 + } + + fn disk_usage(&self) -> DiskUsage { + DiskUsage::default() + } + + fn user_id(&self) -> Option<&Uid> { + None + } + + fn group_id(&self) -> Option<Gid> { + None + } + + fn wait(&self) {} +} diff --git a/vendor/sysinfo-0.26.7/src/unknown/system.rs b/vendor/sysinfo-0.26.7/src/unknown/system.rs new file mode 100644 index 000000000..c205cdf96 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/unknown/system.rs @@ -0,0 +1,182 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + sys::{component::Component, Cpu, Disk, Networks, Process}, + CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, +}; + +use std::collections::HashMap; + +declare_signals! { + (), + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + processes_list: HashMap<Pid, Process>, + networks: Networks, + global_cpu: Cpu, +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = false; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + fn new_with_specifics(_: RefreshKind) -> System { + System { + processes_list: Default::default(), + networks: Networks::new(), + global_cpu: Cpu::new(), + } + } + + fn refresh_memory(&mut self) {} + + fn refresh_cpu_specifics(&mut self, _refresh_kind: CpuRefreshKind) {} + + fn refresh_components_list(&mut self) {} + + fn refresh_processes_specifics(&mut self, _refresh_kind: ProcessRefreshKind) {} + + fn refresh_process_specifics(&mut self, _pid: Pid, _refresh_kind: ProcessRefreshKind) -> bool { + false + } + + fn refresh_disks_list(&mut self) {} + + fn refresh_users_list(&mut self) {} + + // COMMON PART + // + // Need to be moved into a "common" file to avoid duplication. + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.processes_list + } + + fn process(&self, _pid: Pid) -> Option<&Process> { + None + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn global_cpu_info(&self) -> &Cpu { + &self.global_cpu + } + + fn cpus(&self) -> &[Cpu] { + &[] + } + + fn physical_core_count(&self) -> Option<usize> { + None + } + + fn total_memory(&self) -> u64 { + 0 + } + + fn free_memory(&self) -> u64 { + 0 + } + + fn available_memory(&self) -> u64 { + 0 + } + + fn used_memory(&self) -> u64 { + 0 + } + + fn total_swap(&self) -> u64 { + 0 + } + + fn free_swap(&self) -> u64 { + 0 + } + + fn used_swap(&self) -> u64 { + 0 + } + + fn components(&self) -> &[Component] { + &[] + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut [] + } + + fn disks(&self) -> &[Disk] { + &[] + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut [] + } + + fn sort_disks_by<F>(&mut self, _compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + // does nothing. + } + + fn uptime(&self) -> u64 { + 0 + } + + fn boot_time(&self) -> u64 { + 0 + } + + fn load_average(&self) -> LoadAvg { + LoadAvg { + one: 0., + five: 0., + fifteen: 0., + } + } + + fn users(&self) -> &[User] { + &[] + } + + fn name(&self) -> Option<String> { + None + } + + fn long_os_version(&self) -> Option<String> { + None + } + + fn kernel_version(&self) -> Option<String> { + None + } + + fn os_version(&self) -> Option<String> { + None + } + + fn distribution_id(&self) -> String { + std::env::consts::OS.to_owned() + } + + fn host_name(&self) -> Option<String> { + None + } +} + +impl Default for System { + fn default() -> System { + System::new() + } +} diff --git a/vendor/sysinfo-0.26.7/src/users.rs b/vendor/sysinfo-0.26.7/src/users.rs new file mode 100644 index 000000000..1891ec7cc --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/users.rs @@ -0,0 +1,97 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + common::{Gid, Uid}, + User, +}; + +use libc::{getgrgid, getgrouplist}; +use std::fs::File; +use std::io::Read; + +pub fn get_users_list() -> Vec<User> { + let mut s = String::new(); + let mut ngroups = 100; + let mut groups = vec![0; ngroups as usize]; + + let _ = File::open("/etc/passwd").and_then(|mut f| f.read_to_string(&mut s)); + s.lines() + .filter_map(|line| { + 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) { + if let Some(command) = parts.last() { + if command.is_empty() + || command.ends_with("/false") + || command.ends_with("/nologin") + { + // We don't want "fake" users so in case the user command is "bad", we + // ignore this user. + return None; + } + let mut c_user = username.as_bytes().to_vec(); + c_user.push(0); + loop { + let mut current = ngroups; + + unsafe { + if getgrouplist( + c_user.as_ptr() as *const _, + group_id, + groups.as_mut_ptr(), + &mut current, + ) == -1 + { + if current > ngroups { + groups.resize(current as _, 0); + ngroups = current; + continue; + } + // It really failed, let's move on... + return None; + } + // Let's get all the group names! + return Some(User { + uid: Uid(uid), + gid: Gid(group_id), + name: username.to_owned(), + groups: groups[..current as usize] + .iter() + .filter_map(|id| { + let g = getgrgid(*id as _); + if g.is_null() { + return None; + } + let mut group_name = Vec::new(); + let c_group_name = (*g).gr_name; + let mut x = 0; + loop { + let c = *c_group_name.offset(x); + if c == 0 { + break; + } + group_name.push(c as u8); + x += 1; + } + String::from_utf8(group_name).ok() + }) + .collect(), + }); + } + } + } + } + } + } + None + }) + .collect() +} + +#[inline] +fn parse_id(id: &str) -> Option<u32> { + id.parse::<u32>().ok() +} diff --git a/vendor/sysinfo-0.26.7/src/utils.rs b/vendor/sysinfo-0.26.7/src/utils.rs new file mode 100644 index 000000000..70d96d9fa --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/utils.rs @@ -0,0 +1,47 @@ +// 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( + all( + any( + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "windows", + target_os = "freebsd", + ), + feature = "multithread" + ), + not(all(target_os = "macos", feature = "apple-sandbox")), + not(feature = "unknown-ci") +))] +pub(crate) fn into_iter<T>(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(all( + all( + any( + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "windows", + target_os = "freebsd", + ), + not(feature = "multithread") + ), + not(feature = "unknown-ci"), + not(all(target_os = "macos", feature = "apple-sandbox")) +))] +pub(crate) fn into_iter<T>(val: T) -> T::IntoIter +where + T: IntoIterator, +{ + val.into_iter() +} diff --git a/vendor/sysinfo-0.26.7/src/windows/component.rs b/vendor/sysinfo-0.26.7/src/windows/component.rs new file mode 100644 index 000000000..502594e38 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/windows/component.rs @@ -0,0 +1,374 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::ComponentExt; + +use std::ptr::null_mut; + +use winapi::shared::rpcdce::{ + RPC_C_AUTHN_LEVEL_CALL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + RPC_C_IMP_LEVEL_IMPERSONATE, +}; +use winapi::shared::winerror::{FAILED, SUCCEEDED, S_FALSE, S_OK}; +use winapi::shared::wtypesbase::CLSCTX_INPROC_SERVER; +use winapi::um::combaseapi::{ + CoCreateInstance, CoInitializeEx, CoInitializeSecurity, CoSetProxyBlanket, CoUninitialize, +}; +use winapi::um::oaidl::VARIANT; +use winapi::um::objidl::EOAC_NONE; +use winapi::um::oleauto::{SysAllocString, SysFreeString, VariantClear}; +use winapi::um::wbemcli::{ + CLSID_WbemLocator, IEnumWbemClassObject, IID_IWbemLocator, IWbemClassObject, IWbemLocator, + IWbemServices, WBEM_FLAG_FORWARD_ONLY, WBEM_FLAG_NONSYSTEM_ONLY, WBEM_FLAG_RETURN_IMMEDIATELY, +}; + +#[doc = include_str!("../../md_doc/component.md")] +pub struct Component { + temperature: f32, + max: f32, + critical: Option<f32>, + label: String, + connection: Option<Connection>, +} + +impl Component { + /// Creates a new `Component` with the given information. + fn new() -> Option<Component> { + 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)| Component { + temperature, + label: "Computer".to_owned(), + max: temperature, + critical, + connection: Some(c), + }) + } +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + self.critical + } + + fn label(&self) -> &str { + &self.label + } + + 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) fn get_components() -> Vec<Component> { + match Component::new() { + Some(c) => vec![c], + None => Vec::new(), + } +} + +struct Instance(*mut IWbemLocator); + +impl Drop for Instance { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +struct ServerConnection(*mut IWbemServices); + +impl Drop for ServerConnection { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +struct Enumerator(*mut IEnumWbemClassObject); + +impl Drop for Enumerator { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +macro_rules! bstr { + ($($x:expr),*) => {{ + let x: &[u16] = &[$($x as u16),*, 0]; + SysAllocString(x.as_ptr()) + }} +} + +struct Connection { + instance: Option<Instance>, + server_connection: Option<ServerConnection>, + enumerator: Option<Enumerator>, + 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<Connection> { + unsafe { + let val = CoInitializeEx(null_mut(), 0); + Some(Connection { + instance: None, + server_connection: None, + enumerator: None, + initialized: val == S_OK || val == S_FALSE, + }) + } + } + + fn initialize_security(self) -> Option<Connection> { + unsafe { + if FAILED(CoInitializeSecurity( + null_mut(), + -1, + null_mut(), + null_mut(), + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + null_mut(), + EOAC_NONE, + null_mut(), + )) { + None + } else { + Some(self) + } + } + } + + fn create_instance(mut self) -> Option<Connection> { + let mut p_loc = null_mut(); + + unsafe { + if FAILED(CoCreateInstance( + &CLSID_WbemLocator as *const _, + null_mut(), + CLSCTX_INPROC_SERVER, + &IID_IWbemLocator as *const _, + &mut p_loc as *mut _ as *mut _, + )) { + None + } else { + self.instance = Some(Instance(p_loc)); + Some(self) + } + } + } + + fn connect_server(mut self) -> Option<Connection> { + let mut p_svc = null_mut(); + + if let Some(ref instance) = self.instance { + unsafe { + // "root\WMI" + let s = bstr!('r', 'o', 'o', 't', '\\', 'W', 'M', 'I'); + let res = (*instance.0).ConnectServer( + s, + null_mut(), + null_mut(), + null_mut(), + 0, + null_mut(), + null_mut(), + &mut p_svc as *mut _, + ); + SysFreeString(s); + if FAILED(res) { + return None; + } + } + } else { + return None; + } + self.server_connection = Some(ServerConnection(p_svc)); + Some(self) + } + + fn set_proxy_blanket(self) -> Option<Connection> { + if let Some(ref server_connection) = self.server_connection { + unsafe { + if FAILED(CoSetProxyBlanket( + server_connection.0 as *mut _, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + null_mut(), + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + null_mut(), + EOAC_NONE, + )) { + return None; + } + } + } else { + return None; + } + Some(self) + } + + fn exec_query(mut self) -> Option<Connection> { + let mut p_enumerator = null_mut(); + + if let Some(ref server_connection) = self.server_connection { + unsafe { + // "WQL" + let s = bstr!('W', 'Q', 'L'); // query kind + // "SELECT * FROM MSAcpi_ThermalZoneTemperature" + let query = bstr!( + 'S', 'E', 'L', 'E', 'C', 'T', ' ', '*', ' ', 'F', 'R', 'O', 'M', ' ', 'M', 'S', + 'A', 'c', 'p', 'i', '_', 'T', 'h', 'e', 'r', 'm', 'a', 'l', 'Z', 'o', 'n', 'e', + 'T', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', 'r', 'e' + ); + let hres = (*server_connection.0).ExecQuery( + s, + query, + (WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY) as _, + null_mut(), + &mut p_enumerator as *mut _, + ); + SysFreeString(s); + SysFreeString(query); + if FAILED(hres) { + return None; + } + } + } else { + return None; + } + self.enumerator = Some(Enumerator(p_enumerator)); + Some(self) + } + + fn temperature(&mut self, get_critical: bool) -> Option<(f32, Option<f32>)> { + use winapi::um::wbemcli::WBEM_INFINITE; + + let p_enum = match self.enumerator.take() { + Some(x) => x, + None => { + return None; + } + }; + let mut p_obj: *mut IWbemClassObject = null_mut(); + let mut nb_returned = 0; + + unsafe { + (*p_enum.0).Next( + WBEM_INFINITE as _, // Time out + 1, // One object + &mut p_obj as *mut _, + &mut nb_returned, + ); + + if nb_returned == 0 { + return None; // not enough rights I suppose... + } + + (*p_obj).BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY as _); + + let mut p_val = std::mem::MaybeUninit::<VARIANT>::uninit(); + // "CurrentTemperature" + let temp = bstr!( + 'C', 'u', 'r', 'r', 'e', 'n', 't', 'T', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', + 'r', 'e' + ); + let res = (*p_obj).Get(temp, 0, p_val.as_mut_ptr(), null_mut(), null_mut()); + let mut p_val = p_val.assume_init(); + + SysFreeString(temp); + VariantClear(&mut p_val as *mut _ as *mut _); + + let temp = if SUCCEEDED(res) { + // temperature is given in tenth of degrees Kelvin + (p_val.n1.decVal().Lo64 / 10) as f32 - 273.15 + } else { + (*p_obj).Release(); + return None; + }; + + let mut critical = None; + if get_critical { + // "CriticalPoint" + let crit = bstr!( + 'C', 'r', 'i', 't', 'i', 'c', 'a', 'l', 'T', 'r', 'i', 'p', 'P', 'o', 'i', 'n', + 't' + ); + let res = (*p_obj).Get(crit, 0, &mut p_val, null_mut(), null_mut()); + + SysFreeString(crit); + VariantClear(&mut p_val as *mut _ as *mut _); + + if SUCCEEDED(res) { + // temperature is given in tenth of degrees Kelvin + critical = Some((p_val.n1.decVal().Lo64 / 10) as f32 - 273.15); + } + } + (*p_obj).Release(); + 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(); + } + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/windows/cpu.rs b/vendor/sysinfo-0.26.7/src/windows/cpu.rs new file mode 100644 index 000000000..bbaa27ad7 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/windows/cpu.rs @@ -0,0 +1,548 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::tools::KeyHandler; +use crate::{CpuExt, CpuRefreshKind, LoadAvg}; + +use std::collections::HashMap; +use std::io::Error; +use std::mem; +use std::ops::DerefMut; +use std::ptr::null_mut; +use std::sync::Mutex; + +use ntapi::ntpoapi::PROCESSOR_POWER_INFORMATION; + +use winapi::shared::minwindef::FALSE; +use winapi::shared::winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS}; +use winapi::um::handleapi::CloseHandle; +use winapi::um::pdh::{ + PdhAddEnglishCounterA, PdhAddEnglishCounterW, PdhCloseQuery, PdhCollectQueryData, + PdhCollectQueryDataEx, PdhGetFormattedCounterValue, PdhOpenQueryA, PdhRemoveCounter, + PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE, PDH_HCOUNTER, PDH_HQUERY, +}; +use winapi::um::powerbase::CallNtPowerInformation; +use winapi::um::synchapi::CreateEventA; +use winapi::um::sysinfoapi::GetLogicalProcessorInformationEx; +use winapi::um::sysinfoapi::SYSTEM_INFO; +use winapi::um::winbase::{RegisterWaitForSingleObject, INFINITE}; +use winapi::um::winnt::{ + ProcessorInformation, RelationAll, RelationProcessorCore, BOOLEAN, HANDLE, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PVOID, 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<Mutex<Option<LoadAvg>>> = + 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: PVOID, _: BOOLEAN) { + let mut display_value = mem::MaybeUninit::<PDH_FMT_COUNTERVALUE>::uninit(); + + if PdhGetFormattedCounterValue( + counter as _, + PDH_FMT_DOUBLE, + null_mut(), + display_value.as_mut_ptr(), + ) != ERROR_SUCCESS as _ + { + 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.u.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<Option<LoadAvg>> { + // You can see the original implementation here: https://github.com/giampaolo/psutil + let mut query = null_mut(); + + if PdhOpenQueryA(null_mut(), 0, &mut query) != ERROR_SUCCESS as _ { + sysinfo_debug!("init_load_avg: PdhOpenQueryA failed"); + return Mutex::new(None); + } + + let mut counter: PDH_HCOUNTER = mem::zeroed(); + if PdhAddEnglishCounterA( + query, + b"\\System\\Cpu Queue Length\0".as_ptr() as _, + 0, + &mut counter, + ) != ERROR_SUCCESS as _ + { + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: failed to get CPU queue length"); + return Mutex::new(None); + } + + let event = CreateEventA(null_mut(), FALSE, FALSE, b"LoadUpdateEvent\0".as_ptr() as _); + if event.is_null() { + 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 as _ { + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: PdhCollectQueryDataEx failed"); + return Mutex::new(None); + } + + let mut wait_handle = null_mut(); + if RegisterWaitForSingleObject( + &mut wait_handle, + event, + Some(load_avg_callback), + counter as _, + INFINITE, + WT_EXECUTEDEFAULT, + ) == 0 + { + PdhRemoveCounter(counter); + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: RegisterWaitForSingleObject failed"); + Mutex::new(None) + } else { + Mutex::new(Some(LoadAvg::default())) + } +} + +struct InternalQuery { + query: PDH_HQUERY, + event: HANDLE, + data: HashMap<String, PDH_HCOUNTER>, +} + +unsafe impl Send for InternalQuery {} +unsafe impl Sync for InternalQuery {} + +impl Drop for InternalQuery { + fn drop(&mut self) { + unsafe { + for (_, counter) in self.data.iter() { + PdhRemoveCounter(*counter); + } + + if !self.event.is_null() { + CloseHandle(self.event); + } + + if !self.query.is_null() { + PdhCloseQuery(self.query); + } + } + } +} + +pub(crate) struct Query { + internal: InternalQuery, +} + +impl Query { + pub fn new() -> Option<Query> { + let mut query = null_mut(); + unsafe { + if PdhOpenQueryA(null_mut(), 0, &mut query) == ERROR_SUCCESS as i32 { + let q = InternalQuery { + query, + event: null_mut(), + 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<f32> { + if let Some(counter) = self.internal.data.get(name) { + unsafe { + let mut display_value = mem::MaybeUninit::<PDH_FMT_COUNTERVALUE>::uninit(); + let counter: PDH_HCOUNTER = *counter; + + let ret = PdhGetFormattedCounterValue( + counter, + PDH_FMT_DOUBLE, + null_mut(), + display_value.as_mut_ptr(), + ) as u32; + let display_value = display_value.assume_init(); + return if ret == ERROR_SUCCESS as _ { + let data = *display_value.u.doubleValue(); + Some(data 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<u16>) -> bool { + if self.internal.data.contains_key(name) { + sysinfo_debug!("Query::add_english_counter: doesn't have key `{:?}`", name); + return false; + } + unsafe { + let mut counter: PDH_HCOUNTER = std::mem::zeroed(); + let ret = PdhAddEnglishCounterW(self.internal.query, getter.as_ptr(), 0, &mut counter); + if ret == ERROR_SUCCESS as _ { + self.internal.data.insert(name.clone(), counter); + } else { + sysinfo_debug!( + "Query::add_english_counter: failed to add counter '{}': {:x}...", + name, + ret, + ); + return false; + } + } + true + } + + pub fn refresh(&self) { + unsafe { + if PdhCollectQueryData(self.internal.query) != ERROR_SUCCESS as _ { + sysinfo_debug!("failed to refresh CPU data"); + } + } + } +} + +pub(crate) struct CpusWrapper { + global: Cpu, + cpus: Vec<Cpu>, + got_cpu_frequency: bool, +} + +impl CpusWrapper { + pub fn new() -> Self { + Self { + global: Cpu::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() { + let (cpus, vendor_id, brand) = super::tools::init_cpus(refresh_kind); + self.cpus = cpus; + self.global.vendor_id = vendor_id; + self.global.brand = brand; + 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<Item = &mut Cpu> { + 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.set_frequency(frequency); + } + self.got_cpu_frequency = true; + } +} + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + name: String, + cpu_usage: f32, + key_used: Option<KeyHandler>, + vendor_id: String, + brand: String, + frequency: u64, +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + fn brand(&self) -> &str { + &self.brand + } +} + +impl Cpu { + pub(crate) fn new_with_values( + name: String, + vendor_id: String, + brand: String, + frequency: u64, + ) -> Cpu { + Cpu { + 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 { + use winapi::um::winnt; + // https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info + unsafe { + match info.u.s().wProcessorArchitecture { + winnt::PROCESSOR_ARCHITECTURE_INTEL => "Intel x86", + winnt::PROCESSOR_ARCHITECTURE_MIPS => "MIPS", + winnt::PROCESSOR_ARCHITECTURE_ALPHA => "RISC Alpha", + winnt::PROCESSOR_ARCHITECTURE_PPC => "PPC", + winnt::PROCESSOR_ARCHITECTURE_SHX => "SHX", + winnt::PROCESSOR_ARCHITECTURE_ARM => "ARM", + winnt::PROCESSOR_ARCHITECTURE_IA64 => "Intel Itanium-based x64", + winnt::PROCESSOR_ARCHITECTURE_ALPHA64 => "RISC Alpha x64", + winnt::PROCESSOR_ARCHITECTURE_MSIL => "MSIL", + winnt::PROCESSOR_ARCHITECTURE_AMD64 => "(Intel or AMD) x64", + winnt::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 => "Intel Itanium-based x86", + winnt::PROCESSOR_ARCHITECTURE_NEUTRAL => "unknown", + winnt::PROCESSOR_ARCHITECTURE_ARM64 => "ARM x64", + winnt::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 => "ARM", + winnt::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<u8>, 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::<u32>() * 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::<u32>()); + 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<KeyHandler> { + &mut p.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<u64> { + let size = nb_cpus * mem::size_of::<PROCESSOR_POWER_INFORMATION>(); + let mut infos: Vec<PROCESSOR_POWER_INFORMATION> = Vec::with_capacity(nb_cpus); + + unsafe { + if CallNtPowerInformation( + ProcessorInformation, + null_mut(), + 0, + infos.as_mut_ptr() as _, + size as _, + ) == 0 + { + infos.set_len(nb_cpus); + // infos.Number + return infos + .into_iter() + .map(|i| i.CurrentMhz as u64) + .collect::<Vec<_>>(); + } + } + sysinfo_debug!("get_frequencies: CallNtPowerInformation failed"); + vec![0; nb_cpus] +} + +pub(crate) fn get_physical_core_count() -> Option<usize> { + // 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 { + GetLogicalProcessorInformationEx(RelationAll, null_mut(), &mut needed_size); + + let mut buf: Vec<u8> = Vec::with_capacity(needed_size as _); + + loop { + if GetLogicalProcessorInformationEx( + RelationAll, + buf.as_mut_ptr() as *mut _, + &mut needed_size, + ) == FALSE + { + 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 as _ => {} + _ => { + sysinfo_debug!( + "get_physical_core_count: GetLogicalCpuInformationEx failed" + ); + return None; + } + } + } else { + break; + } + buf.reserve(needed_size as usize - buf.capacity()); + } + + 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 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX); + i += p.Size as usize; + if p.Relationship == RelationProcessorCore { + // Only count the physical cores. + count += 1; + } + } + Some(count) + } +} diff --git a/vendor/sysinfo-0.26.7/src/windows/disk.rs b/vendor/sysinfo-0.26.7/src/windows/disk.rs new file mode 100644 index 000000000..215fb8c58 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/windows/disk.rs @@ -0,0 +1,255 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskExt, DiskType}; + +use std::ffi::{OsStr, OsString}; +use std::mem::size_of; +use std::path::Path; + +use winapi::ctypes::c_void; +use winapi::shared::minwindef::{DWORD, MAX_PATH}; +use winapi::um::fileapi::{ + CreateFileW, GetDiskFreeSpaceExW, GetDriveTypeW, GetLogicalDrives, GetVolumeInformationW, + OPEN_EXISTING, +}; +use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; +use winapi::um::ioapiset::DeviceIoControl; +use winapi::um::winbase::{DRIVE_FIXED, DRIVE_REMOVABLE}; +use winapi::um::winioctl::{ + PropertyStandardQuery, StorageDeviceSeekPenaltyProperty, IOCTL_STORAGE_QUERY_PROPERTY, + STORAGE_PROPERTY_QUERY, +}; +use winapi::um::winnt::{BOOLEAN, FILE_SHARE_READ, FILE_SHARE_WRITE, HANDLE, ULARGE_INTEGER}; + +#[doc = include_str!("../../md_doc/disk.md")] +pub struct Disk { + type_: DiskType, + name: OsString, + file_system: Vec<u8>, + mount_point: Vec<u16>, + s_mount_point: String, + total_space: u64, + available_space: u64, + is_removable: bool, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + self.type_ + } + + fn name(&self) -> &OsStr { + &self.name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + Path::new(&self.s_mount_point) + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + fn refresh(&mut self) -> bool { + if self.total_space != 0 { + unsafe { + let mut tmp: ULARGE_INTEGER = std::mem::zeroed(); + if GetDiskFreeSpaceExW( + self.mount_point.as_ptr(), + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut tmp, + ) != 0 + { + self.available_space = *tmp.QuadPart(); + return true; + } + } + } + false + } +} + +struct HandleWrapper(HANDLE); + +impl HandleWrapper { + unsafe fn new(drive_name: &[u16], open_rights: DWORD) -> Option<Self> { + let handle = CreateFileW( + drive_name.as_ptr(), + open_rights, + FILE_SHARE_READ | FILE_SHARE_WRITE, + std::ptr::null_mut(), + OPEN_EXISTING, + 0, + std::ptr::null_mut(), + ); + if handle == INVALID_HANDLE_VALUE { + CloseHandle(handle); + None + } else { + Some(Self(handle)) + } + } +} + +impl Drop for HandleWrapper { + fn drop(&mut self) { + unsafe { + CloseHandle(self.0); + } + } +} + +unsafe fn get_drive_size(mount_point: &[u16]) -> Option<(u64, u64)> { + let mut total_size: ULARGE_INTEGER = std::mem::zeroed(); + let mut available_space: ULARGE_INTEGER = std::mem::zeroed(); + if GetDiskFreeSpaceExW( + mount_point.as_ptr(), + std::ptr::null_mut(), + &mut total_size, + &mut available_space, + ) != 0 + { + Some(( + *total_size.QuadPart() as _, + *available_space.QuadPart() as _, + )) + } else { + None + } +} + +// FIXME: To be removed once <https://github.com/retep998/winapi-rs/pull/1028> has been merged. +#[allow(non_snake_case)] +#[repr(C)] +struct DEVICE_SEEK_PENALTY_DESCRIPTOR { + Version: DWORD, + Size: DWORD, + IncursSeekPenalty: BOOLEAN, +} + +pub(crate) unsafe fn get_disks() -> Vec<Disk> { + let drives = GetLogicalDrives(); + if drives == 0 { + return Vec::new(); + } + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + crate::utils::into_iter(0..DWORD::BITS) + .filter_map(|x| { + if (drives >> x) & 1 == 0 { + return None; + } + let mount_point = [b'A' as u16 + x as u16, b':' as u16, b'\\' as u16, 0]; + + let drive_type = GetDriveTypeW(mount_point.as_ptr()); + + let is_removable = drive_type == DRIVE_REMOVABLE; + + if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE { + return None; + } + let mut name = [0u16; MAX_PATH + 1]; + let mut file_system = [0u16; 32]; + if GetVolumeInformationW( + mount_point.as_ptr(), + name.as_mut_ptr(), + name.len() as DWORD, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + file_system.as_mut_ptr(), + file_system.len() as DWORD, + ) == 0 + { + return None; + } + let mut pos = 0; + for x in name.iter() { + if *x == 0 { + break; + } + pos += 1; + } + let name = String::from_utf16_lossy(&name[..pos]); + let name = OsStr::new(&name); + + pos = 0; + for x in file_system.iter() { + if *x == 0 { + break; + } + pos += 1; + } + let file_system: Vec<u8> = file_system[..pos].iter().map(|x| *x as u8).collect(); + + let drive_name = [ + b'\\' as u16, + b'\\' as u16, + b'.' as u16, + b'\\' as u16, + b'A' as u16 + x as u16, + b':' as u16, + 0, + ]; + let handle = HandleWrapper::new(&drive_name, 0)?; + let (total_space, available_space) = get_drive_size(&mount_point)?; + if total_space == 0 { + return None; + } + let mut 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 type_ = if DeviceIoControl( + handle.0, + IOCTL_STORAGE_QUERY_PROPERTY, + &mut spq_trim as *mut STORAGE_PROPERTY_QUERY as *mut c_void, + size_of::<STORAGE_PROPERTY_QUERY>() as DWORD, + &mut result as *mut DEVICE_SEEK_PENALTY_DESCRIPTOR as *mut c_void, + size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as DWORD, + &mut dw_size, + std::ptr::null_mut(), + ) == 0 + || dw_size != size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as DWORD + { + DiskType::Unknown(-1) + } else { + let is_ssd = result.IncursSeekPenalty == 0; + if is_ssd { + DiskType::SSD + } else { + DiskType::HDD + } + }; + Some(Disk { + type_, + name: name.to_owned(), + file_system: file_system.to_vec(), + mount_point: mount_point.to_vec(), + s_mount_point: String::from_utf16_lossy(&mount_point[..mount_point.len() - 1]), + total_space, + available_space, + is_removable, + }) + }) + .collect::<Vec<_>>() +} diff --git a/vendor/sysinfo-0.26.7/src/windows/mod.rs b/vendor/sysinfo-0.26.7/src/windows/mod.rs new file mode 100644 index 000000000..805e85269 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/windows/mod.rs @@ -0,0 +1,18 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +mod component; +mod cpu; +mod disk; +mod network; +mod process; +mod system; +mod tools; +mod users; +mod utils; + +pub use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo-0.26.7/src/windows/network.rs b/vendor/sysinfo-0.26.7/src/windows/network.rs new file mode 100644 index 000000000..6a09a0490 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/windows/network.rs @@ -0,0 +1,249 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{NetworkExt, NetworksExt, NetworksIter}; + +use std::collections::{hash_map, HashMap}; + +use winapi::shared::ifdef::{MediaConnectStateDisconnected, NET_LUID}; +use winapi::shared::netioapi::{ + FreeMibTable, GetIfEntry2, GetIfTable2, MIB_IF_ROW2, PMIB_IF_TABLE2, +}; +use winapi::shared::winerror::NO_ERROR; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $new_val; + }}; +} + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Networks { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + #[allow(clippy::needless_lifetimes)] + fn iter<'a>(&'a self) -> NetworksIter<'a> { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + let mut table: PMIB_IF_TABLE2 = std::ptr::null_mut(); + + unsafe { + if GetIfTable2(&mut table) != NO_ERROR { + return; + } + + for (_, data) in self.interfaces.iter_mut() { + data.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 mut interface = e.get_mut(); + 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 { + 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, + updated: true, + }); + } + } + } + FreeMibTable(table as _); + } + // Remove interfaces which are gone. + self.interfaces.retain(|_, d| d.updated); + } + + fn refresh(&mut self) { + let entry = std::mem::MaybeUninit::<MIB_IF_ROW2>::zeroed(); + + unsafe { + let mut entry = entry.assume_init(); + for (_, interface) in self.interfaces.iter_mut() { + entry.InterfaceLuid = interface.id; + entry.InterfaceIndex = 0; // to prevent the function to pick this one as index + if GetIfEntry2(&mut entry) != NO_ERROR { + 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); + } + } + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +pub struct NetworkData { + id: NET_LUID, + 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, +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.current_in.saturating_sub(self.old_in) + } + + fn total_received(&self) -> u64 { + self.current_in + } + + fn transmitted(&self) -> u64 { + self.current_out.saturating_sub(self.old_out) + } + + fn total_transmitted(&self) -> u64 { + self.current_out + } + + fn packets_received(&self) -> u64 { + self.packets_in.saturating_sub(self.old_packets_in) + } + + fn total_packets_received(&self) -> u64 { + self.packets_in + } + + fn packets_transmitted(&self) -> u64 { + self.packets_out.saturating_sub(self.old_packets_out) + } + + fn total_packets_transmitted(&self) -> u64 { + self.packets_out + } + + fn errors_on_received(&self) -> u64 { + self.errors_in.saturating_sub(self.old_errors_in) + } + + fn total_errors_on_received(&self) -> u64 { + self.errors_in + } + + fn errors_on_transmitted(&self) -> u64 { + self.errors_out.saturating_sub(self.old_errors_out) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.errors_out + } +} diff --git a/vendor/sysinfo-0.26.7/src/windows/process.rs b/vendor/sysinfo-0.26.7/src/windows/process.rs new file mode 100644 index 000000000..7561c658f --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/windows/process.rs @@ -0,0 +1,1065 @@ +// 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::to_str; +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; + +use std::ffi::OsString; +use std::fmt; +use std::mem::{size_of, zeroed, MaybeUninit}; +use std::ops::Deref; +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, memcpy}; + +use ntapi::ntpebteb::PEB; +use ntapi::ntwow64::{PEB32, PRTL_USER_PROCESS_PARAMETERS32, RTL_USER_PROCESS_PARAMETERS32}; +use once_cell::sync::Lazy; + +use ntapi::ntpsapi::{ + NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation, + ProcessWow64Information, PROCESSINFOCLASS, PROCESS_BASIC_INFORMATION, +}; +use ntapi::ntrtl::{RtlGetVersion, PRTL_USER_PROCESS_PARAMETERS, RTL_USER_PROCESS_PARAMETERS}; +use winapi::shared::basetsd::SIZE_T; +use winapi::shared::minwindef::{DWORD, FALSE, FILETIME, LPVOID, MAX_PATH, TRUE, ULONG}; +use winapi::shared::ntdef::{NT_SUCCESS, UNICODE_STRING}; +use winapi::shared::ntstatus::{ + STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH, +}; +use winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER; +use winapi::um::errhandlingapi::GetLastError; +use winapi::um::handleapi::CloseHandle; +use winapi::um::heapapi::{GetProcessHeap, HeapAlloc, HeapFree}; +use winapi::um::memoryapi::{ReadProcessMemory, VirtualQueryEx}; +use winapi::um::processthreadsapi::{ + GetProcessTimes, GetSystemTimes, OpenProcess, OpenProcessToken, +}; +use winapi::um::psapi::{ + EnumProcessModulesEx, GetModuleBaseNameW, GetModuleFileNameExW, GetProcessMemoryInfo, + LIST_MODULES_ALL, PROCESS_MEMORY_COUNTERS, PROCESS_MEMORY_COUNTERS_EX, +}; +use winapi::um::securitybaseapi::GetTokenInformation; +use winapi::um::winbase::{GetProcessIoCounters, CREATE_NO_WINDOW}; +use winapi::um::winnt::{ + TokenUser, HANDLE, HEAP_ZERO_MEMORY, IO_COUNTERS, MEMORY_BASIC_INFORMATION, + PROCESS_QUERY_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ, + RTL_OSVERSIONINFOEXW, TOKEN_QUERY, TOKEN_USER, ULARGE_INTEGER, +}; + +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<HandleWrapper> { + if pid.0 == 0 { + return None; + } + let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + + HandleWrapper::new(unsafe { OpenProcess(options, FALSE, pid.0 as DWORD) }) + .or_else(|| { + sysinfo_debug!("OpenProcess failed, error: {:?}", unsafe { GetLastError() }); + HandleWrapper::new(unsafe { + OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid.0 as DWORD) + }) + }) + .or_else(|| { + sysinfo_debug!("OpenProcess limited failed, error: {:?}", unsafe { + GetLastError() + }); + None + }) +} + +unsafe fn get_process_user_id( + handle: &HandleWrapper, + refresh_kind: ProcessRefreshKind, +) -> Option<Uid> { + struct HeapWrap<T>(*mut T); + + impl<T> HeapWrap<T> { + unsafe fn new(size: DWORD) -> Option<Self> { + let ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size as _) as *mut T; + if ptr.is_null() { + sysinfo_debug!("HeapAlloc failed"); + None + } else { + Some(Self(ptr)) + } + } + } + + impl<T> Drop for HeapWrap<T> { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + HeapFree(GetProcessHeap(), 0, self.0 as *mut _); + } + } + } + } + + if !refresh_kind.user() { + return None; + } + + let mut token = null_mut(); + + if OpenProcessToken(**handle, TOKEN_QUERY, &mut token) == 0 { + sysinfo_debug!("OpenProcessToken failed"); + return None; + } + + let token = HandleWrapper::new(token)?; + + let mut size = 0; + + if GetTokenInformation(*token, TokenUser, null_mut(), 0, &mut size) == 0 { + let err = GetLastError(); + if err != ERROR_INSUFFICIENT_BUFFER { + sysinfo_debug!("GetTokenInformation failed, error: {:?}", err); + return None; + } + } + + let ptu: HeapWrap<TOKEN_USER> = HeapWrap::new(size)?; + + if GetTokenInformation(*token, TokenUser, ptu.0 as *mut _, size, &mut size) == 0 { + sysinfo_debug!("GetTokenInformation failed, error: {:?}", GetLastError()); + return None; + } + + let mut name_use = 0; + let mut name = [0u16; 256]; + let mut domain_name = [0u16; 256]; + let mut size = 256; + + if winapi::um::winbase::LookupAccountSidW( + std::ptr::null_mut(), + (*ptu.0).User.Sid, + name.as_mut_ptr(), + &mut size, + domain_name.as_mut_ptr(), + &mut size, + &mut name_use, + ) == 0 + { + sysinfo_debug!( + "LookupAccountSidW failed: {:?}", + winapi::um::errhandlingapi::GetLastError(), + ); + None + } else { + Some(Uid(to_str(name.as_mut_ptr()).into_boxed_str())) + } +} + +struct HandleWrapper(HANDLE); + +impl HandleWrapper { + fn new(handle: HANDLE) -> Option<Self> { + if handle.is_null() { + None + } else { + Some(Self(handle)) + } + } +} + +impl Deref for HandleWrapper { + type Target = HANDLE; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for HandleWrapper { + fn drop(&mut self) { + unsafe { + CloseHandle(self.0); + } + } +} + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for HandleWrapper {} +unsafe impl Sync for HandleWrapper {} + +#[doc = include_str!("../../md_doc/process.md")] +pub struct Process { + name: String, + cmd: Vec<String>, + exe: PathBuf, + pid: Pid, + user_id: Option<Uid>, + environ: Vec<String>, + cwd: PathBuf, + root: PathBuf, + pub(crate) memory: u64, + pub(crate) virtual_memory: u64, + parent: Option<Pid>, + status: ProcessStatus, + handle: Option<Arc<HandleWrapper>>, + 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<bool> = Lazy::new(|| unsafe { + let mut version_info: RTL_OSVERSIONINFOEXW = MaybeUninit::zeroed().assume_init(); + + version_info.dwOSVersionInfoSize = std::mem::size_of::<RTL_OSVERSIONINFOEXW>() as u32; + if !NT_SUCCESS(RtlGetVersion( + &mut version_info as *mut RTL_OSVERSIONINFOEXW as *mut _, + )) { + return true; + } + + // Windows 8.1 is 6.3 + version_info.dwMajorVersion > 6 + || version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 3 +}); + +unsafe fn get_process_name(process_handler: &HandleWrapper, h_mod: *mut c_void) -> String { + let mut process_name = [0u16; MAX_PATH + 1]; + + GetModuleBaseNameW( + **process_handler, + h_mod as _, + process_name.as_mut_ptr(), + MAX_PATH as DWORD + 1, + ); + null_terminated_wchar_to_string(&process_name) +} + +unsafe fn get_h_mod(process_handler: &HandleWrapper, h_mod: &mut *mut c_void) -> bool { + let mut cb_needed = 0; + EnumProcessModulesEx( + **process_handler, + h_mod as *mut *mut c_void as _, + size_of::<DWORD>() as DWORD, + &mut cb_needed, + LIST_MODULES_ALL, + ) != 0 +} + +unsafe fn get_exe(process_handler: &HandleWrapper) -> PathBuf { + let mut exe_buf = [0u16; MAX_PATH + 1]; + GetModuleFileNameExW( + **process_handler, + std::ptr::null_mut(), + exe_buf.as_mut_ptr(), + MAX_PATH as DWORD + 1, + ); + + PathBuf::from(null_terminated_wchar_to_string(&exe_buf)) +} + +impl Process { + pub(crate) fn new_from_pid( + pid: Pid, + now: u64, + refresh_kind: ProcessRefreshKind, + ) -> Option<Process> { + unsafe { + let process_handler = get_process_handler(pid)?; + let mut info: MaybeUninit<PROCESS_BASIC_INFORMATION> = MaybeUninit::uninit(); + if NtQueryInformationProcess( + *process_handler, + ProcessBasicInformation, + info.as_mut_ptr() as *mut _, + size_of::<PROCESS_BASIC_INFORMATION>() as _, + null_mut(), + ) != 0 + { + return None; + } + let info = info.assume_init(); + let mut h_mod = null_mut(); + + let name = if get_h_mod(&process_handler, &mut h_mod) { + get_process_name(&process_handler, h_mod) + } else { + String::new() + }; + + let exe = get_exe(&process_handler); + let mut root = exe.clone(); + root.pop(); + let (cmd, environ, cwd) = match get_process_params(&process_handler) { + Ok(args) => args, + Err(_e) => { + sysinfo_debug!("Failed to get process parameters: {}", _e); + (Vec::new(), Vec::new(), PathBuf::new()) + } + }; + let (start_time, run_time) = get_start_and_run_time(*process_handler, now); + let parent = if info.InheritedFromUniqueProcessId as usize != 0 { + Some(Pid(info.InheritedFromUniqueProcessId as _)) + } else { + None + }; + let user_id = get_process_user_id(&process_handler, refresh_kind); + Some(Process { + handle: Some(Arc::new(process_handler)), + name, + pid, + parent, + user_id, + cmd, + environ, + exe, + cwd, + root, + 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<Pid>, + memory: u64, + virtual_memory: u64, + name: String, + now: u64, + refresh_kind: ProcessRefreshKind, + ) -> Process { + if let Some(handle) = get_process_handler(pid) { + unsafe { + let exe = get_exe(&handle); + let mut root = exe.clone(); + root.pop(); + let (cmd, environ, cwd) = match get_process_params(&handle) { + Ok(args) => args, + Err(_e) => { + sysinfo_debug!("Failed to get process parameters: {}", _e); + (Vec::new(), Vec::new(), PathBuf::new()) + } + }; + let (start_time, run_time) = get_start_and_run_time(*handle, now); + let user_id = get_process_user_id(&handle, refresh_kind); + Process { + handle: Some(Arc::new(handle)), + name, + pid, + user_id, + parent, + cmd, + environ, + exe, + cwd, + root, + 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, + } + } + } else { + Process { + handle: None, + name, + pid, + user_id: None, + parent, + cmd: Vec::new(), + environ: Vec::new(), + exe: get_executable_path(pid), + cwd: PathBuf::new(), + root: PathBuf::new(), + status: ProcessStatus::Run, + memory, + virtual_memory, + cpu_usage: 0., + cpu_calc_values: CPUsageCalculationValues::new(), + start_time: 0, + run_time: 0, + 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, + ) { + if refresh_kind.cpu() { + compute_cpu_usage(self, nb_cpus); + } + if refresh_kind.disk_usage() { + update_disk_usage(self); + } + self.run_time = now.saturating_sub(self.start_time()); + self.updated = true; + } + + pub(crate) fn get_handle(&self) -> Option<HANDLE> { + self.handle.as_ref().map(|h| ***h) + } + + pub(crate) fn get_start_time(&self) -> Option<u64> { + self.handle.as_ref().map(|handle| get_start_time(***handle)) + } +} + +impl ProcessExt for Process { + fn kill_with(&self, signal: Signal) -> Option<bool> { + super::system::convert_signal(signal)?; + let mut kill = process::Command::new("taskkill.exe"); + kill.arg("/PID").arg(self.pid.to_string()).arg("/F"); + kill.creation_flags(CREATE_NO_WINDOW); + match kill.output() { + Ok(o) => Some(o.status.success()), + Err(_) => Some(false), + } + } + + fn name(&self) -> &str { + &self.name + } + + fn cmd(&self) -> &[String] { + &self.cmd + } + + fn exe(&self) -> &Path { + self.exe.as_path() + } + + fn pid(&self) -> Pid { + self.pid + } + + fn environ(&self) -> &[String] { + &self.environ + } + + fn cwd(&self) -> &Path { + self.cwd.as_path() + } + + fn root(&self) -> &Path { + self.root.as_path() + } + + fn memory(&self) -> u64 { + self.memory + } + + fn virtual_memory(&self) -> u64 { + self.virtual_memory + } + + fn parent(&self) -> Option<Pid> { + self.parent + } + + fn status(&self) -> ProcessStatus { + self.status + } + + fn start_time(&self) -> u64 { + self.start_time + } + + fn run_time(&self) -> u64 { + self.run_time + } + + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn disk_usage(&self) -> DiskUsage { + DiskUsage { + written_bytes: self.written_bytes - self.old_written_bytes, + total_written_bytes: self.written_bytes, + read_bytes: self.read_bytes - self.old_read_bytes, + total_read_bytes: self.read_bytes, + } + } + + fn user_id(&self) -> Option<&Uid> { + self.user_id.as_ref() + } + + fn group_id(&self) -> Option<Gid> { + None + } + + 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"); + } + } +} + +#[inline] +unsafe fn get_process_times(handle: HANDLE) -> u64 { + let mut fstart: FILETIME = zeroed(); + let mut x = zeroed(); + + 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) +} + +#[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) + } +} + +#[allow(clippy::uninit_vec)] +unsafe fn ph_query_process_variable_size( + process_handle: &HandleWrapper, + process_information_class: PROCESSINFOCLASS, +) -> Option<Vec<u16>> { + let mut return_length = MaybeUninit::<ULONG>::uninit(); + + let mut status = NtQueryInformationProcess( + **process_handle, + process_information_class, + null_mut(), + 0, + return_length.as_mut_ptr() as *mut _, + ); + + if status != STATUS_BUFFER_OVERFLOW + && status != STATUS_BUFFER_TOO_SMALL + && status != STATUS_INFO_LENGTH_MISMATCH + { + return None; + } + + let mut return_length = return_length.assume_init(); + let buf_len = (return_length as usize) / 2; + let mut buffer: Vec<u16> = Vec::with_capacity(buf_len + 1); + buffer.set_len(buf_len); + + status = NtQueryInformationProcess( + **process_handle, + process_information_class, + buffer.as_mut_ptr() as *mut _, + return_length, + &mut return_length as *mut _, + ); + if !NT_SUCCESS(status) { + return None; + } + buffer.push(0); + Some(buffer) +} + +unsafe fn get_cmdline_from_buffer(buffer: *const u16) -> Vec<String> { + // Get argc and argv from the command line + let mut argc = MaybeUninit::<i32>::uninit(); + let argv_p = winapi::um::shellapi::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 { + let len = libc::wcslen(*arg); + let str_slice = std::slice::from_raw_parts(*arg, len); + res.push(String::from_utf16_lossy(str_slice)); + } + + winapi::um::winbase::LocalFree(argv_p as *mut _); + + res +} + +unsafe fn get_region_size(handle: &HandleWrapper, ptr: LPVOID) -> Result<usize, &'static str> { + let mut meminfo = MaybeUninit::<MEMORY_BASIC_INFORMATION>::uninit(); + if VirtualQueryEx( + **handle, + ptr, + meminfo.as_mut_ptr() as *mut _, + size_of::<MEMORY_BASIC_INFORMATION>(), + ) == 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) +} + +#[allow(clippy::uninit_vec)] +unsafe fn get_process_data( + handle: &HandleWrapper, + ptr: LPVOID, + size: usize, +) -> Result<Vec<u16>, &'static str> { + let mut buffer: Vec<u16> = Vec::with_capacity(size / 2 + 1); + buffer.set_len(size / 2); + if ReadProcessMemory( + **handle, + ptr as *mut _, + buffer.as_mut_ptr() as *mut _, + size, + null_mut(), + ) != TRUE + { + return Err("Unable to read process data"); + } + Ok(buffer) +} + +trait RtlUserProcessParameters { + fn get_cmdline(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str>; + fn get_cwd(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str>; + fn get_environ(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str>; +} + +macro_rules! impl_RtlUserProcessParameters { + ($t:ty) => { + impl RtlUserProcessParameters for $t { + fn get_cmdline(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'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: &HandleWrapper) -> Result<Vec<u16>, &'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: &HandleWrapper) -> Result<Vec<u16>, &'static str> { + let ptr = self.Environment; + unsafe { + let size = get_region_size(handle, ptr as LPVOID)?; + get_process_data(handle, ptr as _, size as _) + } + } + } + }; +} + +impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS32); +impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS); + +unsafe fn get_process_params( + handle: &HandleWrapper, +) -> Result<(Vec<String>, Vec<String>, PathBuf), &'static str> { + if !cfg!(target_pointer_width = "64") { + return Err("Non 64 bit targets are not supported"); + } + + // First check if target process is running in wow64 compatibility emulator + let mut pwow32info = MaybeUninit::<LPVOID>::uninit(); + let result = NtQueryInformationProcess( + **handle, + ProcessWow64Information, + pwow32info.as_mut_ptr() as *mut _, + size_of::<LPVOID>() as u32, + null_mut(), + ); + if !NT_SUCCESS(result) { + return Err("Unable to check WOW64 information about the process"); + } + let pwow32info = pwow32info.assume_init(); + + if pwow32info.is_null() { + // target is a 64 bit process + + let mut pbasicinfo = MaybeUninit::<PROCESS_BASIC_INFORMATION>::uninit(); + let result = NtQueryInformationProcess( + **handle, + ProcessBasicInformation, + pbasicinfo.as_mut_ptr() as *mut _, + size_of::<PROCESS_BASIC_INFORMATION>() as u32, + null_mut(), + ); + if !NT_SUCCESS(result) { + return Err("Unable to get basic process information"); + } + let pinfo = pbasicinfo.assume_init(); + + let mut peb = MaybeUninit::<PEB>::uninit(); + if ReadProcessMemory( + **handle, + pinfo.PebBaseAddress as *mut _, + peb.as_mut_ptr() as *mut _, + size_of::<PEB>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read process PEB"); + } + + let peb = peb.assume_init(); + + let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS>::uninit(); + if ReadProcessMemory( + **handle, + peb.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS as *mut _, + proc_params.as_mut_ptr() as *mut _, + size_of::<RTL_USER_PROCESS_PARAMETERS>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read process parameters"); + } + + let proc_params = proc_params.assume_init(); + return Ok(( + get_cmd_line(&proc_params, handle), + get_proc_env(&proc_params, handle), + get_cwd(&proc_params, handle), + )); + } + // target is a 32 bit process in wow64 mode + + let mut peb32 = MaybeUninit::<PEB32>::uninit(); + if ReadProcessMemory( + **handle, + pwow32info, + peb32.as_mut_ptr() as *mut _, + size_of::<PEB32>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read PEB32"); + } + let peb32 = peb32.assume_init(); + + let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS32>::uninit(); + if ReadProcessMemory( + **handle, + peb32.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS32 as *mut _, + proc_params.as_mut_ptr() as *mut _, + size_of::<RTL_USER_PROCESS_PARAMETERS32>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read 32 bit process parameters"); + } + let proc_params = proc_params.assume_init(); + Ok(( + get_cmd_line(&proc_params, handle), + get_proc_env(&proc_params, handle), + get_cwd(&proc_params, handle), + )) +} + +fn get_cwd<T: RtlUserProcessParameters>(params: &T, handle: &HandleWrapper) -> PathBuf { + match params.get_cwd(handle) { + Ok(buffer) => unsafe { PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())) }, + Err(_e) => { + sysinfo_debug!("get_cwd failed to get data: {}", _e); + PathBuf::new() + } + } +} + +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<T: RtlUserProcessParameters>( + params: &T, + handle: &HandleWrapper, +) -> Vec<String> { + match params.get_cmdline(handle) { + Ok(buffer) => unsafe { get_cmdline_from_buffer(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: &HandleWrapper) -> Vec<String> { + 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(buffer) + } else { + vec![] + } + } +} + +fn get_cmd_line<T: RtlUserProcessParameters>(params: &T, handle: &HandleWrapper) -> Vec<String> { + if *WINDOWS_8_1_OR_NEWER { + get_cmd_line_new(handle) + } else { + get_cmd_line_old(params, handle) + } +} + +fn get_proc_env<T: RtlUserProcessParameters>(params: &T, handle: &HandleWrapper) -> Vec<String> { + match params.get_environ(handle) { + Ok(buffer) => { + let equals = "=".encode_utf16().next().unwrap(); + let raw_env = buffer; + let mut result = Vec::new(); + 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) { + result.push( + OsString::from_wide(&raw_env[begin..end]) + .to_string_lossy() + .into_owned(), + ); + begin = end + 1; + } else { + break; + } + } + result + } + Err(_e) => { + sysinfo_debug!("get_proc_env failed to get data: {}", _e); + Vec::new() + } + } +} + +pub(crate) fn get_executable_path(_pid: Pid) -> PathBuf { + /*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(); + } + }*/ + PathBuf::new() +} + +#[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: +/// https://github.com/GuillaumeGomez/sysinfo/issues/459 +pub(crate) fn compute_cpu_usage(p: &mut Process, 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() { + GetProcessTimes( + handle, + &mut ftime as *mut FILETIME, + &mut ftime as *mut FILETIME, + &mut fsys as *mut FILETIME, + &mut fuser as *mut FILETIME, + ); + } + GetSystemTimes( + &mut fglobal_idle_time as *mut FILETIME, + &mut fglobal_kernel_time as *mut FILETIME, + &mut fglobal_user_time as *mut FILETIME, + ); + + let mut sys: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut sys as *mut ULARGE_INTEGER as *mut c_void, + &mut fsys as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + let mut user: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut user as *mut ULARGE_INTEGER as *mut c_void, + &mut fuser as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + let mut global_kernel_time: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut global_kernel_time as *mut ULARGE_INTEGER as *mut c_void, + &mut fglobal_kernel_time as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + let mut global_user_time: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut global_user_time as *mut ULARGE_INTEGER as *mut c_void, + &mut fglobal_user_time as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + + let sys = *sys.QuadPart(); + let user = *user.QuadPart(); + let global_kernel_time = *global_kernel_time.QuadPart(); + let global_user_time = *global_user_time.QuadPart(); + + 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 Process) { + let mut counters = MaybeUninit::<IO_COUNTERS>::uninit(); + + if let Some(handle) = p.get_handle() { + unsafe { + let ret = GetProcessIoCounters(handle, counters.as_mut_ptr()); + if ret == 0 { + 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 Process) { + 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 as *mut c_void + as *mut PROCESS_MEMORY_COUNTERS, + size_of::<PROCESS_MEMORY_COUNTERS_EX>() as DWORD, + ) != 0 + { + p.memory = pmc.WorkingSetSize as _; + p.virtual_memory = pmc.PrivateUsage as _; + } + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/windows/system.rs b/vendor/sysinfo-0.26.7/src/windows/system.rs new file mode 100644 index 000000000..643a7b4bc --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/windows/system.rs @@ -0,0 +1,678 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + CpuRefreshKind, LoadAvg, Networks, Pid, ProcessExt, ProcessRefreshKind, RefreshKind, SystemExt, + User, +}; +use winapi::um::winreg::HKEY_LOCAL_MACHINE; + +use crate::sys::component::{self, Component}; +use crate::sys::cpu::*; +use crate::sys::disk::{get_disks, Disk}; +use crate::sys::process::{get_start_time, update_memory, Process}; +use crate::sys::tools::*; +use crate::sys::users::get_users; +use crate::sys::utils::get_now; + +use crate::utils::into_iter; + +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::mem::{size_of, zeroed}; +use std::os::windows::ffi::OsStrExt; +use std::slice::from_raw_parts; +use std::time::SystemTime; + +use ntapi::ntexapi::{ + NtQuerySystemInformation, SystemProcessInformation, SYSTEM_PROCESS_INFORMATION, +}; +use winapi::ctypes::wchar_t; +use winapi::shared::minwindef::{DWORD, FALSE, HKEY, LPBYTE, TRUE}; +use winapi::shared::ntdef::{PVOID, ULONG}; +use winapi::shared::ntstatus::STATUS_INFO_LENGTH_MISMATCH; +use winapi::shared::winerror; +use winapi::um::minwinbase::STILL_ACTIVE; +use winapi::um::processthreadsapi::GetExitCodeProcess; +use winapi::um::psapi::{GetPerformanceInfo, PERFORMANCE_INFORMATION}; +use winapi::um::sysinfoapi::{ + ComputerNamePhysicalDnsHostname, GetComputerNameExW, GetTickCount64, GlobalMemoryStatusEx, + MEMORYSTATUSEX, +}; +use winapi::um::winnt::{HANDLE, KEY_READ}; +use winapi::um::winreg::{RegOpenKeyExW, RegQueryValueExW}; + +declare_signals! { + (), + Signal::Kill => (), + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: HashMap<Pid, Process>, + mem_total: u64, + mem_available: u64, + swap_total: u64, + swap_used: u64, + cpus: CpusWrapper, + components: Vec<Component>, + disks: Vec<Disk>, + query: Option<Query>, + networks: Networks, + boot_time: u64, + users: Vec<User>, +} + +static WINDOWS_ELEVEN_BUILD_NUMBER: u32 = 22000; + +impl System { + fn is_windows_eleven(&self) -> bool { + WINDOWS_ELEVEN_BUILD_NUMBER + <= self + .kernel_version() + .unwrap_or_default() + .parse() + .unwrap_or(0) + } +} + +// Useful for parallel iterations. +struct Wrap<T>(T); + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl<T> Send for Wrap<T> {} +unsafe impl<T> Sync for Wrap<T> {} + +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 + } + } +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + #[allow(non_snake_case)] + fn new_with_specifics(refreshes: RefreshKind) -> System { + let mut s = System { + process_list: HashMap::with_capacity(500), + mem_total: 0, + mem_available: 0, + swap_total: 0, + swap_used: 0, + cpus: CpusWrapper::new(), + components: Vec::new(), + disks: Vec::with_capacity(2), + query: None, + networks: Networks::new(), + boot_time: unsafe { boot_time() }, + users: Vec::new(), + }; + s.refresh_specifics(refreshes); + s + } + + 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)\% Processor 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})\% Processor Time"), + query, + get_key_used(proc_), + format!("{pos}_0"), + ); + } + } + } + if let Some(ref mut query) = self.query { + query.refresh(); + let mut used_time = None; + if let Some(ref key_used) = *get_key_used(self.cpus.global_cpu_mut()) { + used_time = Some( + query + .get(&key_used.unique_id) + .expect("global_key_idle disappeared"), + ); + } + if let Some(used_time) = used_time { + self.cpus.global_cpu_mut().set_cpu_usage(used_time); + } + for p in self.cpus.iter_mut(refresh_kind) { + let mut used_time = None; + if let Some(ref key_used) = *get_key_used(p) { + used_time = Some( + query + .get(&key_used.unique_id) + .expect("key_used disappeared"), + ); + } + if let Some(used_time) = used_time { + p.set_cpu_usage(used_time); + } + } + if refresh_kind.frequency() { + self.cpus.get_frequencies(); + } + } + } + + fn refresh_memory(&mut self) { + unsafe { + let mut mem_info: MEMORYSTATUSEX = zeroed(); + mem_info.dwLength = size_of::<MEMORYSTATUSEX>() as u32; + GlobalMemoryStatusEx(&mut mem_info); + self.mem_total = mem_info.ullTotalPhys as _; + self.mem_available = mem_info.ullAvailPhys as _; + let mut perf_info: PERFORMANCE_INFORMATION = zeroed(); + if GetPerformanceInfo(&mut perf_info, size_of::<PERFORMANCE_INFORMATION>() as u32) + == TRUE + { + let swap_total = perf_info.PageSize.saturating_mul( + perf_info + .CommitLimit + .saturating_sub(perf_info.PhysicalTotal), + ); + let swap_used = perf_info.PageSize.saturating_mul( + perf_info + .CommitTotal + .saturating_sub(perf_info.PhysicalTotal), + ); + self.swap_total = swap_total as _; + self.swap_used = swap_used as _; + } + } + } + + fn refresh_components_list(&mut self) { + self.components = component::get_components(); + } + + #[allow(clippy::map_entry)] + 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) = Process::new_from_pid(pid, now, refresh_kind) { + p.update(refresh_kind, nb_cpus, now); + p.updated = false; + self.process_list.insert(pid, p); + true + } else { + false + } + } + + #[allow(clippy::cast_ptr_alignment)] + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { + // Windows 10 notebook requires at least 512KiB of memory to make it in one go + let mut buffer_size: usize = 512 * 1024; + let now = get_now(); + + loop { + let mut process_information: Vec<u8> = Vec::with_capacity(buffer_size); + let mut cb_needed = 0; + + unsafe { + process_information.set_len(buffer_size); + let ntstatus = NtQuerySystemInformation( + SystemProcessInformation, + process_information.as_mut_ptr() as PVOID, + buffer_size as ULONG, + &mut cb_needed, + ); + + if ntstatus != STATUS_INFO_LENGTH_MISMATCH { + if ntstatus < 0 { + sysinfo_debug!( + "Couldn't get process infos: NtQuerySystemInformation returned {}", + ntstatus + ); + } + + // 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; + let pi = &*p; + + 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 + }; + + #[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| { + let pi = *pi.0; + let pid = Pid(pi.UniqueProcessId as _); + if let Some(proc_) = (*process_list.0.get()).get_mut(&pid) { + if proc_ + .get_start_time() + .map(|start| start == proc_.start_time()) + .unwrap_or(true) + { + proc_.memory = pi.WorkingSetSize as _; + proc_.virtual_memory = pi.VirtualSize as _; + proc_.update(refresh_kind, nb_cpus, now); + 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 mut p = Process::new_full( + pid, + if pi.InheritedFromUniqueProcessId as usize != 0 { + Some(Pid(pi.InheritedFromUniqueProcessId as _)) + } else { + None + }, + pi.WorkingSetSize as _, + pi.VirtualSize as _, + name, + now, + refresh_kind, + ); + p.update(refresh_kind, nb_cpus, now); + Some(p) + }) + .collect::<Vec<_>>(); + for p in processes.into_iter() { + self.process_list.insert(p.pid(), p); + } + self.process_list.retain(|_, v| { + let x = v.updated; + v.updated = false; + x + }); + + break; + } + + // 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; + } + } + } + + fn refresh_disks_list(&mut self) { + self.disks = unsafe { get_disks() }; + } + + fn refresh_users_list(&mut self) { + self.users = unsafe { get_users() }; + } + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.get(&pid) + } + + fn global_cpu_info(&self) -> &Cpu { + self.cpus.global_cpu() + } + + fn cpus(&self) -> &[Cpu] { + self.cpus.cpus() + } + + fn physical_core_count(&self) -> Option<usize> { + get_physical_core_count() + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + // MEMORYSTATUSEX doesn't report free memory + self.mem_available + } + + fn available_memory(&self) -> u64 { + self.mem_available + } + + fn used_memory(&self) -> u64 { + self.mem_total - self.mem_available + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_total - self.swap_used + } + + fn used_swap(&self) -> u64 { + self.swap_used + } + + fn components(&self) -> &[Component] { + &self.components + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut self.components + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks + } + + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + self.disks.sort_unstable_by(compare); + } + + fn users(&self) -> &[User] { + &self.users + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn uptime(&self) -> u64 { + unsafe { GetTickCount64() / 1_000 } + } + + fn boot_time(&self) -> u64 { + self.boot_time + } + + fn load_average(&self) -> LoadAvg { + get_load_average() + } + + fn name(&self) -> Option<String> { + Some("Windows".to_owned()) + } + + fn long_os_version(&self) -> Option<String> { + 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", + ) + } + + fn host_name(&self) -> Option<String> { + get_dns_hostname() + } + + fn kernel_version(&self) -> Option<String> { + get_reg_string_value( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "CurrentBuildNumber", + ) + } + + fn os_version(&self) -> Option<String> { + 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})")) + } + + fn distribution_id(&self) -> String { + std::env::consts::OS.to_owned() + } +} + +impl Default for System { + fn default() -> System { + System::new() + } +} + +pub(crate) fn is_proc_running(handle: HANDLE) -> bool { + let mut exit_code = 0; + unsafe { + let ret = GetExitCodeProcess(handle, &mut exit_code); + !(ret == FALSE || exit_code != STILL_ACTIVE) + } +} + +/// 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<bool> { + 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); + } + update_memory(proc_); + proc_.update(refresh_kind, nb_cpus, now); + proc_.updated = false; + Some(true) +} + +#[allow(clippy::size_of_in_element_count)] +//^ needed for "name.Length as usize / std::mem::size_of::<u16>()" +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!("<no name> 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::<u16>(), + ); + + String::from_utf16_lossy(slice) + } + } +} + +fn utf16_str<S: AsRef<OsStr> + ?Sized>(text: &S) -> Vec<u16> { + OsStr::new(text) + .encode_wide() + .chain(Some(0).into_iter()) + .collect::<Vec<_>>() +} + +fn get_reg_string_value(hkey: HKEY, path: &str, field_name: &str) -> Option<String> { + let c_path = utf16_str(path); + let c_field_name = utf16_str(field_name); + + let mut new_hkey: HKEY = std::ptr::null_mut(); + unsafe { + if RegOpenKeyExW(hkey, c_path.as_ptr(), 0, KEY_READ, &mut new_hkey) != 0 { + return None; + } + + let mut buf_len: DWORD = 2048; + let mut buf_type: DWORD = 0; + let mut buf: Vec<u8> = Vec::with_capacity(buf_len as usize); + loop { + match RegQueryValueExW( + new_hkey, + c_field_name.as_ptr(), + std::ptr::null_mut(), + &mut buf_type, + buf.as_mut_ptr() as LPBYTE, + &mut buf_len, + ) as DWORD + { + 0 => break, + winerror::ERROR_MORE_DATA => { + buf.reserve(buf_len as _); + } + _ => return None, + } + } + + buf.set_len(buf_len as _); + + let words = 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) + } +} + +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); + + let mut new_hkey: HKEY = std::ptr::null_mut(); + unsafe { + if RegOpenKeyExW(hkey, c_path.as_ptr(), 0, KEY_READ, &mut new_hkey) != 0 { + return None; + } + + let mut buf_len: DWORD = 4; + let mut buf_type: DWORD = 0; + let mut buf = [0u8; 4]; + + match RegQueryValueExW( + new_hkey, + c_field_name.as_ptr(), + std::ptr::null_mut(), + &mut buf_type, + buf.as_mut_ptr() as LPBYTE, + &mut buf_len, + ) as DWORD + { + 0 => Some(buf), + _ => None, + } + } +} + +fn get_dns_hostname() -> Option<String> { + 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 { + GetComputerNameExW( + ComputerNamePhysicalDnsHostname, + std::ptr::null_mut(), + &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, + buffer.as_mut_ptr() as *mut wchar_t, + &mut buffer_size, + ) == TRUE + { + 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 +} diff --git a/vendor/sysinfo-0.26.7/src/windows/tools.rs b/vendor/sysinfo-0.26.7/src/windows/tools.rs new file mode 100644 index 000000000..a0c334010 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/windows/tools.rs @@ -0,0 +1,55 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::cpu::{self, Cpu, Query}; +use crate::CpuRefreshKind; + +use std::mem::zeroed; + +use winapi::um::sysinfoapi::{GetSystemInfo, SYSTEM_INFO}; + +pub(crate) struct KeyHandler { + pub unique_id: String, +} + +impl KeyHandler { + pub fn new(unique_id: String) -> KeyHandler { + KeyHandler { unique_id } + } +} + +pub(crate) fn init_cpus(refresh_kind: CpuRefreshKind) -> (Vec<Cpu>, String, String) { + unsafe { + let mut sys_info: SYSTEM_INFO = zeroed(); + GetSystemInfo(&mut sys_info); + let (vendor_id, brand) = cpu::get_vendor_id_and_brand(&sys_info); + let nb_cpus = sys_info.dwNumberOfProcessors as usize; + let frequencies = if refresh_kind.frequency() { + cpu::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::new_with_values( + format!("CPU {}", nb + 1), + vendor_id.clone(), + brand.clone(), + *frequency, + )); + } + (ret, vendor_id, brand) + } +} + +pub(crate) fn add_english_counter( + s: String, + query: &mut Query, + keys: &mut Option<KeyHandler>, + counter_name: String, +) { + let mut full = s.encode_utf16().collect::<Vec<_>>(); + full.push(0); + if query.add_english_counter(&counter_name, full) { + *keys = Some(KeyHandler::new(counter_name)); + } +} diff --git a/vendor/sysinfo-0.26.7/src/windows/users.rs b/vendor/sysinfo-0.26.7/src/windows/users.rs new file mode 100644 index 000000000..2fef05c3f --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/windows/users.rs @@ -0,0 +1,181 @@ +// 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}, + User, +}; + +use std::ptr::null_mut; +use winapi::shared::lmcons::{MAX_PREFERRED_LENGTH, NET_API_STATUS}; +use winapi::shared::minwindef::DWORD; +use winapi::shared::ntstatus::STATUS_SUCCESS; +use winapi::shared::winerror::ERROR_MORE_DATA; +use winapi::um::lmaccess::{NetUserEnum, NetUserGetLocalGroups}; +use winapi::um::lmaccess::{ + FILTER_NORMAL_ACCOUNT, LG_INCLUDE_INDIRECT, LPLOCALGROUP_USERS_INFO_0, USER_INFO_0, +}; +use winapi::um::lmapibuf::NetApiBufferFree; +use winapi::um::ntlsa::{ + LsaEnumerateLogonSessions, LsaFreeReturnBuffer, LsaGetLogonSessionData, + PSECURITY_LOGON_SESSION_DATA, +}; +use winapi::um::winnt::{LPWSTR, PLUID}; + +// FIXME: once this is mreged in winapi, it can be removed. +#[allow(non_upper_case_globals)] +const NERR_Success: NET_API_STATUS = 0; + +unsafe fn get_groups_for_user(username: LPWSTR) -> Vec<String> { + let mut buf: LPLOCALGROUP_USERS_INFO_0 = null_mut(); + let mut nb_entries = 0; + let mut total_entries = 0; + let mut groups; + + let status = NetUserGetLocalGroups( + [0u16].as_ptr(), + username, + 0, + LG_INCLUDE_INDIRECT, + &mut buf as *mut _ as _, + MAX_PREFERRED_LENGTH, + &mut nb_entries, + &mut total_entries, + ); + + if status == NERR_Success { + groups = Vec::with_capacity(nb_entries as _); + + if !buf.is_null() { + for i in 0..nb_entries { + let tmp = buf.offset(i as _); + if tmp.is_null() { + break; + } + groups.push(to_str((*tmp).lgrui0_name)); + } + } + } else { + groups = Vec::new(); + sysinfo_debug!("NetUserGetLocalGroups failed with ret code {}", status); + } + if !buf.is_null() { + NetApiBufferFree(buf as *mut _); + } + + groups +} + +// FIXME: For now, the Uid is the user name, which is quite bad. Normally, there is `PSID` for +// that. But when getting the `PSID` from the processes, it doesn't match the ones we have for +// the users (`EqualSid`). Anyway, until I have time and motivation to fix this. It'll remain +// like that... +pub unsafe fn get_users() -> Vec<User> { + let mut users = Vec::new(); + let mut buffer: *mut USER_INFO_0 = null_mut(); + let mut nb_read = 0; + let mut total = 0; + let mut resume_handle: DWORD = 0; + + loop { + let status = NetUserEnum( + null_mut(), + 0, + FILTER_NORMAL_ACCOUNT, + &mut buffer as *mut _ as *mut _, + MAX_PREFERRED_LENGTH, + &mut nb_read, + &mut total, + &mut resume_handle as *mut _ as *mut _, + ); + if status == NERR_Success || status == ERROR_MORE_DATA { + let entries: &[USER_INFO_0] = std::slice::from_raw_parts(buffer, nb_read as _); + for entry in entries { + if entry.usri0_name.is_null() { + continue; + } + // let mut user: *mut USER_INFO_23 = null_mut(); + + // if NetUserGetInfo( + // null_mut(), + // entry.usri0_name, + // 23, + // &mut user as *mut _ as *mut _, + // ) == NERR_Success + // { + // let groups = get_groups_for_user((*user).usri23_name); + // users.push(User { + // uid: Uid(name.clone().into_boxed_str()), + // gid: Gid(0), + // name: to_str((*user).usri23_name), + // groups, + // }); + // } + // if !user.is_null() { + // NetApiBufferFree(user as *mut _); + // } + let groups = get_groups_for_user(entry.usri0_name); + let name = to_str(entry.usri0_name); + users.push(User { + uid: Uid(name.clone().into_boxed_str()), + gid: Gid(0), + name, + groups, + }); + } + } else { + sysinfo_debug!( + "NetUserEnum error: {}", + if status == winapi::shared::winerror::ERROR_ACCESS_DENIED { + "access denied" + } else if status == winapi::shared::winerror::ERROR_INVALID_LEVEL { + "invalid level" + } else { + "unknown error" + } + ); + } + if !buffer.is_null() { + NetApiBufferFree(buffer as *mut _); + buffer = null_mut(); + } + if status != ERROR_MORE_DATA { + break; + } + } + + // First part done. Second part now! + let mut nb_sessions = 0; + let mut uids: PLUID = null_mut(); + if LsaEnumerateLogonSessions(&mut nb_sessions, &mut uids) != STATUS_SUCCESS { + sysinfo_debug!("LsaEnumerateLogonSessions failed"); + } else { + for offset in 0..nb_sessions { + let entry = uids.add(offset as _); + let mut data: PSECURITY_LOGON_SESSION_DATA = null_mut(); + + if LsaGetLogonSessionData(entry, &mut data) == STATUS_SUCCESS && !data.is_null() { + let data = *data; + if data.LogonType == winapi::um::ntlsa::Network { + continue; + } + let name = to_str(data.UserName.Buffer); + if users.iter().any(|u| u.name == name) { + continue; + } + users.push(User { + uid: Uid(name.clone().into_boxed_str()), + gid: Gid(0), + name, + // There is no local groups for a non-local user. + groups: Vec::new(), + }); + } + if !data.is_null() { + LsaFreeReturnBuffer(data as *mut _); + } + } + } + + users +} diff --git a/vendor/sysinfo-0.26.7/src/windows/utils.rs b/vendor/sysinfo-0.26.7/src/windows/utils.rs new file mode 100644 index 000000000..419ee195c --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/windows/utils.rs @@ -0,0 +1,36 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use winapi::shared::minwindef::FILETIME; +use winapi::um::winnt::LPWSTR; + +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: LPWSTR) -> String { + let mut i = 0; + + loop { + let c = *p.offset(i); + if c == 0 { + break; + } + i += 1; + } + let s = std::slice::from_raw_parts(p, i as _); + String::from_utf16(s).unwrap_or_else(|_e| { + sysinfo_debug!("Failed to convert to UTF-16 string: {}", _e); + String::new() + }) +} diff --git a/vendor/sysinfo-0.26.7/tests/code_checkers/docs.rs b/vendor/sysinfo-0.26.7/tests/code_checkers/docs.rs new file mode 100644 index 000000000..7720d2dcf --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/code_checkers/docs.rs @@ -0,0 +1,130 @@ +// 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::<Vec<_>>(); + 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 `{}`, should have been `{}`", + md_name, 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::<Vec<_>>(); + + 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 +} diff --git a/vendor/sysinfo-0.26.7/tests/code_checkers/headers.rs b/vendor/sysinfo-0.26.7/tests/code_checkers/headers.rs new file mode 100644 index 000000000..d66aa4202 --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/code_checkers/headers.rs @@ -0,0 +1,59 @@ +// 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 (`{}`), found: `{}`", + header, 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, + } + } + } +} diff --git a/vendor/sysinfo-0.26.7/tests/code_checkers/mod.rs b/vendor/sysinfo-0.26.7/tests/code_checkers/mod.rs new file mode 100644 index 000000000..db645341d --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/code_checkers/mod.rs @@ -0,0 +1,50 @@ +// 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); + } +} diff --git a/vendor/sysinfo-0.26.7/tests/code_checkers/signals.rs b/vendor/sysinfo-0.26.7/tests/code_checkers/signals.rs new file mode 100644 index 000000000..68e47f76e --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/code_checkers/signals.rs @@ -0,0 +1,64 @@ +// 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<Item = &'a str>, 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: &'static [Signal] = supported_signals();" { + show_error( + p, + "SystemExt::SUPPORTED_SIGNALS should be declared using `supported_signals()`", + ); + return 1; + } + break; + } + } + 0 +} + +fn check_kill_decl<'a>(lines: &mut impl Iterator<Item = &'a str>, 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, "`ProcessExt::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("::system::convert_signal(signal)?;") || trimmed == "None" { + continue; + } else { + show_error(p, "`ProcessExt::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 SystemExt for System {") { + res.nb_tests += 1; + res.nb_errors += check_supported_signals_decl(&mut lines, p); + } else if trimmed.starts_with("impl ProcessExt for Process {") { + res.nb_tests += 1; + res.nb_errors += check_kill_decl(&mut lines, p); + } + } + res +} diff --git a/vendor/sysinfo-0.26.7/tests/code_checkers/utils.rs b/vendor/sysinfo-0.26.7/tests/code_checkers/utils.rs new file mode 100644 index 000000000..893201961 --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/code_checkers/utils.rs @@ -0,0 +1,49 @@ +// 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<P: AsRef<Path>, F: FnMut(&Path, &str)>(dirs: &[P], callback: &mut F) { + for dir in dirs { + read_dir(dir, callback); + } +} + +fn read_dir<P: AsRef<Path>, 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 path = entry.path(); + if path.is_dir() { + read_dir(path, callback); + } else { + let content = read_file(&path); + callback(&path, &content); + } + } +} + +fn read_file<P: AsRef<Path>>(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)); + f.read_to_string(&mut content) + .expect("read_file::read_to_end failed"); + content +} + +pub fn show_error(p: &Path, err: &str) { + eprintln!("=> [{}]: {err}", p.display()); +} diff --git a/vendor/sysinfo-0.26.7/tests/cpu.rs b/vendor/sysinfo-0.26.7/tests/cpu.rs new file mode 100644 index 000000000..fbeb65b81 --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/cpu.rs @@ -0,0 +1,32 @@ +// 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() { + use sysinfo::{CpuExt, SystemExt}; + + if sysinfo::System::IS_SUPPORTED { + let mut s = sysinfo::System::new(); + assert!(s.cpus().is_empty()); + 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')); + } +} + +#[test] +fn test_physical_core_numbers() { + use sysinfo::SystemExt; + + if sysinfo::System::IS_SUPPORTED { + let s = sysinfo::System::new(); + let count = s.physical_core_count(); + assert_ne!(count, None); + assert!(count.unwrap() > 0); + } +} diff --git a/vendor/sysinfo-0.26.7/tests/disk_list.rs b/vendor/sysinfo-0.26.7/tests/disk_list.rs new file mode 100644 index 000000000..164d5b931 --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/disk_list.rs @@ -0,0 +1,14 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#[test] +fn test_disks() { + use sysinfo::SystemExt; + + if sysinfo::System::IS_SUPPORTED { + 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 { + assert!(!s.disks().is_empty()); + } + } +} diff --git a/vendor/sysinfo-0.26.7/tests/extras.rs b/vendor/sysinfo-0.26.7/tests/extras.rs new file mode 100644 index 000000000..140dd1131 --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/extras.rs @@ -0,0 +1,3 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +mod code_checkers; diff --git a/vendor/sysinfo-0.26.7/tests/network.rs b/vendor/sysinfo-0.26.7/tests/network.rs new file mode 100644 index 000000000..6925cb3c8 --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/network.rs @@ -0,0 +1,15 @@ +// 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::{NetworksExt, SystemExt}; + + if sysinfo::System::IS_SUPPORTED { + let s = sysinfo::System::new(); + assert_eq!(s.networks().iter().count(), 0); + let s = sysinfo::System::new_all(); + assert!(s.networks().iter().count() > 0); + } +} diff --git a/vendor/sysinfo-0.26.7/tests/process.rs b/vendor/sysinfo-0.26.7/tests/process.rs new file mode 100644 index 000000000..2b88c7b24 --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/process.rs @@ -0,0 +1,457 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use sysinfo::{Pid, PidExt, ProcessExt, SystemExt}; + +#[test] +fn test_process() { + let mut s = sysinfo::System::new(); + assert_eq!(s.processes().len(), 0); + s.refresh_processes(); + if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { + return; + } + assert!(!s.processes().is_empty()); + assert!(s + .processes() + .values() + .any(|p| !p.exe().to_str().unwrap_or("").is_empty())); +} + +#[test] +fn test_cwd() { + if !sysinfo::System::IS_SUPPORTED || 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 = sysinfo::System::new(); + s.refresh_processes(); + 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(), std::env::current_dir().unwrap()); + } else { + panic!("Process not found!"); + } +} + +#[test] +fn test_cmd() { + if !sysinfo::System::IS_SUPPORTED || 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 = sysinfo::System::new(); + assert!(s.processes().is_empty()); + s.refresh_processes(); + 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!"); + } +} + +#[test] +fn test_environ() { + if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { + return; + } + let mut p = if cfg!(target_os = "windows") { + std::process::Command::new("waitfor") + .arg("/t") + .arg("3") + .arg("EnvironSignal") + .stdout(std::process::Stdio::null()) + .env("FOO", "BAR") + .env("OTHER", "VALUE") + .spawn() + .unwrap() + } else { + std::process::Command::new("sleep") + .arg("3") + .stdout(std::process::Stdio::null()) + .env("FOO", "BAR") + .env("OTHER", "VALUE") + .spawn() + .unwrap() + }; + + let pid = Pid::from_u32(p.id() as _); + std::thread::sleep(std::time::Duration::from_secs(1)); + let mut s = sysinfo::System::new(); + s.refresh_processes(); + 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); + // FIXME: instead of ignoring the test on CI, try to find out what's wrong... + if std::env::var("APPLE_CI").is_err() { + assert!(p.environ().iter().any(|e| e == "FOO=BAR")); + assert!(p.environ().iter().any(|e| e == "OTHER=VALUE")); + } + } else { + panic!("Process not found!"); + } +} + +#[test] +fn test_process_refresh() { + let mut s = sysinfo::System::new(); + assert_eq!(s.processes().len(), 0); + + if !sysinfo::System::IS_SUPPORTED || 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(),); +} + +#[test] +fn test_process_disk_usage() { + use std::fs; + use std::fs::File; + use std::io::prelude::*; + use sysinfo::{get_current_pid, ProcessExt, SystemExt}; + + if !sysinfo::System::IS_SUPPORTED || 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() -> sysinfo::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 = sysinfo::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 = sysinfo::System::new(); + system.refresh_processes(); + + if !sysinfo::System::IS_SUPPORTED || 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::<Vec<_>>(); + 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::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { + return; + } + 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 = sysinfo::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 `+ 3` to ensure the test is passing as it should... + assert!( + p.start_time() + 3 + > SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + ); + assert!(p.start_time() >= s.boot_time()); + } else { + panic!("Process not found!"); + } +} + +// Checks that `refresh_processes` is removing dead processes. +#[test] +fn test_refresh_processes() { + if !sysinfo::System::IS_SUPPORTED || 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 = sysinfo::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_process` is NOT removing dead processes. +#[test] +fn test_refresh_process() { + if !sysinfo::System::IS_SUPPORTED || 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 = sysinfo::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::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { + return; + } + let 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 before = std::time::Instant::now(); + let pid = Pid::from_u32(p.id() as _); + + let mut s = sysinfo::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::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { + return; + } + + // 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 before = std::time::Instant::now(); + + let mut s = sysinfo::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(2000)); + assert!(before.elapsed() < std::time::Duration::from_millis(3000)); +} diff --git a/vendor/sysinfo-0.26.7/tests/send_sync.rs b/vendor/sysinfo-0.26.7/tests/send_sync.rs new file mode 100644 index 000000000..1f8cc192c --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/send_sync.rs @@ -0,0 +1,10 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#[test] +fn test_send_sync() { + fn is_send<T: Send>() {} + fn is_sync<T: Sync>() {} + + is_send::<sysinfo::System>(); + is_sync::<sysinfo::System>(); +} diff --git a/vendor/sysinfo-0.26.7/tests/uptime.rs b/vendor/sysinfo-0.26.7/tests/uptime.rs new file mode 100644 index 000000000..d5eae7323 --- /dev/null +++ b/vendor/sysinfo-0.26.7/tests/uptime.rs @@ -0,0 +1,12 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#[test] +fn test_uptime() { + use sysinfo::SystemExt; + + if sysinfo::System::IS_SUPPORTED { + let mut s = sysinfo::System::new(); + s.refresh_all(); + assert!(s.uptime() != 0); + } +} |