Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"c3d6d2569796fff4149e0d69610f20282086712ee751268f8d5bd7d26d3a146c","Cargo.lock":"3113b5186bc1712b680f206f2c4f391b78ff17d3d9e045ab832f619ff730e77f","Cargo.toml":"842dd60ebfd3af809793c4ba8108b36910b2d600f6300d41ecf21c9f7402785f","Cargo.toml.orig":"0919e02907a1b3386921c6f3f424e277b173639b0bf5e493b7b55cca145daed7","LICENSE_APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE_BOOST":"c9bff75738922193e67fa726fa225535870d2aa1059f91452c411736284ad566","LICENSE_MIT":"508a77d2e7b51d98adeed32648ad124b7b30241a8e70b2e72c99f92d8e5874d1","README.md":"d6e2c856d79310245758f0058e91c785007c2a82529543301181d10c9d37dba4","src/api.rs":"3df5973b5738910afe934152174a0859b6558dd04642d7aee13e3c72dd561edf","src/arch.rs":"611f6a57f5c4a6d83b774f336ffae4cb57ba1d20d8091b4194ecda61f0c8499d","src/conversions.rs":"44af1f703666f4421b868057b8e14f2b78355bb226e98f634f9adb535e87c168","src/cpu_arch.rs":"eeacca9d615522544c93781ba038bd081791235b1f37e86e0553c94f014764ea","src/desktop_env.rs":"4e43e680bb12e7adecadcbf12e45b349667515ba3f7c1f7168ce3a440cde5fa8","src/error.rs":"8f8113728c3bb9f26b9793e3892e4cff8264e2fbe1d32f897280943d2d011dcf","src/langs.rs":"25e6a78257c2b8f9e1a1e268e739a74be8de94ad6b05cea97ffb4b90911c5b0e","src/lib.rs":"8ff8679d215a62efc9133d5ba9f3c5cb15c815a499b742cd34c39c4bc3b6f978","src/os.rs":"f010f49f984342e763868d28029d5dd70e5f4abcb837e13ee925110c649ed715","src/os/daku.rs":"fd66686f8f1ea58f73bc6dc2af0deff6c6002bd6622583e9af94f1ad9276c0b3","src/os/redox.rs":"89a744d392b27e8eaffad888ba832b16ef77b2e85fcbcd109052b2f242ae14a2","src/os/stub.rs":"bdc5e57b6044a3f41418bc862ea28b68489e9e6863d1a5ea26eadf1d26c5dab9","src/os/unix.rs":"993fe9d92459b66900054f0bbe22e511a35dffb95b84a44f0b159aeb3c964fda","src/os/wasite.rs":"aad2e731bca77836193b73ef4353f67ef46d3f7cfd221c85c4ab45d8139c34c6","src/os/web.rs":"9f74a217c280eff809220833409fddcf18d5ebf179864df793db7fbdbb064ad6","src/os/windows.rs":"d0100592a1104337cd119c88985a6c7e30a0dc1ad01b3ebaa0d839fc371c0b45","src/platform.rs":"117ce86cef7a83dff7639e7bfd3f09ca589a64dcf4132749104c9b02769be47b","src/result.rs":"13f4d6959ebae24d2d6e8b80412de4314a89f41f6de9e9aaa3acfba103e4ef88"},"package":"998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "ea912a3f7e1fda8dd22dbc1a9ec90ffb4cf25220"
},
"path_in_vcs": "whoami"
}
+202
View File
@@ -0,0 +1,202 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "bumpalo"
version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "js-sys"
version = "0.3.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libredox"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [
"libc",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
]
[[package]]
name = "objc2-system-configuration"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396"
dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "wasi"
version = "0.13.0+wasi-0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "652cd73449d0b957a2743b70c72d79d34a5fa505696488f4ca90b46f6da94118"
dependencies = [
"bitflags",
"wit-bindgen-rt",
]
[[package]]
name = "wasite"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42"
dependencies = [
"wasi",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "whoami"
version = "2.1.2"
dependencies = [
"libc",
"libredox",
"objc2-system-configuration",
"wasite",
"web-sys",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "026d24a27f6712541fa534f2954bd9e0eb66172f033c2157c0f31d106255c497"
+113
View File
@@ -0,0 +1,113 @@
# 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 = "2021"
rust-version = "1.75"
name = "whoami"
version = "2.1.2"
build = false
include = [
"LICENSE_APACHE",
"LICENSE_BOOST",
"LICENSE_MIT",
"../README.md",
"/src/*",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Rust library for getting information about the current user and environment"
homepage = "https://github.com/ardaku/whoami/releases"
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"
[features]
default = [
"std",
"wasi-wasite",
"wasm-web",
]
force-stub = []
std = [
"dep:libc",
"dep:libredox",
"dep:objc2-system-configuration",
"libc?/std",
"web-sys?/std",
]
wasi-wasite = ["dep:wasite"]
wasm-web = ["dep:web-sys"]
[lib]
name = "whoami"
path = "src/lib.rs"
[target.'cfg(all(any(target_vendor = "apple", target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "illumos", target_os = "hurd"), not(target_arch = "wasm32")))'.dependencies.libc]
version = "0.2.186"
features = []
optional = true
default-features = false
[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(daku)))'.dependencies.web-sys]
version = "0.3.85"
features = [
"Navigator",
"Document",
"Window",
"Location",
]
optional = true
default-features = false
[target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies.wasite]
version = "1.0.2"
features = []
optional = true
default-features = false
[target.'cfg(all(target_os = "redox", not(target_arch = "wasm32")))'.dependencies.libredox]
version = "0.1.16"
features = ["call"]
optional = true
default-features = false
[target.'cfg(target_vendor = "apple")'.dependencies.objc2-system-configuration]
version = "0.3.2"
features = [
"SCDynamicStore",
"SCDynamicStoreCopySpecific",
]
optional = true
default-features = false
[lints.rust.unexpected_cfgs]
level = "allow"
priority = 0
check-cfg = ["cfg(daku)"]
+83
View File
@@ -0,0 +1,83 @@
[package]
name = "whoami"
version = "2.1.2"
edition = "2021"
license = "Apache-2.0 OR BSL-1.0 OR MIT"
documentation = "https://docs.rs/whoami"
homepage = "https://github.com/ardaku/whoami/releases"
repository = "https://github.com/ardaku/whoami"
readme = "../README.md"
description = "Rust library for getting information about 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.75"
# Target-specific dependency for apple
[target.'cfg(target_vendor = "apple")'.dependencies.objc2-system-configuration]
version = "0.3.2"
optional = true
features = ["SCDynamicStore", "SCDynamicStoreCopySpecific"]
default-features = false
# Target-specific dependency for unix-like
[target.'cfg(all(any(target_vendor = "apple", target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "illumos", target_os = "hurd"), not(target_arch = "wasm32")))'.dependencies.libc]
version = "0.2.186"
optional = true
features = []
default-features = false
# Target-specific dependency for redox
[target.'cfg(all(target_os = "redox", not(target_arch = "wasm32")))'.dependencies.libredox]
version = "0.1.16"
optional = true
features = ["call"]
default-features = false
# Target-specific dependency for wasite
[target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies.wasite]
version = "1.0.2"
optional = true
features = []
default-features = false
# Target-specific dependency for web browser
[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(daku)))'.dependencies.web-sys]
version = "0.3.85"
optional = true
features = ["Navigator", "Document", "Window", "Location"]
default-features = false
[features]
default = ["std", "wasi-wasite", "wasm-web"]
# Instead of interfacing with the OS, force stubs to be used
force-stub = []
# Allow interfacing with the standard library and OS.
std = [
"dep:libc",
"dep:libredox",
"dep:objc2-system-configuration",
"libc?/std",
"web-sys?/std",
]
# Assume a wasite conforming environment on WASI (takes precedence over
# `wasm-web`).
wasi-wasite = ["dep:wasite"]
# Assume a web environment where whoami can call DOM APIs on WASM.
wasm-web = ["dep:web-sys"]
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(daku)'] }
+176
View File
@@ -0,0 +1,176 @@
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
+23
View File
@@ -0,0 +1,23 @@
Boost 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.
+19
View File
@@ -0,0 +1,19 @@
MIT 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.
+116
View File
@@ -0,0 +1,116 @@
![WhoAmI Logo]
#### [Changelog] | [Source] | [Getting Started]
[![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/search?q=repo%3Aardaku%2Fwhoami+path%3A**%2FLICENSE*&type=code)
[![Docs.rs](https://docs.rs/whoami/badge.svg)](https://docs.rs/whoami/)
Rust library for getting information about the current user and environment.
Check out the [documentation] 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 hostname" 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)
- Redox
- [Web Assembly]
- Fake implementation
- Web Browser - DOM
- WASI (Wasite, others) **untested, testing planned later**
- Daku (Ardaku/Quantii, others) **planned later**
- Android **planned later**
- iOS / watchOS / tvOS **untested**
- Fuchsia **planned later**
- GNU/Hurd **untested**
- Others? (make a PR or open an issue)
## MSRV
MSRV is updated according to the [Ardaku MSRV guidelines], so it will only get
updated on minor and major version bumps. All 2.x releases will be maintained
with bugfixes until the end of 2027 at minimum. Past MSRV bumps and future
plans:
- WhoAmI 2.3: Rust 1.95 MSRV (bump to get `cfg_select! {}` to make FFI code
prettier)
- WhoAmI 2.2: Rust 1.85 MSRV (bump to get the 2024 edition, pull in nix, etc.)
- WhoAmI 2.1: Rust 1.75 MSRV (bump for new lang features, faster CI, pull in
libc, etc.)
- WhoAmI 2.0: Rust 1.65 MSRV (support based on original 2.0 plan)
- WhoAmI 1.x: Rust 1.40 MSRV (old MSRV policy, maintained until end of 2025 at
minimum)
Target-Specific MSRVs:
- When the `std` feature is disabled, WhoAmI bumps the MSRV to Rust 1.81 for
the WhoAmI 2.0 and WhoAmI 2.1 release tracks. This feature should usually
stay enabled, unless you have a specific use-case targeting no-std.
## Binary
[whome] - `whoami` command RiR (Re-written in Rust) that depends on this crate.
## Testing
The interactive testing procedure is documented in the release PR template. The
full manual test suite is run for each change that affects multiple platforms.
## License
Copyright © 2017-2026 The WhoAmI Contributors.
Licensed under any of
- Apache License, Version 2.0, ([LICENSE\_APACHE] or
<https://www.apache.org/licenses/LICENSE-2.0>)
- Boost Software License, Version 1.0, ([LICENSE\_BOOST] or
<https://www.boost.org/LICENSE_1_0.txt>)
- MIT License, ([LICENSE\_MIT] or <https://mit-license.org/>)
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>.
[documentation]: https://docs.rs/whoami
[whome]: https://crates.io/crates/whome
[Ardaku MSRV guidelines]: https://github.com/ardaku/.github/blob/v1/profile/MSRV.md
[Changelog]: https://github.com/ardaku/whoami/releases
[Getting Started]: https://docs.rs/whoami#getting-started
[LICENSE\_APACHE]: https://github.com/ardaku/whoami/blob/v2/LICENSE_APACHE
[LICENSE\_BOOST]: https://github.com/ardaku/whoami/blob/v2/LICENSE_BOOST
[LICENSE\_MIT]: https://github.com/ardaku/whoami/blob/v2/LICENSE_MIT
[Source]: https://github.com/ardaku/whoami
[Web Assembly]: https://github.com/ardaku/whoami/blob/v2/WASM.md
[WhoAmI Logo]: https://raw.githubusercontent.com/ardaku/whoami/v2/res/icon.svg
+182
View File
@@ -0,0 +1,182 @@
use alloc::string::String;
use crate::{
conversions,
os::{Os, Target},
CpuArchitecture, DesktopEnvironment, LanguagePreferences, OsString,
Platform, Result,
};
macro_rules! report_message {
() => {
"Please report this issue at https://github.com/ardaku/whoami/issues"
};
}
/// Get the CPU Architecture.
#[must_use]
#[inline(always)]
pub fn cpu_arch() -> CpuArchitecture {
Target::arch(Os).expect(concat!("arch() failed. ", report_message!()))
}
/// 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<String> {
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<OsString> {
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<String> {
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<OsString> {
Target::username(Os)
}
/// Get the user's real (full) name.
#[inline(always)]
pub fn realname() -> Result<String> {
realname_os().and_then(conversions::string_from_os)
}
/// Get the user's real (full) name.
#[inline(always)]
pub fn realname_os() -> Result<OsString> {
Target::realname(Os)
}
/// Get the host device's hostname.
///
/// Usually hostnames are case-insensitive, but it's not a hard requirement.
///
/// # Platform-Specific Character Limitations
///
/// ## Unix/Linux/BSD
/// - **Maximum length**: 255 bytes (excluding null terminator)
/// - **Encoding**: Must be valid UTF-8
/// - **Characters**: Typically follows RFC 952/1123 DNS hostname rules:
/// - Alphanumeric characters (a-z, A-Z, 0-9)
/// - Hyphens (-), but not at start or end
/// - Note: POSIX allows any character except null and newline, but network
/// hostnames should follow DNS rules for interoperability
///
/// ## Windows
/// - **Maximum length**: 63 characters for DNS hostname (per label)
/// - **Encoding**: UTF-16 (converted to UTF-8 String)
/// - **Characters**: Follows DNS hostname rules (RFC 1123):
/// - Alphanumeric characters (a-z, A-Z, 0-9)
/// - Hyphens (-), but not at start or end
///
/// ## Redox
/// - Reads from `/etc/hostname` file
/// - First line of file is used as hostname
/// - No inherent character limitations beyond file system
///
/// ## Web (WASM)
/// - Returns the document's domain name
/// - Follows DNS hostname rules as enforced by browsers
/// - Must be valid UTF-8
///
/// ## Other Platforms
/// - WASI: Returns system hostname or defaults to "localhost"
/// - Default: Returns "localhost" for unsupported platforms
///
/// # Notes
/// For maximum compatibility across all platforms and network protocols,
/// hostnames should:
/// - Be 63 characters or less
/// - Contain only ASCII alphanumeric characters and hyphens
/// - Not start or end with a hyphen
/// - Be case-insensitive (though case may be preserved)
#[inline(always)]
pub fn hostname() -> Result<String> {
Target::hostname(Os)
}
/// Get the device name (also known as "Pretty Name").
///
/// Often used to identify device for bluetooth pairing.
#[inline(always)]
pub fn devicename() -> Result<String> {
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<OsString> {
Target::devicename(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<String> {
Target::distro(Os)
}
/// Get the desktop environment (if any).
///
/// Example: "gnome" or "windows"
///
/// Returns `None` if a desktop environment is not available (for example in a
/// TTY or over SSH)
#[must_use]
#[inline(always)]
pub fn desktop_env() -> Option<DesktopEnvironment> {
#[cfg(feature = "std")]
{
if std::env::var_os("SSH_CLIENT").is_some()
|| std::env::var_os("SSH_TTY").is_some()
|| std::env::var_os("SSH_CONNECTION").is_some()
{
return None;
}
}
Target::desktop_env(Os)
}
/// Get the platform.
#[must_use]
#[inline(always)]
pub fn platform() -> Platform {
Target::platform(Os)
}
/// Get the user's preferred language(s).
///
/// Returned as an instance of [`LanguagePreferences`]
#[inline(always)]
pub fn lang_prefs() -> Result<LanguagePreferences> {
Target::lang_prefs(Os).map(LanguagePreferences::add_stripped_fallbacks)
}
+144
View File
@@ -0,0 +1,144 @@
use alloc::string::String;
use core::fmt::{self, Display, Formatter};
use crate::{Error, 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
#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum CpuArchitecture {
/// Unknown Architecture
Unknown(String),
/// 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,
}
impl Display for CpuArchitecture {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Self::Unknown(_) = self {
f.write_str("Unknown: ")?;
}
f.write_str(match self {
Self::Unknown(arch) => arch,
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",
})
}
}
impl CpuArchitecture {
/// Get the width of this architecture.
pub fn width(&self) -> Result<Width> {
match self {
Self::ArmV5
| Self::ArmV6
| Self::ArmV7
| Self::I386
| Self::I586
| Self::I686
| Self::Mips
| Self::MipsEl
| Self::PowerPc
| Self::Riscv32
| Self::Sparc
| Self::Wasm32 => Ok(Width::Bits32),
Self::Arm64
| Self::Mips64
| Self::Mips64El
| Self::PowerPc64
| Self::PowerPc64Le
| Self::Riscv64
| Self::S390x
| Self::Sparc64
| Self::Wasm64
| Self::X64 => Ok(Width::Bits64),
Self::Unknown(unknown_arch) => {
Err(Error::with_invalid_data(alloc::format!(
"Tried getting width of unknown arch ({unknown_arch})"
)))
}
}
}
}
+48
View File
@@ -0,0 +1,48 @@
use alloc::string::String;
use crate::Result;
#[cfg(feature = "std")]
pub(super) type OsString = std::ffi::OsString;
#[cfg(not(feature = "std"))]
pub(super) type OsString = String;
#[cfg(not(feature = "std"))]
pub(crate) fn string_from_os(string: String) -> Result<String> {
Ok(string)
}
#[cfg(feature = "std")]
pub(crate) fn string_from_os(string: OsString) -> Result<String> {
use crate::Error;
#[cfg(any(
all(not(target_os = "windows"), not(target_arch = "wasm32")),
all(
target_arch = "wasm32",
target_os = "wasi",
not(target_env = "p2")
),
))]
{
#[cfg(not(target_os = "wasi"))]
use std::os::unix::ffi::OsStringExt;
#[cfg(target_os = "wasi")]
use std::os::wasi::ffi::OsStringExt;
use std::string::ToString;
String::from_utf8(string.into_vec())
.map_err(|e| Error::with_invalid_data(e.to_string()))
}
#[cfg(any(
target_os = "windows",
all(target_arch = "wasm32", not(target_os = "wasi")),
all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"),
))]
{
string
.into_string()
.map_err(|_| Error::with_invalid_data("Not valid unicode"))
}
}
+144
View File
@@ -0,0 +1,144 @@
use alloc::string::String;
use core::fmt::{self, Display, Formatter};
use crate::{Error, 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
#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum CpuArchitecture {
/// Other Architecture
Other(String),
/// 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,
}
impl Display for CpuArchitecture {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Self::Other(_) = self {
f.write_str("Unknown: ")?;
}
f.write_str(match self {
Self::Other(arch) => arch,
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",
})
}
}
impl CpuArchitecture {
/// Get the width of this architecture.
pub fn width(&self) -> Result<Width> {
match self {
Self::ArmV5
| Self::ArmV6
| Self::ArmV7
| Self::I386
| Self::I586
| Self::I686
| Self::Mips
| Self::MipsEl
| Self::PowerPc
| Self::Riscv32
| Self::Sparc
| Self::Wasm32 => Ok(Width::Bits32),
Self::Arm64
| Self::Mips64
| Self::Mips64El
| Self::PowerPc64
| Self::PowerPc64Le
| Self::Riscv64
| Self::S390x
| Self::Sparc64
| Self::Wasm64
| Self::X64 => Ok(Width::Bits64),
Self::Unknown(unknown_arch) => {
Err(Error::with_invalid_data(alloc::format!(
"Tried getting width of unknown arch ({unknown_arch})"
)))
}
}
}
}
+105
View File
@@ -0,0 +1,105 @@
use alloc::string::String;
use core::fmt::{self, Display, Formatter};
/// The desktop environment of a system
#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum DesktopEnvironment {
/// Unknown desktop environment
Unknown(String),
/// Running as Web Assembly on a web page
WebBrowser(String),
/// 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
Plasma,
/// 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,
/// A desktop environment for a video game console
Console,
/// Ubuntu-branded GNOME
Ubuntu,
/// Default shell for Fuchsia
Ermine,
/// Default desktop environment for Redox
Orbital,
/// Wayland scrolling window manager for Linux
Niri,
/// Wayland tiling window manager for Linux
Hyprland,
/// Rust based desktop environment on Linux
Cosmic,
}
impl Display for DesktopEnvironment {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Self::Unknown(_) = self {
f.write_str("Unknown: ")?;
}
f.write_str(match self {
Self::Unknown(de) => de,
Self::WebBrowser(de) => return write!(f, "WebBrowser ({de})"),
Self::Gnome => "Gnome",
Self::Windows => "Windows",
Self::Lxde => "LXDE",
Self::Openbox => "Openbox",
Self::Mate => "Mate",
Self::Xfce => "XFCE",
Self::Plasma => "KDE Plasma",
Self::Cinnamon => "Cinnamon",
Self::I3 => "I3",
Self::Aqua => "Aqua",
Self::Ios => "IOS",
Self::Android => "Android",
Self::Console => "Console",
Self::Ubuntu => "Ubuntu",
Self::Ermine => "Ermine",
Self::Orbital => "Orbital",
Self::Niri => "Niri",
Self::Hyprland => "Hyprland",
Self::Cosmic => "Cosmic",
})
}
}
impl DesktopEnvironment {
/// Returns true if the desktop environment is based on GTK.
#[must_use]
pub const fn is_gtk(&self) -> bool {
matches!(
self,
Self::Gnome
| Self::Ubuntu
| Self::Cinnamon
| Self::Lxde
| Self::Mate
| Self::Xfce
| Self::Niri
)
}
/// Returns true if the desktop environment is based on KDE.
#[must_use]
pub const fn is_kde(&self) -> bool {
matches!(self, Self::Plasma)
}
}
+101
View File
@@ -0,0 +1,101 @@
use alloc::borrow::Cow;
use core::fmt;
#[cfg(feature = "std")]
use std::io::Error as IoError;
#[cfg(not(feature = "std"))]
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct IoError(Cow<'static, str>);
#[cfg(not(feature = "std"))]
impl fmt::Display for IoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
/// An I/O error; can be converted to [`std::io::Error`].
#[derive(Debug)]
pub struct Error(IoError);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
#[cfg(not(feature = "std"))]
impl core::error::Error for Error {}
#[allow(dead_code)]
impl Error {
pub(crate) fn new(message: &'static str) -> Self {
#[cfg(not(feature = "std"))]
{
Self::from_io(IoError(message.into()))
}
#[cfg(feature = "std")]
{
Self::from_io(IoError::new(std::io::ErrorKind::NotFound, message))
}
}
pub(crate) fn with_invalid_data(
message: impl Into<Cow<'static, str>>,
) -> Self {
let message = message.into();
#[cfg(not(feature = "std"))]
{
Self::from_io(IoError(message))
}
#[cfg(feature = "std")]
{
Self::from_io(IoError::new(
std::io::ErrorKind::InvalidData,
message,
))
}
}
pub(crate) fn from_io(err: IoError) -> Self {
Self(err)
}
pub(crate) fn missing_record() -> Self {
Self::new("Missing record")
}
pub(crate) fn null_record() -> Self {
Self::new("Null record")
}
pub(crate) fn empty_record() -> Self {
Self::new("Empty record")
}
pub(crate) fn permission_denied() -> Self {
#[cfg(not(feature = "std"))]
{
Self::from_io(IoError("Permission denied".into()))
}
#[cfg(feature = "std")]
{
Self::from_io(IoError::new(
std::io::ErrorKind::PermissionDenied,
"Permission denied",
))
}
}
}
impl From<Error> for IoError {
fn from(err: Error) -> Self {
err.0
}
}
+372
View File
@@ -0,0 +1,372 @@
use alloc::{string::String, vec::Vec};
use core::{
fmt::{self, Display, Formatter},
num::NonZeroU8,
str::FromStr,
};
use crate::{Error, Result};
/// A spoken language identifier
///
/// Returned from various methods on [`LanguagePreferences`]
///
/// 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`).
///
/// The [`Default`] implementation can be used for fallbacks, and is set to
/// `en/US` since it's a common choice for lingua franca. It is not guaranteed
/// to stay the same across whoami versions.
///
/// Language codes are defined in an unspecified superset of
/// [ISO 639](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes),
/// Country codes are defined in an unspecified superset of
/// [ISO 3166](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).
///
/// You can compare languages with strings (where the separator can be any of
/// `-`, `_`, or `/`).
///
/// [`ToString::to_string()`]: std::string::ToString::to_string
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Language {
/// The language code for this language
///
/// Uses <https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes>
lang: [NonZeroU8; 2],
/// The optional country code for this language dialect
///
/// Uses <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>
country: Option<[NonZeroU8; 2]>,
}
impl Default for Language {
fn default() -> Self {
Self::from_str("en/US")
.expect("this is an internal bug (failed to parse en/US)")
}
}
impl FromStr for Language {
type Err = Error;
/// Reads an `language{/_-}COUNTRY.Encoding` formatted string into a
/// `Language` where language is a two letter language code and country is a
/// two letter country code. The encoding is ignored.
fn from_str(s: &str) -> Result<Self> {
// Strip the encoding off the end if it exists
let lang = s.split_terminator('.').next().unwrap_or_default();
if lang.is_empty() {
return Err(Error::empty_record());
}
// Split apart lang and country
let mut parts = lang.split(SEPARATORS);
let lang = parts
.next()
.ok_or_else(|| Error::with_invalid_data("No lang"))?
.as_bytes();
let country = parts.next().unwrap_or("\0\0").as_bytes();
// Verify that the lengths are valid
if parts.next().is_some() {
return Err(Error::with_invalid_data("Invalid locale"));
} else if lang.len() != 2 {
return Err(Error::with_invalid_data("Invalid length lang code"));
} else if country.len() != 2 {
return Err(Error::with_invalid_data(
"Invalid length country code",
));
}
// Verify the contents are valid
let Some(lang) = NonZeroU8::new(lang[0]).zip(NonZeroU8::new(lang[1]))
else {
return Err(Error::with_invalid_data("Lang code contains NUL"));
};
let lang = [lang.0, lang.1];
if (country[0] == 0 || country[1] == 0)
&& (country[0] != 0 || country[1] != 0)
{
return Err(Error::with_invalid_data("Country code contains NUL"));
}
let country = NonZeroU8::new(country[0])
.zip(NonZeroU8::new(country[1]))
.map(|country| [country.0, country.1]);
if !(lang[0].get().is_ascii_lowercase()
&& lang[1].get().is_ascii_lowercase())
{
return Err(Error::with_invalid_data(
"Lang code not ascii lowercase",
));
}
if let Some(ref country) = country {
if !(country[0].get().is_ascii_uppercase()
&& country[1].get().is_ascii_uppercase())
{
return Err(Error::with_invalid_data(
"Country code not ascii uppercase",
));
}
}
Ok(Self { lang, country })
}
}
impl Display for Language {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(&String::from_utf8_lossy(&[
self.lang[0].get(),
self.lang[1].get(),
]))?;
let Some(country) = self.country.as_ref() else {
return Ok(());
};
f.write_str("/")?;
f.write_str(&String::from_utf8_lossy(&[
country[0].get(),
country[1].get(),
]))
}
}
impl PartialEq<Language> for str {
fn eq(&self, lang: &Language) -> bool {
lang_str_eq(lang, self)
}
}
impl PartialEq<Language> for &str {
fn eq(&self, lang: &Language) -> bool {
lang_str_eq(lang, self)
}
}
impl PartialEq<Language> for String {
fn eq(&self, lang: &Language) -> bool {
lang_str_eq(lang, self)
}
}
impl PartialEq<String> for Language {
fn eq(&self, string: &String) -> bool {
lang_str_eq(self, string)
}
}
impl PartialEq<str> for Language {
fn eq(&self, string: &str) -> bool {
lang_str_eq(self, string)
}
}
impl PartialEq<&str> for Language {
fn eq(&self, string: &&str) -> bool {
lang_str_eq(self, string)
}
}
/// [`Language`] preferences for a user
///
/// Returned from [`lang_prefs()`](crate::lang_prefs)
///
/// Fields are sorted in order of the user's preference.
///
/// POSIX locale values and GNU nonstandard categories are defined in
/// <https://man7.org/linux/man-pages/man7/locale.7.html>. Windows locale values
/// are defined in <https://learn.microsoft.com/en-us/cpp/c-runtime-library/locale-categories>.
#[derive(Debug, Clone, Default)]
pub struct LanguagePreferences {
/// Determines general user language preference, should be used in
/// situations which are not encompassed by other [`LanguagePreferences`].
pub(crate) fallbacks: Vec<Language>,
/// Determines collation rules used for sorting and regular expressions,
/// including character equivalence classes and multicharacter collating
/// elements.
pub(crate) collation: Option<Language>,
/// Determines the interpretation of byte sequences as characters (e.g.,
/// single versus multibyte characters), character classifications (e.g.,
/// alphabetic or digit), and the behavior of character classes.
pub(crate) char_classes: Option<Language>,
/// Determines the formatting used for monetary-related numeric values,
/// i.e, the way numbers are usually printed with details such as
/// decimal point versus decimal comma.
pub(crate) monetary: Option<Language>,
/// Determines the language in which messages are
/// displayed and what an affirmative or negative answer looks
/// like.
pub(crate) messages: Option<Language>,
/// Determines the formatting rules used for nonmonetary numeric values.
/// For example, the thousands separator and the radix character.
pub(crate) numeric: Option<Language>,
/// Determines format and contents of date and time information.
pub(crate) time: Option<Language>,
}
impl Display for LanguagePreferences {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let langs: [(&str, Vec<Language>); 6] = [
("Collation", self.collation_langs().collect()),
("CharClasses", self.char_class_langs().collect()),
("Monetary", self.monetary_langs().collect()),
("Messages", self.message_langs().collect()),
("Numeric", self.numeric_langs().collect()),
("Time", self.time_langs().collect()),
];
for (i, (name, langs)) in langs.iter().enumerate() {
if i != 0 {
f.write_str(",")?;
}
write!(f, "{name}=")?;
for (j, lang) in langs.iter().enumerate() {
if j != 0 {
f.write_str(":")?;
}
write!(f, "{lang}")?;
}
}
Ok(())
}
}
impl LanguagePreferences {
fn chain_fallbacks<'a>(
&'a self,
l: &Option<Language>,
) -> impl Iterator<Item = Language> + 'a {
let lang_without_country = if let Some(ref lang) = l {
lang.country.is_some().then_some(Language {
lang: lang.lang,
country: None,
})
} else {
None
};
(*l).into_iter()
.chain(lang_without_country)
.chain(self.fallbacks.iter().cloned())
}
/// Returns the collation langs of this [`LanguagePreferences`] in order of
/// the user's preference
///
/// Collation langs are used for sorting and regular expressions,
/// including character equivalence classes and multicharacter collating
/// elements.
pub fn collation_langs(&self) -> impl Iterator<Item = Language> + '_ {
self.chain_fallbacks(&self.collation)
}
/// Returns the char class langs of this [`LanguagePreferences`] in order of
/// the user's preference
///
/// Char class langs determine the interpretation of byte sequences as
/// characters (e.g., single versus multibyte characters), character
/// classifications (e.g., alphabetic or digit), and the behavior of
/// character classes.
pub fn char_class_langs(&self) -> impl Iterator<Item = Language> + '_ {
self.chain_fallbacks(&self.char_classes)
}
/// Returns the monetary langs of this [`LanguagePreferences`] in order of
/// the user's preference
///
/// Monetary langs determine the formatting used for monetary-related
/// numeric values, i.e, the way numbers are usually printed with details
/// such as decimal point versus decimal comma.
///
/// For nonmonetary numeric values, see
/// [`LanguagePreferences::numeric_langs`]
pub fn monetary_langs(&self) -> impl Iterator<Item = Language> + '_ {
self.chain_fallbacks(&self.monetary)
}
/// Returns the messages langs of this [`LanguagePreferences`] in order of
/// the user's preference
///
/// Message determines the language in which messages are
/// displayed and what an affirmative or negative answer looks
/// like.
pub fn message_langs(&self) -> impl Iterator<Item = Language> + '_ {
self.chain_fallbacks(&self.messages)
}
/// Returns the numeric langs of this [`LanguagePreferences`] in order of
/// the user's preference
///
/// Numeric langs determine the formatting rules used for nonmonetary
/// numeric values. For example, the thousands separator and the radix
/// character.
///
/// For monetary formatting, see [`LanguagePreferences::monetary_langs`].
pub fn numeric_langs(&self) -> impl Iterator<Item = Language> + '_ {
self.chain_fallbacks(&self.numeric)
}
/// Returns the time langs of this [`LanguagePreferences`] in order of the
/// user's preference
///
/// Time langs determine format and contents of date and time information.
pub fn time_langs(&self) -> impl Iterator<Item = Language> + '_ {
self.chain_fallbacks(&self.time)
}
pub(crate) fn add_stripped_fallbacks(mut self) -> Self {
let mut no_country_langs = Vec::new();
for lang in self.fallbacks.iter() {
if lang.country.is_some() {
no_country_langs.push(Language {
lang: lang.lang,
country: None,
});
} else {
let Some(i) = no_country_langs.iter().position(|x| x == lang)
else {
continue;
};
no_country_langs.remove(i);
}
}
self.fallbacks.extend(no_country_langs);
self
}
}
fn lang_str_eq(language: &Language, string: &str) -> bool {
let mut iter = string.split(SEPARATORS);
let string_lang = iter.next().map(|s| s.as_bytes());
let string_country = iter.next().map(|s| s.as_bytes());
let end = iter.next();
let lang = [language.lang[0].get(), language.lang[1].get()];
let Some(country) = language.country.as_ref() else {
return end.is_none()
&& string_lang == Some(&lang)
&& string_country.is_none();
};
let country = [country[0].get(), country[1].get()];
end.is_none()
&& string_lang == Some(&lang)
&& string_country == Some(&country)
}
const SEPARATORS: &[char] = &['_', '-', '/'];
+121
View File
@@ -0,0 +1,121 @@
//! Rust library for getting information about the current user 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 [`cpu_arch()`],
//! which return enums, and [`lang_prefs()`] that returns
//! [`LanguagePreferences`]). The following example shows how to use all of the
//! functions (except those that return [`OsString`]):
//!
//! ```rust
//! println!(
//! "User's Language whoami::lang_prefs(): {}",
//! whoami::lang_prefs().unwrap_or_default(),
//! );
//! println!(
//! "User's Name whoami::realname(): {}",
//! whoami::realname().unwrap_or_else(|_| "<unknown>".to_string()),
//! );
//! println!(
//! "User's Username whoami::username(): {}",
//! whoami::username().unwrap_or_else(|_| "<unknown>".to_string()),
//! );
//! println!(
//! "User's Username whoami::account(): {}",
//! whoami::account().unwrap_or_else(|_| "<unknown>".to_string()),
//! );
//! println!(
//! "Device's Pretty Name whoami::devicename(): {}",
//! whoami::devicename().unwrap_or_else(|_| "<unknown>".to_string()),
//! );
//! println!(
//! "Device's Hostname whoami::hostname(): {}",
//! whoami::hostname().unwrap_or_else(|_| "<unknown>".to_string()),
//! );
//! println!(
//! "Device's Platform whoami::platform(): {}",
//! whoami::platform(),
//! );
//! println!(
//! "Device's OS Distro whoami::distro(): {}",
//! whoami::distro().unwrap_or_else(|_| "<unknown>".to_string()),
//! );
//! println!(
//! "Device's Desktop Env. whoami::desktop_env(): {}",
//! whoami::desktop_env()
//! .map(|e| e.to_string())
//! .unwrap_or_else(|| "<unknown>".to_string()),
//! );
//! println!(
//! "Device's CPU Arch whoami::cpu_arch(): {}",
//! whoami::cpu_arch(),
//! );
//! ```
//!
//! [`OsString`]: std::ffi::OsString
//! [`String`]: std::string::String
#![no_std]
#![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
)]
#![deny(
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
rustdoc::missing_crate_level_docs,
rustdoc::private_doc_tests,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_html_tags,
rustdoc::invalid_rust_codeblocks,
rustdoc::bare_urls,
rustdoc::unescaped_backticks,
rustdoc::redundant_explicit_links
)]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/ardaku/whoami/v2/res/icon.svg",
html_favicon_url = "https://raw.githubusercontent.com/ardaku/whoami/v2/res/icon.svg"
)]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
mod api;
mod arch;
mod conversions;
mod desktop_env;
mod error;
mod langs;
mod os;
mod platform;
mod result;
use self::conversions::OsString;
pub use self::{
api::{
account, account_os, cpu_arch, desktop_env, devicename, devicename_os,
distro, hostname, lang_prefs, platform, realname, realname_os,
username, username_os,
},
arch::{CpuArchitecture, Width},
desktop_env::DesktopEnvironment,
error::Error,
langs::{Language, LanguagePreferences},
platform::Platform,
result::Result,
};
+187
View File
@@ -0,0 +1,187 @@
#![allow(unsafe_code)]
// Daku
#[cfg_attr(
all(
not(any(
feature = "force-stub",
all(target_os = "wasi", feature = "wasi-wasite")
)),
target_arch = "wasm32",
daku,
),
path = "os/daku.rs"
)]
// Redox
#[cfg_attr(
all(
not(any(feature = "force-stub", target_arch = "wasm32")),
feature = "std",
target_os = "redox",
),
path = "os/redox.rs"
)]
// Unix
#[cfg_attr(
all(
not(any(feature = "force-stub", target_arch = "wasm32")),
feature = "std",
any(
target_vendor = "apple",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "illumos",
target_os = "hurd",
),
),
path = "os/unix.rs"
)]
// Wasite WASM
#[cfg_attr(
all(
not(feature = "force-stub"),
target_arch = "wasm32",
target_os = "wasi",
feature = "wasi-wasite",
),
path = "os/wasite.rs"
)]
// Web WASM
#[cfg_attr(
all(
not(any(
feature = "force-stub",
daku,
all(target_os = "wasi", feature = "wasi-wasite")
)),
target_arch = "wasm32",
feature = "wasm-web",
),
path = "os/web.rs"
)]
// Windows
#[cfg_attr(
all(
not(any(feature = "force-stub", target_arch = "wasm32")),
feature = "std",
target_os = "windows",
),
path = "os/windows.rs"
)]
mod stub;
use alloc::string::String;
use crate::{
CpuArchitecture, DesktopEnvironment, LanguagePreferences, OsString,
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 lang_prefs(self) -> Result<LanguagePreferences>;
/// Return the user's "real" / "full" name.
fn realname(self) -> Result<OsString>;
/// Return the user's username.
fn username(self) -> Result<OsString>;
/// Return the computer's "fancy" / "pretty" name.
fn devicename(self) -> Result<OsString>;
/// Return the computer's hostname.
fn hostname(self) -> Result<String>;
/// Return the OS distribution's name.
fn distro(self) -> Result<String>;
/// Return the desktop environment.
fn desktop_env(self) -> Option<DesktopEnvironment>;
/// Return the target platform.
fn platform(self) -> Platform;
/// Return the computer's CPU architecture.
fn arch(self) -> Result<CpuArchitecture>;
/// Return the user's account name (usually just the username, but may
/// include an account server hostname).
fn account(self) -> Result<OsString> {
self.username()
}
}
// This is only used on some platforms
#[cfg(feature = "std")]
#[allow(dead_code)]
fn unix_lang() -> Result<LanguagePreferences> {
use std::{
env::{self, VarError},
str::FromStr,
vec::Vec,
};
use crate::{Error, Language};
let env_var = |var: &str| match env::var(var) {
Ok(value) => Ok(if value.is_empty() { None } else { Some(value) }),
Err(VarError::NotPresent) => Ok(None),
Err(VarError::NotUnicode(_)) => {
Err(Error::with_invalid_data("not unicode"))
}
};
// Uses priority defined in
// <https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html>
let lc_all = env_var("LC_ALL")?;
let lang = env_var("LANG")?;
if lang.is_none() && lc_all.is_none() {
return Err(Error::empty_record());
}
// Standard locales that have a higher global precedence than their specific
// counterparts, indicating that one should not perform any localization.
// https://www.gnu.org/software/libc/manual/html_node/Standard-Locales.html
if let Some(l) = &lang {
if l == "C" || l == "POSIX" {
return Ok(LanguagePreferences {
fallbacks: Vec::new(),
..Default::default()
});
}
}
// The LANGUAGE environment variable takes precedence if and only if
// localization is enabled, i.e., LC_ALL / LANG is not "C" or "POSIX".
// <https://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html>
if let Some(language) = env_var("LANGUAGE")? {
return Ok(LanguagePreferences {
fallbacks: language
.split(":")
.map(Language::from_str)
.collect::<Result<_>>()?,
..Default::default()
});
}
// All fields other than LANGUAGE can only contain a single value, so we
// don't need to perform any splitting at this point.
let lang_from_var = |var| -> Result<Option<Language>> {
env_var(var)?.as_deref().map(Language::from_str).transpose()
};
Ok(LanguagePreferences {
fallbacks: lang
.as_ref()
.map(|l| -> Result<_> { Ok([Language::from_str(l)?].to_vec()) })
.transpose()?
.unwrap_or(Vec::new()),
collation: lang_from_var("LC_COLLATE")?,
char_classes: lang_from_var("LC_CTYPE")?,
monetary: lang_from_var("LC_MONETARY")?,
messages: lang_from_var("LC_MESSAGES")?,
numeric: lang_from_var("LC_NUMERIC")?,
time: lang_from_var("LC_TIME")?,
})
}
+67
View File
@@ -0,0 +1,67 @@
//! Daku - mostly the same as stub.rs for now.
use alloc::string::{String, ToString};
use crate::{
os::{Os, Target},
CpuArchitecture, DesktopEnvironment, Language, LanguagePreferences,
OsString, Platform, Result,
};
impl Target for Os {
#[inline(always)]
fn lang_prefs(self) -> Result<LanguagePreferences> {
Ok(LanguagePreferences {
fallbacks: [Language::default()].to_vec(),
..Default::default()
})
}
#[inline(always)]
fn realname(self) -> Result<OsString> {
Ok(OsString::from("Anonymous"))
}
#[inline(always)]
fn username(self) -> Result<OsString> {
Ok(OsString::from("anonymous"))
}
#[inline(always)]
fn devicename(self) -> Result<OsString> {
Ok(OsString::from("Unknown"))
}
#[inline(always)]
fn hostname(self) -> Result<String> {
Ok("localhost".to_string())
}
#[inline(always)]
fn distro(self) -> Result<String> {
Ok(alloc::format!("Daku {}", self.platform()))
}
#[inline(always)]
fn desktop_env(self) -> Option<DesktopEnvironment> {
None
}
#[inline(always)]
fn platform(self) -> Platform {
Platform::Unknown("Emulated".to_string())
}
#[inline(always)]
fn arch(self) -> Result<CpuArchitecture> {
#[cfg(target_pointer_width = "32")]
{
Ok(CpuArchitecture::Wasm32)
}
#[cfg(target_pointer_width = "64")]
{
Ok(CpuArchitecture::Wasm64)
}
}
}
+148
View File
@@ -0,0 +1,148 @@
// We don't need unsafe, yay!
#![forbid(unsafe_code)]
use std::{borrow::Cow, fs, io, prelude::rust_2021::*};
use libredox::{call, error};
use crate::{
os::{Os, Target},
CpuArchitecture, DesktopEnvironment, Error, LanguagePreferences, OsString,
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<String> {
self.column(0).map(ToString::to_string)
}
fn uid(&self) -> Option<usize> {
self.column(1)?.parse().ok()
}
fn gid(&self) -> Option<usize> {
self.column(2)?.parse().ok()
}
fn fullname(&self) -> Option<String> {
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<CpuArchitecture> {
// FIXME: Don't hardcode unknown arch
Some(CpuArchitecture::Unknown(self.row(4)?.to_string()))
}
}
fn to_io_error(error: error::Error) -> io::Error {
io::Error::from_raw_os_error(error.errno())
}
fn euid() -> Result<usize> {
call::geteuid().map_err(to_io_error).map_err(Error::from_io)
}
fn egid() -> Result<usize> {
call::getegid().map_err(to_io_error).map_err(Error::from_io)
}
fn passwd() -> Result<Passwd<'static>> {
let (euid, egid) = (euid()?, egid()?);
let passwd_file =
fs::read_to_string("/etc/passwd").map_err(Error::from_io)?;
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(Error::missing_record())
}
fn uname() -> Result<Uname<'static>> {
let uname_file = fs::read_to_string("sys:uname").map_err(Error::from_io)?;
Ok(Uname(uname_file.into()))
}
fn hostname() -> Result<String> {
let hostname_file =
fs::read_to_string("/etc/hostname").map_err(Error::from_io)?;
Ok(hostname_file.lines().next().unwrap_or_default().to_string())
}
impl Target for Os {
fn lang_prefs(self) -> Result<LanguagePreferences> {
super::unix_lang()
}
#[inline(always)]
fn realname(self) -> Result<OsString> {
Ok(passwd()?.fullname().unwrap_or_default().into())
}
#[inline(always)]
fn username(self) -> Result<OsString> {
Ok(passwd()?.username().unwrap_or_default().into())
}
#[inline(always)]
fn devicename(self) -> Result<OsString> {
hostname().map(OsString::from)
}
#[inline(always)]
fn hostname(self) -> Result<String> {
hostname()
}
#[inline(always)]
fn distro(self) -> Result<String> {
let release_file =
fs::read_to_string("/etc/os-release").map_err(Error::from_io)?;
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(Error::missing_record())
}
#[inline(always)]
fn desktop_env(self) -> Option<DesktopEnvironment> {
Some(DesktopEnvironment::Orbital)
}
#[inline(always)]
fn platform(self) -> Platform {
Platform::Redox
}
#[inline(always)]
fn arch(self) -> Result<CpuArchitecture> {
uname()?.machine_arch().ok_or_else(Error::missing_record)
}
}
+124
View File
@@ -0,0 +1,124 @@
//! Unknown target, fake implementation.
//!
//! This can be used as a template when adding new target support.
use alloc::string::{String, ToString};
use crate::{
os::{Os, Target},
CpuArchitecture, DesktopEnvironment, Language, LanguagePreferences,
OsString, Platform, Result,
};
impl Target for Os {
#[inline(always)]
fn lang_prefs(self) -> Result<LanguagePreferences> {
Ok(LanguagePreferences {
fallbacks: [Language::default()].to_vec(),
..Default::default()
})
}
#[inline(always)]
fn realname(self) -> Result<OsString> {
Ok(OsString::from("Anonymous"))
}
#[inline(always)]
fn username(self) -> Result<OsString> {
Ok(OsString::from("anonymous"))
}
#[inline(always)]
fn devicename(self) -> Result<OsString> {
Ok(OsString::from("Unknown"))
}
#[inline(always)]
fn hostname(self) -> Result<String> {
Ok(String::from("localhost"))
}
#[inline(always)]
fn distro(self) -> Result<String> {
Ok(alloc::format!("Unknown {}", self.platform()))
}
#[inline(always)]
fn desktop_env(self) -> Option<DesktopEnvironment> {
None
}
#[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::Mac
} 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::Nintendo3ds
} else if cfg!(target_os = "vita") {
Platform::PlayStation
} else if cfg!(target_os = "hurd") {
Platform::Hurd
} 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<CpuArchitecture> {
#[cfg(target_pointer_width = "32")]
{
Ok(CpuArchitecture::Wasm32)
}
#[cfg(target_pointer_width = "64")]
{
Ok(CpuArchitecture::Wasm64)
}
}
}
+461
View File
@@ -0,0 +1,461 @@
use std::{
borrow::Cow,
ffi::{CStr, OsString},
fs, io, mem,
os::unix::ffi::OsStringExt,
prelude::rust_2021::*,
slice,
};
use crate::{
os::{Os, Target},
CpuArchitecture, DesktopEnvironment, Error, LanguagePreferences, Platform,
Result,
};
enum Name {
User,
Real,
}
trait Terminators {
const CHARS: &'static [u8];
}
struct Nul;
struct NulOrComma;
impl Terminators for Nul {
const CHARS: &'static [u8] = b"\0";
}
impl Terminators for NulOrComma {
const CHARS: &'static [u8] = b"\0,";
}
unsafe fn strlen<T>(mut cs: *const u8) -> usize
where
T: Terminators,
{
let mut len = 0;
while !T::CHARS.contains(&*cs) {
len += 1;
cs = cs.offset(1);
}
len
}
fn os_from_cstring<T>(string: *const u8) -> Result<OsString>
where
T: Terminators,
{
if string.is_null() {
return Err(Error::null_record());
}
// Get a byte slice of the c string.
let slice = unsafe {
let length = strlen::<T>(string);
if length == 0 {
return Err(Error::empty_record());
}
slice::from_raw_parts(string, length)
};
// Turn byte slice into Rust String.
Ok(OsString::from_vec(slice.to_vec()))
}
// This function must allocate, because a slice or `Cow<OsStr>` would still
// reference `passwd` which is dropped when this function returns.
#[inline(always)]
fn getpwuid(name: Name) -> Result<OsString> {
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::<libc::passwd>::uninit();
// Get passwd `struct`.
let passwd = unsafe {
let mut _passwd = mem::MaybeUninit::<*mut libc::passwd>::uninit();
let ret = libc::getpwuid_r(
libc::geteuid(),
passwd.as_mut_ptr(),
buffer.as_mut_ptr().cast(),
BUF_SIZE,
_passwd.as_mut_ptr(),
);
if ret != 0 {
return Err(Error::from_io(io::Error::last_os_error()));
}
let _passwd = _passwd.assume_init();
if _passwd.is_null() {
return Err(Error::null_record());
}
passwd.assume_init()
};
// Extract names.
if let Name::Real = name {
os_from_cstring::<NulOrComma>(passwd.pw_gecos.cast())
} else {
os_from_cstring::<Nul>(passwd.pw_name.cast())
}
}
impl Target for Os {
fn lang_prefs(self) -> Result<LanguagePreferences> {
super::unix_lang()
}
fn realname(self) -> Result<OsString> {
getpwuid(Name::Real)
}
fn username(self) -> Result<OsString> {
getpwuid(Name::User)
}
fn devicename(self) -> Result<OsString> {
#[cfg(target_vendor = "apple")]
{
use std::ptr::null_mut;
use objc2_system_configuration::SCDynamicStore;
let Some(name) =
(unsafe { SCDynamicStore::computer_name(None, null_mut()) })
else {
return Err(Error::missing_record());
};
// this should be able to convert whichever encoding is being used
// to UTF-8, so we shouldn't have to worry about invalid codepoints.
let name = name.to_string();
if name.is_empty() {
return Err(Error::empty_record());
}
Ok(name.into())
}
#[cfg(target_os = "illumos")]
{
let mut nodename =
fs::read("/etc/nodename").map_err(Error::from_io)?;
// 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(Error::empty_record());
}
Ok(OsString::from_vec(nodename))
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "hurd",
))]
{
let machine_info =
fs::read("/etc/machine-info").map_err(Error::from_io)?;
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") {
let pretty_hostname = j
.next()
.ok_or(Error::with_invalid_data("parsing failed"))?;
let pretty_hostname = pretty_hostname
.strip_prefix(b"\"")
.unwrap_or(pretty_hostname)
.strip_suffix(b"\"")
.unwrap_or(pretty_hostname);
let pretty_hostname = {
let mut vec = Vec::with_capacity(pretty_hostname.len());
let mut pretty_hostname = pretty_hostname.iter();
while let Some(&c) = pretty_hostname.next() {
if c == b'\\' {
vec.push(match pretty_hostname.next() {
Some(b'\\') => b'\\',
Some(b't') => b'\t',
Some(b'r') => b'\r',
Some(b'n') => b'\n',
Some(b'\'') => b'\'',
Some(b'"') => b'"',
_ => {
return Err(Error::with_invalid_data(
"parsing failed",
));
}
});
} else {
vec.push(c);
}
}
vec
};
return Ok(OsString::from_vec(pretty_hostname));
}
}
Err(Error::missing_record())
}
}
fn hostname(self) -> Result<String> {
// Maximum hostname length = 255, plus a NULL byte.
let mut string = Vec::<u8>::with_capacity(256);
unsafe {
if libc::gethostname(string.as_mut_ptr().cast(), 255) == -1 {
return Err(Error::from_io(io::Error::last_os_error()));
}
string.set_len(strlen::<Nul>(string.as_ptr().cast()));
};
String::from_utf8(string)
.map_err(|_| Error::with_invalid_data("Hostname not valid UTF-8"))
}
fn distro(self) -> Result<String> {
#[cfg(target_vendor = "apple")]
{
fn distro_xml(data: String) -> Result<String> {
let mut product_name = None;
let mut user_visible_version = None;
if let Some(start) = data.find("<dict>") {
if let Some(end) = data.find("</dict>") {
let mut set_product_name = false;
let mut set_user_visible_version = false;
for line in data[start + "<dict>".len()..end].lines() {
let line = line.trim();
if let Some(key) = line.strip_prefix("<key>") {
match key.trim_end_matches("</key>") {
"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 let Some(value) =
line.strip_prefix("<string>")
{
if set_product_name {
product_name = Some(
value.trim_end_matches("</string>"),
);
set_product_name = false;
} else if set_user_visible_version {
user_visible_version = Some(
value.trim_end_matches("</string>"),
);
set_user_visible_version = false;
}
}
}
}
}
Ok(if let Some(product_name) = product_name {
if let Some(user_visible_version) = user_visible_version {
std::format!("{product_name} {user_visible_version}")
} else {
product_name.to_string()
}
} else {
user_visible_version
.map(|v| std::format!("Mac OS (Unknown) {v}"))
.ok_or_else(|| {
Error::with_invalid_data("Parsing failed")
})?
})
}
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(Error::missing_record())
}
}
#[cfg(not(target_vendor = "apple"))]
{
let program =
fs::read("/etc/os-release").map_err(Error::from_io)?;
let distro = String::from_utf8_lossy(&program);
let err = || Error::with_invalid_data("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) -> Option<DesktopEnvironment> {
#[cfg(target_vendor = "apple")]
let env: Cow<'static, str> = "Aqua".into();
#[cfg(not(target_vendor = "apple"))]
let env: Cow<'static, str> = std::env::var_os("XDG_SESSION_DESKTOP")
.or_else(|| std::env::var_os("DESKTOP_SESSION"))
.or_else(|| std::env::var_os("XDG_CURRENT_DESKTOP"))?
.into_string()
.unwrap_or_else(|e| e.to_string_lossy().into_owned())
.into();
Some(if env.eq_ignore_ascii_case("AQUA") {
DesktopEnvironment::Aqua
} else if env.eq_ignore_ascii_case("GNOME") {
DesktopEnvironment::Gnome
} else if env.eq_ignore_ascii_case("LXDE") {
DesktopEnvironment::Lxde
} else if env.eq_ignore_ascii_case("OPENBOX") {
DesktopEnvironment::Openbox
} else if env.eq_ignore_ascii_case("I3") {
DesktopEnvironment::I3
} else if env.eq_ignore_ascii_case("UBUNTU") {
DesktopEnvironment::Ubuntu
} else if env.eq_ignore_ascii_case("PLASMA5") {
DesktopEnvironment::Plasma
} else if env.eq_ignore_ascii_case("XFCE") {
DesktopEnvironment::Xfce
} else if env.eq_ignore_ascii_case("NIRI") {
DesktopEnvironment::Niri
} else if env.eq_ignore_ascii_case("HYPRLAND") {
DesktopEnvironment::Hyprland
} else if env.eq_ignore_ascii_case("COSMIC") {
DesktopEnvironment::Cosmic
} else {
DesktopEnvironment::Unknown(env.to_string())
})
}
#[inline(always)]
fn platform(self) -> Platform {
#[cfg(target_os = "linux")]
{
Platform::Linux
}
#[cfg(target_vendor = "apple")]
{
Platform::Mac
}
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
))]
{
Platform::Bsd
}
#[cfg(target_os = "illumos")]
{
Platform::Illumos
}
#[cfg(target_os = "hurd")]
{
Platform::Hurd
}
}
#[inline(always)]
fn arch(self) -> Result<CpuArchitecture> {
let mut buf: libc::utsname = unsafe { mem::zeroed() };
if unsafe { libc::uname(&mut buf) } == -1 {
return Err(Error::from_io(io::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" => {
CpuArchitecture::Arm64
}
"armv5" => CpuArchitecture::ArmV5,
"armv6" | "arm" => CpuArchitecture::ArmV6,
"armv7" => CpuArchitecture::ArmV7,
"i386" => CpuArchitecture::I386,
"i586" => CpuArchitecture::I586,
"i686" | "i686-AT386" => CpuArchitecture::I686,
"mips" => CpuArchitecture::Mips,
"mipsel" => CpuArchitecture::MipsEl,
"mips64" => CpuArchitecture::Mips64,
"mips64el" => CpuArchitecture::Mips64El,
"powerpc" | "ppc" | "ppcle" => CpuArchitecture::PowerPc,
"powerpc64" | "ppc64" | "ppc64le" => CpuArchitecture::PowerPc64,
"powerpc64le" => CpuArchitecture::PowerPc64Le,
"riscv32" => CpuArchitecture::Riscv32,
"riscv64" => CpuArchitecture::Riscv64,
"s390x" => CpuArchitecture::S390x,
"sparc" => CpuArchitecture::Sparc,
"sparc64" => CpuArchitecture::Sparc64,
"x86_64" | "amd64" | "i86pc" => CpuArchitecture::X64,
_ => CpuArchitecture::Unknown(arch_str.into_owned()),
})
}
}
+98
View File
@@ -0,0 +1,98 @@
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
compile_error!("Unexpected pointer width for target platform");
use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::str::FromStr;
use crate::{
os::{Os, Target},
CpuArchitecture, DesktopEnvironment, Language, LanguagePreferences,
OsString, Platform, Result,
};
impl Target for Os {
fn lang_prefs(self) -> Result<LanguagePreferences> {
let langs = wasite::environment().user.langs;
let fallbacks = langs
.other()
.map(Language::from_str)
.collect::<Result<Vec<Language>>>()?;
let collation = langs
.collation()
.next()
.map(Language::from_str)
.transpose()?;
let char_classes = langs
.char_class()
.next()
.map(Language::from_str)
.transpose()?;
let monetary = langs
.monetary()
.next()
.map(Language::from_str)
.transpose()?;
let messages =
langs.message().next().map(Language::from_str).transpose()?;
let numeric =
langs.numeric().next().map(Language::from_str).transpose()?;
let time = langs.time().next().map(Language::from_str).transpose()?;
Ok(LanguagePreferences {
fallbacks,
collation,
char_classes,
monetary,
messages,
numeric,
time,
})
}
#[inline(always)]
fn realname(self) -> Result<OsString> {
self.username()
}
#[inline(always)]
fn username(self) -> Result<OsString> {
Ok(wasite::environment().user.username.into())
}
#[inline(always)]
fn devicename(self) -> Result<OsString> {
Ok(wasite::environment().host.name.into())
}
#[inline(always)]
fn hostname(self) -> Result<String> {
Ok(wasite::environment().host.hostname)
}
#[inline(always)]
fn distro(self) -> Result<String> {
Ok("Unknown WASI".to_string())
}
#[inline(always)]
fn desktop_env(self) -> Option<DesktopEnvironment> {
Some(DesktopEnvironment::Unknown("Nucleic".to_string()))
}
#[inline(always)]
fn platform(self) -> Platform {
Platform::Unknown("WASI".to_string())
}
#[inline(always)]
fn arch(self) -> Result<CpuArchitecture> {
Ok(if cfg!(target_pointer_width = "64") {
CpuArchitecture::Wasm64
} else {
CpuArchitecture::Wasm32
})
}
}
+207
View File
@@ -0,0 +1,207 @@
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
compile_error!("Unexpected pointer width for target platform");
use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::str::FromStr;
use web_sys::window;
use crate::{
os::{Os, Target},
CpuArchitecture, DesktopEnvironment, Error, Language, LanguagePreferences,
OsString, Platform, Result,
};
// Get the user agent
fn user_agent() -> Option<String> {
window()?.navigator().user_agent().ok()
}
// Get the document domain
fn document_domain() -> Option<String> {
window()?.document()?.location()?.hostname().ok()
}
// Get the browser name and version for desktop environment
fn browser_info() -> String {
let orig_string = user_agent().unwrap_or_default();
let start = if let Some(s) = orig_string.rfind(' ') {
s
} else {
return "Unknown Browser".to_string();
};
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
};
string
}
impl Target for Os {
fn lang_prefs(self) -> Result<LanguagePreferences> {
if let Some(window) = window() {
let langs = window
.navigator()
.languages()
.to_vec()
.into_iter()
.filter_map(|l| {
l.as_string().as_deref().map(Language::from_str)
})
.collect::<Result<Vec<_>>>()?;
Ok(LanguagePreferences {
fallbacks: langs,
..Default::default()
})
} else {
Err(Error::new(
"Failed to retrieve languages: Window object is missing",
))
}
}
fn realname(self) -> Result<OsString> {
Ok("Anonymous".to_string().into())
}
fn username(self) -> Result<OsString> {
Ok("anonymous".to_string().into())
}
fn devicename(self) -> Result<OsString> {
Ok("Browser".to_string().into())
}
fn hostname(self) -> Result<String> {
document_domain()
.filter(|x| !x.is_empty())
.ok_or_else(|| {
Error::new(
"Domain missing, failed to retrieve document domain from window"
)
})
}
fn distro(self) -> Result<String> {
let string = user_agent().ok_or_else(Error::permission_denied)?;
let err = || Error::with_invalid_data("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];
alloc::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 {
string.to_string()
})
}
#[inline(always)]
fn desktop_env(self) -> Option<DesktopEnvironment> {
Some(DesktopEnvironment::WebBrowser(browser_info()))
}
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::Mac
} else {
Platform::Unknown(string.to_string())
}
}
#[inline(always)]
fn arch(self) -> Result<CpuArchitecture> {
Ok(if cfg!(target_pointer_width = "64") {
CpuArchitecture::Wasm64
} else {
CpuArchitecture::Wasm32
})
}
}
+479
View File
@@ -0,0 +1,479 @@
use std::{
ffi::{c_char, c_int, c_uchar, c_ulong, c_ushort, c_void, OsString},
io::{self, ErrorKind},
mem::{self, MaybeUninit},
os::windows::ffi::OsStringExt,
prelude::rust_2021::*,
ptr,
str::FromStr,
};
use crate::{
conversions,
os::{Os, Target},
CpuArchitecture, DesktopEnvironment, Error, Language, LanguagePreferences,
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 = 0, // Nothing
FullyQualifiedDN = 1, // Nothing
SamCompatible = 2, // Hostname Followed By Username
Display = 3, // Full Name
UniqueId = 6, // Nothing
Canonical = 7, // Nothing
UserPrincipal = 8, // Nothing
CanonicalEx = 9, // Nothing
ServicePrincipal = 10, // Nothing
DnsDomain = 12, // Nothing
GivenName = 13, // Nothing
Surname = 14, // Nothing
}
#[allow(unused)]
#[repr(C)]
enum ComputerNameFormat {
NetBIOS, // All caps hostname
DnsHostname, // Hostname
DnsDomain, // Nothing or domain
DnsFullyQualified, // Hostname with, for example, .com
PhysicalNetBIOS, // All caps name
PhysicalDnsHostname, // Hostname
PhysicalDnsDomain, // Nothing or domain
PhysicalDnsFullyQualified, // Hostname 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;
}
#[link(name = "advapi32")]
extern "system" {
fn GetUserNameW(a: *mut c_char, b: *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 GetComputerNameExW(
a: ComputerNameFormat,
b: *mut c_char,
c: *mut c_ulong,
) -> c_int;
}
fn username() -> Result<OsString> {
// 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 io::Error::last_os_error().raw_os_error()
!= Some(ERR_INSUFFICIENT_BUFFER)
{
return Err(Error::from_io(io::Error::last_os_error()));
}
// Step 2. Allocate memory to put the Windows (UTF-16) string.
let mut name: Vec<u16> =
Vec::with_capacity(size.try_into().unwrap_or(usize::MAX));
size = name.capacity().try_into().unwrap_or(u32::MAX);
let orig_size = size;
let fail =
unsafe { GetUserNameW(name.as_mut_ptr().cast(), &mut size) == 0 };
if fail {
return Err(Error::from_io(io::Error::last_os_error()));
}
debug_assert_eq!(orig_size, size);
unsafe {
name.set_len(size.try_into().unwrap_or(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<OsString> {
// 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 = io::Error::last_os_error().raw_os_error();
if last_err == Some(ERR_NONE_MAPPED) {
return Err(Error::missing_record());
}
if last_err != Some(ERR_MORE_DATA) {
return Err(Error::from_io(io::Error::last_os_error()));
}
// Step 2. Allocate memory to put the Windows (UTF-16) string.
let mut name: Vec<u16> =
Vec::with_capacity(buf_size.try_into().unwrap_or(usize::MAX));
let mut name_len = name.capacity().try_into().unwrap_or(u32::MAX);
let fail = unsafe {
GetUserNameExW(format, name.as_mut_ptr().cast(), &mut name_len) == 0
};
if fail {
return Err(Error::from_io(io::Error::last_os_error()));
}
assert_eq!(buf_size, name_len + 1);
unsafe { name.set_len(name_len.try_into().unwrap_or(usize::MAX)) };
// Step 3. Convert to Rust String
Ok(OsString::from_wide(&name))
}
impl Target for Os {
#[inline(always)]
fn lang_prefs(self) -> Result<LanguagePreferences> {
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(LanguagePreferences {
fallbacks: String::from_utf16_lossy(&buffer)
.split('\0')
.map(Language::from_str)
.collect::<Result<Vec<Language>>>()?,
..Default::default()
})
}
fn realname(self) -> Result<OsString> {
extended_name(ExtendedNameFormat::Display)
}
fn username(self) -> Result<OsString> {
username()
}
fn devicename(self) -> Result<OsString> {
// 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 io::Error::last_os_error().raw_os_error() != Some(ERR_MORE_DATA) {
return Err(Error::from_io(io::Error::last_os_error()));
}
// Step 2. Allocate memory to put the Windows (UTF-16) string.
let mut name: Vec<u16> =
Vec::with_capacity(size.try_into().unwrap_or(usize::MAX));
let mut size = name.capacity().try_into().unwrap_or(u32::MAX);
if unsafe {
GetComputerNameExW(
ComputerNameFormat::DnsHostname,
name.as_mut_ptr().cast(),
&mut size,
) == 0
} {
return Err(Error::from_io(io::Error::last_os_error()));
}
unsafe {
name.set_len(size.try_into().unwrap_or(usize::MAX));
}
// Step 3. Convert to Rust String
Ok(OsString::from_wide(&name))
}
fn hostname(self) -> Result<String> {
// 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::PhysicalDnsHostname,
ptr::null_mut(),
&mut size,
) == 0
};
assert!(fail);
if io::Error::last_os_error().raw_os_error() != Some(ERR_MORE_DATA) {
return Err(Error::from_io(io::Error::last_os_error()));
}
// Step 2. Allocate memory to put the Windows (UTF-16) string.
let mut name: Vec<u16> =
Vec::with_capacity(size.try_into().unwrap_or(usize::MAX));
let mut size = name.capacity().try_into().unwrap_or(u32::MAX);
if unsafe {
GetComputerNameExW(
ComputerNameFormat::PhysicalDnsHostname,
name.as_mut_ptr().cast(),
&mut size,
) == 0
} {
return Err(Error::from_io(io::Error::last_os_error()));
}
unsafe {
name.set_len(size.try_into().unwrap_or(usize::MAX));
}
// Step 3. Convert to Rust String
conversions::string_from_os(OsString::from_wide(&name))
}
fn distro(self) -> Result<String> {
// 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::<Vec<u16>>();
let path = path.as_mut_ptr();
let inst =
unsafe { LoadLibraryExW(path, ptr::null_mut(), 0x0000_0800) };
if inst.is_null() {
return Err(Error::from_io(io::Error::last_os_error()));
}
let mut path = "RtlGetVersion\0".bytes().collect::<Vec<u8>>();
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::from_io(io::Error::last_os_error()));
}
return Err(Error::from_io(io::Error::last_os_error()));
}
let get_version: unsafe extern "system" fn(
a: *mut OsVersionInfoEx,
) -> u32 = unsafe { mem::transmute(func) };
let mut version = MaybeUninit::<OsVersionInfoEx>::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::<OsVersionInfoEx>() as u32;
get_version(version.as_mut_ptr());
if FreeLibrary(inst) == 0 {
return Err(Error::from_io(io::Error::last_os_error()));
}
version.assume_init()
};
let product = match version.product_type {
1 => "Workstation",
2 => "Domain Controller",
3 => "Server",
_ => "Unknown",
};
if version.major_version >= 10 && version.build_number > 22000 {
Ok(alloc::format!(
"Windows 11 ({}.{}.{}) ({})",
version.major_version,
version.minor_version,
version.build_number,
product,
))
} else {
Ok(alloc::format!(
"Windows {}.{}.{} ({})",
version.major_version,
version.minor_version,
version.build_number,
product,
))
}
}
#[inline(always)]
fn desktop_env(self) -> Option<DesktopEnvironment> {
Some(DesktopEnvironment::Windows)
}
#[inline(always)]
fn platform(self) -> Platform {
Platform::Windows
}
#[inline(always)]
fn arch(self) -> Result<CpuArchitecture> {
fn proc(processor_type: c_ulong) -> Result<CpuArchitecture, c_ulong> {
Ok(match processor_type {
// PROCESSOR_INTEL_386
386 => CpuArchitecture::I386,
// PROCESSOR_INTEL_486
486 => CpuArchitecture::Unknown("I486".to_string()),
// PROCESSOR_INTEL_PENTIUM
586 => CpuArchitecture::I586,
// PROCESSOR_INTEL_IA64
2200 => CpuArchitecture::Unknown("IA64".to_string()),
// PROCESSOR_AMD_X8664
8664 => CpuArchitecture::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 => CpuArchitecture::I686,
// PROCESSOR_ARCHITECTURE_ARM
5 => CpuArchitecture::ArmV6,
// PROCESSOR_ARCHITECTURE_IA64
6 => CpuArchitecture::Unknown("IA64".to_string()),
// PROCESSOR_ARCHITECTURE_AMD64
9 => CpuArchitecture::X64,
// PROCESSOR_ARCHITECTURE_ARM64
12 => CpuArchitecture::Arm64,
// PROCESSOR_ARCHITECTURE_UNKNOWN
0xFFFF => proc(buf.processor_type).map_err(|e| {
Error::from_io(io::Error::new(
ErrorKind::InvalidData,
alloc::format!("Unknown arch: {e}"),
))
})?,
invalid => proc(buf.processor_type).map_err(|e| {
Error::from_io(io::Error::new(
ErrorKind::InvalidData,
alloc::format!("Invalid arch: {invalid}/{e}"),
))
})?,
})
}
#[inline(always)]
fn account(self) -> Result<OsString> {
match extended_name(ExtendedNameFormat::UserPrincipal)
.map_err(io::Error::from)
{
Ok(name) => Ok(name),
Err(e) if e.kind() == ErrorKind::NotFound => username(),
Err(e) => Err(Error::from_io(e)),
}
}
}
+46
View File
@@ -0,0 +1,46 @@
use alloc::string::String;
use core::fmt::{self, Display, Formatter};
/// The underlying platform for a system
#[allow(missing_docs)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum Platform {
Unknown(String),
Linux,
Bsd,
Windows,
Mac,
Illumos,
Ios,
Android,
Nintendo3ds,
PlayStation,
Fuchsia,
Redox,
Hurd,
}
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::Unknown(a) => a,
Self::Linux => "Linux",
Self::Bsd => "BSD",
Self::Windows => "Windows",
Self::Mac => "macOS",
Self::Illumos => "illumos",
Self::Ios => "iOS",
Self::Android => "Android",
Self::Nintendo3ds => "Nintendo 3DS",
Self::PlayStation => "PlayStation",
Self::Fuchsia => "Fuchsia",
Self::Redox => "Redox",
Self::Hurd => "GNU Hurd",
})
}
}
+4
View File
@@ -0,0 +1,4 @@
use crate::Error;
/// This crate's convenience type alias for [`Result`](std::result::Result)s
pub type Result<T = (), E = Error> = core::result::Result<T, E>;