install.rs 7.65 KB
Newer Older
Jeremy Soller's avatar
Jeremy Soller committed
1
extern crate liner;
2
extern crate pkgutils;
Jeremy Soller's avatar
Jeremy Soller committed
3 4
extern crate rand;
extern crate termion;
Ivan Chebykin's avatar
Ivan Chebykin committed
5
extern crate redox_users;
Jeremy Soller's avatar
Jeremy Soller committed
6 7 8

use self::rand::Rng;
use self::termion::input::TermRead;
9
use self::pkgutils::{Repo, Package};
Jeremy Soller's avatar
Jeremy Soller committed
10

Jeremy Soller's avatar
Jeremy Soller committed
11
use std::{env, fs};
12
use std::ffi::OsStr;
13
use std::io::{self, stderr, Write};
14
use std::os::unix::ffi::OsStrExt;
15 16 17 18
use std::os::unix::fs::symlink;
use std::path::Path;
use std::process::{self, Command};
use std::str::FromStr;
Jeremy Soller's avatar
Jeremy Soller committed
19 20 21

use config::Config;

22 23 24
const REMOTE: &'static str = "https://static.redox-os.org/pkg";
const TARGET: &'static str = "x86_64-unknown-redox";

Jeremy Soller's avatar
Jeremy Soller committed
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
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());
Ivan Chebykin's avatar
Ivan Chebykin committed
58
                    Ok(redox_users::User::encode_passwd(&password, &salt))
Jeremy Soller's avatar
Jeremy Soller committed
59 60 61 62 63 64 65 66 67 68 69 70
                } else {
                    Err("passwords do not match".to_string())
                }
            } else {
                Err("passwords do not match".to_string())
            }
        }
    } else {
        Ok(String::new())
    }
}

Jeremy Soller's avatar
Jeremy Soller committed
71
fn install_packages<S: AsRef<str>>(config: &Config, dest: &str, cookbook: Option<S>) {
72 73 74
    let mut repo = Repo::new(TARGET);
    repo.add_remote(REMOTE);

75
    if let Some(cookbook) = cookbook {
76
        let status = Command::new("./repo.sh")
Jeremy Soller's avatar
Jeremy Soller committed
77
            .current_dir(cookbook.as_ref())
78 79 80 81 82 83 84
            .args(config.packages.keys())
            .spawn()
            .unwrap()
            .wait()
            .unwrap();

        if !status.success() {
85
            write!(stderr(), "./repo.sh failed.").unwrap();
86 87 88 89
            process::exit(1);
        }

        for (packagename, _package) in &config.packages {
90
            println!("Installing package {}", packagename);
Jeremy Soller's avatar
Jeremy Soller committed
91
            let path = format!("{}/{}/repo/{}/{}.tar.gz",
92
                               env::current_dir().unwrap().to_string_lossy(),
Jeremy Soller's avatar
Jeremy Soller committed
93
                               cookbook.as_ref(), TARGET, packagename);
94
            Package::from_path(&path).unwrap().install(dest).unwrap();
95 96 97 98
        }
    } else {
        for (packagename, _package) in &config.packages {
            println!("Installing package {}", packagename);
99
            repo.fetch(&packagename).unwrap().install(dest).unwrap();
100
        }
101 102 103
    }
}

Jeremy Soller's avatar
Jeremy Soller committed
104
pub fn install<P: AsRef<Path>, S: AsRef<str>>(config: Config, output: P, cookbook: Option<S>) -> Result<(), String> {
105 106 107
    let output = output.as_ref();

    println!("Install {:#?} to {}", config, output.display());
Jeremy Soller's avatar
Jeremy Soller committed
108 109 110 111 112 113 114 115 116 117 118 119 120 121

    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 {
122
            Ok($dst.unwrap_or($def))
Jeremy Soller's avatar
Jeremy Soller committed
123 124 125
        })
    }

126 127
    // TODO: Mount disk if output is a file
    let sysroot = output.to_owned();
Jeremy Soller's avatar
Jeremy Soller committed
128

Jeremy Soller's avatar
Jeremy Soller committed
129 130 131 132 133
    macro_rules! dir {
        ($path:expr) => {{
            let mut path = sysroot.clone();
            path.push($path);
            println!("Create directory {}", path.display());
Jeremy Soller's avatar
Jeremy Soller committed
134
            fs::create_dir_all(&path).map_err(|err| format!("failed to create {}: {}", path.display(), err))?;
Jeremy Soller's avatar
Jeremy Soller committed
135 136 137 138
        }};
    }

    macro_rules! file {
139
        ($path:expr, $data:expr, $symlink:expr) => {{
Jeremy Soller's avatar
Jeremy Soller committed
140 141
            let mut path = sysroot.clone();
            path.push($path);
142 143 144 145
            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))?;
            }
146 147 148 149 150 151 152 153
            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))?;
            }
Jeremy Soller's avatar
Jeremy Soller committed
154 155 156 157
        }};
    }

    dir!("");
Jeremy Soller's avatar
Jeremy Soller committed
158

159
    install_packages(&config, sysroot.to_str().unwrap(), cookbook);
Jeremy Soller's avatar
Jeremy Soller committed
160

161
    for file in config.files {
162
        file!(file.path.trim_matches('/'), file.data.as_bytes(), file.symlink);
163
    }
Jeremy Soller's avatar
Jeremy Soller committed
164

165
    let mut passwd = String::new();
166
    let mut shadow = String::new();
Jeremy Soller's avatar
Jeremy Soller committed
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
    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);

Jeremy Soller's avatar
Jeremy Soller committed
185
        let name = prompt!(user.name, username.clone(), "{}: name [{}]: ", username, username)?;
186 187
        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)?;
Jeremy Soller's avatar
Jeremy Soller committed
188

Jeremy Soller's avatar
Jeremy Soller committed
189
        println!("Adding user {}:", username);
Jeremy Soller's avatar
Jeremy Soller committed
190 191 192 193 194 195 196
        println!("\tPassword: {}", password);
        println!("\tUID: {}", uid);
        println!("\tGID: {}", gid);
        println!("\tName: {}", name);
        println!("\tHome: {}", home);
        println!("\tShell: {}", shell);

Jeremy Soller's avatar
Jeremy Soller committed
197 198
        dir!(home.trim_matches('/'));

199 200
        passwd.push_str(&format!("{};{};{};{};file:{};file:{}\n", username, uid, gid, name, home, shell));
        shadow.push_str(&format!("{};{}\n", username, password));
Jeremy Soller's avatar
Jeremy Soller committed
201
    }
202 203
    
    if !passwd.is_empty() {
204
        file!("etc/passwd", passwd.as_bytes(), false);
205
    }
206 207 208 209 210
    
    if !shadow.is_empty() {
        file!("etc/shadow", shadow.as_bytes(), false);
    }
    
Jeremy Soller's avatar
Jeremy Soller committed
211 212
    Ok(())
}