Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • redox-os/installer
  • Sag0Sag0/installer
  • microcolonel/installer
  • mich/installer
  • potatogim/installer
  • bruceadams/installer
  • rw_van/installer
  • carrot93/installer
  • Ivan/installer
  • josh_williams/installer
  • bjorn3/installer
  • freewilll/installer
  • Chocimier/installer
  • andypython/installer
  • 4lDO2/installer
  • amyipdev/installer
  • coolreader18/installer
  • adi-g15/installer
  • enygmator/installer
  • josh/installer
20 results
Show changes
Commits on Source (167)
pkg pkg
sysroot sysroot
/target/ /target/
/test.bin
This diff is collapsed.
[package] [package]
name = "redox_installer" name = "redox_installer"
version = "0.2.0" version = "0.2.31"
description = "A Redox filesystem builder"
license = "MIT"
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
repository = "https://gitlab.redox-os.org/redox-os/installer"
default-run = "redox_installer"
edition = "2021"
[[bin]] [[bin]]
name = "redox_installer" name = "redox_installer"
path = "src/bin/installer.rs" path = "src/bin/installer.rs"
[[bin]]
name = "redox_installer_tui"
path = "src/bin/installer_tui.rs"
[[bin]]
name = "list_packages"
path = "src/bin/list_packages.rs"
[lib] [lib]
name = "redox_installer" name = "redox_installer"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
arg_parser = { git = "https://gitlab.redox-os.org/redox-os/arg-parser.git" } anyhow = "1.0.89"
argon2rs = { version = "0.2", default-features = false } arg_parser = "0.1.0"
liner = "0.1" cc = "1"
libc = { git = "https://gitlab.redox-os.org/redox-os/liblibc.git", branch = "redox" } fatfs = "0.3.0"
failure = "0.1" fscommon = "0.1.1"
pkgutils = { git = "https://gitlab.redox-os.org/redox-os/pkgutils.git" } gpt = "3.0.0"
rand = "0.3" libc = "0.2.70"
redoxfs = "0.3" pkgar = "0.1.16"
serde = "0.8" pkgar-core = "0.1.16"
serde_derive = "0.8" pkgar-keys = "0.1.16"
termion = "1.5.1" rand = "0.8"
toml = { version = "0.2", default-features = false, features = ["serde"] } redox_liner = "0.5"
redox-pkg = { version = "0.2.1", features = ["indicatif"] }
redox_syscall = "0.5.2"
redoxfs = "0.6.10"
rust-argon2 = "0.8.2"
serde = "=1.0.197"
serde_derive = "1.0.110"
termion = "4"
toml = "0.8"
uuid = { version = "1.4", features = ["v4"] }
[patch.crates-io]
cc-11 = { git = "https://github.com/tea/cc-rs", branch="riscv-abi-arch-fix", package = "cc" }
# https://github.com/briansmith/ring/issues/1999
ring = { git = "https://gitlab.redox-os.org/redox-os/ring.git", branch = "redox-0.17.8" }
...@@ -69,6 +69,10 @@ home = "/root" ...@@ -69,6 +69,10 @@ home = "/root"
# Password is unset # Password is unset
password = "" password = ""
[groups.sudo]
gid = 1
members = ["user"]
[[files]] [[files]]
path = "/etc/init.d/00_base" path = "/etc/init.d/00_base"
data = """ data = """
...@@ -91,13 +95,13 @@ dhcpd -b ...@@ -91,13 +95,13 @@ dhcpd -b
[[files]] [[files]]
path = "/etc/init.d/20_orbital" path = "/etc/init.d/20_orbital"
data = """ data = """
orbital display:3/activate orblogin launcher orbital orblogin launcher
""" """
[[files]] [[files]]
path = "/etc/init.d/30_console" path = "/etc/init.d/30_console"
data = """ data = """
getty display:2 getty display/vesa:2
getty debug: -J getty debug: -J
""" """
...@@ -135,19 +139,9 @@ data = """ ...@@ -135,19 +139,9 @@ data = """
path = "/etc/pkg.d/50_redox" path = "/etc/pkg.d/50_redox"
data = "https://static.redox-os.org/pkg" data = "https://static.redox-os.org/pkg"
[[files]]
path = "/etc/group"
data = """
root;0;root
user;1000;user
sudo;1;user
"""
[[files]] [[files]]
path = "/etc/hostname" path = "/etc/hostname"
data = """ data = "redox"
redox
"""
[[files]] [[files]]
path = "/etc/issue" path = "/etc/issue"
......
# Automatically generated by update.sh
include = []
[general]
prompt = false
filesystem_size = 256
[packages.bootloader]
[packages.bootstrap]
[packages.ca-certificates]
[packages.coreutils]
[packages.drivers]
[packages.escalated]
[packages.extrautils]
[packages.findutils]
[packages.initfs]
[packages.ion]
[packages.ipcd]
[packages.kernel]
[packages.netdb]
[packages.netstack]
[packages.netutils]
[packages.pkgutils]
[packages.ptyd]
[packages.smith]
[packages.userutils]
[packages.uutils]
[[files]]
path = "/usr/lib/init.d/00_base"
data = """
# clear and recreate tmpdir with 0o1777 permission
rm -r /tmp
mkdir -m a=rwxt /tmp
ipcd
ptyd
escalated
"""
symlink = false
directory = false
recursive_chown = false
[[files]]
path = "/usr/lib/init.d/00_drivers"
data = """
pcid /etc/pcid.d/
"""
symlink = false
directory = false
recursive_chown = false
[[files]]
path = "/etc/hostname"
data = """
redox
"""
symlink = false
directory = false
recursive_chown = false
[[files]]
path = "/usr/lib/os-release"
data = '''
PRETTY_NAME="Redox OS 0.9.0"
NAME="Redox OS"
VERSION_ID="0.9.0"
VERSION="0.9.0"
ID="redox-os"
HOME_URL="https://redox-os.org/"
DOCUMENTATION_URL="https://redox-os.org/docs/"
SUPPORT_URL="https://redox-os.org/community/"
'''
symlink = false
directory = false
recursive_chown = false
[[files]]
path = "/etc/os-release"
data = "../usr/lib/os-release"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/usr"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
[[files]]
path = "/usr/bin"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
[[files]]
path = "/bin"
data = "usr/bin"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/usr/include"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
[[files]]
path = "/include"
data = "usr/include"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/usr/lib"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
[[files]]
path = "/lib"
data = "usr/lib"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/usr/libexec"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
[[files]]
path = "/libexec"
data = "usr/libexec"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/usr/share"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
[[files]]
path = "/share"
data = "usr/share"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/dev/null"
data = "/scheme/null"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/dev/random"
data = "/scheme/rand"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/dev/urandom"
data = "/scheme/rand"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/dev/zero"
data = "/scheme/zero"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/dev/tty"
data = "libc:tty"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/dev/stdin"
data = "libc:stdin"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/dev/stdout"
data = "libc:stdout"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/dev/stderr"
data = "libc:stderr"
symlink = true
directory = false
recursive_chown = false
[[files]]
path = "/usr/lib/init.d/10_net"
data = """
smolnetd
dnsd
dhcpd -b
"""
symlink = false
directory = false
recursive_chown = false
[[files]]
path = "/etc/net/dns"
data = """
208.67.222.222
"""
symlink = false
directory = false
recursive_chown = false
[[files]]
path = "/etc/net/ip"
data = """
10.0.2.15
"""
symlink = false
directory = false
recursive_chown = false
[[files]]
path = "/etc/net/ip_router"
data = """
10.0.2.2
"""
symlink = false
directory = false
recursive_chown = false
[[files]]
path = "/etc/net/ip_subnet"
data = """
255.255.255.0
"""
symlink = false
directory = false
recursive_chown = false
[[files]]
path = "/usr/lib/init.d/30_console"
data = """
inputd -A 2
getty 2
getty /scheme/debug -J
"""
symlink = false
directory = false
recursive_chown = false
[[files]]
path = "/etc/pkg.d/50_redox"
data = "https://static.redox-os.org/pkg"
symlink = false
directory = false
recursive_chown = false
[users.root]
password = "password"
uid = 0
gid = 0
name = "root"
home = "/root"
shell = "/usr/bin/ion"
[users.user]
password = ""
shell = "/usr/bin/ion"
[groups.sudo]
gid = 1
members = ["user"]
#!/usr/bin/env bash
set -e
RES_PATH="$(dirname "$0")"
if [ -d "$1" ]
then
REDOX_PATH="$1"
else
echo "$0 [path to redox repository]" >&2
exit 1
fi
set -x
# Update res/test.toml from the redoxer.toml template
cargo run --release --manifest-path "${RES_PATH}/../Cargo.toml" -- \
--config="${REDOX_PATH}/config/x86_64/minimal-net.toml" \
--output-config="${RES_PATH}/test.toml"
sed -i '1s/^/# Automatically generated by update.sh\n\n/' "${RES_PATH}/test.toml"
#![deny(warnings)]
extern crate arg_parser; extern crate arg_parser;
extern crate redox_installer; extern crate redox_installer;
extern crate serde; extern crate serde;
extern crate toml; extern crate toml;
use std::{env, io, process}; use std::io::Write;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path; use std::path::Path;
use std::{env, fs, io, process};
use arg_parser::ArgParser; use arg_parser::ArgParser;
use redox_installer::{Config, PackageConfig};
fn main() { fn main() {
let stderr = io::stderr(); let stderr = io::stderr();
let mut stderr = stderr.lock(); let mut stderr = stderr.lock();
let mut parser = ArgParser::new(3) let mut parser = ArgParser::new(4)
.add_opt("b", "cookbook") .add_opt("b", "cookbook")
.add_opt("c", "config") .add_opt("c", "config")
.add_flag(&["l", "list-packages"]); .add_opt("o", "output-config")
.add_flag(&["filesystem-size"])
.add_flag(&["r", "repo-binary"])
.add_flag(&["l", "list-packages"])
.add_flag(&["live"]);
parser.parse(env::args()); parser.parse(env::args());
let config = if let Some(path) = parser.get_opt("config") { // Use pre-built binaries for packages as the default.
match File::open(&path) { // If not set on the command line or the filesystem config, then build packages from source.
Ok(mut config_file) => { let repo_binary = parser.found("repo-binary");
let mut config_data = String::new();
match config_file.read_to_string(&mut config_data) { let mut config = if let Some(path) = parser.get_opt("config") {
Ok(_) => { match Config::from_file(Path::new(&path)) {
let mut parser = toml::Parser::new(&config_data); Ok(config) => config,
match parser.parse() {
Some(parsed) => {
let mut decoder = toml::Decoder::new(toml::Value::Table(parsed));
match serde::Deserialize::deserialize(&mut decoder) {
Ok(config) => {
config
},
Err(err) => {
writeln!(stderr, "installer: {}: failed to decode: {}", path, err).unwrap();
process::exit(1);
}
}
},
None => {
for error in parser.errors {
writeln!(stderr, "installer: {}: failed to parse: {}", path, error).unwrap();
}
process::exit(1);
}
}
},
Err(err) => {
writeln!(stderr, "installer: {}: failed to read: {}", path, err).unwrap();
process::exit(1);
}
}
},
Err(err) => { Err(err) => {
writeln!(stderr, "installer: {}: failed to open: {}", path, err).unwrap(); writeln!(stderr, "installer: {err}").unwrap();
process::exit(1); process::exit(1);
} }
} }
...@@ -65,24 +41,111 @@ fn main() { ...@@ -65,24 +41,111 @@ fn main() {
redox_installer::Config::default() redox_installer::Config::default()
}; };
let cookbook = if let Some(path) = parser.get_opt("cookbook") { // Get toml of merged config
if ! Path::new(&path).is_dir() { let merged_toml = toml::to_string_pretty(&config).unwrap();
writeln!(stderr, "installer: {}: cookbook not found", path).unwrap();
process::exit(1);
}
Some(path) // Just output merged config and exit
} else { if let Some(path) = parser.get_opt("output-config") {
None fs::write(path, merged_toml).unwrap();
}; return;
}
// Add filesystem.toml to config
config.files.push(redox_installer::FileConfig {
path: "filesystem.toml".to_string(),
data: merged_toml,
..Default::default()
});
if parser.found("list-packages") { // Add command line flags to config, command line takes priority
for (packagename, _package) in &config.packages { if repo_binary {
println!("{}", packagename); config.general.repo_binary = Some(true);
}
if parser.found("filesystem-size") {
println!("{}", config.general.filesystem_size.unwrap_or(0));
} else if parser.found("list-packages") {
// List the packages that should be fetched or built by the cookbook
for (packagename, package) in &config.packages {
match package {
PackageConfig::Build(rule) if rule == "recipe" || rule == "source" => {
println!("{}", packagename);
}
PackageConfig::Build(rule) if rule == "binary" => {
// skip this package
}
_ => {
if config.general.repo_binary == Some(true) {
// default action is to not build this package, skip it
} else {
// default action is to build
println!("{}", packagename);
}
}
}
} }
} else { } else {
let cookbook = if let Some(path) = parser.get_opt("cookbook") {
if !Path::new(&path).is_dir() {
writeln!(stderr, "installer: {}: cookbook not found", path).unwrap();
process::exit(1);
}
// Add cookbook key to config
let key_path = Path::new(&path).join("build/id_ed25519.pub.toml");
match fs::read_to_string(&key_path) {
Ok(data) => {
config.files.push(redox_installer::FileConfig {
path: "pkg/id_ed25519.pub.toml".to_string(),
data: data,
..Default::default()
});
Some(path)
}
Err(err) => {
// if there are no recipes coming from the cookbook, this is not a fatal error
if config
.packages
.clone()
.into_iter()
.any(|(_packagename, package)| match package {
PackageConfig::Empty => false,
PackageConfig::Spec {
version: None,
git: None,
path: None,
} => false,
_ => true,
})
{
writeln!(
stderr,
"installer: {}: failed to read cookbook key: {}",
key_path.display(),
err
)
.unwrap();
process::exit(1);
} else {
writeln!(
stderr,
"installer: {}: (non-fatal) missing cookbook key: {}",
key_path.display(),
err
)
.unwrap();
None
}
}
}
} else {
None
};
if let Some(path) = parser.args.get(0) { if let Some(path) = parser.args.get(0) {
if let Err(err) = redox_installer::install(config, path, cookbook) { if let Err(err) =
redox_installer::install(config, path, cookbook.as_deref(), parser.found("live"))
{
writeln!(stderr, "installer: failed to install: {}", err).unwrap(); writeln!(stderr, "installer: failed to install: {}", err).unwrap();
process::exit(1); process::exit(1);
} }
......
use anyhow::{anyhow, bail, Result};
use pkgar::{ext::EntryExt, PackageHead};
use pkgar_core::PackageSrc;
use pkgar_keys::PublicKeyFile;
use redox_installer::{with_whole_disk, Config, DiskOption};
use std::{
ffi::OsStr,
fs,
io::{self, Read, Write},
os::unix::fs::{symlink, MetadataExt, OpenOptionsExt},
path::{Path, PathBuf},
process,
};
use termion::input::TermRead;
#[cfg(not(target_os = "redox"))]
fn disk_paths(_paths: &mut Vec<(PathBuf, u64)>) {}
#[cfg(target_os = "redox")]
fn disk_paths(paths: &mut Vec<(PathBuf, u64)>) {
let mut schemes = Vec::new();
match fs::read_dir("/scheme") {
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Ok(file_name) = entry.file_name().into_string() {
if file_name.starts_with("disk") {
schemes.push(entry.path());
}
}
}
}
}
Err(err) => {
eprintln!("installer_tui: failed to list schemes: {}", err);
}
}
for scheme in schemes {
if scheme.is_dir() {
match fs::read_dir(&scheme) {
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Ok(file_name) = entry.file_name().into_string() {
if file_name.contains('p') {
// Skip partitions
continue;
}
if let Ok(metadata) = entry.metadata() {
let size = metadata.len();
if size > 0 {
paths.push((entry.path(), size));
}
}
}
}
}
}
Err(err) => {
eprintln!("installer_tui: failed to list '{}': {}", scheme.display(), err);
}
}
}
}
}
const KIB: u64 = 1024;
const MIB: u64 = 1024 * KIB;
const GIB: u64 = 1024 * MIB;
const TIB: u64 = 1024 * GIB;
fn format_size(size: u64) -> String {
if size >= 4 * TIB {
format!("{:.1} TiB", size as f64 / TIB as f64)
} else if size >= GIB {
format!("{:.1} GiB", size as f64 / GIB as f64)
} else if size >= MIB {
format!("{:.1} MiB", size as f64 / MIB as f64)
} else if size >= KIB {
format!("{:.1} KiB", size as f64 / KIB as f64)
} else {
format!("{} B", size)
}
}
fn copy_file(src: &Path, dest: &Path, buf: &mut [u8]) -> Result<()> {
if let Some(parent) = dest.parent() {
// Parent may be a symlink
if !parent.is_symlink() {
match fs::create_dir_all(&parent) {
Ok(()) => (),
Err(err) => {
bail!("failed to create directory {}: {}", parent.display(), err);
}
}
}
}
let metadata = match fs::symlink_metadata(&src) {
Ok(ok) => ok,
Err(err) => {
bail!("failed to read metadata of {}: {}", src.display(), err);
}
};
if metadata.file_type().is_symlink() {
let real_src = match fs::read_link(&src) {
Ok(ok) => ok,
Err(err) => {
bail!("failed to read link {}: {}", src.display(), err);
}
};
match symlink(&real_src, &dest) {
Ok(()) => (),
Err(err) => {
bail!(
"failed to copy link {} ({}) to {}: {}",
src.display(),
real_src.display(),
dest.display(),
err
);
}
}
} else {
let mut src_file = match fs::File::open(&src) {
Ok(ok) => ok,
Err(err) => {
bail!("failed to open file {}: {}", src.display(), err);
}
};
let mut dest_file = match fs::OpenOptions::new()
.write(true)
.create_new(true)
.mode(metadata.mode())
.open(&dest)
{
Ok(ok) => ok,
Err(err) => {
bail!("failed to create file {}: {}", dest.display(), err);
}
};
loop {
let count = match src_file.read(buf) {
Ok(ok) => ok,
Err(err) => {
bail!("failed to read file {}: {}", src.display(), err);
}
};
if count == 0 {
break;
}
match dest_file.write_all(&buf[..count]) {
Ok(()) => (),
Err(err) => {
bail!("failed to write file {}: {}", dest.display(), err);
}
}
}
}
Ok(())
}
fn package_files(
root_path: &Path,
config: &mut Config,
files: &mut Vec<String>,
) -> Result<(), pkgar::Error> {
//TODO: Remove packages from config where all files are located (and have valid shasum?)
config.packages.clear();
let pkey_path = "pkg/id_ed25519.pub.toml";
let pkey = PublicKeyFile::open(&root_path.join(pkey_path))?.pkey;
files.push(pkey_path.to_string());
for item_res in fs::read_dir(&root_path.join("pkg"))? {
let item = item_res?;
let pkg_path = item.path();
if pkg_path.extension() == Some(OsStr::new("pkgar_head")) {
let mut pkg = PackageHead::new(&pkg_path, &root_path, &pkey)?;
for entry in pkg.read_entries()? {
files.push(entry.check_path()?.to_str().unwrap().to_string());
}
files.push(
pkg_path
.strip_prefix(root_path)
.unwrap()
.to_str()
.unwrap()
.to_string(),
);
}
}
Ok(())
}
fn choose_disk() -> PathBuf {
let mut paths = Vec::new();
disk_paths(&mut paths);
loop {
for (i, (path, size)) in paths.iter().enumerate() {
eprintln!("\x1B[1m{}\x1B[0m: {}: {}", i + 1, path.display(), format_size(*size));
}
if paths.is_empty() {
eprintln!("installer_tui: no drives found");
process::exit(1);
} else {
eprint!("Select a drive from 1 to {}: ", paths.len());
let mut line = String::new();
match io::stdin().read_line(&mut line) {
Ok(0) => {
eprintln!("installer_tui: failed to read line: end of input");
process::exit(1);
}
Ok(_) => (),
Err(err) => {
eprintln!("installer_tui: failed to read line: {}", err);
process::exit(1);
}
}
match line.trim().parse::<usize>() {
Ok(i) => {
if i >= 1 && i <= paths.len() {
break paths[i - 1].0.clone();
} else {
eprintln!("{} not from 1 to {}", i, paths.len());
}
}
Err(err) => {
eprintln!("invalid input: {}", err);
}
}
}
}
}
fn choose_password() -> Option<String> {
eprint!("installer_tui: redoxfs password (empty for none): ");
let password = io::stdin()
.read_passwd(&mut io::stderr())
.unwrap()
.unwrap_or(String::new());
eprintln!();
if password.is_empty() {
return None;
}
Some(password)
}
fn main() {
let root_path = Path::new("/");
let disk_path = choose_disk();
let password_opt = choose_password();
let bootloader_bios = {
let path = root_path.join("boot").join("bootloader.bios");
if path.exists() {
match fs::read(&path) {
Ok(ok) => ok,
Err(err) => {
eprintln!("installer_tui: {}: failed to read: {}", path.display(), err);
process::exit(1);
}
}
} else {
Vec::new()
}
};
let bootloader_efi = {
let path = root_path.join("boot").join("bootloader.efi");
if path.exists() {
match fs::read(&path) {
Ok(ok) => ok,
Err(err) => {
eprintln!("installer_tui: {}: failed to read: {}", path.display(), err);
process::exit(1);
}
}
} else {
Vec::new()
}
};
let disk_option = DiskOption {
bootloader_bios: &bootloader_bios,
bootloader_efi: &bootloader_efi,
password_opt: password_opt.as_ref().map(|x| x.as_bytes()),
efi_partition_size: None,
skip_partitions: false, // TODO?
};
let res = with_whole_disk(&disk_path, &disk_option, |mount_path| -> Result<()> {
let mut config: Config = Config::from_file(&root_path.join("filesystem.toml"))?;
// Copy filesystem.toml, which is not packaged
let mut files = vec!["filesystem.toml".to_string()];
// Copy files from locally installed packages
package_files(&root_path, &mut config, &mut files)
// TODO: implement Error trait
.map_err(|err| anyhow!("failed to read package files: {err}"))?;
// Perform config install (after packages have been converted to files)
eprintln!("configuring system");
let cookbook: Option<&'static str> = None;
redox_installer::install_dir(config, mount_path, cookbook)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
// Sort and remove duplicates
files.sort();
files.dedup();
// Install files
let mut buf = vec![0; 4 * MIB as usize];
for (i, name) in files.iter().enumerate() {
eprintln!("copy {} [{}/{}]", name, i, files.len());
let src = root_path.join(name);
let dest = mount_path.join(name);
copy_file(&src, &dest, &mut buf)?;
}
eprintln!("finished installing, unmounting filesystem");
Ok(())
});
match res {
Ok(()) => {
eprintln!("installer_tui: installed successfully");
process::exit(0);
}
Err(err) => {
eprintln!("installer_tui: failed to install: {}", err);
process::exit(1);
}
}
}
/// List packages for compilation, skip binary packages to be downloaded
extern crate arg_parser;
extern crate redox_installer;
extern crate serde;
extern crate toml;
use std::io::Write;
use std::path::Path;
use std::{env, io, process};
use arg_parser::ArgParser;
use redox_installer::{Config, PackageConfig};
fn main() {
let stderr = io::stderr();
let mut stderr = stderr.lock();
let mut parser = ArgParser::new(4)
.add_opt("c", "config")
.add_flag(&["r", "repo-binary"]);
parser.parse(env::args());
// Use pre-built binaries for packages as the default.
// If not set on the command line or the filesystem config, then build packages from source.
let repo_binary = parser.found("repo-binary");
let mut config = if let Some(path) = parser.get_opt("config") {
match Config::from_file(Path::new(&path)) {
Ok(config) => config,
Err(err) => {
writeln!(stderr, "installer: {err}").unwrap();
process::exit(1);
}
}
} else {
redox_installer::Config::default()
};
// Get toml of merged config
let merged_toml = toml::to_string_pretty(&config).unwrap();
// Add filesystem.toml to config
config.files.push(redox_installer::FileConfig {
path: "filesystem.toml".to_string(),
data: merged_toml,
..Default::default()
});
// Add command line flags to config, command line takes priority
if repo_binary {
config.general.repo_binary = Some(true);
}
// List the packages that should be fetched or built by the cookbook
for (packagename, package) in &config.packages {
match package {
PackageConfig::Build(rule) if rule == "recipe" || rule == "source" => {
println!("{}", packagename);
}
PackageConfig::Build(rule) if rule == "binary" => {
// skip this package
}
_ => {
if config.general.repo_binary == Some(true) {
// default action is to not build this package, skip it
} else {
// default action is to build
println!("{}", packagename);
}
}
}
}
}
use crate::Result;
use libc::{gid_t, uid_t};
use Result;
use libc::{chown, gid_t, uid_t};
use std::io::{Error, Write};
use std::ffi::{CString, OsStr}; use std::ffi::{CString, OsStr};
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::{Error, Write};
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{PermissionsExt, symlink}; use std::os::unix::fs::{symlink, PermissionsExt};
use std::path::Path; use std::path::Path;
//type Result<T> = std::result::Result<T, Error>; //type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Default, Deserialize)] fn chown<P: AsRef<Path>>(path: P, uid: uid_t, gid: gid_t, recursive: bool) -> Result<()> {
let path = path.as_ref();
let c_path = CString::new(path.as_os_str().as_bytes()).unwrap();
if unsafe { libc::chown(c_path.as_ptr(), uid, gid) } != 0 {
return Err(Error::last_os_error().into());
}
if recursive && path.is_dir() {
for entry_res in fs::read_dir(path)? {
let entry = entry_res?;
chown(entry.path(), uid, gid, recursive)?;
}
}
Ok(())
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct FileConfig { pub struct FileConfig {
pub path: String, pub path: String,
pub data: String, pub data: String,
...@@ -21,17 +38,17 @@ pub struct FileConfig { ...@@ -21,17 +38,17 @@ pub struct FileConfig {
pub directory: bool, pub directory: bool,
pub mode: Option<u32>, pub mode: Option<u32>,
pub uid: Option<u32>, pub uid: Option<u32>,
pub gid: Option<u32> pub gid: Option<u32>,
#[serde(default)]
pub recursive_chown: bool,
} }
// TODO: Rewrite impls // TODO: Rewrite impls
impl FileConfig { impl FileConfig {
pub(crate) fn create<P: AsRef<Path>>(&self, prefix: P) -> Result<()> {
pub(crate) fn create<P: AsRef<Path>>(self, prefix: P) -> Result<()> { let path = self.path.trim_start_matches('/');
let path = self.path.trim_left_matches('/'); let target_file = prefix.as_ref().join(path);
let target_file = prefix.as_ref()
.join(path);
if self.directory { if self.directory {
println!("Create directory {}", target_file.display()); println!("Create directory {}", target_file.display());
fs::create_dir_all(&target_file)?; fs::create_dir_all(&target_file)?;
...@@ -41,7 +58,7 @@ impl FileConfig { ...@@ -41,7 +58,7 @@ impl FileConfig {
println!("Create file parent {}", parent.display()); println!("Create file parent {}", parent.display());
fs::create_dir_all(parent)?; fs::create_dir_all(parent)?;
} }
if self.symlink { if self.symlink {
println!("Create symlink {}", target_file.display()); println!("Create symlink {}", target_file.display());
symlink(&OsStr::new(&self.data), &target_file)?; symlink(&OsStr::new(&self.data), &target_file)?;
...@@ -50,34 +67,23 @@ impl FileConfig { ...@@ -50,34 +67,23 @@ impl FileConfig {
println!("Create file {}", target_file.display()); println!("Create file {}", target_file.display());
let mut file = File::create(&target_file)?; let mut file = File::create(&target_file)?;
file.write_all(self.data.as_bytes())?; file.write_all(self.data.as_bytes())?;
self.apply_perms(target_file) self.apply_perms(target_file)
} }
} }
fn apply_perms<P: AsRef<Path>>(&self, target: P) -> Result<()> { fn apply_perms<P: AsRef<Path>>(&self, target: P) -> Result<()> {
let path = target.as_ref(); let path = target.as_ref();
let mode = self.mode.unwrap_or_else(|| if self.directory { let mode = self
0o0755 .mode
} else { .unwrap_or_else(|| if self.directory { 0o0755 } else { 0o0644 });
0o0644
});
let uid = self.uid.unwrap_or(!0); let uid = self.uid.unwrap_or(!0);
let gid = self.gid.unwrap_or(!0); let gid = self.gid.unwrap_or(!0);
// chmod // chmod
fs::set_permissions(path, fs::Permissions::from_mode(mode))?; fs::set_permissions(path, fs::Permissions::from_mode(mode))?;
// chown // chown
let c_path = CString::new(path.as_os_str().as_bytes()).unwrap(); chown(path, uid, gid, self.recursive_chown)
let ret = unsafe {
chown(c_path.as_ptr(), uid as uid_t, gid as gid_t)
};
// credit to uutils
if ret == 0 {
Ok(())
} else {
Err(Error::last_os_error().into())
}
} }
} }
#[derive(Debug, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct GeneralConfig { pub struct GeneralConfig {
pub prompt: bool pub prompt: Option<bool>,
// Allow config to specify cookbook recipe or binary package as default
pub repo_binary: Option<bool>,
pub filesystem_size: Option<u32>, //MiB
pub efi_partition_size: Option<u32>, //MiB
pub skip_partitions: Option<bool>,
}
impl GeneralConfig {
pub(super) fn merge(&mut self, other: GeneralConfig) {
self.prompt = other.prompt.or(self.prompt);
self.repo_binary = other.repo_binary.or(self.repo_binary);
self.filesystem_size = other.filesystem_size.or(self.filesystem_size);
self.efi_partition_size = other.efi_partition_size.or(self.efi_partition_size);
self.skip_partitions = other.skip_partitions.or(self.skip_partitions);
}
} }
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fs;
use std::mem;
use std::path::{Path, PathBuf};
mod general; use anyhow::bail;
pub(crate) mod file; use anyhow::Result;
mod package;
mod user;
#[derive(Debug, Default, Deserialize)] pub mod file;
pub mod general;
pub mod package;
pub mod user;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Config { pub struct Config {
#[serde(default)]
pub include: Vec<PathBuf>,
#[serde(default)]
pub general: general::GeneralConfig, pub general: general::GeneralConfig,
#[serde(default)]
pub packages: BTreeMap<String, package::PackageConfig>, pub packages: BTreeMap<String, package::PackageConfig>,
#[serde(default)]
pub files: Vec<file::FileConfig>, pub files: Vec<file::FileConfig>,
#[serde(default)]
pub users: BTreeMap<String, user::UserConfig>, pub users: BTreeMap<String, user::UserConfig>,
#[serde(default)]
pub groups: BTreeMap<String, user::GroupConfig>,
}
impl Config {
pub fn from_file(path: &Path) -> Result<Self> {
let mut config: Config = match fs::read_to_string(&path) {
Ok(config_data) => match toml::from_str(&config_data) {
Ok(config) => config,
Err(err) => {
bail!("{}: failed to decode: {}", path.display(), err);
}
},
Err(err) => {
bail!("{}: failed to read: {}", path.display(), err);
}
};
let config_dir = path.parent().unwrap();
let mut configs = mem::take(&mut config.include)
.into_iter()
.map(|path| Config::from_file(&config_dir.join(path)))
.collect::<Result<Vec<Config>>>()?;
configs.push(config); // Put ourself last to ensure that it overwrites anything else.
config = configs.remove(0);
for other_config in configs {
config.merge(other_config);
}
Ok(config)
}
pub fn merge(&mut self, other: Config) {
assert!(self.include.is_empty());
assert!(other.include.is_empty());
let Config {
include: _,
general: other_general,
packages: other_packages,
files: other_files,
users: other_users,
groups: other_groups,
} = other;
self.general.merge(other_general);
for (package, package_config) in other_packages {
self.packages.insert(package, package_config);
}
self.files.extend(other_files);
for (user, user_config) in other_users {
self.users.insert(user, user_config);
}
for (group, group_config) in other_groups {
self.groups.insert(group, group_config);
}
}
} }
#[derive(Debug, Default, Deserialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PackageConfig { #[serde(untagged)]
pub version: Option<String>, pub enum PackageConfig {
pub git: Option<String>, Empty,
pub path: Option<String>, Build(String),
// TODO: Sum type
Spec {
version: Option<String>,
git: Option<String>,
path: Option<String>,
},
}
impl Default for PackageConfig {
fn default() -> Self {
Self::Empty
}
} }
#[derive(Debug, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct UserConfig { pub struct UserConfig {
pub password: Option<String>, pub password: Option<String>,
pub uid: Option<u32>, pub uid: Option<u32>,
...@@ -7,3 +7,10 @@ pub struct UserConfig { ...@@ -7,3 +7,10 @@ pub struct UserConfig {
pub home: Option<String>, pub home: Option<String>,
pub shell: Option<String>, pub shell: Option<String>,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct GroupConfig {
pub gid: Option<u32>,
// FIXME move this to the UserConfig struct as extra_groups
pub members: Vec<String>,
}
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/env bash
IMAGE=test.bin
QEMU_ARGS=(
-cpu max
-machine q35
-m 2048
-smp 4
-serial mon:stdio
-netdev user,id=net0
-device e1000,netdev=net0
)
if [ -e /dev/kvm ]
then
QEMU_ARGS+=(-accel kvm)
fi
set -ex
cargo build --release
rm -f "${IMAGE}"
fallocate -l 1GiB "${IMAGE}"
target/release/redox_installer -c res/test.toml "${IMAGE}"
qemu-system-x86_64 "${QEMU_ARGS[@]}" -drive "file=${IMAGE},format=raw"