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