From 953e702c0b24789a359a4027818af53bcb979db6 Mon Sep 17 00:00:00 2001 From: Jeron Aldaron Lau Date: Sat, 2 Mar 2024 21:45:26 -0600 Subject: [PATCH] Support Redox (#92) --- .github/workflows/ci.yml | 20 ++++- .gitignore | 1 + CHANGELOG.md | 1 + Cargo.toml | 13 +++- README.md | 11 ++- TESTING.md | 89 ++++++++++++++++++++++ recipe.toml | 7 ++ src/os.rs | 31 ++++++-- src/os/daku.rs | 66 +++++++++++++++++ src/os/fake.rs | 2 +- src/os/redox.rs | 154 +++++++++++++++++++++++++++++++++++++++ src/os/unix.rs | 18 ++--- 12 files changed, 387 insertions(+), 26 deletions(-) create mode 100644 TESTING.md create mode 100644 recipe.toml create mode 100644 src/os/daku.rs create mode 100644 src/os/redox.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f5f91a..626cb89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,7 +125,6 @@ jobs: - i686-unknown-linux-gnu - wasm32-wasi - x86_64-apple-darwin - - x86_64-unknown-redox steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -198,8 +197,23 @@ jobs: matrix: os: [ubuntu-latest] tc: [1.65.0, stable, beta, nightly] - cc: - - x86_64-unknown-illumos + cc: [x86_64-unknown-illumos] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.tc }} + target: ${{ matrix.cc }} + override: true + - run: cargo build --all-features --target=${{ matrix.cc }} + cross-compile-redox: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + tc: [1.65.0, stable, beta, nightly] + cc: [x86_64-unknown-redox] steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 diff --git a/.gitignore b/.gitignore index 7ed80a4..6ec0057 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Cargo.lock /ignore/ /a.out /**/*.swp +/build/ diff --git a/CHANGELOG.md b/CHANGELOG.md index a11c093..b30c1c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog], and this project adheres to ### Added - WASI support + - Redox support - Fallible functions - `whoami::fallible::devicename()` - `whoami::fallible::devicename_os()` diff --git a/Cargo.toml b/Cargo.toml index b54095c..462cdc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,15 @@ include = [ ] rust-version = "1.40" -# Target-specific dependency required to work with wasm-bindgen +# Target specific dependencies for redox +[target.'cfg(all(target_os = "redox", not(target_arch = "wasm32")))'.dependencies.redox_syscall] +version = "0.4" + +# Target specific dependencies for wasite +[target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies.wasite] +version = "0.1" + +# Target-specific dependency for wasm-bindgen [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(target_os = "daku")))'.dependencies.web-sys] version = "0.3" features = ["Navigator", "Document", "Window", "Location"] @@ -34,9 +42,6 @@ optional = true version = "0.2" optional = true -[target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies.wasite] -version = "0.1" - [features] default = ["web"] # Enabling this feature indicates that the wasm32-unknown-unknown target should diff --git a/README.md b/README.md index 5340904..13e0bed 100644 --- a/README.md +++ b/README.md @@ -32,15 +32,15 @@ WhoAmI targets all platforms that can run Rust, including: - Windows - Mac OS - BSD variants (FreeBSD, others) + - Illumos variants (SmartOS, OmniOS, others) **Target-Specific MSRV 1.65** + - Redox **Target-Specific MSRV 1.65** - [Web Assembly](https://github.com/ardaku/whoami/blob/stable/WASM.md) - Mock implementation - Web Browser - DOM - WASI (Wasite, others) **may partially or fully work - but untested** - Daku (Ardaku/Quantii, others) **mock implementation, full implementation planned later** - - Illumos variants (SmartOS, OmniOS, others) **may partially or fully work - but untested** - Android **may partially or fully work - but untested, planned later** - iOS **planned later** - - Redox **planned later** - Fuchsia **planned later** - Various game consoles **planned later** - Others? (make a PR or open an issue) @@ -49,8 +49,11 @@ WhoAmI targets all platforms that can run Rust, including: WhoAmI 1.x.y targets Rust 1.40.0 stable and later, and the 1.x.y track will be maintained at least until the release of the Rust 2024 edition. -The MSRV will only be updated on major version bumps, and version 2.0.0 will -target Rust 1.65.0 and later to make use of the `let else` syntax. +The MSRV will not be updated until version 2.0.0, after which breaking changes +may happen on minor releases, and version 2.0.0 will target Rust 1.65.0 and +later to make use of the `let else` syntax. The current plan is for all 2.x +releases to be supported and receive bugfixes at least until sometime in 2027, +bumping MSRV only as needed. ## Binary [whome](https://crates.io/crates/whome): `whoami` command RiR (Re-written in diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..7dc6b61 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,89 @@ +# Testing + +This file outlines the regression testing plan for all platforms. + +## Redox + + + +Tested on Fedora Silverblue 39 + +### Update Rust Nightly and Stable + +```shell +rustup update nightly stable +rustup target add --toolchain stable x86_64-unknown-redox +``` + +### Install pre-requisites + +```shell +sudo dnf install git file autoconf vim bison flex genisoimage gperf glibc-devel.i686 expat expat-devel fuse-devel fuse3-devel gmp-devel perl-HTML-Parser libpng-devel libtool libjpeg-turbo-devel libvorbis-devel SDL2_ttf-devel mesa-libOSMesa-devel m4 nasm po4a syslinux texinfo sdl12-compat-devel ninja-build meson python3-mako make gcc gcc-c++ openssl patch automake perl-Pod-Html perl-FindBin gperf curl gettext-devel perl-Pod-Xhtml pkgconf-pkg-config cmake cbindgen just qemu doxygen 'perl(ExtUtils::MakeMaker)' + +cargo install --locked --force --version 0.1.1 cargo-config +``` + +### Get redox source + +```shell +mkdir -p build/ +cd build/ +git clone https://gitlab.redox-os.org/redox-os/redox.git --origin upstream --recursive +``` + +### Create our demo recipe + +Make sure whome is updated to the whoami testing branch. + +```shell +mkdir -p build/redox/cookbook/recipes/demos/whome/ +cp recipe.toml build/redox/cookbook/recipes/demos/whome/ +cp build/redox/config/desktop.toml config/x86_64/ardaku.toml +``` + +In `config/x86_64/ardaku.toml`, under `[packages]`: + +```toml +whome = {} +``` + +### Build Redox + +This takes a while + +```shell +make all +``` + +or + +```shell +make rebuild +``` + +### Run Redox + +```shell +make qemu +``` + +### Test it + +Verify you are on the new version + +```shell +whome --version +``` + +Default settings should output: + +```console +realname: user +username: user +devicename: redox +hostname: redox +distro: Redox OS 0.8.0 +desktop_env: Orbital +platform: Redox +arch: Unknown: x86_64 +``` diff --git a/recipe.toml b/recipe.toml new file mode 100644 index 0000000..10ad316 --- /dev/null +++ b/recipe.toml @@ -0,0 +1,7 @@ +# Redox recipe for testing whoami + +[source] +git = "https://github.com/AldaronLau/whome.git" + +[build] +template = "cargo" diff --git a/src/os.rs b/src/os.rs index ccd9c89..0c6c45e 100644 --- a/src/os.rs +++ b/src/os.rs @@ -1,9 +1,9 @@ #![allow(unsafe_code)] -// Redox - FIXME: Currently routes to fake.rs +// Redox #[cfg_attr( all(target_os = "redox", not(target_arch = "wasm32")), - path = "os/fake.rs" + path = "os/redox.rs" )] // Unix #[cfg_attr( @@ -14,10 +14,10 @@ )), path = "os/unix.rs" )] -// Wasm32 (Daku) - FIXME: Currently routes to fake.rs +// Wasm32 (Daku) #[cfg_attr( all(target_arch = "wasm32", target_os = "daku"), - path = "os/fake.rs" + path = "os/daku.rs" )] // Wasm32 (Wasi) #[cfg_attr( @@ -51,7 +51,10 @@ )] mod target; -use std::ffi::OsString; +use std::{ + ffi::OsString, + io::{Error, ErrorKind}, +}; pub(crate) use self::target::*; use crate::{Arch, DesktopEnv, Language, Platform, Result}; @@ -81,3 +84,21 @@ pub(crate) trait Target { /// Return the computer's CPU architecture. fn arch(self) -> Result; } + +// This is only used on some platforms +#[allow(dead_code)] +fn err_missing_record() -> Error { + Error::new(ErrorKind::NotFound, "Missing record") +} + +// This is only used on some platforms +#[allow(dead_code)] +fn err_null_record() -> Error { + Error::new(ErrorKind::NotFound, "Null record") +} + +// This is only used on some platforms +#[allow(dead_code)] +fn err_empty_record() -> Error { + Error::new(ErrorKind::NotFound, "Empty record") +} diff --git a/src/os/daku.rs b/src/os/daku.rs new file mode 100644 index 0000000..a4cf16f --- /dev/null +++ b/src/os/daku.rs @@ -0,0 +1,66 @@ +//! This is mostly the same as fake.rs for now + +#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] +compile_error!("Unexpected pointer width for target platform"); + +use std::ffi::OsString; + +use crate::{ + os::{Os, Target}, + Arch, DesktopEnv, Language, Platform, Result, +}; + +#[inline(always)] +pub(crate) fn lang() -> impl Iterator { + std::iter::once("en/US".to_string()) +} + +impl Target for Os { + fn langs(self) -> Vec { + todo!() + } + + #[inline(always)] + fn realname(self) -> Result { + Ok("Anonymous".to_string().into()) + } + + #[inline(always)] + fn username(self) -> Result { + Ok("anonymous".to_string().into()) + } + + #[inline(always)] + fn devicename(self) -> Result { + Ok("Unknown".to_string().into()) + } + + #[inline(always)] + fn hostname(self) -> Result { + Ok("localhost".to_string()) + } + + #[inline(always)] + fn distro(self) -> Result { + Ok("Emulated".to_string()) + } + + #[inline(always)] + fn desktop_env(self) -> DesktopEnv { + DesktopEnv::Unknown("Unknown Daku".to_string()) + } + + #[inline(always)] + fn platform(self) -> Platform { + Platform::Unknown("Daku".to_string()) + } + + #[inline(always)] + fn arch(self) -> Result { + Ok(if cfg!(target_pointer_width = "64") { + Arch::Wasm64 + } else { + Arch::Wasm32 + }) + } +} diff --git a/src/os/fake.rs b/src/os/fake.rs index ae43aab..152bc88 100644 --- a/src/os/fake.rs +++ b/src/os/fake.rs @@ -12,7 +12,7 @@ use crate::{ #[inline(always)] pub(crate) fn lang() -> impl Iterator { - std::iter::once("en-US".to_string()) + std::iter::once("en/US".to_string()) } impl Target for Os { diff --git a/src/os/redox.rs b/src/os/redox.rs new file mode 100644 index 0000000..ab3c3a7 --- /dev/null +++ b/src/os/redox.rs @@ -0,0 +1,154 @@ +// We don't need unsafe, yay! +#![forbid(unsafe_code)] +// Redox support has its own MSRV of 1.65, is nightly lint +#![allow(unknown_lints, clippy::incompatible_msrv)] + +use std::{borrow::Cow, ffi::OsString, fs, io::Error}; + +use syscall::{call, error}; + +use crate::{ + os::{Os, Target}, + Arch, DesktopEnv, Language, Platform, Result, +}; + +/// Row in the Redox /etc/passwd file +struct Passwd<'a>(Cow<'a, str>); + +impl Passwd<'_> { + fn column(&self, number: usize) -> Option<&str> { + self.0.split(';').nth(number) + } + + fn username(&self) -> Option { + self.column(0).map(ToString::to_string) + } + + fn uid(&self) -> Option { + self.column(1)?.parse().ok() + } + + fn gid(&self) -> Option { + self.column(2)?.parse().ok() + } + + fn fullname(&self) -> Option { + self.column(3).map(ToString::to_string) + } +} + +struct Uname<'a>(Cow<'a, str>); + +impl Uname<'_> { + fn row(&self, number: usize) -> Option<&str> { + self.0.lines().nth(number) + } + + fn machine_arch(&self) -> Option { + // FIXME: Don't hardcode unknown arch + Some(Arch::Unknown(self.row(4)?.to_string())) + } +} + +fn to_io_error(error: error::Error) -> Error { + Error::from_raw_os_error(error.errno) +} + +fn euid() -> Result { + call::geteuid().map_err(to_io_error) +} + +fn egid() -> Result { + call::getegid().map_err(to_io_error) +} + +fn passwd() -> Result> { + let (euid, egid) = (euid()?, egid()?); + let passwd_file = fs::read_to_string("/etc/passwd")?; + + for user in passwd_file.lines() { + let passwd = Passwd(user.into()); + + if passwd.uid() == Some(euid) && passwd.gid() == Some(egid) { + return Ok(Passwd(passwd.0.into_owned().into())); + } + } + + Err(super::err_missing_record()) +} + +fn uname() -> Result> { + let uname_file = fs::read_to_string("sys:uname")?; + + Ok(Uname(uname_file.into())) +} + +fn hostname() -> Result { + let hostname_file = fs::read_to_string("/etc/hostname")?; + + Ok(hostname_file.lines().next().unwrap_or_default().to_string()) +} + +#[inline(always)] +pub(crate) fn lang() -> impl Iterator { + // FIXME: Look for unix-like language env vars + std::iter::once("en/US".to_string()) +} + +impl Target for Os { + fn langs(self) -> Vec { + todo!() + } + + #[inline(always)] + fn realname(self) -> Result { + Ok(passwd()?.fullname().unwrap_or_default().into()) + } + + #[inline(always)] + fn username(self) -> Result { + Ok(passwd()?.username().unwrap_or_default().into()) + } + + #[inline(always)] + fn devicename(self) -> Result { + hostname().map(OsString::from) + } + + #[inline(always)] + fn hostname(self) -> Result { + hostname() + } + + #[inline(always)] + fn distro(self) -> Result { + let release_file = fs::read_to_string("/etc/os-release")?; + + for kv in release_file.lines() { + if let Some(kv) = kv.strip_prefix("PRETTY_NAME=\"") { + if let Some(kv) = kv.strip_suffix('\"') { + return Ok(kv.to_string()); + } + } + } + + Err(super::err_missing_record()) + } + + #[inline(always)] + fn desktop_env(self) -> DesktopEnv { + DesktopEnv::Orbital + } + + #[inline(always)] + fn platform(self) -> Platform { + Platform::Redox + } + + #[inline(always)] + fn arch(self) -> Result { + uname()? + .machine_arch() + .ok_or_else(super::err_missing_record) + } +} diff --git a/src/os/unix.rs b/src/os/unix.rs index 17dfb34..bde8d7b 100644 --- a/src/os/unix.rs +++ b/src/os/unix.rs @@ -126,7 +126,7 @@ unsafe fn strlen_gecos(cs: *const c_void) -> usize { fn os_from_cstring_gecos(string: *const c_void) -> Result { if string.is_null() { - return Err(Error::new(ErrorKind::NotFound, "Null record")); + return Err(super::err_null_record()); } // Get a byte slice of the c string. @@ -134,7 +134,7 @@ fn os_from_cstring_gecos(string: *const c_void) -> Result { let length = strlen_gecos(string); if length == 0 { - return Err(Error::new(ErrorKind::InvalidData, "Empty record")); + return Err(super::err_empty_record()); } std::slice::from_raw_parts(string.cast(), length) @@ -146,7 +146,7 @@ fn os_from_cstring_gecos(string: *const c_void) -> Result { fn os_from_cstring(string: *const c_void) -> Result { if string.is_null() { - return Err(Error::new(ErrorKind::NotFound, "Null record")); + return Err(super::err_null_record()); } // Get a byte slice of the c string. @@ -154,7 +154,7 @@ fn os_from_cstring(string: *const c_void) -> Result { let length = strlen(string); if length == 0 { - return Err(Error::new(ErrorKind::InvalidData, "Empty record")); + return Err(super::err_empty_record()); } std::slice::from_raw_parts(string.cast(), length) @@ -219,7 +219,7 @@ fn getpwuid(name: Name) -> Result { let _passwd = _passwd.assume_init(); if _passwd.is_null() { - return Err(Error::new(ErrorKind::NotFound, "Null record")); + return Err(super::err_null_record()); } passwd.assume_init() @@ -424,7 +424,7 @@ impl Target for Os { }); if out.as_bytes().is_empty() { - return Err(Error::new(ErrorKind::InvalidData, "Empty record")); + return Err(super::err_empty_record()); } Ok(out) @@ -440,7 +440,7 @@ impl Target for Os { } if nodename.is_empty() { - return Err(Error::new(ErrorKind::InvalidData, "Empty record")); + return Err(super::err_empty_record()); } Ok(OsString::from_vec(nodename)) @@ -461,7 +461,7 @@ impl Target for Os { } } - Err(Error::new(ErrorKind::NotFound, "Missing record")) + Err(super::err_missing_record()) } } @@ -494,7 +494,7 @@ impl Target for Os { ) { distro_xml(data) } else { - Err(Error::new(ErrorKind::NotFound, "Missing record")) + Err(super::err_missing_record()) } }