whoami-1.5.2/.cargo_vcs_info.json0000644000000001360000000000100123250ustar { "git": { "sha1": "c1dfdec7562b79f4f9772a4eb3e69f766cda966a" }, "path_in_vcs": "" }whoami-1.5.2/Cargo.lock0000644000000103610000000000100103010ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bumpalo" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "js-sys" version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "proc-macro2" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ "bitflags", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "wasite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" [[package]] name = "web-sys" version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "whoami" version = "1.5.2" dependencies = [ "redox_syscall", "wasite", "web-sys", ] whoami-1.5.2/Cargo.toml0000644000000034450000000000100103310ustar # 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.40" name = "whoami" version = "1.5.2" build = false include = [ "/LICENSE_APACHE", "/LICENSE_BOOST", "/LICENSE_MIT", "/README.md", "/src/*", ] autobins = false autoexamples = false autotests = false autobenches = false description = "Retrieve the current user and environment." homepage = "https://github.com/ardaku/whoami/blob/v1/CHANGELOG.md" documentation = "https://docs.rs/whoami" readme = "README.md" keywords = [ "user", "username", "whoami", "platform", "detect", ] categories = [ "os", "wasm", "internationalization", "localization", "value-formatting", ] license = "Apache-2.0 OR BSL-1.0 OR MIT" repository = "https://github.com/ardaku/whoami" [lib] name = "whoami" path = "src/lib.rs" [features] default = ["web"] web = ["web-sys"] [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(daku)))'.dependencies.web-sys] version = "0.3" features = [ "Navigator", "Document", "Window", "Location", ] optional = true [target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies.wasite] version = "0.1" [target.'cfg(all(target_os = "redox", not(target_arch = "wasm32")))'.dependencies.redox_syscall] version = "0.5" [lints.rust.unexpected_cfgs] level = "allow" priority = 0 check-cfg = ["cfg(daku)"] whoami-1.5.2/Cargo.toml.orig000064400000000000000000000026551046102023000140140ustar 00000000000000[package] name = "whoami" version = "1.5.2" edition = "2018" license = "Apache-2.0 OR BSL-1.0 OR MIT" documentation = "https://docs.rs/whoami" homepage = "https://github.com/ardaku/whoami/blob/v1/CHANGELOG.md" repository = "https://github.com/ardaku/whoami" readme = "README.md" description = "Retrieve the current user and environment." keywords = ["user", "username", "whoami", "platform", "detect"] categories = [ "os", "wasm", "internationalization", "localization", "value-formatting", ] include = [ "/LICENSE_APACHE", "/LICENSE_BOOST", "/LICENSE_MIT", "/README.md", "/src/*", ] rust-version = "1.40" # Target specific dependency for redox [target.'cfg(all(target_os = "redox", not(target_arch = "wasm32")))'.dependencies.redox_syscall] version = "0.5" # Target specific dependency for wasite [target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies.wasite] version = "0.1" # Target-specific dependency for web browser [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(daku)))'.dependencies.web-sys] version = "0.3" features = ["Navigator", "Document", "Window", "Location"] optional = true [features] default = ["web"] # Enabling this feature indicates that the wasm32-unknown-unknown target should # be assumed to be in a web environment where it can call DOM APIs. web = ["web-sys"] [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(daku)'] } whoami-1.5.2/LICENSE_APACHE000064400000000000000000000227731046102023000131360ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS whoami-1.5.2/LICENSE_BOOST000064400000000000000000000024721046102023000130750ustar 00000000000000Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. whoami-1.5.2/LICENSE_MIT000064400000000000000000000020141046102023000126300ustar 00000000000000MIT License 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. whoami-1.5.2/README.md000064400000000000000000000103601046102023000123740ustar 00000000000000![WhoAmI Logo](https://raw.githubusercontent.com/ardaku/whoami/v1/res/icon.svg) #### [Changelog][3] | [Source][4] | [Getting Started][5] [![tests](https://github.com/ardaku/whoami/actions/workflows/ci.yml/badge.svg)](https://github.com/ardaku/whoami/actions/workflows/ci.yml) [![GitHub commit activity](https://img.shields.io/github/commit-activity/y/ardaku/whoami)](https://github.com/ardaku/whoami/) [![GitHub contributors](https://img.shields.io/github/contributors/ardaku/whoami)](https://github.com/ardaku/whoami/graphs/contributors) [![Crates.io](https://img.shields.io/crates/v/whoami)](https://crates.io/crates/whoami) [![Crates.io](https://img.shields.io/crates/d/whoami)](https://crates.io/crates/whoami) [![Crates.io (recent)](https://img.shields.io/crates/dr/whoami)](https://crates.io/crates/whoami) [![Crates.io](https://img.shields.io/crates/l/whoami)](https://github.com/ardaku/whoami/search?l=Text&q=license) [![Docs.rs](https://docs.rs/whoami/badge.svg)](https://docs.rs/whoami/) Retrieve the current user and environment through simple functions. Check out the [documentation][0] for examples. ### Features - Get the user's full name - Get the user's username - Get the user's preferred language(s) - Get the devices's hostname - Get the devices's "pretty" or "fancy" name - Get the devices's desktop environment - Get the devices's OS name and version - Get the devices's platform name - Get the devices's CPU architecture and its width ### Supported Platforms WhoAmI targets all platforms that can run Rust, including: - Linux - Windows - Mac OS - BSD variants (FreeBSD, others) - illumos variants (SmartOS, OmniOS, others) **Target-Specific MSRV 1.65** - Redox **Target-Specific MSRV 1.65** - [Web Assembly](https://github.com/ardaku/whoami/blob/v1/WASM.md) - Fake implementation - Web Browser - DOM - WASI (Wasite, others) **untested, testing planned later** - Daku (Ardaku/Quantii, others) **planned later** - Android **planned later** - iOS / watchOS / tvOS **planned later** - Fuchsia **planned later** - Others? (make a PR or open an issue) ## MSRV WhoAmI 1.x.y targets Rust 1.40.0 stable and later, and the 1.x.y track will be maintained at least until the release of the Rust 2024 edition. The MSRV will not be updated until version 2.0.0, after which breaking changes may happen on minor releases, and version 2.0.0 will target Rust 1.65.0 and later to make use of the `let else` syntax. The current plan is for all 2.x releases to be supported and receive bugfixes at least until sometime in 2027, bumping MSRV only as needed. ## Binary [whome](https://crates.io/crates/whome): `whoami` command RiR (Re-written in Rust) that depends on this crate. ## Testing The testing procedure is documented at . The full manual test suite is run for each change that affects multiple platforms. ## License Copyright © 2017-2024 The WhoAmI Contributors. Licensed under any of - Apache License, Version 2.0, ([LICENSE_APACHE][7] or [https://www.apache.org/licenses/LICENSE-2.0][8]) - Boost Software License, Version 1.0, ([LICENSE_BOOST][11] or [https://www.boost.org/LICENSE_1_0.txt][12]) - MIT License, ([LICENSE_MIT][9] or [https://mit-license.org/][10]) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as described above, without any additional terms or conditions. ## Help If you want help using or contributing to this library, feel free to send me an email at [aldaronlau@gmail.com][13]. [0]: https://docs.rs/whoami [1]: https://crates.io/crates/whoami [2]: https://github.com/ardaku/whoami/actions?query=workflow%3Atests [3]: https://github.com/ardaku/whoami/blob/v1/CHANGELOG.md [4]: https://github.com/ardaku/whoami/ [5]: https://docs.rs/whoami#getting-started [6]: https://aldaronlau.com/ [7]: https://github.com/ardaku/whoami/blob/v1/LICENSE_APACHE [8]: https://www.apache.org/licenses/LICENSE-2.0 [9]: https://github.com/ardaku/whoami/blob/v1/LICENSE_MIT [10]: https://mit-license.org/ [11]: https://github.com/ardaku/whoami/blob/v1/LICENSE_BOOST [12]: https://www.boost.org/LICENSE_1_0.txt [13]: mailto:aldaronlau@gmail.com whoami-1.5.2/src/api.rs000064400000000000000000000133321046102023000130250ustar 00000000000000use std::ffi::OsString; use crate::{ fallible, os::{Os, Target}, Arch, DesktopEnv, Language, Platform, Result, }; macro_rules! report_message { () => { "Please report this issue at https://github.com/ardaku/whoami/issues" }; } const DEFAULT_USERNAME: &str = "Unknown"; const DEFAULT_HOSTNAME: &str = "LocalHost"; /// Get the CPU Architecture. #[inline(always)] pub fn arch() -> Arch { Target::arch(Os).expect(concat!("arch() failed. ", report_message!())) } /// Get the user's username. /// /// On unix-systems this differs from [`realname()`] most notably in that spaces /// are not allowed in the username. #[inline(always)] pub fn username() -> String { fallible::username().unwrap_or_else(|_| DEFAULT_USERNAME.to_lowercase()) } /// Get the user's username. /// /// On unix-systems this differs from [`realname_os()`] most notably in that /// spaces are not allowed in the username. #[inline(always)] pub fn username_os() -> OsString { fallible::username_os() .unwrap_or_else(|_| DEFAULT_USERNAME.to_lowercase().into()) } /// Get the user's real (full) name. #[inline(always)] pub fn realname() -> String { fallible::realname() .or_else(|_| fallible::username()) .unwrap_or_else(|_| DEFAULT_USERNAME.to_owned()) } /// Get the user's real (full) name. #[inline(always)] pub fn realname_os() -> OsString { fallible::realname_os() .or_else(|_| fallible::username_os()) .unwrap_or_else(|_| DEFAULT_USERNAME.to_owned().into()) } /// Get the device name (also known as "Pretty Name"). /// /// Often used to identify device for bluetooth pairing. #[inline(always)] pub fn devicename() -> String { fallible::devicename() .or_else(|_| fallible::hostname()) .unwrap_or_else(|_| DEFAULT_HOSTNAME.to_string()) } /// Get the device name (also known as "Pretty Name"). /// /// Often used to identify device for bluetooth pairing. #[inline(always)] pub fn devicename_os() -> OsString { fallible::devicename_os() .or_else(|_| fallible::hostname().map(OsString::from)) .unwrap_or_else(|_| DEFAULT_HOSTNAME.to_string().into()) } /// Get the host device's hostname. /// /// Limited to a-z (case insensitive), 0-9, and dashes. This limit also applies /// to `devicename()` with the exeception of case sensitivity when targeting /// Windows. This method normalizes to lowercase. Usually hostnames will be /// case-insensitive, but it's not a hard requirement. /// /// Use [`fallible::hostname()`] for case-sensitive hostname. #[inline(always)] #[deprecated(note = "use `fallible::hostname()` instead", since = "1.5.0")] pub fn hostname() -> String { let mut hostname = fallible::hostname() .unwrap_or_else(|_| DEFAULT_HOSTNAME.to_lowercase()); hostname.make_ascii_lowercase(); hostname } /// Get the host device's hostname. /// /// Limited to a-z (case insensitive), 0-9, and dashes. This limit also applies /// to `devicename()` with the exeception of case sensitivity when targeting /// Windows. This method normalizes to lowercase. Usually hostnames will be /// case-insensitive, but it's not a hard requirement. /// /// Use [`fallible::hostname()`] for case-sensitive hostname. #[inline(always)] #[deprecated(note = "use `fallible::hostname()` instead", since = "1.5.0")] pub fn hostname_os() -> OsString { #[allow(deprecated)] hostname().into() } /// Get the name of the operating system distribution and (possibly) version. /// /// Example: "Windows 10" or "Fedora 26 (Workstation Edition)" #[inline(always)] pub fn distro() -> String { fallible::distro().unwrap_or_else(|_| format!("Unknown {}", platform())) } /// Get the name of the operating system distribution and (possibly) version. /// /// Example: "Windows 10" or "Fedora 26 (Workstation Edition)" #[inline(always)] #[deprecated(note = "use `distro()` instead", since = "1.5.0")] pub fn distro_os() -> OsString { fallible::distro() .map(OsString::from) .unwrap_or_else(|_| format!("Unknown {}", platform()).into()) } /// Get the desktop environment. /// /// Example: "gnome" or "windows" #[inline(always)] pub fn desktop_env() -> DesktopEnv { Target::desktop_env(Os) } /// Get the platform. #[inline(always)] pub fn platform() -> Platform { Target::platform(Os) } /// Get the user's preferred language(s). /// /// Returned as iterator of two letter language codes (lowercase), optionally /// followed by a dash (-) and a two letter country code (uppercase). The most /// preferred language is returned first, followed by next preferred, and so on. #[inline(always)] #[deprecated(note = "use `langs()` instead", since = "1.5.0")] pub fn lang() -> impl Iterator { let langs_vec = if let Ok(langs) = langs() { langs .map(|lang| lang.to_string().replace('/', "-")) .collect() } else { ["en-US".to_string()].to_vec() }; langs_vec.into_iter() } /// Get the user's preferred language(s). /// /// Returned as iterator of [`Language`]s. The most preferred language is /// returned first, followed by next preferred, and so on. Unrecognized /// languages may either return an error or be skipped. #[inline(always)] pub fn langs() -> Result> { // FIXME: Could do less allocation let langs = Target::langs(Os)?; let langs = langs .split(';') .map(ToString::to_string) .filter_map(|lang| { let lang = lang .split_terminator('.') .next() .unwrap_or_default() .replace(|x| ['_', '-'].contains(&x), "/"); if lang == "C" { return None; } Some(Language::__(Box::new(lang))) }); Ok(langs.collect::>().into_iter()) } whoami-1.5.2/src/arch.rs000064400000000000000000000067041046102023000131760ustar 00000000000000use std::{ fmt::{self, Display, Formatter}, io::{Error, ErrorKind}, }; use crate::Result; /// The address width of a CPU architecture #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[non_exhaustive] pub enum Width { /// 32 bits Bits32, /// 64 bits Bits64, } impl Display for Width { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(match self { Width::Bits32 => "32 bits", Width::Bits64 => "64 bits", }) } } /// The architecture of a CPU #[non_exhaustive] #[derive(Debug, PartialEq, Eq, Clone)] pub enum Arch { /// ARMv5 ArmV5, /// ARMv6 (Sometimes just referred to as ARM) ArmV6, /// ARMv7 (May or may not support Neon/Thumb) ArmV7, /// ARM64 (aarch64) Arm64, /// i386 (x86) I386, /// i586 (x86) I586, /// i686 (x86) I686, /// X86_64 / Amd64 X64, /// MIPS Mips, /// MIPS (LE) MipsEl, /// MIPS64 Mips64, /// MIPS64 (LE) Mips64El, /// PowerPC PowerPc, /// PowerPC64 PowerPc64, /// PowerPC64LE PowerPc64Le, /// 32-bit RISC-V Riscv32, /// 64-bit RISC-V Riscv64, /// S390x S390x, /// SPARC Sparc, /// SPARC64 Sparc64, /// 32-bit Web Assembly Wasm32, /// 64-bit Web Assembly Wasm64, /// Unknown Architecture Unknown(String), } impl Display for Arch { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Self::Unknown(_) = self { f.write_str("Unknown: ")?; } f.write_str(match self { Self::ArmV5 => "armv5", Self::ArmV6 => "armv6", Self::ArmV7 => "armv7", Self::Arm64 => "arm64", Self::I386 => "i386", Self::I586 => "i586", Self::I686 => "i686", Self::Mips => "mips", Self::MipsEl => "mipsel", Self::Mips64 => "mips64", Self::Mips64El => "mips64el", Self::PowerPc => "powerpc", Self::PowerPc64 => "powerpc64", Self::PowerPc64Le => "powerpc64le", Self::Riscv32 => "riscv32", Self::Riscv64 => "riscv64", Self::S390x => "s390x", Self::Sparc => "sparc", Self::Sparc64 => "sparc64", Self::Wasm32 => "wasm32", Self::Wasm64 => "wasm64", Self::X64 => "x86_64", Self::Unknown(arch) => arch, }) } } impl Arch { /// Get the width of this architecture. pub fn width(&self) -> Result { match self { Arch::ArmV5 | Arch::ArmV6 | Arch::ArmV7 | Arch::I386 | Arch::I586 | Arch::I686 | Arch::Mips | Arch::MipsEl | Arch::PowerPc | Arch::Riscv32 | Arch::Sparc | Arch::Wasm32 => Ok(Width::Bits32), Arch::Arm64 | Arch::Mips64 | Arch::Mips64El | Arch::PowerPc64 | Arch::PowerPc64Le | Arch::Riscv64 | Arch::S390x | Arch::Sparc64 | Arch::Wasm64 | Arch::X64 => Ok(Width::Bits64), Arch::Unknown(unknown_arch) => Err(Error::new( ErrorKind::InvalidData, format!( "Tried getting width of unknown arch ({})", unknown_arch, ), )), } } } whoami-1.5.2/src/conversions.rs000064400000000000000000000015071046102023000146250ustar 00000000000000use std::{ ffi::OsString, io::{Error, ErrorKind}, }; use crate::Result; pub(crate) fn string_from_os(string: OsString) -> Result { #[cfg(any( all(not(target_os = "windows"), not(target_arch = "wasm32")), all(target_arch = "wasm32", target_os = "wasi"), ))] { #[cfg(not(target_os = "wasi"))] use std::os::unix::ffi::OsStringExt; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::OsStringExt; String::from_utf8(string.into_vec()) .map_err(|e| Error::new(ErrorKind::InvalidData, e)) } #[cfg(any( target_os = "windows", all(target_arch = "wasm32", not(target_os = "wasi")), ))] { string.into_string().map_err(|_| { Error::new(ErrorKind::InvalidData, "Not valid unicode") }) } } whoami-1.5.2/src/desktop_env.rs000064400000000000000000000052571046102023000146040ustar 00000000000000use std::fmt::{self, Display, Formatter}; // FIXME: V2: Move `Unknown` variants to the top of the enum. /// The desktop environment of a system #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum DesktopEnv { /// Popular GTK-based desktop environment on Linux Gnome, /// One of the desktop environments for a specific version of Windows Windows, /// Linux desktop environment optimized for low resource requirements Lxde, /// Stacking window manager for X Windows on Linux Openbox, /// Desktop environment for Linux, BSD and illumos Mate, /// Lightweight desktop enivornment for unix-like operating systems Xfce, /// KDE Plasma desktop enviroment // FIXME: Rename to 'Plasma' in whoami 2.0.0 Kde, /// Default desktop environment on Linux Mint Cinnamon, /// Tiling window manager for Linux I3, /// Desktop environment for MacOS Aqua, /// Desktop environment for iOS Ios, /// Desktop environment for Android Android, /// Running as Web Assembly on a web page WebBrowser, /// A desktop environment for a video game console Console, /// Ubuntu-branded GNOME Ubuntu, /// Default shell for Fuchsia Ermine, /// Default desktop environment for Redox Orbital, /// Unknown desktop environment Unknown(String), } impl Display for DesktopEnv { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Self::Unknown(_) = self { f.write_str("Unknown: ")?; } f.write_str(match self { Self::Gnome => "Gnome", Self::Windows => "Windows", Self::Lxde => "LXDE", Self::Openbox => "Openbox", Self::Mate => "Mate", Self::Xfce => "XFCE", Self::Kde => "KDE", Self::Cinnamon => "Cinnamon", Self::I3 => "I3", Self::Aqua => "Aqua", Self::Ios => "IOS", Self::Android => "Android", Self::WebBrowser => "Web Browser", Self::Console => "Console", Self::Ubuntu => "Ubuntu", Self::Ermine => "Ermine", Self::Orbital => "Orbital", Self::Unknown(a) => a, }) } } impl DesktopEnv { /// Returns true if the desktop environment is based on GTK. pub fn is_gtk(&self) -> bool { *self == Self::Gnome || *self == Self::Ubuntu || *self == Self::Cinnamon || *self == Self::Lxde || *self == Self::Mate || *self == Self::Xfce } /// Returns true if the desktop environment is based on KDE. pub fn is_kde(&self) -> bool { *self == Self::Kde } } whoami-1.5.2/src/fallible.rs000064400000000000000000000053121046102023000140250ustar 00000000000000//! Fallible versions of the whoami APIs. //! //! Some of the functions in the root module will return "Unknown" or //! "localhost" on error. This might not be desirable in some situations. The //! functions in this module all return a [`Result`]. use std::ffi::OsString; use crate::{ conversions, os::{Os, Target}, Result, }; /// Get the user's account name; usually just the username, but may include an /// account server hostname. /// /// If you don't want the account server hostname, use [`username()`]. /// /// Example: `username@example.com` #[inline(always)] pub fn account() -> Result { account_os().and_then(conversions::string_from_os) } /// Get the user's account name; usually just the username, but may include an /// account server hostname. /// /// If you don't want the account server hostname, use [`username()`]. /// /// Example: `username@example.com` #[inline(always)] pub fn account_os() -> Result { Target::account(Os) } /// Get the user's username. /// /// On unix-systems this differs from [`realname()`] most notably in that spaces /// are not allowed in the username. #[inline(always)] pub fn username() -> Result { username_os().and_then(conversions::string_from_os) } /// Get the user's username. /// /// On unix-systems this differs from [`realname_os()`] most notably in that /// spaces are not allowed in the username. #[inline(always)] pub fn username_os() -> Result { Target::username(Os) } /// Get the user's real (full) name. #[inline(always)] pub fn realname() -> Result { realname_os().and_then(conversions::string_from_os) } /// Get the user's real (full) name. #[inline(always)] pub fn realname_os() -> Result { Target::realname(Os) } /// Get the name of the operating system distribution and (possibly) version. /// /// Example: "Windows 10" or "Fedora 26 (Workstation Edition)" #[inline(always)] pub fn distro() -> Result { Target::distro(Os) } /// Get the device name (also known as "Pretty Name"). /// /// Often used to identify device for bluetooth pairing. #[inline(always)] pub fn devicename() -> Result { devicename_os().and_then(conversions::string_from_os) } /// Get the device name (also known as "Pretty Name"). /// /// Often used to identify device for bluetooth pairing. #[inline(always)] pub fn devicename_os() -> Result { Target::devicename(Os) } /// Get the host device's hostname. /// /// Limited to a-z, A-Z, 0-9, and dashes. This limit also applies to /// [`devicename()`] when targeting Windows. Usually hostnames are /// case-insensitive, but it's not a hard requirement. #[inline(always)] pub fn hostname() -> Result { Target::hostname(Os) } whoami-1.5.2/src/language.rs000064400000000000000000000042441046102023000140410ustar 00000000000000use std::fmt::{self, Display, Formatter}; /// Country code for a [`Language`] dialect /// /// Uses #[non_exhaustive] #[repr(u32)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum Country { // FIXME: V2: u32::from_ne_bytes for country codes, with `\0` for unused // FIXME: Add aliases up to 3-4 letters, but hidden /// Any dialect Any, /// `US`: United States of America #[doc(hidden)] Us, } impl Display for Country { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(match self { Self::Any => "**", Self::Us => "US", }) } } /// A spoken language /// /// Use [`ToString::to_string()`] to convert to string of two letter lowercase /// language code followed an forward slash and uppercase country code (example: /// `en/US`). /// /// Uses #[non_exhaustive] #[derive(Clone, Eq, PartialEq, Debug)] // #[allow(variant_size_differences)] pub enum Language { #[doc(hidden)] __(Box), /// `en`: English #[doc(hidden)] En(Country), /// `es`: Spanish #[doc(hidden)] Es(Country), } impl Language { /// Retrieve the country code for this language dialect. pub fn country(&self) -> Country { match self { Self::__(_) => Country::Any, Self::En(country) | Self::Es(country) => *country, } } } impl Display for Language { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::__(code) => f.write_str(code.as_str()), Self::En(country) => { if *country != Country::Any { f.write_str("en/")?; ::fmt(country, f) } else { f.write_str("en") } } Self::Es(country) => { if *country != Country::Any { f.write_str("es/")?; ::fmt(country, f) } else { f.write_str("es") } } } } } whoami-1.5.2/src/lib.rs000064400000000000000000000051051046102023000130210ustar 00000000000000//! Crate for getting the user's username, realname and environment. //! //! ## Getting Started //! Using the whoami crate is super easy! All of the public items are simple //! functions with no parameters that return [`String`]s or [`OsString`]s (with //! the exception of [`desktop_env()`], [`platform()`], and [`arch()`], which //! return enums, and [`langs()`] that returns an iterator of [`String`]s). The //! following example shows how to use all of the functions (except those that //! return [`OsString`]): //! //! ```rust //! println!( //! "User's Name whoami::realname(): {}", //! whoami::realname(), //! ); //! println!( //! "User's Username whoami::username(): {}", //! whoami::username(), //! ); //! println!( //! "User's Language whoami::lang(): {:?}", //! whoami::lang().collect::>(), //! ); //! println!( //! "Device's Pretty Name whoami::devicename(): {}", //! whoami::devicename(), //! ); //! println!( //! "Device's Hostname whoami::hostname(): {}", //! whoami::hostname(), //! ); //! println!( //! "Device's Platform whoami::platform(): {}", //! whoami::platform(), //! ); //! println!( //! "Device's OS Distro whoami::distro(): {}", //! whoami::distro(), //! ); //! println!( //! "Device's Desktop Env. whoami::desktop_env(): {}", //! whoami::desktop_env(), //! ); //! println!( //! "Device's CPU Arch whoami::arch(): {}", //! whoami::arch(), //! ); //! ``` //! //! [`OsString`]: std::ffi::OsString #![warn( anonymous_parameters, missing_copy_implementations, missing_debug_implementations, missing_docs, nonstandard_style, rust_2018_idioms, single_use_lifetimes, trivial_casts, trivial_numeric_casts, unreachable_pub, unused_extern_crates, unused_qualifications, variant_size_differences, unsafe_code )] #![doc( html_logo_url = "https://raw.githubusercontent.com/ardaku/whoami/v1/res/icon.svg", html_favicon_url = "https://raw.githubusercontent.com/ardaku/whoami/v1/res/icon.svg" )] mod api; mod arch; mod conversions; mod desktop_env; pub mod fallible; mod language; mod os; mod platform; mod result; #[allow(deprecated)] pub use self::{ api::{ arch, desktop_env, devicename, devicename_os, distro, distro_os, hostname, hostname_os, lang, langs, platform, realname, realname_os, username, username_os, }, arch::{Arch, Width}, desktop_env::DesktopEnv, language::{Country, Language}, platform::Platform, result::Result, }; whoami-1.5.2/src/os/daku.rs000064400000000000000000000030011046102023000136110ustar 00000000000000//! This is mostly the same as fake.rs for now use std::{ ffi::OsString, io::{Error, ErrorKind}, }; use crate::{ os::{Os, Target}, Arch, DesktopEnv, Platform, Result, }; impl Target for Os { #[inline(always)] fn langs(self) -> Result { Ok("en/US".to_string()) } #[inline(always)] fn realname(self) -> Result { Ok("Anonymous".to_string().into()) } #[inline(always)] fn username(self) -> Result { Ok("anonymous".to_string().into()) } #[inline(always)] fn devicename(self) -> Result { Ok("Unknown".to_string().into()) } #[inline(always)] fn hostname(self) -> Result { Ok("localhost".to_string()) } #[inline(always)] fn distro(self) -> Result { Ok("Emulated".to_string()) } #[inline(always)] fn desktop_env(self) -> DesktopEnv { DesktopEnv::Unknown("Unknown Daku".to_string()) } #[inline(always)] fn platform(self) -> Platform { Platform::Unknown("Daku".to_string()) } #[inline(always)] fn arch(self) -> Result { Ok(if cfg!(target_pointer_width = "64") { Arch::Wasm64 } else if cfg!(target_pointer_width = "32") { Arch::Wasm32 } else { return Err(Error::new( ErrorKind::Other, // FIXME: WhoAmI 2.0, Unsupported "Unexpected pointer width for target platform", )); }) } } whoami-1.5.2/src/os/redox.rs000064400000000000000000000066171046102023000140260ustar 00000000000000// We don't need unsafe, yay! #![forbid(unsafe_code)] // Redox support has its own MSRV of 1.65, is nightly lint #![allow(unknown_lints, clippy::incompatible_msrv)] use std::{borrow::Cow, ffi::OsString, fs, io::Error}; use syscall::{call, error}; use crate::{ os::{Os, Target}, Arch, DesktopEnv, Platform, Result, }; /// Row in the Redox /etc/passwd file struct Passwd<'a>(Cow<'a, str>); impl Passwd<'_> { fn column(&self, number: usize) -> Option<&str> { self.0.split(';').nth(number) } fn username(&self) -> Option { self.column(0).map(ToString::to_string) } fn uid(&self) -> Option { self.column(1)?.parse().ok() } fn gid(&self) -> Option { self.column(2)?.parse().ok() } fn fullname(&self) -> Option { self.column(3).map(ToString::to_string) } } struct Uname<'a>(Cow<'a, str>); impl Uname<'_> { fn row(&self, number: usize) -> Option<&str> { self.0.lines().nth(number) } fn machine_arch(&self) -> Option { // FIXME: Don't hardcode unknown arch Some(Arch::Unknown(self.row(4)?.to_string())) } } fn to_io_error(error: error::Error) -> Error { Error::from_raw_os_error(error.errno) } fn euid() -> Result { call::geteuid().map_err(to_io_error) } fn egid() -> Result { call::getegid().map_err(to_io_error) } fn passwd() -> Result> { let (euid, egid) = (euid()?, egid()?); let passwd_file = fs::read_to_string("/etc/passwd")?; for user in passwd_file.lines() { let passwd = Passwd(user.into()); if passwd.uid() == Some(euid) && passwd.gid() == Some(egid) { return Ok(Passwd(passwd.0.into_owned().into())); } } Err(super::err_missing_record()) } fn uname() -> Result> { let uname_file = fs::read_to_string("sys:uname")?; Ok(Uname(uname_file.into())) } fn hostname() -> Result { let hostname_file = fs::read_to_string("/etc/hostname")?; Ok(hostname_file.lines().next().unwrap_or_default().to_string()) } impl Target for Os { fn langs(self) -> Result { super::unix_lang() } #[inline(always)] fn realname(self) -> Result { Ok(passwd()?.fullname().unwrap_or_default().into()) } #[inline(always)] fn username(self) -> Result { Ok(passwd()?.username().unwrap_or_default().into()) } #[inline(always)] fn devicename(self) -> Result { hostname().map(OsString::from) } #[inline(always)] fn hostname(self) -> Result { hostname() } #[inline(always)] fn distro(self) -> Result { let release_file = fs::read_to_string("/etc/os-release")?; for kv in release_file.lines() { if let Some(kv) = kv.strip_prefix("PRETTY_NAME=\"") { if let Some(kv) = kv.strip_suffix('\"') { return Ok(kv.to_string()); } } } Err(super::err_missing_record()) } #[inline(always)] fn desktop_env(self) -> DesktopEnv { DesktopEnv::Orbital } #[inline(always)] fn platform(self) -> Platform { Platform::Redox } #[inline(always)] fn arch(self) -> Result { uname()? .machine_arch() .ok_or_else(super::err_missing_record) } } whoami-1.5.2/src/os/target.rs000064400000000000000000000075211046102023000141660ustar 00000000000000//! Unknown target, fake implementation. //! //! This can be used as a template when adding new target support. use std::{ ffi::OsString, io::{Error, ErrorKind}, }; use crate::{ os::{Os, Target}, Arch, DesktopEnv, Platform, Result, }; impl Target for Os { #[inline(always)] fn langs(self) -> Result { Ok("en/US".to_string()) } #[inline(always)] fn realname(self) -> Result { Ok("Anonymous".to_string().into()) } #[inline(always)] fn username(self) -> Result { Ok("anonymous".to_string().into()) } #[inline(always)] fn devicename(self) -> Result { Ok("Unknown".to_string().into()) } #[inline(always)] fn hostname(self) -> Result { Ok("localhost".to_string()) } #[inline(always)] fn distro(self) -> Result { Ok(format!("Unknown {}", self.platform())) } #[inline(always)] fn desktop_env(self) -> DesktopEnv { DesktopEnv::Unknown("Unknown".to_string()) } #[inline(always)] fn platform(self) -> Platform { if cfg!(daku) { Platform::Unknown("Daku".to_string()) } else if cfg!(target_os = "wasi") { Platform::Unknown("WASI".to_string()) } else if cfg!(target_os = "windows") { Platform::Windows } else if cfg!(target_os = "macos") { Platform::MacOS } else if cfg!(target_os = "redox") { Platform::Redox } else if cfg!(target_os = "linux") { Platform::Linux } else if cfg!(target_os = "android") { Platform::Android } else if cfg!(target_os = "tvos") { Platform::Unknown("tvOS".to_string()) } else if cfg!(target_os = "watchos") { Platform::Unknown("watchOS".to_string()) } else if cfg!(target_os = "ios") { Platform::Unknown("iOS".to_string()) } else if cfg!(target_os = "fuchsia") { Platform::Fuchsia } else if cfg!(target_os = "illumos") { Platform::Illumos } else if cfg!(any( target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", )) { Platform::Bsd } else if cfg!(target_os = "haiku") { Platform::Unknown("Haiku".to_string()) } else if cfg!(target_os = "vxworks") { Platform::Unknown("VxWorks".to_string()) } else if cfg!(target_os = "nto") { Platform::Unknown("QNX Neutrino".to_string()) } else if cfg!(target_os = "horizon") { Platform::Nintendo } else if cfg!(target_os = "vita") { Platform::PlayStation } else if cfg!(target_os = "hurd") { Platform::Unknown("GNU Hurd".to_string()) } else if cfg!(target_os = "aix") { Platform::Unknown("AIX OS".to_string()) } else if cfg!(target_os = "espidf") { Platform::Unknown("ESP-IDF".to_string()) } else if cfg!(target_os = "emscripten") { Platform::Unknown("Emscripten".to_string()) } else if cfg!(target_os = "solaris") { Platform::Unknown("Solaris".to_string()) } else if cfg!(target_os = "l4re") { Platform::Unknown("L4 Runtime Environment".to_string()) } else { Platform::Unknown("Unknown".to_string()) } } #[inline(always)] fn arch(self) -> Result { Ok(if cfg!(target_pointer_width = "64") { Arch::Wasm64 } else if cfg!(target_pointer_width = "32") { Arch::Wasm32 } else { return Err(Error::new( ErrorKind::Other, // FIXME: WhoAmI 2.0, Unsupported "Unexpected pointer width for target platform", )); }) } } whoami-1.5.2/src/os/unix.rs000064400000000000000000000445331046102023000136670ustar 00000000000000#[cfg(target_os = "illumos")] use std::convert::TryInto; #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "illumos", ))] use std::env; use std::{ ffi::{c_void, CStr, OsString}, fs, io::{Error, ErrorKind}, mem, os::{ raw::{c_char, c_int}, unix::ffi::OsStringExt, }, slice, }; #[cfg(target_os = "macos")] use std::{ os::{ raw::{c_long, c_uchar}, unix::ffi::OsStrExt, }, ptr::null_mut, }; use crate::{ os::{Os, Target}, Arch, DesktopEnv, Platform, Result, }; #[cfg(target_os = "linux")] #[repr(C)] struct PassWd { pw_name: *const c_void, pw_passwd: *const c_void, pw_uid: u32, pw_gid: u32, pw_gecos: *const c_void, pw_dir: *const c_void, pw_shell: *const c_void, } #[cfg(any( target_os = "macos", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd" ))] #[repr(C)] struct PassWd { pw_name: *const c_void, pw_passwd: *const c_void, pw_uid: u32, pw_gid: u32, pw_change: isize, pw_class: *const c_void, pw_gecos: *const c_void, pw_dir: *const c_void, pw_shell: *const c_void, pw_expire: isize, pw_fields: i32, } #[cfg(target_os = "illumos")] #[repr(C)] struct PassWd { pw_name: *const c_void, pw_passwd: *const c_void, pw_uid: u32, pw_gid: u32, pw_age: *const c_void, pw_comment: *const c_void, pw_gecos: *const c_void, pw_dir: *const c_void, pw_shell: *const c_void, } #[cfg(target_os = "illumos")] extern "system" { fn getpwuid_r( uid: u32, pwd: *mut PassWd, buf: *mut c_void, buflen: c_int, ) -> *mut PassWd; } #[cfg(any( target_os = "linux", target_os = "macos", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", ))] extern "system" { fn getpwuid_r( uid: u32, pwd: *mut PassWd, buf: *mut c_void, buflen: usize, result: *mut *mut PassWd, ) -> i32; } extern "system" { fn geteuid() -> u32; fn gethostname(name: *mut c_void, len: usize) -> i32; } #[cfg(target_os = "macos")] // FIXME: seemingly false positive for link lint #[allow(clippy::duplicated_attributes)] #[link(name = "CoreFoundation", kind = "framework")] #[link(name = "SystemConfiguration", kind = "framework")] extern "system" { fn CFStringGetCString( the_string: *mut c_void, buffer: *mut u8, buffer_size: c_long, encoding: u32, ) -> c_uchar; fn CFStringGetLength(the_string: *mut c_void) -> c_long; fn CFStringGetMaximumSizeForEncoding( length: c_long, encoding: u32, ) -> c_long; fn SCDynamicStoreCopyComputerName( store: *mut c_void, encoding: *mut u32, ) -> *mut c_void; fn CFRelease(cf: *const c_void); } enum Name { User, Real, } unsafe fn strlen(cs: *const c_void) -> usize { let mut len = 0; let mut cs: *const u8 = cs.cast(); while *cs != 0 { len += 1; cs = cs.offset(1); } len } unsafe fn strlen_gecos(cs: *const c_void) -> usize { let mut len = 0; let mut cs: *const u8 = cs.cast(); while *cs != 0 && *cs != b',' { len += 1; cs = cs.offset(1); } len } fn os_from_cstring_gecos(string: *const c_void) -> Result { if string.is_null() { return Err(super::err_null_record()); } // Get a byte slice of the c string. let slice = unsafe { let length = strlen_gecos(string); if length == 0 { return Err(super::err_empty_record()); } slice::from_raw_parts(string.cast(), length) }; // Turn byte slice into Rust String. Ok(OsString::from_vec(slice.to_vec())) } fn os_from_cstring(string: *const c_void) -> Result { if string.is_null() { return Err(super::err_null_record()); } // Get a byte slice of the c string. let slice = unsafe { let length = strlen(string); if length == 0 { return Err(super::err_empty_record()); } slice::from_raw_parts(string.cast(), length) }; // Turn byte slice into Rust String. Ok(OsString::from_vec(slice.to_vec())) } #[cfg(target_os = "macos")] fn os_from_cfstring(string: *mut c_void) -> OsString { if string.is_null() { return "".to_string().into(); } unsafe { let len = CFStringGetLength(string); let capacity = CFStringGetMaximumSizeForEncoding(len, 134_217_984 /* UTF8 */) + 1; let mut out = Vec::with_capacity(capacity as usize); if CFStringGetCString( string, out.as_mut_ptr(), capacity, 134_217_984, /* UTF8 */ ) != 0 { out.set_len(strlen(out.as_ptr().cast())); // Remove trailing NUL byte out.shrink_to_fit(); CFRelease(string); OsString::from_vec(out) } else { CFRelease(string); "".to_string().into() } } } // This function must allocate, because a slice or `Cow` would still // reference `passwd` which is dropped when this function returns. #[inline(always)] fn getpwuid(name: Name) -> Result { const BUF_SIZE: usize = 16_384; // size from the man page let mut buffer = mem::MaybeUninit::<[u8; BUF_SIZE]>::uninit(); let mut passwd = mem::MaybeUninit::::uninit(); // Get PassWd `struct`. let passwd = unsafe { #[cfg(any( target_os = "linux", target_os = "macos", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", ))] { let mut _passwd = mem::MaybeUninit::<*mut PassWd>::uninit(); let ret = getpwuid_r( geteuid(), passwd.as_mut_ptr(), buffer.as_mut_ptr() as *mut c_void, BUF_SIZE, _passwd.as_mut_ptr(), ); if ret != 0 { return Err(Error::last_os_error()); } let _passwd = _passwd.assume_init(); if _passwd.is_null() { return Err(super::err_null_record()); } passwd.assume_init() } #[cfg(target_os = "illumos")] { let ret = getpwuid_r( geteuid(), passwd.as_mut_ptr(), buffer.as_mut_ptr() as *mut c_void, BUF_SIZE.try_into().unwrap_or(c_int::MAX), ); if ret.is_null() { return Err(Error::last_os_error()); } passwd.assume_init() } }; // Extract names. if let Name::Real = name { os_from_cstring_gecos(passwd.pw_gecos) } else { os_from_cstring(passwd.pw_name) } } #[cfg(target_os = "macos")] fn distro_xml(data: String) -> Result { let mut product_name = None; let mut user_visible_version = None; if let Some(start) = data.find("") { if let Some(end) = data.find("") { let mut set_product_name = false; let mut set_user_visible_version = false; for line in data[start + "".len()..end].lines() { let line = line.trim(); if line.starts_with("") { match line["".len()..].trim_end_matches("") { "ProductName" => set_product_name = true, "ProductUserVisibleVersion" => { set_user_visible_version = true } "ProductVersion" => { if user_visible_version.is_none() { set_user_visible_version = true } } _ => {} } } else if line.starts_with("") { if set_product_name { product_name = Some( line["".len()..] .trim_end_matches(""), ); set_product_name = false; } else if set_user_visible_version { user_visible_version = Some( line["".len()..] .trim_end_matches(""), ); set_user_visible_version = false; } } } } } Ok(if let Some(product_name) = product_name { if let Some(user_visible_version) = user_visible_version { format!("{} {}", product_name, user_visible_version) } else { product_name.to_string() } } else { user_visible_version .map(|v| format!("Mac OS (Unknown) {}", v)) .ok_or_else(|| { Error::new(ErrorKind::InvalidData, "Parsing failed") })? }) } #[cfg(any( target_os = "macos", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", ))] #[repr(C)] struct UtsName { sysname: [c_char; 256], nodename: [c_char; 256], release: [c_char; 256], version: [c_char; 256], machine: [c_char; 256], } #[cfg(target_os = "illumos")] #[repr(C)] struct UtsName { sysname: [c_char; 257], nodename: [c_char; 257], release: [c_char; 257], version: [c_char; 257], machine: [c_char; 257], } #[cfg(target_os = "dragonfly")] #[repr(C)] struct UtsName { sysname: [c_char; 32], nodename: [c_char; 32], release: [c_char; 32], version: [c_char; 32], machine: [c_char; 32], } #[cfg(any(target_os = "linux", target_os = "android",))] #[repr(C)] struct UtsName { sysname: [c_char; 65], nodename: [c_char; 65], release: [c_char; 65], version: [c_char; 65], machine: [c_char; 65], domainname: [c_char; 65], } // Buffer initialization impl Default for UtsName { fn default() -> Self { unsafe { mem::zeroed() } } } #[inline(always)] unsafe fn uname(buf: *mut UtsName) -> c_int { extern "C" { #[cfg(any( target_os = "linux", target_os = "macos", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd", target_os = "illumos", ))] fn uname(buf: *mut UtsName) -> c_int; #[cfg(target_os = "freebsd")] fn __xuname(nmln: c_int, buf: *mut c_void) -> c_int; } // Polyfill `uname()` for FreeBSD #[inline(always)] #[cfg(target_os = "freebsd")] unsafe extern "C" fn uname(buf: *mut UtsName) -> c_int { __xuname(256, buf.cast()) } uname(buf) } impl Target for Os { fn langs(self) -> Result { super::unix_lang() } fn realname(self) -> Result { getpwuid(Name::Real) } fn username(self) -> Result { getpwuid(Name::User) } fn devicename(self) -> Result { #[cfg(target_os = "macos")] { let out = os_from_cfstring(unsafe { SCDynamicStoreCopyComputerName(null_mut(), null_mut()) }); if out.as_bytes().is_empty() { return Err(super::err_empty_record()); } Ok(out) } #[cfg(target_os = "illumos")] { let mut nodename = fs::read("/etc/nodename")?; // Remove all at and after the first newline (before end of file) if let Some(slice) = nodename.split(|x| *x == b'\n').next() { nodename.drain(slice.len()..); } if nodename.is_empty() { return Err(super::err_empty_record()); } Ok(OsString::from_vec(nodename)) } #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", ))] { let machine_info = fs::read("/etc/machine-info")?; for i in machine_info.split(|b| *b == b'\n') { let mut j = i.split(|b| *b == b'='); if j.next() == Some(b"PRETTY_HOSTNAME") { if let Some(value) = j.next() { // FIXME: Can " be escaped in pretty name? return Ok(OsString::from_vec(value.to_vec())); } } } Err(super::err_missing_record()) } } fn hostname(self) -> Result { // Maximum hostname length = 255, plus a NULL byte. let mut string = Vec::::with_capacity(256); unsafe { if gethostname(string.as_mut_ptr().cast(), 255) == -1 { return Err(Error::last_os_error()); } string.set_len(strlen(string.as_ptr().cast())); }; String::from_utf8(string).map_err(|_| { Error::new(ErrorKind::InvalidData, "Hostname not valid UTF-8") }) } fn distro(self) -> Result { #[cfg(target_os = "macos")] { if let Ok(data) = fs::read_to_string( "/System/Library/CoreServices/ServerVersion.plist", ) { distro_xml(data) } else if let Ok(data) = fs::read_to_string( "/System/Library/CoreServices/SystemVersion.plist", ) { distro_xml(data) } else { Err(super::err_missing_record()) } } #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "illumos", ))] { let program = fs::read("/etc/os-release")?; let distro = String::from_utf8_lossy(&program); let err = || Error::new(ErrorKind::InvalidData, "Parsing failed"); let mut fallback = None; for i in distro.split('\n') { let mut j = i.split('='); match j.next().ok_or_else(err)? { "PRETTY_NAME" => { return Ok(j .next() .ok_or_else(err)? .trim_matches('"') .to_string()); } "NAME" => { fallback = Some( j.next() .ok_or_else(err)? .trim_matches('"') .to_string(), ) } _ => {} } } fallback.ok_or_else(err) } } fn desktop_env(self) -> DesktopEnv { #[cfg(target_os = "macos")] let env = "Aqua"; // FIXME: WhoAmI 2.0: use `let else` #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "illumos", ))] let env = env::var_os("DESKTOP_SESSION"); #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "illumos", ))] let env = if let Some(ref env) = env { env.to_string_lossy() } else { return DesktopEnv::Unknown("Unknown".to_string()); }; if env.eq_ignore_ascii_case("AQUA") { DesktopEnv::Aqua } else if env.eq_ignore_ascii_case("GNOME") { DesktopEnv::Gnome } else if env.eq_ignore_ascii_case("LXDE") { DesktopEnv::Lxde } else if env.eq_ignore_ascii_case("OPENBOX") { DesktopEnv::Openbox } else if env.eq_ignore_ascii_case("I3") { DesktopEnv::I3 } else if env.eq_ignore_ascii_case("UBUNTU") { DesktopEnv::Ubuntu } else if env.eq_ignore_ascii_case("PLASMA5") { DesktopEnv::Kde // TODO: Other Linux Desktop Environments } else { DesktopEnv::Unknown(env.to_string()) } } #[inline(always)] fn platform(self) -> Platform { #[cfg(target_os = "linux")] { Platform::Linux } #[cfg(target_os = "macos")] { Platform::MacOS } #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", ))] { Platform::Bsd } #[cfg(target_os = "illumos")] { Platform::Illumos } } #[inline(always)] fn arch(self) -> Result { let mut buf = UtsName::default(); if unsafe { uname(&mut buf) } == -1 { return Err(Error::last_os_error()); } let arch_str = unsafe { CStr::from_ptr(buf.machine.as_ptr()) }.to_string_lossy(); Ok(match arch_str.as_ref() { "aarch64" | "arm64" | "aarch64_be" | "armv8b" | "armv8l" => { Arch::Arm64 } "armv5" => Arch::ArmV5, "armv6" | "arm" => Arch::ArmV6, "armv7" => Arch::ArmV7, "i386" => Arch::I386, "i586" => Arch::I586, "i686" | "i686-AT386" => Arch::I686, "mips" => Arch::Mips, "mipsel" => Arch::MipsEl, "mips64" => Arch::Mips64, "mips64el" => Arch::Mips64El, "powerpc" | "ppc" | "ppcle" => Arch::PowerPc, "powerpc64" | "ppc64" | "ppc64le" => Arch::PowerPc64, "powerpc64le" => Arch::PowerPc64Le, "riscv32" => Arch::Riscv32, "riscv64" => Arch::Riscv64, "s390x" => Arch::S390x, "sparc" => Arch::Sparc, "sparc64" => Arch::Sparc64, "x86_64" | "amd64" => Arch::X64, _ => Arch::Unknown(arch_str.into_owned()), }) } } whoami-1.5.2/src/os/wasi.rs000064400000000000000000000032771046102023000136470ustar 00000000000000#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] compile_error!("Unexpected pointer width for target platform"); use std::{env, ffi::OsString}; use crate::{ os::{Os, Target}, Arch, DesktopEnv, Platform, Result, }; impl Target for Os { fn langs(self) -> Result { super::unix_lang() } #[inline(always)] fn realname(self) -> Result { Ok(wasite::user() .unwrap_or_else(|_e| "Anonymous".to_string()) .into()) } #[inline(always)] fn username(self) -> Result { Ok(wasite::user() .unwrap_or_else(|_e| "anonymous".to_string()) .into()) } #[inline(always)] fn devicename(self) -> Result { Ok(wasite::name() .unwrap_or_else(|_e| "Unknown".to_string()) .into()) } #[inline(always)] fn hostname(self) -> Result { Ok(wasite::hostname().unwrap_or_else(|_e| "localhost".to_string())) } #[inline(always)] fn distro(self) -> Result { Ok("Unknown WASI".to_string()) } #[inline(always)] fn desktop_env(self) -> DesktopEnv { if let Some(ref env) = env::var_os("DESKTOP_SESSION") { DesktopEnv::Unknown(env.to_string_lossy().to_string()) } else { DesktopEnv::Unknown("Unknown WASI".to_string()) } } #[inline(always)] fn platform(self) -> Platform { Platform::Unknown("WASI".to_string()) } #[inline(always)] fn arch(self) -> Result { Ok(if cfg!(target_pointer_width = "64") { Arch::Wasm64 } else { Arch::Wasm32 }) } } whoami-1.5.2/src/os/web.rs000064400000000000000000000145361046102023000134610ustar 00000000000000#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] compile_error!("Unexpected pointer width for target platform"); use std::{ ffi::OsString, io::{Error, ErrorKind}, }; use web_sys::window; use crate::{ os::{Os, Target}, Arch, DesktopEnv, Platform, Result, }; // Get the user agent fn user_agent() -> Option { window()?.navigator().user_agent().ok() } // Get the document domain fn document_domain() -> Option { window()?.document()?.location()?.hostname().ok() } impl Target for Os { fn langs(self) -> Result { if let Some(window) = window() { Ok(window .navigator() .languages() .to_vec() .into_iter() .filter_map(|l| l.as_string()) .collect::>() .join(";")) } else { Err(Error::new(ErrorKind::NotFound, "Window missing")) } } fn realname(self) -> Result { Ok("Anonymous".to_string().into()) } fn username(self) -> Result { Ok("anonymous".to_string().into()) } fn devicename(self) -> Result { let orig_string = user_agent().unwrap_or_default(); let start = if let Some(s) = orig_string.rfind(' ') { s } else { return Ok("Unknown Browser".to_string().into()); }; let string = orig_string .get(start + 1..) .unwrap_or("Unknown Browser") .replace('/', " "); let string = if let Some(s) = string.rfind("Safari") { if let Some(s) = orig_string.rfind("Chrome") { if let Some(e) = orig_string.get(s..).unwrap_or_default().find(' ') { orig_string .get(s..) .unwrap_or("Chrome") .get(..e) .unwrap_or("Chrome") .replace('/', " ") } else { "Chrome".to_string() } } else if orig_string.contains("Linux") { "GNOME Web".to_string() } else { string.get(s..).unwrap_or("Safari").replace('/', " ") } } else if string.contains("Edg ") { string.replace("Edg ", "Edge ") } else if string.contains("OPR ") { string.replace("OPR ", "Opera ") } else { string }; Ok(string.into()) } fn hostname(self) -> Result { document_domain() .filter(|x| !x.is_empty()) .ok_or_else(|| Error::new(ErrorKind::NotFound, "Domain missing")) } fn distro(self) -> Result { let string = user_agent() .ok_or_else(|| Error::from(ErrorKind::PermissionDenied))?; let err = || Error::new(ErrorKind::InvalidData, "Parsing failed"); let begin = string.find('(').ok_or_else(err)?; let end = string.find(')').ok_or_else(err)?; let string = &string[begin + 1..end]; Ok(if string.contains("Win32") || string.contains("Win64") { let begin = if let Some(b) = string.find("NT") { b } else { return Ok("Windows".to_string()); }; let end = if let Some(e) = string.find('.') { e } else { return Ok("Windows".to_string()); }; let string = &string[begin + 3..end]; format!("Windows {}", string) } else if string.contains("Linux") { let string = if string.contains("X11") || string.contains("Wayland") { let begin = if let Some(b) = string.find(';') { b } else { return Ok("Unknown Linux".to_string()); }; &string[begin + 2..] } else { string }; if string.starts_with("Linux") { "Unknown Linux".to_string() } else { let end = if let Some(e) = string.find(';') { e } else { return Ok("Unknown Linux".to_string()); }; string[..end].to_string() } } else if let Some(begin) = string.find("Mac OS X") { if let Some(end) = string[begin..].find(';') { string[begin..begin + end].to_string() } else { string[begin..].to_string().replace('_', ".") } } else { // TODO: // Platform::FreeBsd, // Platform::Ios, // Platform::Android, // Platform::Nintendo, // Platform::Xbox, // Platform::PlayStation, // Platform::Dive, // Platform::Fuchsia, // Platform::Redox string.to_string() }) } #[inline(always)] fn desktop_env(self) -> DesktopEnv { DesktopEnv::WebBrowser } fn platform(self) -> Platform { let string = user_agent().unwrap_or_default(); let begin = if let Some(b) = string.find('(') { b } else { return Platform::Unknown("Unknown".to_string()); }; let end = if let Some(e) = string.find(')') { e } else { return Platform::Unknown("Unknown".to_string()); }; let string = &string[begin + 1..end]; if string.contains("Win32") || string.contains("Win64") { Platform::Windows } else if string.contains("Linux") { Platform::Linux } else if string.contains("Mac OS X") { Platform::MacOS } else { // TODO: // Platform::FreeBsd, // Platform::Ios, // Platform::Android, // Platform::Nintendo, // Platform::Xbox, // Platform::PlayStation, // Platform::Dive, // Platform::Fuchsia, // Platform::Redox, Platform::Unknown(string.to_string()) } } #[inline(always)] fn arch(self) -> Result { Ok(if cfg!(target_pointer_width = "64") { Arch::Wasm64 } else { Arch::Wasm32 }) } } whoami-1.5.2/src/os/windows.rs000064400000000000000000000327371046102023000144010ustar 00000000000000use std::{ convert::TryInto, ffi::OsString, io::{Error, ErrorKind}, mem::{self, MaybeUninit}, os::{ raw::{c_char, c_int, c_uchar, c_ulong, c_ushort, c_void}, windows::ffi::OsStringExt, }, ptr, }; use crate::{ conversions, os::{Os, Target}, Arch, DesktopEnv, Platform, Result, }; #[repr(C)] struct OsVersionInfoEx { os_version_info_size: c_ulong, major_version: c_ulong, minor_version: c_ulong, build_number: c_ulong, platform_id: c_ulong, sz_csd_version: [u16; 128], service_pack_major: c_ushort, service_pack_minor: c_ushort, suite_mask: c_ushort, product_type: c_uchar, reserved: c_uchar, } // Source: // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info#syntax #[repr(C)] struct SystemInfo { processor_architecture: c_ushort, reserved: c_ushort, dw_page_size: c_ulong, minimum_application_address: *mut c_void, maximum_application_address: *mut c_void, active_processor_mask: usize, number_of_processors: c_ulong, processor_type: c_ulong, allocation_granularity: c_ulong, processor_level: c_ushort, processor_revision: c_ushort, } #[allow(unused)] #[repr(C)] #[derive(Copy, Clone)] enum ExtendedNameFormat { Unknown, // Nothing FullyQualifiedDN, // Nothing SamCompatible, // Hostname Followed By Username Display, // Full Name UniqueId, // Nothing Canonical, // Nothing UserPrincipal, // Nothing CanonicalEx, // Nothing ServicePrincipal, // Nothing DnsDomain, // Nothing GivenName, // Nothing Surname, // Nothing } #[allow(unused)] #[repr(C)] enum ComputerNameFormat { NetBIOS, // Same as GetComputerNameW DnsHostname, // Fancy Name DnsDomain, // Nothing DnsFullyQualified, // Fancy Name with, for example, .com PhysicalNetBIOS, // Same as GetComputerNameW PhysicalDnsHostname, // Same as GetComputerNameW PhysicalDnsDomain, // Nothing PhysicalDnsFullyQualified, // Fancy Name with, for example, .com Max, } const ERR_MORE_DATA: i32 = 0xEA; const ERR_INSUFFICIENT_BUFFER: i32 = 0x7A; const ERR_NONE_MAPPED: i32 = 0x534; #[link(name = "secur32")] extern "system" { fn GetUserNameExW( a: ExtendedNameFormat, b: *mut c_char, c: *mut c_ulong, ) -> c_uchar; fn GetUserNameW(a: *mut c_char, b: *mut c_ulong) -> c_int; fn GetComputerNameExW( a: ComputerNameFormat, b: *mut c_char, c: *mut c_ulong, ) -> c_int; } #[link(name = "kernel32")] extern "system" { fn GetUserPreferredUILanguages( dw_flags: c_ulong, pul_num_languages: *mut c_ulong, pwsz_languages_buffer: *mut u16, pcch_languages_buffer: *mut c_ulong, ) -> c_int; fn GetNativeSystemInfo(system_info: *mut SystemInfo); } fn username() -> Result { // Step 1. Retreive the entire length of the username let mut size = 0; let fail = unsafe { GetUserNameW(ptr::null_mut(), &mut size) == 0 }; assert!(fail); if Error::last_os_error().raw_os_error() != Some(ERR_INSUFFICIENT_BUFFER) { return Err(Error::last_os_error()); } // Step 2. Allocate memory to put the Windows (UTF-16) string. let mut name: Vec = Vec::with_capacity(size.try_into().unwrap_or(std::usize::MAX)); size = name.capacity().try_into().unwrap_or(std::u32::MAX); let orig_size = size; let fail = unsafe { GetUserNameW(name.as_mut_ptr().cast(), &mut size) == 0 }; if fail { return Err(Error::last_os_error()); } debug_assert_eq!(orig_size, size); unsafe { name.set_len(size.try_into().unwrap_or(std::usize::MAX)); } let terminator = name.pop(); // Remove Trailing Null debug_assert_eq!(terminator, Some(0u16)); // Step 3. Convert to Rust String Ok(OsString::from_wide(&name)) } fn extended_name(format: ExtendedNameFormat) -> Result { // Step 1. Retrieve the entire length of the username let mut buf_size = 0; let fail = unsafe { GetUserNameExW(format, ptr::null_mut(), &mut buf_size) == 0 }; assert!(fail); let last_err = Error::last_os_error().raw_os_error(); if last_err == Some(ERR_NONE_MAPPED) { return Err(super::err_missing_record()); } if last_err != Some(ERR_MORE_DATA) { return Err(Error::last_os_error()); } // Step 2. Allocate memory to put the Windows (UTF-16) string. let mut name: Vec = Vec::with_capacity(buf_size.try_into().unwrap_or(std::usize::MAX)); let mut name_len = name.capacity().try_into().unwrap_or(std::u32::MAX); let fail = unsafe { GetUserNameExW(format, name.as_mut_ptr().cast(), &mut name_len) == 0 }; if fail { return Err(Error::last_os_error()); } assert_eq!(buf_size, name_len + 1); unsafe { name.set_len(name_len.try_into().unwrap_or(std::usize::MAX)) }; // Step 3. Convert to Rust String Ok(OsString::from_wide(&name)) } impl Target for Os { #[inline(always)] fn langs(self) -> Result { let mut num_languages = 0; let mut buffer_size = 0; let mut buffer; unsafe { assert_ne!( GetUserPreferredUILanguages( 0x08, /* MUI_LANGUAGE_NAME */ &mut num_languages, ptr::null_mut(), // List of languages. &mut buffer_size, ), 0 ); buffer = Vec::with_capacity(buffer_size as usize); assert_ne!( GetUserPreferredUILanguages( 0x08, /* MUI_LANGUAGE_NAME */ &mut num_languages, buffer.as_mut_ptr(), // List of languages. &mut buffer_size, ), 0 ); buffer.set_len(buffer_size as usize); } // We know it ends in two null characters. buffer.pop(); buffer.pop(); // Combine into a single string Ok(String::from_utf16_lossy(&buffer) .split('\0') .collect::>() .join(";")) } fn realname(self) -> Result { extended_name(ExtendedNameFormat::Display) } fn username(self) -> Result { username() } fn devicename(self) -> Result { // Step 1. Retreive the entire length of the device name let mut size = 0; let fail = unsafe { // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER GetComputerNameExW( ComputerNameFormat::DnsHostname, ptr::null_mut(), &mut size, ) == 0 }; assert!(fail); if Error::last_os_error().raw_os_error() != Some(ERR_MORE_DATA) { return Err(Error::last_os_error()); } // Step 2. Allocate memory to put the Windows (UTF-16) string. let mut name: Vec = Vec::with_capacity(size.try_into().unwrap_or(std::usize::MAX)); let mut size = name.capacity().try_into().unwrap_or(std::u32::MAX); if unsafe { GetComputerNameExW( ComputerNameFormat::DnsHostname, name.as_mut_ptr().cast(), &mut size, ) == 0 } { return Err(Error::last_os_error()); } unsafe { name.set_len(size.try_into().unwrap_or(std::usize::MAX)); } // Step 3. Convert to Rust String Ok(OsString::from_wide(&name)) } fn hostname(self) -> Result { // Step 1. Retreive the entire length of the username let mut size = 0; let fail = unsafe { // Ignore error, we know that it will be ERROR_INSUFFICIENT_BUFFER GetComputerNameExW( ComputerNameFormat::NetBIOS, ptr::null_mut(), &mut size, ) == 0 }; assert!(fail); if Error::last_os_error().raw_os_error() != Some(ERR_MORE_DATA) { return Err(Error::last_os_error()); } // Step 2. Allocate memory to put the Windows (UTF-16) string. let mut name: Vec = Vec::with_capacity(size.try_into().unwrap_or(std::usize::MAX)); let mut size = name.capacity().try_into().unwrap_or(std::u32::MAX); if unsafe { GetComputerNameExW( ComputerNameFormat::NetBIOS, name.as_mut_ptr().cast(), &mut size, ) == 0 } { return Err(Error::last_os_error()); } unsafe { name.set_len(size.try_into().unwrap_or(std::usize::MAX)); } // Step 3. Convert to Rust String conversions::string_from_os(OsString::from_wide(&name)) } fn distro(self) -> Result { // Due to MingW Limitations, we must dynamically load ntdll.dll extern "system" { fn LoadLibraryExW( filename: *const u16, hfile: *mut c_void, dwflags: c_ulong, ) -> *mut c_void; fn FreeLibrary(hmodule: *mut c_void) -> i32; fn GetProcAddress( hmodule: *mut c_void, procname: *const c_char, ) -> *mut c_void; } let mut path = "ntdll.dll\0".encode_utf16().collect::>(); let path = path.as_mut_ptr(); let inst = unsafe { LoadLibraryExW(path, ptr::null_mut(), 0x0000_0800) }; if inst.is_null() { return Err(Error::last_os_error()); } let mut path = "RtlGetVersion\0".bytes().collect::>(); let path = path.as_mut_ptr().cast(); let func = unsafe { GetProcAddress(inst, path) }; if func.is_null() { if unsafe { FreeLibrary(inst) } == 0 { return Err(Error::last_os_error()); } return Err(Error::last_os_error()); } let get_version: unsafe extern "system" fn( a: *mut OsVersionInfoEx, ) -> u32 = unsafe { mem::transmute(func) }; let mut version = MaybeUninit::::zeroed(); // FIXME `mem::size_of` seemingly false positive for lint #[allow(unused_qualifications)] let version = unsafe { (*version.as_mut_ptr()).os_version_info_size = mem::size_of::() as u32; get_version(version.as_mut_ptr()); if FreeLibrary(inst) == 0 { return Err(Error::last_os_error()); } version.assume_init() }; let product = match version.product_type { 1 => "Workstation", 2 => "Domain Controller", 3 => "Server", _ => "Unknown", }; Ok(format!( "Windows {}.{}.{} ({})", version.major_version, version.minor_version, version.build_number, product, )) } #[inline(always)] fn desktop_env(self) -> DesktopEnv { DesktopEnv::Windows } #[inline(always)] fn platform(self) -> Platform { Platform::Windows } #[inline(always)] fn arch(self) -> Result { fn proc(processor_type: c_ulong) -> Result { Ok(match processor_type { // PROCESSOR_INTEL_386 386 => Arch::I386, // PROCESSOR_INTEL_486 486 => Arch::Unknown("I486".to_string()), // PROCESSOR_INTEL_PENTIUM 586 => Arch::I586, // PROCESSOR_INTEL_IA64 2200 => Arch::Unknown("IA64".to_string()), // PROCESSOR_AMD_X8664 8664 => Arch::X64, v => return Err(v), }) } let buf: SystemInfo = unsafe { let mut buf = MaybeUninit::uninit(); GetNativeSystemInfo(buf.as_mut_ptr()); buf.assume_init() }; // Supported architectures, source: // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info#members Ok(match buf.processor_architecture { // PROCESSOR_ARCHITECTURE_INTEL 0 => Arch::I686, // PROCESSOR_ARCHITECTURE_ARM 5 => Arch::ArmV6, // PROCESSOR_ARCHITECTURE_IA64 6 => Arch::Unknown("IA64".to_string()), // PROCESSOR_ARCHITECTURE_AMD64 9 => Arch::X64, // PROCESSOR_ARCHITECTURE_ARM64 12 => Arch::Arm64, // PROCESSOR_ARCHITECTURE_UNKNOWN 0xFFFF => proc(buf.processor_type).map_err(|e| { Error::new( ErrorKind::InvalidData, format!("Unknown arch: {}", e), ) })?, invalid => proc(buf.processor_type).map_err(|e| { Error::new( ErrorKind::InvalidData, format!("Invalid arch: {}/{}", invalid, e), ) })?, }) } #[inline(always)] fn account(self) -> Result { match extended_name(ExtendedNameFormat::UserPrincipal) { Ok(name) => Ok(name), Err(e) if e.kind() == ErrorKind::NotFound => username(), Err(e) => Err(e), } } } whoami-1.5.2/src/os.rs000064400000000000000000000062231046102023000126760ustar 00000000000000#![allow(unsafe_code)] // Daku #[cfg_attr(all(target_arch = "wasm32", daku), path = "os/daku.rs")] // Redox #[cfg_attr( all(target_os = "redox", not(target_arch = "wasm32")), path = "os/redox.rs" )] // Unix #[cfg_attr( all( any( target_os = "linux", target_os = "macos", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "illumos", ), not(target_arch = "wasm32") ), path = "os/unix.rs" )] // Wasi #[cfg_attr( all(target_arch = "wasm32", target_os = "wasi"), path = "os/wasi.rs" )] // Web #[cfg_attr( all( target_arch = "wasm32", not(target_os = "wasi"), not(daku), feature = "web", ), path = "os/web.rs" )] // Windows #[cfg_attr( all(target_os = "windows", not(target_arch = "wasm32")), path = "os/windows.rs" )] mod target; use std::{ env::{self, VarError}, ffi::OsString, io::{Error, ErrorKind}, }; use crate::{Arch, DesktopEnv, Platform, Result}; /// Implement `Target for Os` to add platform support for a target. pub(crate) struct Os; /// Target platform support pub(crate) trait Target: Sized { /// Return a semicolon-delimited string of language/COUNTRY codes. fn langs(self) -> Result; /// Return the user's "real" / "full" name. fn realname(self) -> Result; /// Return the user's username. fn username(self) -> Result; /// Return the computer's "fancy" / "pretty" name. fn devicename(self) -> Result; /// Return the computer's hostname. fn hostname(self) -> Result; /// Return the OS distribution's name. fn distro(self) -> Result; /// Return the desktop environment. fn desktop_env(self) -> DesktopEnv; /// Return the target platform. fn platform(self) -> Platform; /// Return the computer's CPU architecture. fn arch(self) -> Result; /// Return the user's account name (usually just the username, but may /// include an account server hostname). fn account(self) -> Result { self.username() } } // This is only used on some platforms #[allow(dead_code)] fn err_missing_record() -> Error { Error::new(ErrorKind::NotFound, "Missing record") } // This is only used on some platforms #[allow(dead_code)] fn err_null_record() -> Error { Error::new(ErrorKind::NotFound, "Null record") } // This is only used on some platforms #[allow(dead_code)] fn err_empty_record() -> Error { Error::new(ErrorKind::NotFound, "Empty record") } // This is only used on some platforms #[allow(dead_code)] fn unix_lang() -> Result { let check_var = |var| { env::var(var).map_err(|e| { let kind = match e { VarError::NotPresent => ErrorKind::NotFound, VarError::NotUnicode(_) => ErrorKind::InvalidData, }; Error::new(kind, e) }) }; let langs = check_var("LANGS").or_else(|_| check_var("LANG"))?; if langs.is_empty() { return Err(err_empty_record()); } Ok(langs) } whoami-1.5.2/src/platform.rs000064400000000000000000000024611046102023000141010ustar 00000000000000use std::fmt::{self, Display, Formatter}; /// The underlying platform for a system #[allow(missing_docs)] #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum Platform { Linux, Bsd, Windows, // FIXME: Non-standard casing; Rename to 'Mac' rather than 'MacOs' in // whoami 2.0.0 MacOS, Illumos, Ios, Android, // FIXME: Separate for different Nintendo consoles in whoami 2.0.0, // currently only used for 3DS Nintendo, // FIXME: Currently unused, remove in whoami 2.0.0 Xbox, PlayStation, Fuchsia, Redox, Unknown(String), } impl Display for Platform { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Self::Unknown(_) = self { f.write_str("Unknown: ")?; } f.write_str(match self { Self::Linux => "Linux", Self::Bsd => "BSD", Self::Windows => "Windows", Self::MacOS => "Mac OS", Self::Illumos => "illumos", Self::Ios => "iOS", Self::Android => "Android", Self::Nintendo => "Nintendo", Self::Xbox => "XBox", Self::PlayStation => "PlayStation", Self::Fuchsia => "Fuchsia", Self::Redox => "Redox", Self::Unknown(a) => a, }) } } whoami-1.5.2/src/result.rs000064400000000000000000000002421046102023000135660ustar 00000000000000use std::io::Error; /// This crate's convenience type alias for [`Result`](std::result::Result)s pub type Result = std::result::Result;