From 80add18dc937291a5235ecb5ca62cdc542decd44 Mon Sep 17 00:00:00 2001
From: MggMuggins <mggmugginsmc@gmail.com>
Date: Thu, 26 Jul 2018 12:43:57 -0500
Subject: [PATCH] Functional Partial rewrite

---
 Cargo.lock     | 110 ++++++++++++++++++++----
 Cargo.toml     |   3 +-
 src/install.rs | 205 --------------------------------------------
 src/lib.rs     | 226 ++++++++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 319 insertions(+), 225 deletions(-)
 delete mode 100644 src/install.rs

diff --git a/Cargo.lock b/Cargo.lock
index 85bf9c5..8eb6809 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,27 @@ dependencies = [
  "scoped_threadpool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "backtrace"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace-sys 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "backtrace-sys"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "base64"
 version = "0.6.0"
@@ -64,6 +85,16 @@ name = "byteorder"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "cc"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "clap"
 version = "2.27.1"
@@ -108,9 +139,23 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "extra"
-version = "0.1.0"
-source = "git+https://github.com/redox-os/libextra.git#402932084acd5fef4812945887ceaaa2ddd5f264"
+name = "failure"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "failure_derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "fixedbitset"
@@ -391,10 +436,11 @@ name = "redox_installer"
 version = "0.2.0"
 dependencies = [
  "arg_parser 0.1.0 (git+https://github.com/redox-os/arg-parser.git)",
+ "argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "liner 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkgutils 0.1.1 (git+https://github.com/redox-os/pkgutils.git)",
  "rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_users 0.1.0 (git+https://github.com/redox-os/users.git)",
  "redoxfs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -415,16 +461,6 @@ dependencies = [
  "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "redox_users"
-version = "0.1.0"
-source = "git+https://github.com/redox-os/users.git#50f4022e6c713a131bbbfde360cb0f369e96c672"
-dependencies = [
- "argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "extra 0.1.0 (git+https://github.com/redox-os/libextra.git)",
- "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "redoxfs"
 version = "0.3.1"
@@ -450,6 +486,11 @@ dependencies = [
  "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "rustc-demangle"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "rustls"
 version = "0.9.0"
@@ -574,6 +615,15 @@ dependencies = [
  "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "synstructure"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "tar"
 version = "0.4.13"
@@ -736,11 +786,30 @@ name = "winapi"
 version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "winapi"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "winapi-build"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "xattr"
 version = "0.1.11"
@@ -753,6 +822,8 @@ dependencies = [
 "checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45"
 "checksum arg_parser 0.1.0 (git+https://github.com/redox-os/arg-parser.git)" = "<none>"
 "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392"
+"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a"
+"checksum backtrace-sys 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "bff67d0c06556c0b8e6b5f090f0eac52d950d9dfd1d35ba04e4ca3543eaf6a7e"
 "checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9"
 "checksum bidir-map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6c3d05037e57974413eef55a8505df19de3cb4dc7a8f8389e1588ec492ae9c73"
 "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
@@ -761,13 +832,16 @@ dependencies = [
 "checksum build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e90dc84f5e62d2ebe7676b83c22d33b6db8bd27340fb6ffbff0a364efa0cb9c9"
 "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855"
 "checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d"
+"checksum cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "2119ea4867bd2b8ed3aecab467709720b2d55b1bcfe09f772fd68066eaf15275"
+"checksum cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efe5c877e17a9c717a0bf3613b2709f723202c4e4675cc8f12926ded29bcb17e"
 "checksum clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b8c532887f1a292d17de05ae858a8fe50a301e196f9ef0ddb7ccd0d1d00f180"
 "checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd"
 "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
 "checksum crc 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fba69ea0e15e720f7e1cfe1cf3bc55007fbd41e32b8ae11cfa343e7e5961e79a"
 "checksum crc-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "003d1170779d405378223470f5864b41b79a91969be1260e4de7b4ec069af69c"
 "checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3"
-"checksum extra 0.1.0 (git+https://github.com/redox-os/libextra.git)" = "<none>"
+"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
+"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
 "checksum fixedbitset 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "85cb8fec437468d86dc7c83ca7cfc933341d561873275f22dd5eedefa63a6478"
 "checksum fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159"
 "checksum fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43f3795b4bae048dc6123a6b972cadde2e676f9ded08aef6bb77f5f157684a82"
@@ -803,9 +877,9 @@ dependencies = [
 "checksum rayon-core 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e64b609139d83da75902f88fd6c01820046840a18471e4dfcd5ac7c0f46bea53"
 "checksum redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "8dde11f18c108289bef24469638a04dce49da56084f2d50618b226e47eb04509"
 "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
-"checksum redox_users 0.1.0 (git+https://github.com/redox-os/users.git)" = "<none>"
 "checksum redoxfs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f800e00e74d5e26b5066798195a0bffab834ecf0ae7f460d82d884c9569fa36"
 "checksum ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2a6dc7fc06a05e6de183c5b97058582e9da2de0c136eafe49609769c507724"
+"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
 "checksum rustls 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17727f4b991294da2c84d75a43c003151ff58072212768800f66c56ee46dca43"
 "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
 "checksum scoped_threadpool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "4ea459fe3ceff01e09534847c49860891d3ff1c12b4eb7731b67f2778fb60190"
@@ -822,6 +896,7 @@ dependencies = [
 "checksum syn 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)" = "58fd09df59565db3399efbba34ba8a2fec1307511ebd245d0061ff9d42691673"
 "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
 "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
+"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
 "checksum tar 0.4.13 (git+https://github.com/redox-os/tar-rs)" = "<none>"
 "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
 "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
@@ -845,5 +920,8 @@ dependencies = [
 "checksum webpki 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e499345fc4c6b7c79a5b8756d4592c4305510a13512e79efafe00dfbd67bbac6"
 "checksum webpki-roots 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5bfb3f50499f21ad2317f442845e3b5805b007f1e728f59885c99e61b8c181a7"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 "checksum xattr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "5f04de8a1346489a2f9e9bd8526b73d135ec554227b17568456e86aa35b6f3fc"
diff --git a/Cargo.toml b/Cargo.toml
index 5faa37f..b873ce4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,9 @@ path = "src/lib.rs"
 
 [dependencies]
 arg_parser = { git = "https://github.com/redox-os/arg-parser.git" }
+argon2rs = { version = "0.2", default-features = false }
 liner = "0.1"
+failure = "0.1"
 pkgutils = { git = "https://github.com/redox-os/pkgutils.git" }
 rand = "0.3"
 redoxfs = "0.3"
@@ -20,4 +22,3 @@ serde = "0.8"
 serde_derive = "0.8"
 termion = "1.5.1"
 toml = { version = "0.2", default-features = false, features = ["serde"] }
-redox_users = { git = "https://github.com/redox-os/users.git" }
diff --git a/src/install.rs b/src/install.rs
deleted file mode 100644
index ad76f80..0000000
--- a/src/install.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-extern crate liner;
-extern crate pkgutils;
-extern crate rand;
-extern crate termion;
-extern crate redox_users;
-
-use self::rand::Rng;
-use self::termion::input::TermRead;
-use self::pkgutils::{Repo, Package};
-
-use std::{env, fs};
-use std::ffi::OsStr;
-use std::io::{self, stderr, Write};
-use std::os::unix::ffi::OsStrExt;
-use std::os::unix::fs::symlink;
-use std::path::Path;
-use std::process::{self, Command};
-use std::str::FromStr;
-
-use config::Config;
-
-const REMOTE: &'static str = "https://static.redox-os.org/pkg";
-const TARGET: &'static str = "x86_64-unknown-redox";
-
-fn unwrap_or_prompt<T: FromStr>(option: Option<T>, context: &mut liner::Context, prompt: &str) -> Result<T, String> {
-    match option {
-        None => {
-            let line = context.read_line(prompt, &mut |_| {}).map_err(|err| format!("failed to read line: {}", err))?;
-            T::from_str(&line).map_err(|_| format!("failed to parse '{}'", line))
-        },
-        Some(t) => Ok(t)
-    }
-}
-
-fn prompt_password(prompt: &str, confirm_prompt: &str) -> Result<String, String> {
-    let stdin = io::stdin();
-    let mut stdin = stdin.lock();
-    let stdout = io::stdout();
-    let mut stdout = stdout.lock();
-
-    stdout.write(prompt.as_bytes()).map_err(|err| format!("failed to write to stdout: {}", err))?;
-    stdout.flush().map_err(|err| format!("failed to flush stdout: {}", err))?;
-    if let Some(password) = stdin.read_passwd(&mut stdout).map_err(|err| format!("failed to read password: {}", err))? {
-        stdout.write(b"\n").map_err(|err| format!("failed to write to stdout: {}", err))?;
-        stdout.flush().map_err(|err| format!("failed to flush stdout: {}", err))?;
-
-        if password.is_empty() {
-            Ok(password)
-        } else {
-            stdout.write(confirm_prompt.as_bytes()).map_err(|err| format!("failed to write to stdout: {}", err))?;
-            stdout.flush().map_err(|err| format!("failed to flush stdout: {}", err))?;
-            if let Some(confirm_password) = stdin.read_passwd(&mut stdout).map_err(|err| format!("failed to read password: {}", err))? {
-                stdout.write(b"\n").map_err(|err| format!("failed to write to stdout: {}", err))?;
-                stdout.flush().map_err(|err| format!("failed to flush stdout: {}", err))?;
-
-                if confirm_password == password {
-                    let salt = format!("{:X}", rand::OsRng::new().unwrap().next_u64());
-                    Ok(redox_users::User::encode_passwd(&password, &salt))
-                } else {
-                    Err("passwords do not match".to_string())
-                }
-            } else {
-                Err("passwords do not match".to_string())
-            }
-        }
-    } else {
-        Ok(String::new())
-    }
-}
-
-fn install_packages<S: AsRef<str>>(config: &Config, dest: &str, cookbook: Option<S>) {
-    let mut repo = Repo::new(TARGET);
-    repo.add_remote(REMOTE);
-
-    if let Some(cookbook) = cookbook {
-        let status = Command::new("./repo.sh")
-            .current_dir(cookbook.as_ref())
-            .args(config.packages.keys())
-            .spawn()
-            .unwrap()
-            .wait()
-            .unwrap();
-
-        if !status.success() {
-            write!(stderr(), "./repo.sh failed.").unwrap();
-            process::exit(1);
-        }
-
-        for (packagename, _package) in &config.packages {
-            println!("Installing package {}", packagename);
-            let path = format!("{}/{}/repo/{}/{}.tar.gz",
-                               env::current_dir().unwrap().to_string_lossy(),
-                               cookbook.as_ref(), TARGET, packagename);
-            Package::from_path(&path).unwrap().install(dest).unwrap();
-        }
-    } else {
-        for (packagename, _package) in &config.packages {
-            println!("Installing package {}", packagename);
-            repo.fetch(&packagename).unwrap().install(dest).unwrap();
-        }
-    }
-}
-
-pub fn install<P: AsRef<Path>, S: AsRef<str>>(config: Config, output: P, cookbook: Option<S>) -> Result<(), String> {
-    let output = output.as_ref();
-
-    println!("Install {:#?} to {}", config, output.display());
-
-    let mut context = liner::Context::new();
-
-    macro_rules! prompt {
-        ($dst:expr, $def:expr, $($arg:tt)*) => (if config.general.prompt {
-            match unwrap_or_prompt($dst, &mut context, &format!($($arg)*)) {
-                Ok(res) => if res.is_empty() {
-                    Ok($def)
-                } else {
-                    Ok(res)
-                },
-                Err(err) => Err(err)
-            }
-        } else {
-            Ok($dst.unwrap_or($def))
-        })
-    }
-
-    // TODO: Mount disk if output is a file
-    let sysroot = output.to_owned();
-
-    macro_rules! dir {
-        ($path:expr) => {{
-            let mut path = sysroot.clone();
-            path.push($path);
-            println!("Create directory {}", path.display());
-            fs::create_dir_all(&path).map_err(|err| format!("failed to create {}: {}", path.display(), err))?;
-        }};
-    }
-
-    macro_rules! file {
-        ($path:expr, $data:expr, $symlink:expr) => {{
-            let mut path = sysroot.clone();
-            path.push($path);
-            if let Some(parent) = path.parent() {
-                println!("Create file parent {}", parent.display());
-                fs::create_dir_all(parent).map_err(|err| format!("failed to create file parent {}: {}", parent.display(), err))?;
-            }
-            if $symlink {
-                println!("Create symlink {}", path.display());
-                symlink(&OsStr::from_bytes($data), &path).map_err(|err| format!("failed to symlink {}: {}", path.display(), err))?;
-            } else {
-                println!("Create file {}", path.display());
-                let mut file = fs::File::create(&path).map_err(|err| format!("failed to create {}: {}", path.display(), err))?;
-                file.write_all($data).map_err(|err| format!("failed to write {}: {}", path.display(), err))?;
-            }
-        }};
-    }
-
-    dir!("");
-
-    install_packages(&config, sysroot.to_str().unwrap(), cookbook);
-
-    for file in config.files {
-        file!(file.path.trim_matches('/'), file.data.as_bytes(), file.symlink);
-    }
-
-    let mut passwd = String::new();
-    let mut next_uid = 1000;
-    for (username, user) in config.users {
-        let password = if let Some(password) = user.password {
-            password
-        } else if config.general.prompt {
-            prompt_password(&format!("{}: enter password: ", username), &format!("{}: confirm password: ", username))?
-        } else {
-            String::new()
-        };
-
-        let uid = user.uid.unwrap_or(next_uid);
-
-        if uid >= next_uid {
-            next_uid = uid + 1;
-        }
-
-        let gid = user.gid.unwrap_or(uid);
-
-        let name = prompt!(user.name, username.clone(), "{}: name [{}]: ", username, username)?;
-        let home = prompt!(user.home, format!("/home/{}", username), "{}: home [/home/{}]: ", username, username)?;
-        let shell = prompt!(user.shell, "/bin/ion".to_string(), "{}: shell [/bin/ion]: ", username)?;
-
-        println!("Adding user {}:", username);
-        println!("\tPassword: {}", password);
-        println!("\tUID: {}", uid);
-        println!("\tGID: {}", gid);
-        println!("\tName: {}", name);
-        println!("\tHome: {}", home);
-        println!("\tShell: {}", shell);
-
-        dir!(home.trim_matches('/'));
-
-        passwd.push_str(&format!("{};{};{};{};{};file:{};file:{}\n", username, password, uid, gid, name, home, shell));
-    }
-    if ! passwd.is_empty() {
-        file!("etc/passwd", passwd.as_bytes(), false);
-    }
-
-    Ok(())
-}
diff --git a/src/lib.rs b/src/lib.rs
index 3fe6a21..6a2c50d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,9 +2,229 @@
 
 #[macro_use]
 extern crate serde_derive;
+extern crate argon2rs;
+extern crate liner;
+extern crate failure;
+extern crate pkgutils;
+extern crate rand;
+extern crate termion;
+
+mod config;
 
 pub use config::Config;
-pub use install::install;
 
-mod config;
-mod install;
+use argon2rs::verifier::Encoded;
+use argon2rs::{Argon2, Variant};
+use failure::{Error, err_msg};
+use rand::{OsRng, Rng};
+use termion::input::TermRead;
+use pkgutils::{Repo, Package};
+
+use std::{env, fs};
+use std::ffi::OsStr;
+use std::io::{self, stderr, Write};
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::fs::symlink;
+use std::path::Path;
+use std::process::{self, Command};
+use std::str::FromStr;
+
+type Result<T> = std::result::Result<T, Error>;
+
+const REMOTE: &'static str = "https://static.redox-os.org/pkg";
+const TARGET: &'static str = "x86_64-unknown-redox";
+
+/// Converts a password to a serialized argon2rs hash, understandable
+/// by redox_users. If the password is blank, the hash is blank.
+fn hash_password(password: &str) -> Result<String> {
+    if password != "" {
+        let a2 = Argon2::new(10, 1, 4096, Variant::Argon2i)?;
+        let salt = format!("{:X}", OsRng::new()?.next_u64());
+        let enc = Encoded::new(
+            a2,
+            password.as_bytes(),
+            salt.as_bytes(),
+            &[],
+            &[]
+        );
+
+        Ok(String::from_utf8(enc.to_u8())?)
+    } else {
+        Ok("".to_string())
+    }
+}
+
+fn unwrap_or_prompt<T: FromStr>(option: Option<T>, context: &mut liner::Context, prompt: &str) -> Result<T> {
+    match option {
+        Some(t) => Ok(t),
+        None => {
+            let line = context.read_line(prompt, &mut |_| {})?;
+            T::from_str(&line).map_err(|_err| err_msg("failed to parse input"))
+        }
+    }
+}
+
+/// Returns a password collected from the user (plaintext)
+fn prompt_password(prompt: &str, confirm_prompt: &str) -> Result<String> {
+    let stdin = io::stdin();
+    let mut stdin = stdin.lock();
+    let stdout = io::stdout();
+    let mut stdout = stdout.lock();
+
+    print!("{}", prompt);
+    let password = stdin.read_passwd(&mut stdout)?;
+
+    print!("\n{}", confirm_prompt);
+    let confirm_password = stdin.read_passwd(&mut stdout)?;
+    // TODO: Remove this debug msg
+    println!("\nPass: {:?}; ConfPass: {:?};", password, confirm_password);
+
+    // Note: Actually comparing two Option<String> values
+    if confirm_password == password {
+        Ok(password.unwrap_or("".to_string()))
+    } else {
+        Err(err_msg("passwords do not match"))
+    }
+}
+
+fn install_packages<S: AsRef<str>>(config: &Config, dest: &str, cookbook: Option<S>) {
+    let mut repo = Repo::new(TARGET);
+    repo.add_remote(REMOTE);
+
+    if let Some(cookbook) = cookbook {
+        let status = Command::new("./repo.sh")
+            .current_dir(cookbook.as_ref())
+            .args(config.packages.keys())
+            .spawn()
+            .unwrap()
+            .wait()
+            .unwrap();
+
+        if !status.success() {
+            write!(stderr(), "./repo.sh failed.").unwrap();
+            process::exit(1);
+        }
+
+        for (packagename, _package) in &config.packages {
+            println!("Installing package {}", packagename);
+            let path = format!("{}/{}/repo/{}/{}.tar.gz",
+                               env::current_dir().unwrap().to_string_lossy(),
+                               cookbook.as_ref(), TARGET, packagename);
+            Package::from_path(&path).unwrap().install(dest).unwrap();
+        }
+    } else {
+        for (packagename, _package) in &config.packages {
+            println!("Installing package {}", packagename);
+            repo.fetch(&packagename).unwrap().install(dest).unwrap();
+        }
+    }
+}
+
+pub fn install<P: AsRef<Path>, S: AsRef<str>>(config: Config, output: P, cookbook: Option<S>) -> Result<()> {
+    let output = output.as_ref();
+    println!("Install {:#?} to {}", config, output.display());
+
+    let mut context = liner::Context::new();
+
+    macro_rules! prompt {
+        ($dst:expr, $def:expr, $($arg:tt)*) => (if config.general.prompt {
+            match unwrap_or_prompt($dst, &mut context, &format!($($arg)*)) {
+                Ok(res) => if res.is_empty() {
+                    Ok($def)
+                } else {
+                    Ok(res)
+                },
+                Err(err) => Err(err)
+            }
+        } else {
+            Ok($dst.unwrap_or($def))
+        })
+    }
+
+    // TODO: Mount disk if output is a file
+    let sysroot = output.to_owned();
+
+    macro_rules! dir {
+        ($path:expr) => {{
+            let mut path = sysroot.clone();
+            path.push($path);
+            println!("Create directory {}", path.display());
+            fs::create_dir_all(&path)?;
+        }};
+    }
+
+    macro_rules! file {
+        ($path:expr, $data:expr, $symlink:expr) => {{
+            let mut path = sysroot.clone();
+            path.push($path);
+            if let Some(parent) = path.parent() {
+                println!("Create file parent {}", parent.display());
+                fs::create_dir_all(parent)?;
+            }
+            if $symlink {
+                println!("Create symlink {}", path.display());
+                symlink(&OsStr::from_bytes($data), &path)?;
+            } else {
+                println!("Create file {}", path.display());
+                let mut file = fs::File::create(&path)?;
+                file.write_all($data)?;
+            }
+        }};
+    }
+
+    dir!("");
+
+    install_packages(&config, sysroot.to_str().unwrap(), cookbook);
+
+    for file in config.files {
+        file!(file.path.trim_matches('/'), file.data.as_bytes(), file.symlink);
+    }
+
+    let mut passwd = String::new();
+    let mut next_uid = 1000;
+    
+    for (username, user) in config.users {
+        // plaintext
+        let password = if let Some(password) = user.password {
+            password
+        } else if config.general.prompt {
+            prompt_password(
+                &format!("{}: enter password: ", username),
+                &format!("{}: confirm password: ", username))?
+        } else {
+            String::new()
+        };
+
+        let uid = user.uid.unwrap_or(next_uid);
+
+        if uid >= next_uid {
+            next_uid = uid + 1;
+        }
+
+        let gid = user.gid.unwrap_or(uid);
+
+        let name = prompt!(user.name, username.clone(), "{}: name [{}]: ", username, username)?;
+        let home = prompt!(user.home, format!("/home/{}", username), "{}: home [/home/{}]: ", username, username)?;
+        let shell = prompt!(user.shell, "/bin/ion".to_string(), "{}: shell [/bin/ion]: ", username)?;
+
+        println!("Adding user {}:", username);
+        println!("\tPassword: {}", password);
+        println!("\tUID: {}", uid);
+        println!("\tGID: {}", gid);
+        println!("\tName: {}", name);
+        println!("\tHome: {}", home);
+        println!("\tShell: {}", shell);
+
+        dir!(home.trim_matches('/'));
+        
+        let password = hash_password(&password)?;
+        
+        passwd.push_str(&format!("{};{};{};{};{};file:{};file:{}\n", username, password, uid, gid, name, home, shell));
+    }
+    
+    if ! passwd.is_empty() {
+        file!("etc/passwd", passwd.as_bytes(), false);
+    }
+
+    Ok(())
+}
-- 
GitLab