From 947aa07bbf27fc7234c17c4a2ce088c2437bfb6d Mon Sep 17 00:00:00 2001 From: Alex Lyon <arcterus@mail.com> Date: Sun, 11 Mar 2018 05:33:21 -0700 Subject: [PATCH] Add Windows support and enable CI --- .cargo/config | 2 + .travis.yml | 150 +++++++++++++++ .travis/redox-toolchain.sh | 7 + Cargo.toml | 2 +- appveyor.yml | 91 +++++++++ ci/before_deploy.ps1 | 23 +++ ci/before_deploy.sh | 33 ++++ ci/install.sh | 47 +++++ ci/script.sh | 21 +++ src/lib.rs | 27 ++- src/platform/mod.rs | 21 --- src/platform/windows.rs | 41 ----- src/{platform => }/redox.rs | 23 ++- src/{platform => }/unix.rs | 19 +- src/windows.rs | 354 ++++++++++++++++++++++++++++++++++++ 15 files changed, 770 insertions(+), 91 deletions(-) create mode 100644 .cargo/config create mode 100644 .travis.yml create mode 100644 .travis/redox-toolchain.sh create mode 100644 appveyor.yml create mode 100644 ci/before_deploy.ps1 create mode 100644 ci/before_deploy.sh create mode 100644 ci/install.sh create mode 100644 ci/script.sh delete mode 100644 src/platform/mod.rs delete mode 100644 src/platform/windows.rs rename src/{platform => }/redox.rs (80%) rename src/{platform => }/unix.rs (76%) create mode 100644 src/windows.rs diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..58e1381 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[target.x86_64-unknown-redox] +linker = "x86_64-unknown-redox-gcc" diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..258081d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,150 @@ +# Based on the "trust" template v0.1.2 +# https://github.com/japaric/trust/tree/v0.1.2 + +dist: trusty +language: rust +services: docker +sudo: required + +addons: + apt: + packages: + - libssl-dev + +after_success: | + if [ "$TARGET" = x86_64-unknown-linux-gnu -a "$TRAVIS_RUST_VERSION" = stable ]; then + bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) + cargo tarpaulin --out Xml + bash <(curl -s https://codecov.io/bash) + fi + +# TODO Rust builds on stable by default, this can be +# overridden on a case by case basis down below. + +env: + global: + # TODO Update this to match the name of your project. + - CRATE_NAME=platform-info + +matrix: + # TODO These are all the build jobs. Adjust as necessary. Comment out what you + # don't need + include: + # Android + - env: TARGET=aarch64-linux-android DISABLE_TESTS=1 + - env: TARGET=arm-linux-androideabi DISABLE_TESTS=1 + - env: TARGET=armv7-linux-androideabi DISABLE_TESTS=1 + - env: TARGET=i686-linux-android DISABLE_TESTS=1 + - env: TARGET=x86_64-linux-android DISABLE_TESTS=1 + + # iOS + - env: TARGET=aarch64-apple-ios DISABLE_TESTS=1 + os: osx + - env: TARGET=armv7-apple-ios DISABLE_TESTS=1 + os: osx + - env: TARGET=armv7s-apple-ios DISABLE_TESTS=1 + os: osx + - env: TARGET=i386-apple-ios DISABLE_TESTS=1 + os: osx + - env: TARGET=x86_64-apple-ios DISABLE_TESTS=1 + os: osx + + # Linux + - env: TARGET=aarch64-unknown-linux-gnu + - env: TARGET=arm-unknown-linux-gnueabi + - env: TARGET=armv7-unknown-linux-gnueabihf + - env: TARGET=i686-unknown-linux-gnu + - env: TARGET=i686-unknown-linux-musl + - env: TARGET=mips-unknown-linux-gnu + - env: TARGET=mips64-unknown-linux-gnuabi64 + - env: TARGET=mips64el-unknown-linux-gnuabi64 + - env: TARGET=mipsel-unknown-linux-gnu + - env: TARGET=powerpc-unknown-linux-gnu + - env: TARGET=powerpc64-unknown-linux-gnu + - env: TARGET=powerpc64le-unknown-linux-gnu + - env: TARGET=s390x-unknown-linux-gnu DISABLE_TESTS=1 + - env: TARGET=x86_64-unknown-linux-gnu + - env: TARGET=x86_64-unknown-linux-musl + + # OSX + - env: TARGET=i686-apple-darwin + os: osx + - env: TARGET=x86_64-apple-darwin + os: osx + + # *BSD + - env: TARGET=i686-unknown-freebsd DISABLE_TESTS=1 + - env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1 + - env: TARGET=x86_64-unknown-netbsd DISABLE_TESTS=1 + + # Windows + - env: TARGET=x86_64-pc-windows-gnu + + # Redox + - env: TARGET=x86_64-unknown-redox CC=x86_64-unknown-redox-gcc REDOX=1 DISABLE_TESTS=1 + + # Bare metal + # These targets don't support std and as such are likely not suitable for + # most crates. + # - env: TARGET=thumbv6m-none-eabi + # - env: TARGET=thumbv7em-none-eabi + # - env: TARGET=thumbv7em-none-eabihf + # - env: TARGET=thumbv7m-none-eabi + + # Testing other channels + - env: TARGET=x86_64-unknown-linux-gnu + rust: nightly + - env: TARGET=x86_64-apple-darwin + os: osx + rust: nightly + +before_install: + - set -e + - rustup self update + - if [ $REDOX ]; then ./.travis/redox-toolchain.sh; fi + +install: + - sh ci/install.sh + - source ~/.cargo/env || true + +script: + - bash ci/script.sh + +after_script: set +e + +before_deploy: + - sh ci/before_deploy.sh + +deploy: + # TODO update `api_key.secure` + # - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new + # - Encrypt it: `travis encrypt 0123456789012345678901234567890123456789 + # - Paste the output down here + api_key: + secure: A9v3PIzQQ4U08OHFmDPQzNXbNHEb7YHyLXCvMF+dXFuNSvhUNlmQYykxqUf3dvikhJL2/bsZ14umm7ni7fQh0tGwJ4+lPpNzYAcweGgNXnWvjTpY6ovuRbr3gs4/srkyxp/GBDmSW5L8wFN3hKCB+Lm0YnIPB9IA2afP8a30+8VTXT9nv7pNqGny4ilN41ycr4DZi3sQoXdbruy7ClN7gsWW/GUiudBccHVIjmTapOFKLwZHODaUl/1/RDWQzh+i+17e1ivXuJPktDSrqmHPTZ15OjklnHKd6t179ry6VkGRg4R/R/YukVIqGzeaXGWAwdAQ5gE8cjGZghJLVi2jkDBJ85z8MvT+zLZLyliiuhLc/X8y7mkE1n0FKFtXXzFVt0l7V1LaEKbIbiV6XX3jsir4qgkqWjPHBZqO5mkGNFS16Dmt30/ZtEPAzXiINFXbWuWrpQ/LZ4NSto8IMrRTcoyDbAga/KYxJiNIeVuCe1E9dbytDM7K0GLtxJTul/WnnSeI6r//EFyC4bxYjyHhCXaag4q14KM+ak4rB0QgxsYzyGuh2MqyCoVj8YJLjLdKnL/SV7W7LPD40xlxvI6VCYTVi2ILHwL6vCxpukXYteX0c5IAIWkISDKu6nNBEgmCHXXPSqYSrgE5g7/QoCQHI8++nR8iKe0s7TWxZRydby8= + file_glob: true + file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.* + on: + # TODO Here you can pick which targets will generate binary releases + # In this example, there are some targets that are tested using the stable + # and nightly channels. This condition makes sure there is only one release + # for such targets and that's generated using the stable channel + condition: $TRAVIS_RUST_VERSION = stable + tags: true + provider: releases + skip_cleanup: true + +cache: cargo +before_cache: + # Travis can't cache files that are not readable by "others" + - chmod -R a+r $HOME/.cargo + +branches: + only: + # release tags + - /^v\d+\.\d+\.\d+.*$/ + - master + +notifications: + email: + on_success: never diff --git a/.travis/redox-toolchain.sh b/.travis/redox-toolchain.sh new file mode 100644 index 0000000..83bc8fc --- /dev/null +++ b/.travis/redox-toolchain.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +rustup target add x86_64-unknown-redox +sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys AA12E97F0881517F +sudo add-apt-repository 'deb https://static.redox-os.org/toolchain/apt /' +sudo apt-get update -qq +sudo apt-get install -y x86-64-unknown-redox-gcc diff --git a/Cargo.toml b/Cargo.toml index 2f3c571..1e197aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,4 @@ authors = ["Alex Lyon <arcterus@mail.com>"] [dependencies] libc = "0.2" -winapi = { version = "0.3", features = ["sysinfoapi"] } +winapi = { version = "0.3", features = ["libloaderapi", "sysinfoapi", "winbase", "winver"] } diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..9244b35 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,91 @@ +# Based on the "trust" template v0.1.2 +# https://github.com/japaric/trust/tree/v0.1.2 + +environment: + global: + # TODO This is the Rust channel that build jobs will use by default but can be + # overridden on a case by case basis down below + RUST_VERSION: stable + + # TODO Update this to match the name of your project. + CRATE_NAME: platform-info + + # TODO These are all the build jobs. Adjust as necessary. Comment out what you + # don't need + matrix: + # MinGW + - TARGET: i686-pc-windows-gnu + - TARGET: x86_64-pc-windows-gnu + + # MSVC + - TARGET: i686-pc-windows-msvc + - TARGET: x86_64-pc-windows-msvc + + # Testing other channels + - TARGET: x86_64-pc-windows-gnu + RUST_VERSION: nightly + - TARGET: x86_64-pc-windows-msvc + RUST_VERSION: nightly + +install: + - ps: >- + If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') { + $Env:PATH += ';C:\msys64\mingw64\bin' + } ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') { + $Env:PATH += ';C:\msys64\mingw32\bin' + } + - curl -sSf -o rustup-init.exe https://win.rustup.rs/ + - rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_VERSION% + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -Vv + - cargo -V + +# TODO This is the "test phase", tweak it as you see fit +test_script: + # we don't run the "test phase" when doing deploys + - if [%APPVEYOR_REPO_TAG%]==[false] ( + cargo build --target %TARGET% && + cargo build --target %TARGET% --release && + cargo test --target %TARGET% && + cargo test --target %TARGET% --release + ) + +before_deploy: + # TODO Update this to build the artifacts that matter to you + - cargo rustc --target %TARGET% --release -- -C lto + - ps: ci\before_deploy.ps1 + +deploy: + artifact: /.*\.zip/ + # TODO update `auth_token.secure` + # - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new + # - Encrypt it. Go to https://ci.appveyor.com/tools/encrypt + # - Paste the output down here + auth_token: + secure: Xv7lZVmUMWpCKov9dU4t3bl4hhHob31MqgX16HOeJzKiTbHEstWaUc8seGkvwJXT + description: '' + on: + # TODO Here you can pick which targets will generate binary releases + # In this example, there are some targets that are tested using the stable + # and nightly channels. This condition makes sure there is only one release + # for such targets and that's generated using the stable channel + RUST_VERSION: stable + appveyor_repo_tag: true + provider: GitHub + +cache: + - C:\Users\appveyor\.cargo\registry + - target + +branches: + only: + # Release tags + - /^v\d+\.\d+\.\d+.*$/ + - master + +notifications: + - provider: Email + on_build_success: false + +# Building is done in the test phase, so we disable Appveyor's build phase. +build: false diff --git a/ci/before_deploy.ps1 b/ci/before_deploy.ps1 new file mode 100644 index 0000000..d4b8ad3 --- /dev/null +++ b/ci/before_deploy.ps1 @@ -0,0 +1,23 @@ +# This script takes care of packaging the build artifacts that will go in the +# release zipfile + +$SRC_DIR = $PWD.Path +$STAGE = [System.Guid]::NewGuid().ToString() + +Set-Location $ENV:Temp +New-Item -Type Directory -Name $STAGE +Set-Location $STAGE + +$ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip" + +# TODO Update this to package the right artifacts +Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\libplatform_info.rlib" '.\' + +7z a "$ZIP" * + +Push-AppveyorArtifact "$ZIP" + +Remove-Item *.* -Force +Set-Location .. +Remove-Item $STAGE +Set-Location $SRC_DIR diff --git a/ci/before_deploy.sh b/ci/before_deploy.sh new file mode 100644 index 0000000..9d0de6a --- /dev/null +++ b/ci/before_deploy.sh @@ -0,0 +1,33 @@ +# This script takes care of building your crate and packaging it for release + +set -ex + +main() { + local src=$(pwd) \ + stage= + + case $TRAVIS_OS_NAME in + linux) + stage=$(mktemp -d) + ;; + osx) + stage=$(mktemp -d -t tmp) + ;; + esac + + test -f Cargo.lock || cargo generate-lockfile + + # TODO Update this to build the artifacts that matter to you + cross rustc --target $TARGET --release -- -C lto + + # TODO Update this to package the right artifacts + cp target/$TARGET/release/libplatform_info.rlib $stage/ + + cd $stage + tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz * + cd $src + + rm -rf $stage +} + +main diff --git a/ci/install.sh b/ci/install.sh new file mode 100644 index 0000000..80e18e4 --- /dev/null +++ b/ci/install.sh @@ -0,0 +1,47 @@ +set -ex + +main() { + local target= + if [ $TRAVIS_OS_NAME = linux ]; then + target=x86_64-unknown-linux-musl + sort=sort + else + target=x86_64-apple-darwin + sort=gsort # for `sort --sort-version`, from brew's coreutils. + fi + + # Builds for iOS are done on OSX, but require the specific target to be + # installed. + case $TARGET in + aarch64-apple-ios) + rustup target install aarch64-apple-ios + ;; + armv7-apple-ios) + rustup target install armv7-apple-ios + ;; + armv7s-apple-ios) + rustup target install armv7s-apple-ios + ;; + i386-apple-ios) + rustup target install i386-apple-ios + ;; + x86_64-apple-ios) + rustup target install x86_64-apple-ios + ;; + esac + + # This fetches latest stable release + local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \ + | cut -d/ -f3 \ + | grep -E '^v[0.1.0-9.]+$' \ + | $sort --version-sort \ + | tail -n1) + curl -LSfs https://japaric.github.io/trust/install.sh | \ + sh -s -- \ + --force \ + --git japaric/cross \ + --tag $tag \ + --target $target +} + +main diff --git a/ci/script.sh b/ci/script.sh new file mode 100644 index 0000000..293504d --- /dev/null +++ b/ci/script.sh @@ -0,0 +1,21 @@ +# This script takes care of testing your crate + +set -ex + +# TODO This is the "test phase", tweak it as you see fit +main() { + cross build --target $TARGET + cross build --target $TARGET --release + + if [ ! -z $DISABLE_TESTS ]; then + return + fi + + cross test --target $TARGET + cross test --target $TARGET --release +} + +# we don't run the "test phase" when doing deploys +if [ -z $TRAVIS_TAG ]; then + main +fi diff --git a/src/lib.rs b/src/lib.rs index 144e297..26048af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,22 +1,29 @@ // This file is part of the uutils coreutils package. // -// (c) Jian Zeng <anonymousknight96 AT gmail.com> // (c) Alex Lyon <arcterus@mail.com> // // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. // -pub use platform::*; +pub use self::sys::*; -mod platform; +use std::borrow::Cow; -//pub trait Uname +#[cfg(unix)] +#[path = "unix.rs"] +mod sys; +#[cfg(windows)] +#[path = "windows.rs"] +mod sys; +#[cfg(target_os = "redox")] +#[path = "redox.rs"] +mod sys; -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } +pub trait Uname { + fn sysname(&self) -> Cow<str>; + fn nodename(&self) -> Cow<str>; + fn release(&self) -> Cow<str>; + fn version(&self) -> Cow<str>; + fn machine(&self) -> Cow<str>; } diff --git a/src/platform/mod.rs b/src/platform/mod.rs deleted file mode 100644 index 8e628dc..0000000 --- a/src/platform/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// (c) Alex Lyon <arcterus@mail.com> -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. -// - -#[cfg(unix)] -pub use self::unix::*; -#[cfg(windows)] -pub use self::windows::*; -#[cfg(target_os = "redox")] -pub use self::redox::*; - -#[cfg(unix)] -mod unix; -#[cfg(windows)] -mod windows; -#[cfg(target_os = "redox")] -mod redox; diff --git a/src/platform/windows.rs b/src/platform/windows.rs deleted file mode 100644 index af51a69..0000000 --- a/src/platform/windows.rs +++ /dev/null @@ -1,41 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// (c) Alex Lyon <arcterus@mail.com> -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. -// - -extern crate winapi; - -use self::winapi::um::sysinfoapi::{SYSTEM_INFO, GetSystemInfo}; -use self::winapi::um::winnt::*; -use std::mem; -use std::borrow::Cow; -use std::io; - -pub struct Uname { - inner: SYSTEM_INFO -} - -impl Uname { - pub fn new() -> io::Result<Uname> { - unsafe { - let mut info = mem::uninitialized(); - GetSystemInfo(&mut info); - Ok(Uname { inner: info }) - } - } - - // FIXME: need to implement more architectures (e.g. ARM) - pub fn machine(&self) -> Cow<str> { - let arch = unsafe { - match self.inner.u.s().wProcessorArchitecture { - PROCESSOR_ARCHITECTURE_AMD64 => "x86_64", - PROCESSOR_ARCHITECTURE_INTEL => "x86", - _ => unimplemented!() - } - }; - Cow::from(arch) - } -} diff --git a/src/platform/redox.rs b/src/redox.rs similarity index 80% rename from src/platform/redox.rs rename to src/redox.rs index 8a59987..b2fef76 100644 --- a/src/platform/redox.rs +++ b/src/redox.rs @@ -6,11 +6,12 @@ // that was distributed with this source code. // +use super::Uname; use std::borrow::Cow; use std::io::{self, Read}; use std::fs::File; -pub struct Uname { +pub struct PlatformInfo { kernel_name: String, nodename: String, kernel_release: String, @@ -18,8 +19,8 @@ pub struct Uname { machine: String } -impl Uname { - pub fn new() -> io::Result<Uname> { +impl PlatformInfo { + pub fn new() -> io::Result<Self> { let mut inner = Box::new(String::new()); File::open("sys:uname")?.read_to_string(&mut inner)?; @@ -32,7 +33,7 @@ impl Uname { let machine = lines.next().unwrap(); // FIXME: don't actually duplicate the data as doing so is wasteful - Ok(Uname { + Ok(Self { kernel_name: kernel_name.to_owned(), nodename: nodename.to_owned(), kernel_release: kernel_release.to_owned(), @@ -40,24 +41,26 @@ impl Uname { machine: machine.to_owned() }) } +} - pub fn sysname(&self) -> Cow<str> { +impl Uname for PlatformInfo { + fn sysname(&self) -> Cow<str> { Cow::from(self.kernel_name.as_str()) } - pub fn nodename(&self) -> Cow<str> { + fn nodename(&self) -> Cow<str> { Cow::from(self.nodename.as_str()) } - pub fn release(&self) -> Cow<str> { + fn release(&self) -> Cow<str> { Cow::from(self.kernel_release.as_str()) } - pub fn version(&self) -> Cow<str> { + fn version(&self) -> Cow<str> { Cow::from(self.kernel_version.as_str()) } - pub fn machine(&self) -> Cow<str> { + fn machine(&self) -> Cow<str> { Cow::from(self.machine.as_str()) } -} \ No newline at end of file +} diff --git a/src/platform/unix.rs b/src/unix.rs similarity index 76% rename from src/platform/unix.rs rename to src/unix.rs index f96a2c5..2e9cd01 100644 --- a/src/platform/unix.rs +++ b/src/unix.rs @@ -10,6 +10,7 @@ extern crate libc; use self::libc::{uname, utsname}; +use super::Uname; use std::mem; use std::ffi::CStr; use std::borrow::Cow; @@ -21,39 +22,41 @@ macro_rules! cstr2cow { ) } -pub struct Uname { +pub struct PlatformInfo { inner: utsname, } -impl Uname { +impl PlatformInfo { pub fn new() -> io::Result<Self> { unsafe { let mut uts: utsname = mem::uninitialized(); if uname(&mut uts) == 0 { - Ok(Uname { inner: uts }) + Ok(Self { inner: uts }) } else { Err(io::Error::last_os_error()) } } } +} - pub fn sysname(&self) -> Cow<str> { +impl Uname for PlatformInfo { + fn sysname(&self) -> Cow<str> { cstr2cow!(self.inner.sysname) } - pub fn nodename(&self) -> Cow<str> { + fn nodename(&self) -> Cow<str> { cstr2cow!(self.inner.nodename) } - pub fn release(&self) -> Cow<str> { + fn release(&self) -> Cow<str> { cstr2cow!(self.inner.release) } - pub fn version(&self) -> Cow<str> { + fn version(&self) -> Cow<str> { cstr2cow!(self.inner.version) } - pub fn machine(&self) -> Cow<str> { + fn machine(&self) -> Cow<str> { cstr2cow!(self.inner.machine) } } diff --git a/src/windows.rs b/src/windows.rs new file mode 100644 index 0000000..6c322fe --- /dev/null +++ b/src/windows.rs @@ -0,0 +1,354 @@ +// This file is part of the uutils coreutils package. +// +// (c) Alex Lyon <arcterus@mail.com> +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +extern crate winapi; + +use self::winapi::um::libloaderapi::*; +use self::winapi::um::sysinfoapi::*; +use self::winapi::um::winbase::*; +use self::winapi::um::winnt::*; +use self::winapi::um::winver::*; +use self::winapi::shared::ntstatus::*; +use self::winapi::shared::ntdef::NTSTATUS; +use self::winapi::shared::minwindef::*; +use super::Uname; +use std::ffi::{CStr, OsStr, OsString}; +use std::mem; +use std::borrow::Cow; +use std::io; +use std::iter; +use std::os::windows::ffi::{OsStrExt, OsStringExt}; +use std::path::PathBuf; +use std::ptr; + +#[allow(unused_variables)] +#[allow(non_snake_case)] +#[repr(C)] +struct VS_FIXEDFILEINFO { + dwSignature: DWORD, + dwStrucVersion: DWORD, + dwFileVersionMS: DWORD, + dwFileVersionLS: DWORD, + dwProductVersionMS: DWORD, + dwProductVersionLS: DWORD, + dwFileFlagsMask: DWORD, + dwFileFlags: DWORD, + dwFileOS: DWORD, + dwFileType: DWORD, + dwFileSubtype: DWORD, + dwFileDateMS: DWORD, + dwFileDateLS: DWORD, +} + +pub struct PlatformInfo { + sysinfo: SYSTEM_INFO, + nodename: String, + release: String, + version: String, +} + +impl PlatformInfo { + pub fn new() -> io::Result<Self> { + unsafe { + let mut sysinfo = mem::uninitialized(); + + GetNativeSystemInfo(&mut sysinfo); + + let (version, release) = Self::version_info()?; + let nodename = Self::computer_name()?; + + Ok(Self { + sysinfo: sysinfo, + nodename: nodename, + version: version, + release: release + }) + } + } + + fn computer_name() -> io::Result<String> { + let mut size = 0; + unsafe { + // NOTE: shouldn't need to check the error because, on error, the required size will be + // stored in the size variable + // XXX: verify that ComputerNameDnsHostname is the best option + GetComputerNameExW(ComputerNameDnsHostname, ptr::null_mut(), &mut size); + } + let mut data = Vec::with_capacity(size as usize); + unsafe { + // we subtract one from the size because the returned size includes the null terminator + data.set_len(size as usize - 1); + + if GetComputerNameExW(ComputerNameDnsHostname, data.as_mut_ptr(), &mut size) != 0 { + Ok(String::from_utf16_lossy(&data)) + } else { + // XXX: should this error or just return localhost? + Err(io::Error::last_os_error()) + } + } + } + + // NOTE: the only reason any of this has to be done is Microsoft deprecated GetVersionEx() and + // it is now basically useless for us on Windows 8.1 and Windows 10 + unsafe fn version_info() -> io::Result<(String, String)> { + let dll_wide: Vec<WCHAR> = OsStr::new("ntdll.dll").encode_wide().chain(iter::once(0)).collect(); + let module = GetModuleHandleW(dll_wide.as_ptr()); + if !module.is_null() { + let funcname = CStr::from_bytes_with_nul_unchecked(b"RtlGetVersion\0"); + let func = GetProcAddress(module, funcname.as_ptr()); + if !func.is_null() { + let func: extern "stdcall" fn(*mut RTL_OSVERSIONINFOEXW) -> NTSTATUS = mem::transmute(func as *const ()); + + let mut osinfo: RTL_OSVERSIONINFOEXW = mem::zeroed(); + osinfo.dwOSVersionInfoSize = mem::size_of::<RTL_OSVERSIONINFOEXW>() as _; + + if func(&mut osinfo) == STATUS_SUCCESS { + let version = String::from_utf16_lossy(&osinfo.szCSDVersion.split(|&v| v == 0).next().unwrap()); + let release = Self::determine_release(osinfo.dwMajorVersion, osinfo.dwMinorVersion, osinfo.wProductType, osinfo.wSuiteMask); + + return Ok((version, release)); + } + } + } + + // as a last resort, try to get the relevant info by loading the version info from a system + // file (specifically Kernel32.dll) + Self::version_info_from_file() + } + + fn version_info_from_file() -> io::Result<(String, String)> { + use self::winapi::um::sysinfoapi; + + let pathbuf = Self::get_kernel32_path()?; + + let file_info = Self::get_file_version_info(pathbuf)?; + let (major, minor) = Self::query_version_info(file_info)?; + + let mut info: OSVERSIONINFOEXW = unsafe { mem::uninitialized() }; + info.wSuiteMask = VER_SUITE_WH_SERVER as WORD; + info.wProductType = VER_NT_WORKSTATION; + + let mask = unsafe { sysinfoapi::VerSetConditionMask(0, VER_SUITENAME, VER_EQUAL) }; + let suite_mask = if unsafe { VerifyVersionInfoW(&mut info, VER_SUITENAME, mask) } != 0 { + VER_SUITE_WH_SERVER as USHORT + } else { + 0 + }; + + let mask = unsafe { sysinfoapi::VerSetConditionMask(0, VER_PRODUCT_TYPE, VER_EQUAL) }; + let product_type = if unsafe { VerifyVersionInfoW(&mut info, VER_PRODUCT_TYPE, mask) } != 0 { + VER_NT_WORKSTATION + } else { + 0 + }; + + Ok((String::new(), Self::determine_release(major, minor, product_type, suite_mask))) + } + + fn get_kernel32_path() -> io::Result<PathBuf> { + let file = OsStr::new("Kernel32.dll"); + // the "- 1" is to account for the path separator + let buf_capacity = MAX_PATH - file.len() - 1; + + let mut buffer = Vec::with_capacity(buf_capacity); + let buf_size = unsafe { GetSystemDirectoryW(buffer.as_mut_ptr(), buf_capacity as UINT) }; + + if buf_size >= buf_capacity as UINT || buf_size == 0 { + Err(io::Error::last_os_error()) + } else { + unsafe { + buffer.set_len(buf_size as usize); + } + + let mut pathbuf = PathBuf::from(OsString::from_wide(&buffer)); + pathbuf.push(file); + + Ok(pathbuf) + } + } + + fn get_file_version_info(path: PathBuf) -> io::Result<Vec<u8>> { + let path_wide: Vec<_> = path.as_os_str().encode_wide().chain(iter::once(0)).collect(); + let fver_size = unsafe { GetFileVersionInfoSizeW(path_wide.as_ptr(), ptr::null_mut()) }; + + if fver_size == 0 { + return Err(io::Error::last_os_error()); + } + + let mut buffer = Vec::with_capacity(fver_size as usize); + if unsafe { GetFileVersionInfoW(path_wide.as_ptr(), 0, fver_size, buffer.as_mut_ptr() as *mut _) } == 0 { + Err(io::Error::last_os_error()) + } else { + unsafe { + buffer.set_len(fver_size as usize); + } + Ok(buffer) + } + } + + fn query_version_info(buffer: Vec<u8>) -> io::Result<(ULONG, ULONG)> { + let mut block_size = 0; + let mut block = unsafe { mem::uninitialized() }; + + let sub_block: Vec<_> = OsStr::new("\\").encode_wide().chain(iter::once(0)).collect(); + if unsafe { VerQueryValueW(buffer.as_ptr() as *const _, sub_block.as_ptr(), &mut block, &mut block_size) == 0 } { + if block_size < mem::size_of::<VS_FIXEDFILEINFO>() as UINT { + return Err(io::Error::last_os_error()) + } + } + + let info = unsafe { &*(block as *const VS_FIXEDFILEINFO) }; + + Ok((HIWORD(info.dwProductVersionMS) as _, LOWORD(info.dwProductVersionMS) as _)) + } + + fn determine_release(major: ULONG, minor: ULONG, product_type: UCHAR, suite_mask: USHORT) -> String { + let mut name = match major { + 5 => match minor { + 0 => "Windows 2000", + 1 => "Windows XP", + 2 if product_type == VER_NT_WORKSTATION => "Windows XP Professional x64 Edition", + 2 if suite_mask as UINT == VER_SUITE_WH_SERVER => "Windows Home Server", + 2 => "Windows Server 2003", + _ => "" + } + 6 => match minor { + 0 if product_type == VER_NT_WORKSTATION => "Windows Vista", + 0 => "Windows Server 2008", + 1 if product_type != VER_NT_WORKSTATION => "Windows Server 2008 R2", + 1 => "Windows 7", + 2 if product_type != VER_NT_WORKSTATION => "Windows Server 2012", + 2 => "Windows 8", + 3 if product_type != VER_NT_WORKSTATION => "Windows Server 2012 R2", + 3 => "Windows 8.1", + _ => "" + } + 10 => match minor { + 0 if product_type != VER_NT_WORKSTATION => "Windows Server 2016", + _ => "" + } + _ => "" + }; + + // we're doing this down here so we don't have to copy this into multiple branches + if name.len() == 0 { + name = if product_type == VER_NT_WORKSTATION { + "Windows" + } else { + "Windows Server" + }; + } + + format!("{} {}.{}", name, major, minor) + } +} + +impl Uname for PlatformInfo { + fn sysname(&self) -> Cow<str> { + // TODO: report if using MinGW instead of MSVC + + // XXX: if Rust ever works on Windows CE and winapi has the VER_PLATFORM_WIN32_CE + // constant, we should probably check for that + Cow::from("WindowsNT") + } + + fn nodename(&self) -> Cow<str> { + Cow::from(self.nodename.as_str()) + } + + // FIXME: definitely wrong + fn release(&self) -> Cow<str> { + Cow::from(self.release.as_str()) + } + + // FIXME: this is prob wrong + fn version(&self) -> Cow<str> { + Cow::from(self.version.as_str()) + } + + fn machine(&self) -> Cow<str> { + let arch = unsafe { self.sysinfo.u.s().wProcessorArchitecture }; + + let arch_str = match arch { + PROCESSOR_ARCHITECTURE_AMD64 => "x86_64", + PROCESSOR_ARCHITECTURE_INTEL => { + match self.sysinfo.wProcessorLevel { + 4 => "i486", + 5 => "i586", + 6 => "i686", + _ => "i386" + } + } + PROCESSOR_ARCHITECTURE_IA64 => "ia64", + // FIXME: not sure if this is wrong because I think uname usually returns stuff like + // armv7l on Linux, but can't find a way to figure that out on Windows + PROCESSOR_ARCHITECTURE_ARM => "arm", + // XXX: I believe this is correct for GNU compat, but differs from LLVM? Like the ARM + // branch above, I'm not really sure about this one either + PROCESSOR_ARCHITECTURE_ARM64 => "aarch64", + PROCESSOR_ARCHITECTURE_MIPS => "mips", + PROCESSOR_ARCHITECTURE_PPC => "powerpc", + PROCESSOR_ARCHITECTURE_ALPHA | PROCESSOR_ARCHITECTURE_ALPHA64 => "alpha", + // FIXME: I don't know anything about this architecture, so this may be incorrect + PROCESSOR_ARCHITECTURE_SHX => "sh", + _ => "unknown" + }; + + Cow::from(arch_str) + } +} + +#[test] +fn test_sysname() { + assert_eq!(PlatformInfo::new().unwrap().sysname(), "WindowsNT"); +} + +#[test] +fn test_machine() { + let target = if cfg!(target_arch = "x86_64") { + vec!["x86_64"] + } else if cfg!(target_arch = "x86") { + vec!["i386", "i486", "i586", "i686"] + } else if cfg!(target_arch = "arm") { + vec!["arm"] + } else if cfg!(target_arch = "aarch64") { + // NOTE: keeping both of these until the correct behavior is sorted out + vec!["arm64", "aarch64"] + } else if cfg!(target_arch = "powerpc") { + vec!["powerpc"] + } else if cfg!(target_arch = "mips") { + vec!["mips"] + } else { + // NOTE: the other architecture are currently not valid targets for Rust (in fact, I am + // almost certain some of these are not even valid targets for the Windows build) + vec!["unknown"] + }; + + assert!(target.contains(&&*PlatformInfo::new().unwrap().machine())); +} + +// TODO: figure out a way to test these +/* +#[test] +fn test_nodename() { + let info = PlatformInfo::new().unwrap(); + panic!("{}", info.nodename()); +} + +#[test] +fn test_version() { + let info = PlatformInfo::new().unwrap(); + panic!("{}", info.version()); +} + +#[test] +fn test_release() { + let info = PlatformInfo::new().unwrap(); + panic!("{}", info.release()); +} +*/ -- GitLab