Refactor of error handling to replace 'failure' and to display more context for some errors.
This commit is contained in:
parent
9c77af6eff
commit
c9d36aca59
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -178,7 +178,6 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libcitadel 0.1.0",
|
"libcitadel 0.1.0",
|
||||||
@ -635,7 +634,6 @@ dependencies = [
|
|||||||
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"dbus 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"dbus 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"inotify 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"inotify 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -207,7 +207,7 @@ impl ConfigDialog {
|
|||||||
|
|
||||||
let path = self.realm.base_path_file("config");
|
let path = self.realm.base_path_file("config");
|
||||||
|
|
||||||
if let Err(e) = self.realm.config().write_config(&path) {
|
if let Err(e) = self.realm.config().write_to(&path) {
|
||||||
warn!("Error writing config file {}: {}", path.display(), e);
|
warn!("Error writing config file {}: {}", path.display(), e);
|
||||||
}
|
}
|
||||||
info!("Config file written to {}", path.display());
|
info!("Config file written to {}", path.display());
|
||||||
|
@ -54,19 +54,19 @@ impl TerminalTools {
|
|||||||
let mut t = self.terminal()?;
|
let mut t = self.terminal()?;
|
||||||
let mut palette = TerminalPalette::default();
|
let mut palette = TerminalPalette::default();
|
||||||
palette.load(&mut t)
|
palette.load(&mut t)
|
||||||
.map_err(|e| format_err!("error reading palette colors from terminal: {}", e))?;
|
.map_err(context!("error reading palette colors from terminal"))?;
|
||||||
Ok(palette)
|
Ok(palette)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_palette(&self, palette: &TerminalPalette) -> Result<()> {
|
fn apply_palette(&self, palette: &TerminalPalette) -> Result<()> {
|
||||||
let mut t = self.terminal()?;
|
let mut t = self.terminal()?;
|
||||||
palette.apply(&mut t)
|
palette.apply(&mut t)
|
||||||
.map_err(|e| format_err!("error setting palette on terminal: {}", e))
|
.map_err(context!("error setting palette on terminal"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn terminal(&self) -> Result<AnsiTerminal> {
|
fn terminal(&self) -> Result<AnsiTerminal> {
|
||||||
AnsiTerminal::new()
|
AnsiTerminal::new()
|
||||||
.map_err(|e| format_err!("failed to create AnsiTerminal: {}", e))
|
.map_err(context!("failed to create AnsiTerminal"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_base16_by_slug<S: AsRef<str>>(&self, slug: S) {
|
pub fn apply_base16_by_slug<S: AsRef<str>>(&self, slug: S) {
|
||||||
@ -84,9 +84,9 @@ impl TerminalTools {
|
|||||||
fn apply_base16(&self, scheme: &Base16Scheme) -> Result<()> {
|
fn apply_base16(&self, scheme: &Base16Scheme) -> Result<()> {
|
||||||
let mut t = self.terminal()?;
|
let mut t = self.terminal()?;
|
||||||
t.apply_base16(scheme)
|
t.apply_base16(scheme)
|
||||||
.map_err(|e| format_err!("error setting base16 palette colors: {}", e))?;
|
.map_err(context!("error setting base16 palette colors"))?;
|
||||||
t.clear_screen()
|
t.clear_screen()
|
||||||
.map_err(|e| format_err!("error clearing screen: {}", e))
|
.map_err(context!("error clearing screen"))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -6,7 +6,6 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libcitadel = { path = "../libcitadel" }
|
libcitadel = { path = "../libcitadel" }
|
||||||
failure = "0.1"
|
|
||||||
rpassword = "4.0"
|
rpassword = "4.0"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
use libcitadel::Result;
|
use libcitadel::{Result, util};
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Represents a disk partition device on the system
|
/// Represents a disk partition device on the system
|
||||||
@ -21,12 +20,12 @@ impl DiskPartition {
|
|||||||
|
|
||||||
/// Return list of all vfat partitions on the system as a `Vec<DiskPartition>`
|
/// Return list of all vfat partitions on the system as a `Vec<DiskPartition>`
|
||||||
pub fn boot_partitions(check_guid: bool) -> Result<Vec<DiskPartition>> {
|
pub fn boot_partitions(check_guid: bool) -> Result<Vec<DiskPartition>> {
|
||||||
let pp = fs::read_to_string("/proc/partitions")?;
|
let pp = util::read_to_string("/proc/partitions")?;
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for line in pp.lines().skip(2)
|
for line in pp.lines().skip(2)
|
||||||
{
|
{
|
||||||
let part = DiskPartition::from_proc_line(&line)
|
let part = DiskPartition::from_proc_line(&line)
|
||||||
.map_err(|e| format_err!("Failed to parse line '{}': {}", line, e))?;
|
.map_err(context!("failed to parse line '{}'", line))?;
|
||||||
|
|
||||||
if part.is_boot_partition(check_guid)? {
|
if part.is_boot_partition(check_guid)? {
|
||||||
v.push(part);
|
v.push(part);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::thread::{self,JoinHandle};
|
use std::thread::{self,JoinHandle};
|
||||||
use std::time::{self,Instant};
|
use std::time::{self,Instant};
|
||||||
|
|
||||||
use libcitadel::{Result, UtsName};
|
use libcitadel::{Result, UtsName, util};
|
||||||
use libcitadel::ResourceImage;
|
use libcitadel::ResourceImage;
|
||||||
|
|
||||||
use crate::boot::disks;
|
use crate::boot::disks;
|
||||||
use crate::boot::rootfs::setup_rootfs_resource;
|
use crate::boot::rootfs::setup_rootfs_resource;
|
||||||
use crate::install::installer::Installer;
|
use crate::install::installer::Installer;
|
||||||
@ -36,7 +36,7 @@ fn copy_artifacts() -> Result<()> {
|
|||||||
info!("Failed to find partition with images, trying again in 2 seconds");
|
info!("Failed to find partition with images, trying again in 2 seconds");
|
||||||
thread::sleep(time::Duration::from_secs(2));
|
thread::sleep(time::Duration::from_secs(2));
|
||||||
}
|
}
|
||||||
Err(format_err!("Could not find partition containing resource images"))
|
bail!("could not find partition containing resource images")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,24 +69,23 @@ fn kernel_version() -> String {
|
|||||||
fn deploy_artifacts() -> Result<()> {
|
fn deploy_artifacts() -> Result<()> {
|
||||||
let run_images = Path::new(IMAGE_DIRECTORY);
|
let run_images = Path::new(IMAGE_DIRECTORY);
|
||||||
if !run_images.exists() {
|
if !run_images.exists() {
|
||||||
fs::create_dir_all(run_images)?;
|
util::create_dir(run_images)?;
|
||||||
cmd!("/bin/mount", "-t tmpfs -o size=4g images /run/citadel/images")?;
|
cmd!("/bin/mount", "-t tmpfs -o size=4g images /run/citadel/images")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry in fs::read_dir("/boot/images")? {
|
util::read_directory("/boot/images", |dent| {
|
||||||
let entry = entry?;
|
println!("Copying {:?} from /boot/images to /run/citadel/images", dent.file_name());
|
||||||
println!("Copying {:?} from /boot/images to /run/citadel/images", entry.file_name());
|
util::copy_file(dent.path(), run_images.join(dent.file_name()))
|
||||||
fs::copy(entry.path(), run_images.join(entry.file_name()))?;
|
})?;
|
||||||
}
|
|
||||||
|
|
||||||
let kv = kernel_version();
|
let kv = kernel_version();
|
||||||
println!("Copying bzImage-{} to /run/citadel/images", kv);
|
println!("Copying bzImage-{} to /run/citadel/images", kv);
|
||||||
let from = format!("/boot/bzImage-{}", kv);
|
let from = format!("/boot/bzImage-{}", kv);
|
||||||
let to = format!("/run/citadel/images/bzImage-{}", kv);
|
let to = format!("/run/citadel/images/bzImage-{}", kv);
|
||||||
fs::copy(from, to)?;
|
util::copy_file(&from, &to)?;
|
||||||
|
|
||||||
println!("Copying bootx64.efi to /run/citadel/images");
|
println!("Copying bootx64.efi to /run/citadel/images");
|
||||||
fs::copy("/boot/EFI/BOOT/bootx64.efi", "/run/citadel/images/bootx64.efi")?;
|
util::copy_file("/boot/EFI/BOOT/bootx64.efi", "/run/citadel/images/bootx64.efi")?;
|
||||||
|
|
||||||
deploy_syslinux_artifacts()?;
|
deploy_syslinux_artifacts()?;
|
||||||
|
|
||||||
@ -104,21 +103,23 @@ fn deploy_syslinux_artifacts() -> Result<()> {
|
|||||||
println!("Copying contents of /boot/syslinux to /run/citadel/images/syslinux");
|
println!("Copying contents of /boot/syslinux to /run/citadel/images/syslinux");
|
||||||
|
|
||||||
let run_images_syslinux = Path::new("/run/citadel/images/syslinux");
|
let run_images_syslinux = Path::new("/run/citadel/images/syslinux");
|
||||||
fs::create_dir_all(run_images_syslinux)?;
|
util::create_dir(run_images_syslinux)?;
|
||||||
for entry in fs::read_dir(boot_syslinux)? {
|
|
||||||
let entry = entry?;
|
util::read_directory(boot_syslinux, |dent| {
|
||||||
if let Some(ext) = entry.path().extension() {
|
if let Some(ext) = dent.path().extension() {
|
||||||
if ext == "c32" || ext == "bin" {
|
if ext == "c32" || ext == "bin" {
|
||||||
fs::copy(entry.path(), run_images_syslinux.join(entry.file_name()))?;
|
util::copy_file(dent.path(), run_images_syslinux.join(dent.file_name()))?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_rootfs_image() -> Result<ResourceImage> {
|
fn find_rootfs_image() -> Result<ResourceImage> {
|
||||||
for entry in fs::read_dir(IMAGE_DIRECTORY)? {
|
let entries = fs::read_dir(IMAGE_DIRECTORY)
|
||||||
let entry = entry?;
|
.map_err(context!("error reading directory {}", IMAGE_DIRECTORY))?;
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry.map_err(context!("error reading directory entry"))?;
|
||||||
if entry.path().extension() == Some(OsStr::new("img")) {
|
if entry.path().extension() == Some(OsStr::new("img")) {
|
||||||
if let Ok(image) = ResourceImage::from_path(&entry.path()) {
|
if let Ok(image) = ResourceImage::from_path(&entry.path()) {
|
||||||
if image.metainfo().image_type() == "rootfs" {
|
if image.metainfo().image_type() == "rootfs" {
|
||||||
@ -127,23 +128,23 @@ fn find_rootfs_image() -> Result<ResourceImage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(format_err!("Unable to find rootfs resource image in {}", IMAGE_DIRECTORY))
|
bail!("unable to find rootfs resource image in {}", IMAGE_DIRECTORY)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decompress_images() -> Result<()> {
|
fn decompress_images() -> Result<()> {
|
||||||
info!("Decompressing images");
|
info!("Decompressing images");
|
||||||
let mut threads = Vec::new();
|
let mut threads = Vec::new();
|
||||||
for entry in fs::read_dir("/run/citadel/images")? {
|
util::read_directory("/run/citadel/images", |dent| {
|
||||||
let entry = entry?;
|
if dent.path().extension() == Some(OsStr::new("img")) {
|
||||||
if entry.path().extension() == Some(OsStr::new("img")) {
|
if let Ok(image) = ResourceImage::from_path(&dent.path()) {
|
||||||
if let Ok(image) = ResourceImage::from_path(&entry.path()) {
|
|
||||||
if image.is_compressed() {
|
if image.is_compressed() {
|
||||||
threads.push(decompress_one_image(image));
|
threads.push(decompress_one_image(image));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
for t in threads {
|
for t in threads {
|
||||||
t.join().unwrap()?;
|
t.join().unwrap()?;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
use libcitadel::{Result,ResourceImage,CommandLine,format_error,KeyRing,LogLevel,Logger};
|
use libcitadel::{Result, ResourceImage, CommandLine, KeyRing, LogLevel, Logger, util};
|
||||||
use libcitadel::RealmManager;
|
use libcitadel::RealmManager;
|
||||||
use crate::boot::disks::DiskPartition;
|
use crate::boot::disks::DiskPartition;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@ -21,11 +21,11 @@ pub fn main(args: Vec<String>) {
|
|||||||
Some(s) if s == "rootfs" => do_rootfs(),
|
Some(s) if s == "rootfs" => do_rootfs(),
|
||||||
Some(s) if s == "setup" => do_setup(),
|
Some(s) if s == "setup" => do_setup(),
|
||||||
Some(s) if s == "start-realms" => do_start_realms(),
|
Some(s) if s == "start-realms" => do_start_realms(),
|
||||||
_ => Err(format_err!("Bad or missing argument")),
|
_ => Err(format_err!("Bad or missing argument").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(ref e) = result {
|
if let Err(ref e) = result {
|
||||||
warn!("Failed: {}", format_error(e));
|
warn!("Failed: {}", e);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,21 +69,21 @@ fn mount_overlay() -> Result<()> {
|
|||||||
info!("Creating rootfs overlay");
|
info!("Creating rootfs overlay");
|
||||||
|
|
||||||
info!("Moving /sysroot mount to /rootfs.ro");
|
info!("Moving /sysroot mount to /rootfs.ro");
|
||||||
fs::create_dir_all("/rootfs.ro")?;
|
util::create_dir("/rootfs.ro")?;
|
||||||
cmd!("/usr/bin/mount", "--make-private /")?;
|
cmd!("/usr/bin/mount", "--make-private /")?;
|
||||||
cmd!("/usr/bin/mount", "--move /sysroot /rootfs.ro")?;
|
cmd!("/usr/bin/mount", "--move /sysroot /rootfs.ro")?;
|
||||||
info!("Mounting tmpfs on /rootfs.rw");
|
info!("Mounting tmpfs on /rootfs.rw");
|
||||||
fs::create_dir_all("/rootfs.rw")?;
|
util::create_dir("/rootfs.rw")?;
|
||||||
cmd!("/usr/bin/mount", "-t tmpfs -orw,noatime,mode=755 rootfs.rw /rootfs.rw")?;
|
cmd!("/usr/bin/mount", "-t tmpfs -orw,noatime,mode=755 rootfs.rw /rootfs.rw")?;
|
||||||
info!("Creating /rootfs.rw/work /rootfs.rw/upperdir");
|
info!("Creating /rootfs.rw/work /rootfs.rw/upperdir");
|
||||||
fs::create_dir_all("/rootfs.rw/upperdir")?;
|
util::create_dir("/rootfs.rw/upperdir")?;
|
||||||
fs::create_dir_all("/rootfs.rw/work")?;
|
util::create_dir("/rootfs.rw/work")?;
|
||||||
info!("Mounting overlay on /sysroot");
|
info!("Mounting overlay on /sysroot");
|
||||||
cmd!("/usr/bin/mount", "-t overlay overlay -olowerdir=/rootfs.ro,upperdir=/rootfs.rw/upperdir,workdir=/rootfs.rw/work /sysroot")?;
|
cmd!("/usr/bin/mount", "-t overlay overlay -olowerdir=/rootfs.ro,upperdir=/rootfs.rw/upperdir,workdir=/rootfs.rw/work /sysroot")?;
|
||||||
|
|
||||||
info!("Moving /rootfs.ro and /rootfs.rw to new root");
|
info!("Moving /rootfs.ro and /rootfs.rw to new root");
|
||||||
fs::create_dir_all("/sysroot/rootfs.ro")?;
|
util::create_dir("/sysroot/rootfs.ro")?;
|
||||||
fs::create_dir_all("/sysroot/rootfs.rw")?;
|
util::create_dir("/sysroot/rootfs.rw")?;
|
||||||
cmd!("/usr/bin/mount", "--move /rootfs.ro /sysroot/rootfs.ro")?;
|
cmd!("/usr/bin/mount", "--move /rootfs.ro /sysroot/rootfs.ro")?;
|
||||||
cmd!("/usr/bin/mount", "--move /rootfs.rw /sysroot/rootfs.rw")?;
|
cmd!("/usr/bin/mount", "--move /rootfs.rw /sysroot/rootfs.rw")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -134,7 +134,8 @@ const LOADER_EFI_VAR_PATH: &str =
|
|||||||
fn read_loader_dev_efi_var() -> Result<Option<String>> {
|
fn read_loader_dev_efi_var() -> Result<Option<String>> {
|
||||||
let efi_var = Path::new(LOADER_EFI_VAR_PATH);
|
let efi_var = Path::new(LOADER_EFI_VAR_PATH);
|
||||||
if efi_var.exists() {
|
if efi_var.exists() {
|
||||||
let s = fs::read(efi_var)?
|
let s = fs::read(efi_var)
|
||||||
|
.map_err(context!("could not read {:?}", efi_var))?
|
||||||
.into_iter().skip(4) // u32 'attribute'
|
.into_iter().skip(4) // u32 'attribute'
|
||||||
.filter(|b| *b != 0) // string is utf16 ascii
|
.filter(|b| *b != 0) // string is utf16 ascii
|
||||||
.map(|b| (b as char).to_ascii_lowercase())
|
.map(|b| (b as char).to_ascii_lowercase())
|
||||||
@ -150,8 +151,8 @@ pub fn write_automount_units(partition: &DiskPartition) -> Result<()> {
|
|||||||
let dev = partition.path().display().to_string();
|
let dev = partition.path().display().to_string();
|
||||||
info!("Writing /boot automount units to /run/systemd/system for {}", dev);
|
info!("Writing /boot automount units to /run/systemd/system for {}", dev);
|
||||||
let mount_unit = BOOT_MOUNT_UNIT.replace("$PARTITION", &dev);
|
let mount_unit = BOOT_MOUNT_UNIT.replace("$PARTITION", &dev);
|
||||||
fs::write("/run/systemd/system/boot.mount", mount_unit)?;
|
util::write_file("/run/systemd/system/boot.mount", &mount_unit)?;
|
||||||
fs::write("/run/systemd/system/boot.automount", BOOT_AUTOMOUNT_UNIT)?;
|
util::write_file("/run/systemd/system/boot.automount", BOOT_AUTOMOUNT_UNIT)?;
|
||||||
info!("Starting /boot automount service");
|
info!("Starting /boot automount service");
|
||||||
cmd!("/usr/bin/systemctl", "start boot.automount")?;
|
cmd!("/usr/bin/systemctl", "start boot.automount")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::process::Command;
|
use std::path::Path;
|
||||||
|
use std::process::{Command,Stdio};
|
||||||
|
|
||||||
use libcitadel::{BlockDev, ResourceImage, CommandLine, ImageHeader, Partition, Result, LoopDevice};
|
use libcitadel::{BlockDev, ResourceImage, CommandLine, ImageHeader, Partition, Result, LoopDevice};
|
||||||
use std::path::Path;
|
|
||||||
use std::process::Stdio;
|
|
||||||
use libcitadel::verity::Verity;
|
use libcitadel::verity::Verity;
|
||||||
|
|
||||||
pub fn setup_rootfs() -> Result<()> {
|
pub fn setup_rootfs() -> Result<()> {
|
||||||
@ -45,11 +44,11 @@ fn setup_partition_verified(p: &mut Partition) -> Result<()> {
|
|||||||
info!("Creating /dev/mapper/rootfs dm-verity device");
|
info!("Creating /dev/mapper/rootfs dm-verity device");
|
||||||
if !CommandLine::nosignatures() {
|
if !CommandLine::nosignatures() {
|
||||||
if !p.has_public_key() {
|
if !p.has_public_key() {
|
||||||
bail!("No public key available for channel {}", p.metainfo().channel())
|
bail!("no public key available for channel {}", p.metainfo().channel())
|
||||||
}
|
}
|
||||||
if !p.is_signature_valid() {
|
if !p.is_signature_valid() {
|
||||||
p.write_status(ImageHeader::STATUS_BAD_SIG)?;
|
p.write_status(ImageHeader::STATUS_BAD_SIG)?;
|
||||||
bail!("Signature verification failed on partition");
|
bail!("signature verification failed on partition");
|
||||||
}
|
}
|
||||||
info!("Image signature is valid for channel {}", p.metainfo().channel());
|
info!("Image signature is valid for channel {}", p.metainfo().channel());
|
||||||
}
|
}
|
||||||
@ -71,7 +70,7 @@ fn setup_linear_mapping(blockdev: &Path) -> Result<()> {
|
|||||||
.success();
|
.success();
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
bail!("Failed to set up linear identity mapping with /usr/sbin/dmsetup");
|
bail!("failed to set up linear identity mapping with /usr/sbin/dmsetup");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -89,7 +88,7 @@ fn choose_boot_partiton(scan: bool) -> Result<Partition> {
|
|||||||
for p in partitions {
|
for p in partitions {
|
||||||
best = compare_boot_partitions(best, p);
|
best = compare_boot_partitions(best, p);
|
||||||
}
|
}
|
||||||
best.ok_or_else(|| format_err!("No partition found to boot from"))
|
best.ok_or_else(|| format_err!("No partition found to boot from").into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_boot_partitions(a: Option<Partition>, b: Partition) -> Option<Partition> {
|
fn compare_boot_partitions(a: Option<Partition>, b: Partition) -> Option<Partition> {
|
||||||
|
@ -3,8 +3,7 @@ use std::process::exit;
|
|||||||
|
|
||||||
use clap::{App,Arg,SubCommand,ArgMatches};
|
use clap::{App,Arg,SubCommand,ArgMatches};
|
||||||
use clap::AppSettings::*;
|
use clap::AppSettings::*;
|
||||||
use libcitadel::{Result,ResourceImage,Logger,LogLevel,format_error,Partition,KeyPair,ImageHeader};
|
use libcitadel::{Result, ResourceImage, Logger, LogLevel, Partition, KeyPair, ImageHeader, util};
|
||||||
use std::fs;
|
|
||||||
use hex;
|
use hex;
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
pub fn main(args: Vec<String>) {
|
||||||
@ -89,7 +88,7 @@ pub fn main(args: Vec<String>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Err(ref e) = result {
|
if let Err(ref e) = result {
|
||||||
println!("Error: {}", format_error(e));
|
println!("Error: {}", e);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -252,8 +251,7 @@ fn install_image(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
if image_dest.exists() {
|
if image_dest.exists() {
|
||||||
rotate(&image_dest)?;
|
rotate(&image_dest)?;
|
||||||
}
|
}
|
||||||
fs::rename(source,image_dest)?;
|
util::rename(source, &image_dest)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate(path: &Path) -> Result<()> {
|
fn rotate(path: &Path) -> Result<()> {
|
||||||
@ -262,11 +260,8 @@ fn rotate(path: &Path) -> Result<()> {
|
|||||||
}
|
}
|
||||||
let filename = path.file_name().unwrap();
|
let filename = path.file_name().unwrap();
|
||||||
let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy()));
|
let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy()));
|
||||||
if dot_zero.exists() {
|
util::remove_file(&dot_zero)?;
|
||||||
fs::remove_file(&dot_zero)?;
|
util::rename(path, &dot_zero)
|
||||||
}
|
|
||||||
fs::rename(path, &dot_zero)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genkeys() -> Result<()> {
|
fn genkeys() -> Result<()> {
|
||||||
@ -334,5 +329,5 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
|||||||
return Ok(p.clone())
|
return Ok(p.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(format_err!("No suitable install partition found"))
|
bail!("No suitable install partition found")
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ pub fn run_cli_install() -> Result<bool> {
|
|||||||
|
|
||||||
display_disk(&disk);
|
display_disk(&disk);
|
||||||
|
|
||||||
let passphrase = match read_passphrase()? {
|
let passphrase = match read_passphrase().map_err(context!("error reading passphrase"))? {
|
||||||
Some(passphrase) => passphrase,
|
Some(passphrase) => passphrase,
|
||||||
None => return Ok(false),
|
None => return Ok(false),
|
||||||
};
|
};
|
||||||
@ -29,7 +29,7 @@ pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
|
|||||||
let disk = find_disk_by_path(target.as_ref())?;
|
let disk = find_disk_by_path(target.as_ref())?;
|
||||||
display_disk(&disk);
|
display_disk(&disk);
|
||||||
|
|
||||||
let passphrase = match read_passphrase()? {
|
let passphrase = match read_passphrase().map_err(context!("error reading passphrase"))? {
|
||||||
Some(passphrase) => passphrase,
|
Some(passphrase) => passphrase,
|
||||||
None => return Ok(false),
|
None => return Ok(false),
|
||||||
};
|
};
|
||||||
@ -66,17 +66,17 @@ fn find_disk_by_path(path: &Path) -> Result<Disk> {
|
|||||||
return Ok(disk.clone());
|
return Ok(disk.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(format_err!("Installation target {} is not a valid disk", path.display()))
|
bail!("installation target {} is not a valid disk", path.display())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_disk() -> Result<Option<Disk>> {
|
fn choose_disk() -> Result<Option<Disk>> {
|
||||||
let disks = Disk::probe_all()?;
|
let disks = Disk::probe_all()?;
|
||||||
if disks.is_empty() {
|
if disks.is_empty() {
|
||||||
bail!("No disks found.");
|
bail!("no disks found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
prompt_choose_disk(&disks)?;
|
prompt_choose_disk(&disks);
|
||||||
let line = read_line()?;
|
let line = read_line()?;
|
||||||
if line == "q" || line == "Q" {
|
if line == "q" || line == "Q" {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@ -89,26 +89,26 @@ fn choose_disk() -> Result<Option<Disk>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt_choose_disk(disks: &[Disk]) -> Result<()> {
|
fn prompt_choose_disk(disks: &[Disk]) {
|
||||||
println!("Available disks:\n");
|
println!("Available disks:\n");
|
||||||
for (idx,disk) in disks.iter().enumerate() {
|
for (idx,disk) in disks.iter().enumerate() {
|
||||||
println!(" [{}]: {} Size: {} Model: {}", idx + 1, disk.path().display(), disk.size_str(), disk.model());
|
println!(" [{}]: {} Size: {} Model: {}", idx + 1, disk.path().display(), disk.size_str(), disk.model());
|
||||||
}
|
}
|
||||||
print!("\nChoose a disk to install to (q to quit): ");
|
print!("\nChoose a disk to install to (q to quit): ");
|
||||||
io::stdout().flush()?;
|
let _ = io::stdout().flush();
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_line() -> Result<String> {
|
fn read_line() -> Result<String> {
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
io::stdin().read_line(&mut input)?;
|
io::stdin().read_line(&mut input)
|
||||||
|
.map_err(context!("error reading line from stdin"))?;
|
||||||
if input.ends_with('\n') {
|
if input.ends_with('\n') {
|
||||||
input.pop();
|
input.pop();
|
||||||
}
|
}
|
||||||
Ok(input)
|
Ok(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_passphrase() -> Result<Option<String>> {
|
fn read_passphrase() -> io::Result<Option<String>> {
|
||||||
loop {
|
loop {
|
||||||
println!("Enter a disk encryption passphrase (or 'q' to quit)");
|
println!("Enter a disk encryption passphrase (or 'q' to quit)");
|
||||||
println!();
|
println!();
|
||||||
@ -141,7 +141,7 @@ fn confirm_install(disk: &Disk) -> Result<bool> {
|
|||||||
println!(" Model: {}", disk.model());
|
println!(" Model: {}", disk.model());
|
||||||
println!();
|
println!();
|
||||||
print!("Type YES (uppercase) to continue with install: ");
|
print!("Type YES (uppercase) to continue with install: ");
|
||||||
io::stdout().flush()?;
|
let _ = io::stdout().flush();
|
||||||
let answer = read_line()?;
|
let answer = read_line()?;
|
||||||
Ok(answer == "YES")
|
Ok(answer == "YES")
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path,PathBuf};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use libcitadel::Result;
|
use libcitadel::{Result, util};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -15,13 +15,15 @@ pub struct Disk {
|
|||||||
impl Disk {
|
impl Disk {
|
||||||
pub fn probe_all() -> Result<Vec<Disk>> {
|
pub fn probe_all() -> Result<Vec<Disk>> {
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for entry in fs::read_dir("/sys/block")? {
|
util::read_directory("/sys/block", |dent| {
|
||||||
let path = entry?.path();
|
let path = dent.path();
|
||||||
if Disk::is_disk_device(&path) {
|
if Disk::is_disk_device(&path) {
|
||||||
let disk = Disk::read_device(&path)?;
|
let disk = Disk::read_device(&path)?;
|
||||||
v.push(disk);
|
v.push(disk);
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,18 +34,20 @@ impl Disk {
|
|||||||
fn read_device(device: &Path) -> Result<Disk> {
|
fn read_device(device: &Path) -> Result<Disk> {
|
||||||
let path = Path::new("/dev/").join(device.file_name().unwrap());
|
let path = Path::new("/dev/").join(device.file_name().unwrap());
|
||||||
|
|
||||||
let size = fs::read_to_string(device.join("size"))?
|
let size = fs::read_to_string(device.join("size"))
|
||||||
|
.map_err(context!("failed to read device size for {:?}", device))?
|
||||||
.trim()
|
.trim()
|
||||||
.parse::<usize>()?;
|
.parse::<usize>()
|
||||||
|
.map_err(context!("error parsing device size for {:?}", device))?;
|
||||||
|
|
||||||
let size_str = format!("{}G", size >> 21);
|
let size_str = format!("{}G", size >> 21);
|
||||||
|
|
||||||
let model = fs::read_to_string(device.join("device/model"))?
|
let model = fs::read_to_string(device.join("device/model"))
|
||||||
|
.map_err(context!("failed to read device/model for {:?}", device))?
|
||||||
.trim()
|
.trim()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
Ok(Disk { path, size, size_str, model })
|
Ok(Disk { path, size, size_str, model })
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> &Path {
|
pub fn path(&self) -> &Path {
|
||||||
@ -57,5 +61,4 @@ impl Disk {
|
|||||||
pub fn model(&self) -> &str {
|
pub fn model(&self) -> &str {
|
||||||
&self.model
|
&self.model
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fs::{self,File};
|
use std::fs::{self,File};
|
||||||
use std::io::{self,Write};
|
use std::io::{self,Write};
|
||||||
use std::os::unix::fs as unixfs;
|
|
||||||
use std::os::unix::process::CommandExt;
|
use std::os::unix::process::CommandExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@ -183,12 +182,12 @@ impl Installer {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if !self.target().exists() {
|
if !self.target().exists() {
|
||||||
bail!("Target device {} does not exist", self.target().display());
|
bail!("target device {:?} does not exist", self.target());
|
||||||
}
|
}
|
||||||
|
|
||||||
for a in artifacts {
|
for a in artifacts {
|
||||||
if !self.artifact_path(a).exists() {
|
if !self.artifact_path(a).exists() {
|
||||||
bail!("Required install artifact {} does not exist in {}", a, self.artifact_directory);
|
bail!("required install artifact {} does not exist in {}", a, self.artifact_directory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,10 +221,11 @@ impl Installer {
|
|||||||
"/bin/mount -t tmpfs storage-tmpfs /sysroot/storage",
|
"/bin/mount -t tmpfs storage-tmpfs /sysroot/storage",
|
||||||
], &[])?;
|
], &[])?;
|
||||||
|
|
||||||
fs::create_dir_all("/sysroot/storage/realms")?;
|
util::create_dir("/sysroot/storage/realms")?;
|
||||||
|
|
||||||
self.cmd("/bin/mount --bind /sysroot/storage/realms /sysroot/realms")?;
|
self.cmd("/bin/mount --bind /sysroot/storage/realms /sysroot/realms")?;
|
||||||
|
|
||||||
let cmdline = fs::read_to_string("/proc/cmdline")?;
|
let cmdline = util::read_to_string("/proc/cmdline")?;
|
||||||
if cmdline.contains("citadel.live") {
|
if cmdline.contains("citadel.live") {
|
||||||
self.setup_live_realm()?;
|
self.setup_live_realm()?;
|
||||||
}
|
}
|
||||||
@ -238,10 +238,10 @@ impl Installer {
|
|||||||
let base_realmfs = realmfs_dir.join("base-realmfs.img");
|
let base_realmfs = realmfs_dir.join("base-realmfs.img");
|
||||||
|
|
||||||
self.info(format!("creating directory {}", realmfs_dir.display()))?;
|
self.info(format!("creating directory {}", realmfs_dir.display()))?;
|
||||||
fs::create_dir_all(&realmfs_dir)?;
|
util::create_dir(&realmfs_dir)?;
|
||||||
|
|
||||||
self.info(format!("creating symlink {} -> {}", base_realmfs.display(), "/run/citadel/images/base-realmfs.img"))?;
|
self.info(format!("creating symlink {} -> {}", base_realmfs.display(), "/run/citadel/images/base-realmfs.img"))?;
|
||||||
unixfs::symlink("/run/citadel/images/base-realmfs.img", &base_realmfs)?;
|
util::symlink("/run/citadel/images/base-realmfs.img", &base_realmfs)?;
|
||||||
|
|
||||||
let realmfs = RealmFS::load_from_path("/run/citadel/images/base-realmfs.img")?;
|
let realmfs = RealmFS::load_from_path("/run/citadel/images/base-realmfs.img")?;
|
||||||
realmfs.activate()?;
|
realmfs.activate()?;
|
||||||
@ -260,8 +260,9 @@ impl Installer {
|
|||||||
|
|
||||||
fn setup_luks(&self) -> Result<()> {
|
fn setup_luks(&self) -> Result<()> {
|
||||||
self.header("Setting up LUKS disk encryption")?;
|
self.header("Setting up LUKS disk encryption")?;
|
||||||
fs::create_dir_all(INSTALL_MOUNT)?;
|
util::create_dir(INSTALL_MOUNT)?;
|
||||||
fs::write(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?;
|
util::write_file(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?;
|
||||||
|
|
||||||
let luks_partition = self.target_partition(2);
|
let luks_partition = self.target_partition(2);
|
||||||
|
|
||||||
self.cmd_list(LUKS_COMMANDS, &[
|
self.cmd_list(LUKS_COMMANDS, &[
|
||||||
@ -270,8 +271,7 @@ impl Installer {
|
|||||||
("$LUKS_PASSFILE", LUKS_PASSPHRASE_FILE),
|
("$LUKS_PASSFILE", LUKS_PASSPHRASE_FILE),
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
fs::remove_file(LUKS_PASSPHRASE_FILE)?;
|
util::remove_file(LUKS_PASSPHRASE_FILE)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_lvm(&self) -> Result<()> {
|
fn setup_lvm(&self) -> Result<()> {
|
||||||
@ -286,17 +286,16 @@ impl Installer {
|
|||||||
|
|
||||||
self.cmd(format!("/bin/mount {} {}", boot_partition, INSTALL_MOUNT))?;
|
self.cmd(format!("/bin/mount {} {}", boot_partition, INSTALL_MOUNT))?;
|
||||||
|
|
||||||
fs::create_dir_all(format!("{}/loader/entries", INSTALL_MOUNT))?;
|
util::create_dir(format!("{}/loader/entries", INSTALL_MOUNT))?;
|
||||||
|
|
||||||
self.info("Writing /boot/loader/loader.conf")?;
|
self.info("Writing /boot/loader/loader.conf")?;
|
||||||
fs::write(format!("{}/loader/loader.conf", INSTALL_MOUNT), LOADER_CONF)?;
|
util::write_file(format!("{}/loader/loader.conf", INSTALL_MOUNT), LOADER_CONF)?;
|
||||||
|
|
||||||
let kernel_version = self.kernel_version();
|
let kernel_version = self.kernel_version();
|
||||||
self.info("Writing /boot/entries/boot.conf")?;
|
self.info("Writing /boot/entries/boot.conf")?;
|
||||||
fs::write(format!("{}/loader/entries/boot.conf", INSTALL_MOUNT), BOOT_CONF
|
util::write_file(format!("{}/loader/entries/boot.conf", INSTALL_MOUNT), BOOT_CONF
|
||||||
.replace("$KERNEL_CMDLINE", KERNEL_CMDLINE)
|
.replace("$KERNEL_CMDLINE", KERNEL_CMDLINE)
|
||||||
.replace("$KERNEL_VERSION", &kernel_version)
|
.replace("$KERNEL_VERSION", &kernel_version))?;
|
||||||
)?;
|
|
||||||
|
|
||||||
let kernel_bzimage = format!("bzImage-{}", kernel_version);
|
let kernel_bzimage = format!("bzImage-{}", kernel_version);
|
||||||
self.copy_artifact(&kernel_bzimage, INSTALL_MOUNT)?;
|
self.copy_artifact(&kernel_bzimage, INSTALL_MOUNT)?;
|
||||||
@ -318,26 +317,26 @@ impl Installer {
|
|||||||
self.header("Installing syslinux")?;
|
self.header("Installing syslinux")?;
|
||||||
let syslinux_src = self.artifact_path("syslinux");
|
let syslinux_src = self.artifact_path("syslinux");
|
||||||
if !syslinux_src.exists() {
|
if !syslinux_src.exists() {
|
||||||
bail!("No syslinux directory found in artifact directory, cannot install syslinux");
|
bail!("no syslinux directory found in artifact directory, cannot install syslinux");
|
||||||
}
|
}
|
||||||
let dst = Path::new(INSTALL_MOUNT).join("syslinux");
|
let dst = Path::new(INSTALL_MOUNT).join("syslinux");
|
||||||
fs::create_dir_all(&dst)?;
|
util::create_dir(&dst)?;
|
||||||
|
|
||||||
self.info("Copying syslinux files to /boot/syslinux")?;
|
self.info("Copying syslinux files to /boot/syslinux")?;
|
||||||
for entry in fs::read_dir(&syslinux_src)? {
|
util::read_directory(&syslinux_src, |dent| {
|
||||||
let entry = entry?;
|
util::copy_file(dent.path(), dst.join(dent.file_name()))
|
||||||
fs::copy(entry.path(), dst.join(entry.file_name()))?;
|
})?;
|
||||||
}
|
|
||||||
self.info("Writing syslinux.cfg")?;
|
self.info("Writing syslinux.cfg")?;
|
||||||
fs::write(dst.join("syslinux.cfg"),
|
util::write_file(dst.join("syslinux.cfg"),
|
||||||
SYSLINUX_CONF.replace("$KERNEL_CMDLINE", KERNEL_CMDLINE))?;
|
SYSLINUX_CONF.replace("$KERNEL_CMDLINE", KERNEL_CMDLINE))?;
|
||||||
self.cmd(format!("/sbin/extlinux --install {}", dst.display()))?;
|
self.cmd(format!("/sbin/extlinux --install {}", dst.display()))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_syslinux_post_umount(&self) -> Result<()> {
|
fn setup_syslinux_post_umount(&self) -> Result<()> {
|
||||||
let mbrbin = self.artifact_path("syslinux/gptmbr.bin");
|
let mbrbin = self.artifact_path("syslinux/gptmbr.bin");
|
||||||
if !mbrbin.exists() {
|
if !mbrbin.exists() {
|
||||||
bail!("Could not find MBR image: {}", mbrbin.display());
|
bail!("could not find MBR image: {:?}", mbrbin);
|
||||||
}
|
}
|
||||||
self.cmd(format!("/bin/dd bs=440 count=1 conv=notrunc if={} of={}", mbrbin.display(), self.target().display()))?;
|
self.cmd(format!("/bin/dd bs=440 count=1 conv=notrunc if={} of={}", mbrbin.display(), self.target().display()))?;
|
||||||
self.cmd(format!("/sbin/parted -s {} set 1 legacy_boot on", self.target_str()))
|
self.cmd(format!("/sbin/parted -s {} set 1 legacy_boot on", self.target_str()))
|
||||||
@ -351,8 +350,7 @@ impl Installer {
|
|||||||
&[("$INSTALL_MOUNT", INSTALL_MOUNT)])?;
|
&[("$INSTALL_MOUNT", INSTALL_MOUNT)])?;
|
||||||
|
|
||||||
self.setup_storage()?;
|
self.setup_storage()?;
|
||||||
self.cmd(format!("/bin/umount {}", INSTALL_MOUNT))?;
|
self.cmd(format!("/bin/umount {}", INSTALL_MOUNT))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_storage(&self) -> Result<()> {
|
fn setup_storage(&self) -> Result<()> {
|
||||||
@ -367,12 +365,12 @@ impl Installer {
|
|||||||
self.setup_apt_cacher_realm()?;
|
self.setup_apt_cacher_realm()?;
|
||||||
|
|
||||||
self.info("Creating global realm config file")?;
|
self.info("Creating global realm config file")?;
|
||||||
fs::write(self.storage().join("realms/config"), self.global_realm_config())?;
|
util::write_file(self.storage().join("realms/config"), self.global_realm_config())?;
|
||||||
|
|
||||||
self.info("Creating /Shared realms directory")?;
|
self.info("Creating /Shared realms directory")?;
|
||||||
|
|
||||||
let shared = self.storage().join("realms/Shared");
|
let shared = self.storage().join("realms/Shared");
|
||||||
fs::create_dir_all(&shared)?;
|
util::create_dir(&shared)?;
|
||||||
util::chown_user(&shared)?;
|
util::chown_user(&shared)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -386,17 +384,20 @@ impl Installer {
|
|||||||
|
|
||||||
fn setup_base_realmfs(&self) -> Result<()> {
|
fn setup_base_realmfs(&self) -> Result<()> {
|
||||||
let realmfs_dir = self.storage().join("realms/realmfs-images");
|
let realmfs_dir = self.storage().join("realms/realmfs-images");
|
||||||
fs::create_dir_all(&realmfs_dir)?;
|
util::create_dir(&realmfs_dir)?;
|
||||||
self.sparse_copy_artifact("base-realmfs.img", &realmfs_dir)?;
|
self.sparse_copy_artifact("base-realmfs.img", &realmfs_dir)?;
|
||||||
self.cmd(format!("/usr/bin/citadel-image decompress {}/base-realmfs.img", realmfs_dir.display()))?;
|
self.cmd(format!("/usr/bin/citadel-image decompress {}/base-realmfs.img", realmfs_dir.display()))
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_realm_skel(&self) -> Result<()> {
|
fn setup_realm_skel(&self) -> Result<()> {
|
||||||
let realm_skel = self.storage().join("realms/skel");
|
let realm_skel = self.storage().join("realms/skel");
|
||||||
fs::create_dir_all(&realm_skel)?;
|
util::create_dir(&realm_skel)?;
|
||||||
util::copy_tree_with_chown(&self.skel(), &realm_skel, (1000,1000))?;
|
util::copy_tree_with_chown(&self.skel(), &realm_skel, (1000,1000))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_realmlock(&self, dir: &Path) -> Result<()> {
|
||||||
|
fs::File::create(dir.join(".realmlock"))
|
||||||
|
.map_err(context!("failed to create {:?}/.realmlock file", dir))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,7 +408,7 @@ impl Installer {
|
|||||||
|
|
||||||
self.info("Creating home directory /realms/realm-main/home")?;
|
self.info("Creating home directory /realms/realm-main/home")?;
|
||||||
let home = realm.join("home");
|
let home = realm.join("home");
|
||||||
fs::create_dir_all(&home)?;
|
util::create_dir(&home)?;
|
||||||
util::chown_user(&home)?;
|
util::chown_user(&home)?;
|
||||||
|
|
||||||
self.info("Copying /realms/skel into home diectory")?;
|
self.info("Copying /realms/skel into home diectory")?;
|
||||||
@ -415,16 +416,14 @@ impl Installer {
|
|||||||
|
|
||||||
if let Some(scheme) = Base16Scheme::by_name(MAIN_TERMINAL_SCHEME) {
|
if let Some(scheme) = Base16Scheme::by_name(MAIN_TERMINAL_SCHEME) {
|
||||||
scheme.write_realm_files(&home)?;
|
scheme.write_realm_files(&home)?;
|
||||||
fs::write(realm.join("config"), MAIN_CONFIG.replace("$SCHEME", MAIN_TERMINAL_SCHEME))?;
|
util::write_file(realm.join("config"), MAIN_CONFIG.replace("$SCHEME", MAIN_TERMINAL_SCHEME))?;
|
||||||
}
|
}
|
||||||
util::chown_tree(&home, (1000,1000), false)?;
|
util::chown_tree(&home, (1000,1000), false)?;
|
||||||
|
|
||||||
self.info("Creating default.realm symlink")?;
|
self.info("Creating default.realm symlink")?;
|
||||||
unixfs::symlink("/realms/realm-main", self.storage().join("realms/default.realm"))?;
|
util::symlink("/realms/realm-main", self.storage().join("realms/default.realm"))?;
|
||||||
|
|
||||||
fs::File::create(realm.join(".realmlock"))?;
|
self.create_realmlock(&realm)
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_apt_cacher_realm(&self) -> Result<()> {
|
fn setup_apt_cacher_realm(&self) -> Result<()> {
|
||||||
@ -433,19 +432,18 @@ impl Installer {
|
|||||||
|
|
||||||
self.info("Creating home directory /realms/realm-apt-cacher/home")?;
|
self.info("Creating home directory /realms/realm-apt-cacher/home")?;
|
||||||
let home = realm_base.join("home");
|
let home = realm_base.join("home");
|
||||||
fs::create_dir_all(&home)?;
|
util::create_dir(&home)?;
|
||||||
util::chown_user(&home)?;
|
util::chown_user(&home)?;
|
||||||
let path = home.join("apt-cacher-ng");
|
let path = home.join("apt-cacher-ng");
|
||||||
fs::create_dir_all(&path)?;
|
util::create_dir(&path)?;
|
||||||
util::chown_user(&path)?;
|
util::chown_user(&path)?;
|
||||||
|
|
||||||
self.info("Copying /realms/skel into home diectory")?;
|
self.info("Copying /realms/skel into home diectory")?;
|
||||||
util::copy_tree(&self.storage().join("realms/skel"), &home)?;
|
util::copy_tree(&self.storage().join("realms/skel"), &home)?;
|
||||||
|
|
||||||
self.info("Creating apt-cacher config file")?;
|
self.info("Creating apt-cacher config file")?;
|
||||||
fs::write(realm_base.join("config"), APT_CACHER_CONFIG)?;
|
util::write_file(realm_base.join("config"), APT_CACHER_CONFIG)?;
|
||||||
fs::File::create(realm_base.join(".realmlock"))?;
|
self.create_realmlock(&realm_base)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_storage_resources(&self) -> Result<()> {
|
fn setup_storage_resources(&self) -> Result<()> {
|
||||||
@ -454,22 +452,19 @@ impl Installer {
|
|||||||
None => "dev",
|
None => "dev",
|
||||||
};
|
};
|
||||||
let resources = self.storage().join("resources").join(channel);
|
let resources = self.storage().join("resources").join(channel);
|
||||||
fs::create_dir_all(&resources)?;
|
util::create_dir(&resources)?;
|
||||||
|
|
||||||
self.sparse_copy_artifact(EXTRA_IMAGE_NAME, &resources)?;
|
self.sparse_copy_artifact(EXTRA_IMAGE_NAME, &resources)?;
|
||||||
|
|
||||||
let kernel_img = self.kernel_imagename();
|
let kernel_img = self.kernel_imagename();
|
||||||
self.sparse_copy_artifact(&kernel_img, &resources)?;
|
self.sparse_copy_artifact(&kernel_img, &resources)
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_rootfs_partitions(&self) -> Result<()> {
|
fn install_rootfs_partitions(&self) -> Result<()> {
|
||||||
self.header("Installing rootfs partitions")?;
|
self.header("Installing rootfs partitions")?;
|
||||||
let rootfs = self.artifact_path("citadel-rootfs.img");
|
let rootfs = self.artifact_path("citadel-rootfs.img");
|
||||||
self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha {}", rootfs.display()))?;
|
self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha {}", rootfs.display()))?;
|
||||||
self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha --no-prefer {}", rootfs.display()))?;
|
self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha --no-prefer {}", rootfs.display()))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_install(&self) -> Result<()> {
|
fn finish_install(&self) -> Result<()> {
|
||||||
@ -522,14 +517,12 @@ impl Installer {
|
|||||||
self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?;
|
self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?;
|
||||||
let src = self.artifact_path(filename);
|
let src = self.artifact_path(filename);
|
||||||
let target = target.as_ref();
|
let target = target.as_ref();
|
||||||
if !target.exists() {
|
util::create_dir(target)?;
|
||||||
fs::create_dir_all(target)?;
|
|
||||||
}
|
|
||||||
let dst = target.join(filename);
|
let dst = target.join(filename);
|
||||||
if sparse {
|
if sparse {
|
||||||
self.cmd(format!("/bin/cp --sparse=always {} {}", src.display(), dst.display()))?;
|
self.cmd(format!("/bin/cp --sparse=always {} {}", src.display(), dst.display()))?;
|
||||||
} else {
|
} else {
|
||||||
fs::copy(src, dst)?;
|
util::copy_file(src, dst)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -542,12 +535,17 @@ impl Installer {
|
|||||||
self.output(format!(" [>] {}", s.as_ref()))
|
self.output(format!(" [>] {}", s.as_ref()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn output<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
fn output<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
||||||
println!("{}", s.as_ref());
|
self.write_output(s.as_ref()).map_err(context!("error writing output"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_output(&self, s: &str) -> io::Result<()> {
|
||||||
|
println!("{}", s);
|
||||||
io::stdout().flush()?;
|
io::stdout().flush()?;
|
||||||
|
|
||||||
if let Some(ref file) = self.logfile {
|
if let Some(ref file) = self.logfile {
|
||||||
writeln!(file.borrow_mut(), "{}", s.as_ref())?;
|
writeln!(file.borrow_mut(), "{}", s)?;
|
||||||
file.borrow_mut().flush()?;
|
file.borrow_mut().flush()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -580,7 +578,8 @@ impl Installer {
|
|||||||
|
|
||||||
command.args(&args[1..]);
|
command.args(&args[1..]);
|
||||||
|
|
||||||
let result = command.output()?;
|
let result = command.output()
|
||||||
|
.map_err(context!("error running command {}", args[0]))?;
|
||||||
|
|
||||||
for line in String::from_utf8_lossy(&result.stdout).lines() {
|
for line in String::from_utf8_lossy(&result.stdout).lines() {
|
||||||
self.output(format!(" {}", line))?;
|
self.output(format!(" {}", line))?;
|
||||||
|
@ -4,8 +4,6 @@ pub(crate) mod installer;
|
|||||||
mod cli;
|
mod cli;
|
||||||
mod disk;
|
mod disk;
|
||||||
|
|
||||||
use libcitadel::format_error;
|
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
pub fn main(args: Vec<String>) {
|
||||||
let mut args = args.iter().skip(1);
|
let mut args = args.iter().skip(1);
|
||||||
let result = if let Some(dev) = args.next() {
|
let result = if let Some(dev) = args.next() {
|
||||||
@ -17,7 +15,7 @@ pub fn main(args: Vec<String>) {
|
|||||||
let ok = match result {
|
let ok = match result {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
Err(ref err) => {
|
Err(ref err) => {
|
||||||
println!("Install failed: {}", format_error(err));
|
println!("Install failed: {}", err);
|
||||||
exit(1);
|
exit(1);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -3,8 +3,7 @@ use std::fs::OpenOptions;
|
|||||||
use std::fs::{self,File};
|
use std::fs::{self,File};
|
||||||
use std::io::{self,Write};
|
use std::io::{self,Write};
|
||||||
|
|
||||||
use failure::ResultExt;
|
use libcitadel::{Result, ImageHeader, devkeys, util};
|
||||||
use libcitadel::{Result,ImageHeader,devkeys};
|
|
||||||
|
|
||||||
use super::config::BuildConfig;
|
use super::config::BuildConfig;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@ -52,13 +51,13 @@ impl UpdateBuilder {
|
|||||||
|
|
||||||
pub fn build(&mut self) -> Result<()> {
|
pub fn build(&mut self) -> Result<()> {
|
||||||
info!("Copying source file to {}", self.image_data.display());
|
info!("Copying source file to {}", self.image_data.display());
|
||||||
fs::copy(self.config.source(), &self.image_data)?;
|
util::copy_file(self.config.source(), &self.image_data)?;
|
||||||
|
|
||||||
self.pad_image()
|
self.pad_image()
|
||||||
.context("failed writing padding to image")?;
|
.map_err(context!("failed writing padding to image"))?;
|
||||||
|
|
||||||
self.generate_verity()
|
self.generate_verity()
|
||||||
.context("failed generating dm-verity hash tree")?;
|
.map_err(context!("failed generating dm-verity hash tree"))?;
|
||||||
|
|
||||||
self.calculate_shasum()?;
|
self.calculate_shasum()?;
|
||||||
|
|
||||||
@ -66,8 +65,7 @@ impl UpdateBuilder {
|
|||||||
|
|
||||||
self.compress_image()?;
|
self.compress_image()?;
|
||||||
|
|
||||||
self.write_final_image()
|
self.write_final_image()?;
|
||||||
.context("failed to write final image file")?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -77,10 +75,11 @@ impl UpdateBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn pad_image(&mut self) -> Result<()> {
|
fn pad_image(&mut self) -> Result<()> {
|
||||||
let meta = self.image().metadata()?;
|
let meta = self.image().metadata()
|
||||||
|
.map_err(context!("failed to read metadata from {:?}", self.image()))?;
|
||||||
let len = meta.len() as usize;
|
let len = meta.len() as usize;
|
||||||
if len % 512 != 0 {
|
if len % 512 != 0 {
|
||||||
bail!("Image file size is not a multiple of sector size (512 bytes)");
|
bail!("image file size is not a multiple of sector size (512 bytes)");
|
||||||
}
|
}
|
||||||
let padlen = align(len, BLOCK_SIZE) - len;
|
let padlen = align(len, BLOCK_SIZE) - len;
|
||||||
|
|
||||||
@ -89,8 +88,11 @@ impl UpdateBuilder {
|
|||||||
let zeros = vec![0u8; padlen];
|
let zeros = vec![0u8; padlen];
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new()
|
||||||
.append(true)
|
.append(true)
|
||||||
.open(self.image())?;
|
.open(self.image())
|
||||||
file.write_all(&zeros)?;
|
.map_err(context!("failed to open file {:?}", self.image()))?;
|
||||||
|
|
||||||
|
file.write_all(&zeros)
|
||||||
|
.map_err(context!("error writing image file"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nblocks = (len + padlen) / 4096;
|
let nblocks = (len + padlen) / 4096;
|
||||||
@ -102,7 +104,7 @@ impl UpdateBuilder {
|
|||||||
|
|
||||||
fn calculate_shasum(&mut self) -> Result<()> {
|
fn calculate_shasum(&mut self) -> Result<()> {
|
||||||
let output = cmd_with_output!("sha256sum", "{}", self.image().display())
|
let output = cmd_with_output!("sha256sum", "{}", self.image().display())
|
||||||
.context(format!("failed to calculate sha256 on {}", self.image().display()))?;
|
.map_err(context!("failed to calculate sha256 on {:?}", self.image()))?;
|
||||||
let v: Vec<&str> = output.split_whitespace().collect();
|
let v: Vec<&str> = output.split_whitespace().collect();
|
||||||
let shasum = v[0].trim().to_owned();
|
let shasum = v[0].trim().to_owned();
|
||||||
info!("Sha256 of image data is {}", shasum);
|
info!("Sha256 of image data is {}", shasum);
|
||||||
@ -113,7 +115,7 @@ impl UpdateBuilder {
|
|||||||
fn prepend_empty_block(&mut self) -> Result<()> {
|
fn prepend_empty_block(&mut self) -> Result<()> {
|
||||||
let tmpfile = self.image().with_extension("tmp");
|
let tmpfile = self.image().with_extension("tmp");
|
||||||
cmd!("/bin/dd", "if={} of={} bs=4096 seek=1 conv=sparse", self.image().display(), tmpfile.display())?;
|
cmd!("/bin/dd", "if={} of={} bs=4096 seek=1 conv=sparse", self.image().display(), tmpfile.display())?;
|
||||||
fs::rename(tmpfile, self.image())?;
|
util::rename(tmpfile, self.image())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,8 +126,9 @@ impl UpdateBuilder {
|
|||||||
let verity = Verity::new(self.image())?;
|
let verity = Verity::new(self.image())?;
|
||||||
let output = verity.generate_initial_hashtree(&hashfile)?;
|
let output = verity.generate_initial_hashtree(&hashfile)?;
|
||||||
|
|
||||||
fs::write(outfile, output.output())
|
if let Err(err) = fs::write(outfile, output.output()) {
|
||||||
.context("failed to write veritysetup command output to a file")?;
|
bail!("Failed to write veritysetup command output to a file: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
let root = match output.root_hash() {
|
let root = match output.root_hash() {
|
||||||
Some(s) => s.to_owned(),
|
Some(s) => s.to_owned(),
|
||||||
@ -148,10 +151,11 @@ impl UpdateBuilder {
|
|||||||
fn compress_image(&self) -> Result<()> {
|
fn compress_image(&self) -> Result<()> {
|
||||||
if self.config.compress() {
|
if self.config.compress() {
|
||||||
info!("Compressing image data");
|
info!("Compressing image data");
|
||||||
cmd!("xz", "-T0 {}", self.image().display())
|
if let Err(err) = cmd!("xz", "-T0 {}", self.image().display()) {
|
||||||
.context(format!("failed to compress {}", self.image().display()))?;
|
bail!("failed to compress {:?}: {}", self.image(), err);
|
||||||
|
}
|
||||||
// Rename back to original image_data filename
|
// Rename back to original image_data filename
|
||||||
fs::rename(self.image().with_extension("xz"), self.image())?;
|
util::rename(self.image().with_extension("xz"), self.image())?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -161,14 +165,17 @@ impl UpdateBuilder {
|
|||||||
let target = self.config.workdir_path(self.target_filename());
|
let target = self.config.workdir_path(self.target_filename());
|
||||||
|
|
||||||
let mut out = File::create(&target)
|
let mut out = File::create(&target)
|
||||||
.context(format!("could not open output file {}", target.display()))?;
|
.map_err(context!("could not open output file {:?}", target))?;
|
||||||
|
|
||||||
header.write_header(&out)?;
|
header.write_header(&out)
|
||||||
|
.map_err(context!("error writing header to {:?}", target))?;
|
||||||
|
|
||||||
let mut data = File::open(&self.image())
|
let mut data = File::open(&self.image())
|
||||||
.context(format!("could not open image data file {}", self.image().display()))?;
|
.map_err(context!("could not open image data file {:?}", self.image()))?;
|
||||||
|
|
||||||
io::copy(&mut data, &mut out)
|
io::copy(&mut data, &mut out)
|
||||||
.context("error copying image data to output file")?;
|
.map_err(context!("error copying image data to output file"))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,12 +187,12 @@ impl UpdateBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let metainfo = self.generate_metainfo();
|
let metainfo = self.generate_metainfo();
|
||||||
fs::write(self.config.workdir_path("metainfo"), &metainfo)?;
|
util::write_file(self.config.workdir_path("metainfo"), &metainfo)?;
|
||||||
hdr.set_metainfo_bytes(&metainfo)?;
|
hdr.set_metainfo_bytes(&metainfo)?;
|
||||||
|
|
||||||
if self.config.channel() == "dev" {
|
if self.config.channel() == "dev" {
|
||||||
let sig = devkeys().sign(&metainfo);
|
let sig = devkeys().sign(&metainfo);
|
||||||
hdr.set_signature(sig.to_bytes())?;
|
hdr.set_signature(sig.to_bytes());
|
||||||
}
|
}
|
||||||
Ok(hdr)
|
Ok(hdr)
|
||||||
}
|
}
|
||||||
@ -195,7 +202,7 @@ impl UpdateBuilder {
|
|||||||
self._generate_metainfo().unwrap()
|
self._generate_metainfo().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _generate_metainfo(&self) -> Result<Vec<u8>> {
|
fn _generate_metainfo(&self) -> io::Result<Vec<u8>> {
|
||||||
assert!(self.verity_salt.is_some() && self.verity_root.is_some(),
|
assert!(self.verity_salt.is_some() && self.verity_root.is_some(),
|
||||||
"no verity-salt/verity-root in generate_metainfo()");
|
"no verity-salt/verity-root in generate_metainfo()");
|
||||||
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::io::Read;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
use libcitadel::Result;
|
use libcitadel::{Result, util};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct BuildConfig {
|
pub struct BuildConfig {
|
||||||
@ -39,10 +37,7 @@ impl BuildConfig {
|
|||||||
path.push("mkimage.conf");
|
path.push("mkimage.conf");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut config = match BuildConfig::from_path(&path) {
|
let mut config = BuildConfig::from_path(&path)?;
|
||||||
Ok(config) => config,
|
|
||||||
Err(e) => bail!("Failed to load config file {}: {}", path.display(), e),
|
|
||||||
};
|
|
||||||
|
|
||||||
path.pop();
|
path.pop();
|
||||||
config.basedir = path;
|
config.basedir = path;
|
||||||
@ -55,10 +50,9 @@ impl BuildConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn from_path(path: &Path) -> Result<BuildConfig> {
|
fn from_path(path: &Path) -> Result<BuildConfig> {
|
||||||
let mut f = File::open(path)?;
|
let s = util::read_to_string(path)?;
|
||||||
let mut s = String::new();
|
let config = toml::from_str::<BuildConfig>(&s)
|
||||||
f.read_to_string(&mut s)?;
|
.map_err(context!("Failed to parse build config file {:?}", path))?;
|
||||||
let config = toml::from_str::<BuildConfig>(&s)?;
|
|
||||||
config.validate()?;
|
config.validate()?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ use clap::SubCommand;
|
|||||||
use clap::AppSettings::*;
|
use clap::AppSettings::*;
|
||||||
use clap::Arg;
|
use clap::Arg;
|
||||||
use libcitadel::ResizeSize;
|
use libcitadel::ResizeSize;
|
||||||
use libcitadel::format_error;
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
pub fn main(args: Vec<String>) {
|
||||||
@ -87,7 +86,7 @@ is the final absolute size of the image.")
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Err(ref e) = result {
|
if let Err(ref e) = result {
|
||||||
eprintln!("Error: {}", format_error(e));
|
eprintln!("Error: {}", e);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,12 +124,13 @@ fn parse_resize_size(s: &str) -> Result<ResizeSize> {
|
|||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.map_err(|_| format_err!("Unable to parse size value '{}'",s))?;
|
.map_err(|_| format_err!("Unable to parse size value '{}'",s))?;
|
||||||
|
|
||||||
match unit {
|
let sz = match unit {
|
||||||
Some('g') | Some('G') => Ok(ResizeSize::gigs(size)),
|
Some('g') | Some('G') => ResizeSize::gigs(size),
|
||||||
Some('m') | Some('M') => Ok(ResizeSize::megs(size)),
|
Some('m') | Some('M') => ResizeSize::megs(size),
|
||||||
Some(c) => Err(format_err!("Unknown size unit '{}'", c)),
|
Some(c) => bail!("Unknown size unit '{}'", c),
|
||||||
None => Ok(ResizeSize::blocks(size)),
|
None => ResizeSize::blocks(size),
|
||||||
}
|
};
|
||||||
|
Ok(sz)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(arg_matches: &ArgMatches) -> Result<()> {
|
fn resize(arg_matches: &ArgMatches) -> Result<()> {
|
||||||
|
@ -4,6 +4,7 @@ use std::path::Path;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use libcitadel::Result;
|
use libcitadel::Result;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
|
||||||
pub struct DesktopFile {
|
pub struct DesktopFile {
|
||||||
@ -20,14 +21,17 @@ impl DesktopFile {
|
|||||||
|
|
||||||
pub fn write_to_dir<P: AsRef<Path>>(&self, directory: P) -> Result<()> {
|
pub fn write_to_dir<P: AsRef<Path>>(&self, directory: P) -> Result<()> {
|
||||||
let path = directory.as_ref().join(&self.filename);
|
let path = directory.as_ref().join(&self.filename);
|
||||||
let f = File::create(&path)?;
|
let f = File::create(&path)
|
||||||
self.write_to(f)?;
|
.map_err(context!("failed to open desktop file {:?}", path))?;
|
||||||
|
self.write_to(f)
|
||||||
|
.map_err(context!("error writing to desktop file {:?}", path))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to<W: Write>(&self, mut w: W) -> Result<()> {
|
pub fn write_to<W: Write>(&self, mut w: W) -> Result<()> {
|
||||||
for line in &self.lines {
|
for line in &self.lines {
|
||||||
line.write_to(&mut w)?;
|
line.write_to(&mut w)
|
||||||
|
.map_err(context!("error writing line ({:?}) to desktop file", line))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -173,7 +177,7 @@ impl Line {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_to<W: Write>(&self, mut w: W) -> Result<()> {
|
fn write_to<W: Write>(&self, mut w: W) -> io::Result<()> {
|
||||||
match *self {
|
match *self {
|
||||||
Line::Empty => writeln!(w)?,
|
Line::Empty => writeln!(w)?,
|
||||||
Line::Comment(ref s) => writeln!(w, "#{}", s)?,
|
Line::Comment(ref s) => writeln!(w, "#{}", s)?,
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::ffi::{OsStr,OsString};
|
use std::ffi::{OsStr,OsString};
|
||||||
use std::fs;
|
|
||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path,PathBuf};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use libcitadel::{Realm,Realms,Result};
|
use libcitadel::{Realm, Realms, Result, util};
|
||||||
use crate::sync::parser::DesktopFileParser;
|
use crate::sync::parser::DesktopFileParser;
|
||||||
use std::fs::DirEntry;
|
use std::fs::DirEntry;
|
||||||
use crate::sync::icons::IconSync;
|
use crate::sync::icons::IconSync;
|
||||||
@ -69,9 +68,7 @@ impl DesktopFileSync {
|
|||||||
|
|
||||||
let target = Path::new(Self::CITADEL_APPLICATIONS);
|
let target = Path::new(Self::CITADEL_APPLICATIONS);
|
||||||
|
|
||||||
if !target.exists() {
|
util::create_dir(&target)?;
|
||||||
fs::create_dir_all(&target)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if clear {
|
if clear {
|
||||||
Self::clear_target_files()?;
|
Self::clear_target_files()?;
|
||||||
@ -89,14 +86,15 @@ impl DesktopFileSync {
|
|||||||
fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<()> {
|
fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<()> {
|
||||||
let directory = Realms::current_realm_symlink().join(directory.as_ref());
|
let directory = Realms::current_realm_symlink().join(directory.as_ref());
|
||||||
if directory.exists() {
|
if directory.exists() {
|
||||||
for entry in fs::read_dir(directory)? {
|
util::read_directory(&directory, |dent| {
|
||||||
self.process_source_entry(entry?);
|
self.process_source_entry(dent);
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_source_entry(&mut self, entry: DirEntry) {
|
fn process_source_entry(&mut self, entry: &DirEntry) {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if path.extension() == Some(OsStr::new("desktop")) {
|
if path.extension() == Some(OsStr::new("desktop")) {
|
||||||
if let Some(mtime) = Self::mtime(&path) {
|
if let Some(mtime) = Self::mtime(&path) {
|
||||||
@ -106,24 +104,21 @@ impl DesktopFileSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_target_files() -> Result<()> {
|
pub fn clear_target_files() -> Result<()> {
|
||||||
for entry in fs::read_dir(Self::CITADEL_APPLICATIONS)? {
|
util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
|
||||||
let entry = entry?;
|
util::remove_file(dent.path())
|
||||||
fs::remove_file(entry.path())?;
|
})
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_missing_target_files(&mut self) -> Result<()> {
|
fn remove_missing_target_files(&mut self) -> Result<()> {
|
||||||
let sources = self.source_filenames();
|
let sources = self.source_filenames();
|
||||||
for entry in fs::read_dir(Self::CITADEL_APPLICATIONS)? {
|
util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
|
||||||
let entry = entry?;
|
if !sources.contains(&dent.file_name()) {
|
||||||
if !sources.contains(&entry.file_name()) {
|
let path = dent.path();
|
||||||
let path = entry.path();
|
|
||||||
verbose!("Removing desktop entry that no longer exists: {:?}", path);
|
verbose!("Removing desktop entry that no longer exists: {:?}", path);
|
||||||
fs::remove_file(path)?;
|
util::remove_file(path)?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mtime(path: &Path) -> Option<SystemTime> {
|
fn mtime(path: &Path) -> Option<SystemTime> {
|
||||||
|
@ -13,7 +13,8 @@ pub struct IconCache {
|
|||||||
impl IconCache {
|
impl IconCache {
|
||||||
|
|
||||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
|
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
let file = File::open(path.as_ref())?;
|
let path = path.as_ref();
|
||||||
|
let file = File::open(path).map_err(context!("failed to open {:?}", path))?;
|
||||||
Ok(IconCache { file })
|
Ok(IconCache { file })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +47,8 @@ impl IconCache {
|
|||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let mut nread = 0;
|
let mut nread = 0;
|
||||||
loop {
|
loop {
|
||||||
let n = self.file.read_at(&mut buf, (offset + nread)as u64 )?;
|
let n = self.file.read_at(&mut buf, (offset + nread)as u64 )
|
||||||
|
.map_err(context!("failed to read from icon cache at offset {}", (offset + nread)))?;
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return Ok(output);
|
return Ok(output);
|
||||||
}
|
}
|
||||||
@ -80,7 +82,8 @@ impl IconCache {
|
|||||||
fn read_exact_at(&self, buf: &mut [u8], offset: usize) -> Result<()> {
|
fn read_exact_at(&self, buf: &mut [u8], offset: usize) -> Result<()> {
|
||||||
let mut nread = 0;
|
let mut nread = 0;
|
||||||
while nread < buf.len() {
|
while nread < buf.len() {
|
||||||
let sz = self.file.read_at(&mut buf[nread..], (offset + nread) as u64)?;
|
let sz = self.file.read_at(&mut buf[nread..], (offset + nread) as u64)
|
||||||
|
.map_err(context!("failed to read from icon cache at offset {}", (offset + nread)))?;
|
||||||
nread += sz;
|
nread += sz;
|
||||||
if sz == 0 {
|
if sz == 0 {
|
||||||
bail!("bad offset");
|
bail!("bad offset");
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use crate::sync::icon_cache::IconCache;
|
use crate::sync::icon_cache::IconCache;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use libcitadel::{Result, Realms};
|
use libcitadel::{Result, Realms, util};
|
||||||
use std::cell::{RefCell, Cell};
|
use std::cell::{RefCell, Cell};
|
||||||
|
|
||||||
pub struct IconSync {
|
pub struct IconSync {
|
||||||
@ -57,14 +56,14 @@ impl IconSync {
|
|||||||
let mut names: Vec<String> = self.known.borrow().iter().map(|s| s.to_string()).collect();
|
let mut names: Vec<String> = self.known.borrow().iter().map(|s| s.to_string()).collect();
|
||||||
names.sort_unstable();
|
names.sort_unstable();
|
||||||
let out = names.join("\n") + "\n";
|
let out = names.join("\n") + "\n";
|
||||||
fs::write(Self::KNOWN_ICONS_FILE, out)?;
|
util::write_file(Self::KNOWN_ICONS_FILE, out)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_known_cache() -> Result<HashSet<String>> {
|
fn read_known_cache() -> Result<HashSet<String>> {
|
||||||
let target = Path::new(Self::KNOWN_ICONS_FILE);
|
let target = Path::new(Self::KNOWN_ICONS_FILE);
|
||||||
if target.exists() {
|
if target.exists() {
|
||||||
let content = fs::read_to_string(target)?;
|
let content = util::read_to_string(target)?;
|
||||||
Ok(content.lines().map(|s| s.to_string()).collect())
|
Ok(content.lines().map(|s| s.to_string()).collect())
|
||||||
} else {
|
} else {
|
||||||
Ok(HashSet::new())
|
Ok(HashSet::new())
|
||||||
@ -77,13 +76,14 @@ impl IconSync {
|
|||||||
return Ok(false)
|
return Ok(false)
|
||||||
}
|
}
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for entry in fs::read_dir(&base)? {
|
util::read_directory(&base, |dent| {
|
||||||
let entry = entry?;
|
let apps = dent.path().join("apps");
|
||||||
let apps = entry.path().join("apps");
|
|
||||||
if apps.exists() && self.search_subdirectory(&base, &apps, icon_name)? {
|
if apps.exists() && self.search_subdirectory(&base, &apps, icon_name)? {
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
self.add_known(icon_name);
|
self.add_known(icon_name);
|
||||||
}
|
}
|
||||||
@ -92,28 +92,28 @@ impl IconSync {
|
|||||||
|
|
||||||
fn search_subdirectory(&self, base: &Path, subdir: &Path, icon_name: &str) -> Result<bool> {
|
fn search_subdirectory(&self, base: &Path, subdir: &Path, icon_name: &str) -> Result<bool> {
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for entry in fs::read_dir(subdir)? {
|
util::read_directory(subdir, |dent| {
|
||||||
let entry = entry?;
|
let path = dent.path();
|
||||||
let path = entry.path();
|
|
||||||
if let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) {
|
if let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) {
|
||||||
if stem == icon_name {
|
if stem == icon_name {
|
||||||
self.copy_icon_file(base, &path)?;
|
self.copy_icon_file(base, &path)?;
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(found)
|
Ok(found)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_icon_file(&self, base: &Path, icon_path: &Path) -> Result<()> {
|
fn copy_icon_file(&self, base: &Path, icon_path: &Path) -> Result<()> {
|
||||||
verbose!("copy icon file {}", icon_path.display());
|
verbose!("copy icon file {}", icon_path.display());
|
||||||
let stripped = icon_path.strip_prefix(base)?;
|
let stripped = icon_path.strip_prefix(base)
|
||||||
|
.map_err(|_| format_err!("Failed to strip base path {:?} from icon path {:?}", base, icon_path))?;
|
||||||
let target = Path::new(Self::CITADEL_ICONS).join("hicolor").join(stripped);
|
let target = Path::new(Self::CITADEL_ICONS).join("hicolor").join(stripped);
|
||||||
let parent = target.parent().unwrap();
|
let parent = target.parent().unwrap();
|
||||||
if !parent.exists() {
|
util::create_dir(parent)?;
|
||||||
fs::create_dir_all(parent)?;
|
util::copy_file(icon_path, target)?;
|
||||||
}
|
|
||||||
fs::copy(icon_path, target)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,7 @@
|
|||||||
use std::io::Read;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use libcitadel::Result;
|
use libcitadel::{Result, util};
|
||||||
use crate::sync::desktop_file::{DesktopFile,Line};
|
use crate::sync::desktop_file::{DesktopFile,Line};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@ -36,11 +34,11 @@ fn is_whitelisted_key(key: &str) -> bool {
|
|||||||
fn filename_from_path(path: &Path) -> Result<&str> {
|
fn filename_from_path(path: &Path) -> Result<&str> {
|
||||||
let filename = match path.file_name() {
|
let filename = match path.file_name() {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
None => return Err(format_err!("Path {:?} has no filename component", path)),
|
None => bail!("Path {:?} has no filename component", path),
|
||||||
};
|
};
|
||||||
match filename.to_str() {
|
match filename.to_str() {
|
||||||
Some(s) => Ok(s),
|
Some(s) => Ok(s),
|
||||||
None => Err(format_err!("Filename has invalid utf8 encoding")),
|
None => bail!("Filename has invalid utf8 encoding"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct DesktopFileParser {
|
pub struct DesktopFileParser {
|
||||||
@ -66,14 +64,9 @@ impl DesktopFileParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_from_path<P: AsRef<Path>>(path: P, exec_prefix: &str) -> Result<DesktopFile> {
|
pub fn parse_from_path<P: AsRef<Path>>(path: P, exec_prefix: &str) -> Result<DesktopFile> {
|
||||||
let filename = filename_from_path(path.as_ref())?;
|
let path = path.as_ref();
|
||||||
let f = File::open(path.as_ref())?;
|
let filename = filename_from_path(path)?;
|
||||||
DesktopFileParser::parse_from_reader(f, filename, exec_prefix)
|
let buffer = util::read_to_string(path)?;
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_from_reader<T: Read>(mut r: T, filename: &str, exec_prefix: &str) -> Result<DesktopFile> {
|
|
||||||
let mut buffer = String::new();
|
|
||||||
r.read_to_string(&mut buffer)?;
|
|
||||||
DesktopFileParser::parse_from_string(&buffer, filename, exec_prefix)
|
DesktopFileParser::parse_from_string(&buffer, filename, exec_prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +75,7 @@ impl DesktopFileParser {
|
|||||||
for s in body.lines() {
|
for s in body.lines() {
|
||||||
match LineParser::parse(s) {
|
match LineParser::parse(s) {
|
||||||
Some(line) => parser.process_line(line)?,
|
Some(line) => parser.process_line(line)?,
|
||||||
None => return Err(format_err!("Failed to parse line: '{}'", s))
|
None => bail!("failed to parse line: '{}'", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(parser.desktop_file)
|
Ok(parser.desktop_file)
|
||||||
@ -92,7 +85,7 @@ impl DesktopFileParser {
|
|||||||
match line {
|
match line {
|
||||||
Line::Comment(_) | Line::Empty => {},
|
Line::Comment(_) | Line::Empty => {},
|
||||||
Line::DesktopHeader => self.seen_header = true,
|
Line::DesktopHeader => self.seen_header = true,
|
||||||
_ => return Err(format_err!("Missing Desktop Entry header"))
|
_ => bail!("missing Desktop Entry header")
|
||||||
}
|
}
|
||||||
self.desktop_file.add_line(line);
|
self.desktop_file.add_line(line);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -119,13 +112,13 @@ impl DesktopFileParser {
|
|||||||
Line::ExecLine(ref mut s) => {
|
Line::ExecLine(ref mut s) => {
|
||||||
s.insert_str(0,self.exec_prefix.as_str())
|
s.insert_str(0,self.exec_prefix.as_str())
|
||||||
},
|
},
|
||||||
Line::DesktopHeader => return Err(format_err!("Duplicate Desktop Entry header")),
|
Line::DesktopHeader => bail!("duplicate Desktop Entry header"),
|
||||||
Line::ActionHeader(ref action) => {
|
Line::ActionHeader(ref action) => {
|
||||||
if self.known_actions.contains(action) {
|
if self.known_actions.contains(action) {
|
||||||
self.current_action = Some(action.to_string());
|
self.current_action = Some(action.to_string());
|
||||||
self.in_ignored_group = false;
|
self.in_ignored_group = false;
|
||||||
} else {
|
} else {
|
||||||
return Err(format_err!("Desktop Action header with undecleared action: {}", action))
|
bail!("desktop Action header with undecleared action: {}", action)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Line::GroupHeader(_) => {
|
Line::GroupHeader(_) => {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::fs;
|
|
||||||
use std::fmt::{self,Write};
|
use std::fmt::{self,Write};
|
||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path,PathBuf};
|
||||||
|
|
||||||
@ -48,7 +47,7 @@ impl KernelInstaller {
|
|||||||
pub fn install(&mut self) -> Result<PathBuf> {
|
pub fn install(&mut self) -> Result<PathBuf> {
|
||||||
let install_path = self.install_kernel_path()?;
|
let install_path = self.install_kernel_path()?;
|
||||||
info!("Copying kernel bzImage to {}", install_path.display());
|
info!("Copying kernel bzImage to {}", install_path.display());
|
||||||
fs::copy(&self.new_kernel.path, &install_path)?;
|
util::copy_file(&self.new_kernel.path, &install_path)?;
|
||||||
|
|
||||||
self.boot_entries.rotate()?;
|
self.boot_entries.rotate()?;
|
||||||
|
|
||||||
@ -61,9 +60,6 @@ impl KernelInstaller {
|
|||||||
e.remove()?;
|
e.remove()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 0) if boot.conf does not exist, just write it. done.
|
// 0) if boot.conf does not exist, just write it. done.
|
||||||
// 1) if current boot.conf is not verified, just replace it. done.
|
// 1) if current boot.conf is not verified, just replace it. done.
|
||||||
// 2) rotate boot.conf to boot.1.conf
|
// 2) rotate boot.conf to boot.1.conf
|
||||||
@ -200,13 +196,12 @@ impl BootEntries {
|
|||||||
if !base_path.exists() {
|
if !base_path.exists() {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
for dirent in fs::read_dir(base_path)? {
|
util::read_directory(base_path, |dent| {
|
||||||
let dirent = dirent?;
|
if let Some(fname) = dent.file_name().to_str() {
|
||||||
if let Some(fname) = dirent.file_name().to_str() {
|
|
||||||
self.load_filename(fname);
|
self.load_filename(fname);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_filename(&mut self, fname: &str) {
|
fn load_filename(&mut self, fname: &str) {
|
||||||
@ -250,7 +245,7 @@ impl BootEntries {
|
|||||||
fn _rotate(&mut self) -> Result<()> {
|
fn _rotate(&mut self) -> Result<()> {
|
||||||
for entry in self.0.iter_mut().rev() {
|
for entry in self.0.iter_mut().rev() {
|
||||||
if !entry.rotate()? {
|
if !entry.rotate()? {
|
||||||
bail!("Failed to rotate boot entry {} because next index already exists", entry.path().display());
|
bail!("failed to rotate boot entry {} because next index already exists", entry.path().display());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -333,8 +328,7 @@ impl BootEntry {
|
|||||||
writeln!(&mut buffer, "title {}", self.title)?;
|
writeln!(&mut buffer, "title {}", self.title)?;
|
||||||
writeln!(&mut buffer, "linux /{}", kernel)?;
|
writeln!(&mut buffer, "linux /{}", kernel)?;
|
||||||
writeln!(&mut buffer, "options {}", self.options)?;
|
writeln!(&mut buffer, "options {}", self.options)?;
|
||||||
fs::write(self.path(), buffer)?;
|
util::write_file(self.path(), buffer)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_good(&self) -> bool {
|
fn is_good(&self) -> bool {
|
||||||
@ -351,7 +345,7 @@ impl BootEntry {
|
|||||||
|
|
||||||
fn load(&mut self) -> Result<()> {
|
fn load(&mut self) -> Result<()> {
|
||||||
let path = self.path();
|
let path = self.path();
|
||||||
for line in fs::read_to_string(&path)?.lines() {
|
for line in util::read_to_string(&path)?.lines() {
|
||||||
if line.starts_with("title ") {
|
if line.starts_with("title ") {
|
||||||
self.title = line.trim_start_matches("title ").to_owned();
|
self.title = line.trim_start_matches("title ").to_owned();
|
||||||
} else if line.starts_with("linux /") {
|
} else if line.starts_with("linux /") {
|
||||||
@ -408,7 +402,7 @@ impl BootEntry {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
verbose!("Rotating boot entry {} to {}", old_path.display(), new_path.display());
|
verbose!("Rotating boot entry {} to {}", old_path.display(), new_path.display());
|
||||||
fs::rename(old_path, new_path)?;
|
util::rename(old_path, new_path)?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,7 +412,7 @@ impl BootEntry {
|
|||||||
bzimage.remove_file()?;
|
bzimage.remove_file()?;
|
||||||
self.bzimage = None;
|
self.bzimage = None;
|
||||||
}
|
}
|
||||||
fs::remove_file(self.path())?;
|
util::remove_file(self.path())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,8 +441,7 @@ impl KernelBzImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remove_file(&self) -> Result<()> {
|
fn remove_file(&self) -> Result<()> {
|
||||||
fs::remove_file(&self.path)?;
|
util::remove_file(&self.path)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
use libcitadel::{Result, Partition, ResourceImage, ImageHeader, LogLevel, Logger};
|
use libcitadel::{Result, Partition, ResourceImage, ImageHeader, LogLevel, Logger, util};
|
||||||
use crate::update::kernel::{KernelInstaller, KernelVersion};
|
use crate::update::kernel::{KernelInstaller, KernelVersion};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs::DirEntry;
|
use std::fs::DirEntry;
|
||||||
@ -57,16 +56,15 @@ fn detect_duplicates(image: &ResourceImage) -> Result<()> {
|
|||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
for dirent in fs::read_dir(resource_dir)? {
|
util::read_directory(&resource_dir, |dent| {
|
||||||
let dirent = dirent?;
|
match ResourceImage::from_path(dent.path()) {
|
||||||
match ResourceImage::from_path(dirent.path()) {
|
|
||||||
Ok(img) => if img.metainfo().shasum() == shasum {
|
Ok(img) => if img.metainfo().shasum() == shasum {
|
||||||
bail!("A duplicate image file with the same shasum already exists at {}", img.path().display());
|
bail!("A duplicate image file with the same shasum already exists at {}", img.path().display());
|
||||||
},
|
},
|
||||||
Err(err) => warn!("{}", err),
|
Err(err) => warn!("{}", err),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_image(path: &Path, flags: u32) -> Result<()> {
|
fn install_image(path: &Path, flags: u32) -> Result<()> {
|
||||||
@ -118,12 +116,10 @@ fn remove_old_extra_images(image: &ResourceImage) -> Result<()> {
|
|||||||
let new_meta = image.header().metainfo();
|
let new_meta = image.header().metainfo();
|
||||||
let shasum = new_meta.shasum();
|
let shasum = new_meta.shasum();
|
||||||
let target_dir = target_directory(image)?;
|
let target_dir = target_directory(image)?;
|
||||||
for dirent in fs::read_dir(target_dir)? {
|
util::read_directory(&target_dir, |dent| {
|
||||||
let dirent = dirent?;
|
let path = dent.path();
|
||||||
let path = dirent.path();
|
maybe_remove_old_extra_image(&path, shasum)
|
||||||
maybe_remove_old_extra_image(&path, shasum)?;
|
})
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_remove_old_extra_image(path: &Path, shasum: &str) -> Result<()> {
|
fn maybe_remove_old_extra_image(path: &Path, shasum: &str) -> Result<()> {
|
||||||
@ -137,13 +133,11 @@ fn maybe_remove_old_extra_image(path: &Path, shasum: &str) -> Result<()> {
|
|||||||
}
|
}
|
||||||
if meta.shasum() != shasum {
|
if meta.shasum() != shasum {
|
||||||
info!("Removing old extra resource image {}", path.display());
|
info!("Removing old extra resource image {}", path.display());
|
||||||
fs::remove_file(&path)?;
|
util::remove_file(&path)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
|
fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
|
||||||
if !Path::new("/boot/loader/loader.conf").exists() {
|
if !Path::new("/boot/loader/loader.conf").exists() {
|
||||||
bail!("failed to automount /boot partition. Please manually mount correct partition.");
|
bail!("failed to automount /boot partition. Please manually mount correct partition.");
|
||||||
@ -153,7 +147,7 @@ fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
|
|||||||
let version = metainfo.version();
|
let version = metainfo.version();
|
||||||
let kernel_version = match metainfo.kernel_version() {
|
let kernel_version = match metainfo.kernel_version() {
|
||||||
Some(kv) => kv,
|
Some(kv) => kv,
|
||||||
None => bail!("Kernel image does not have kernel version field"),
|
None => bail!("kernel image does not have kernel version field"),
|
||||||
};
|
};
|
||||||
info!("kernel version is {}", kernel_version);
|
info!("kernel version is {}", kernel_version);
|
||||||
install_kernel_file(image, &kernel_version)?;
|
install_kernel_file(image, &kernel_version)?;
|
||||||
@ -164,16 +158,16 @@ fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
|
|||||||
let all_versions = all_boot_kernel_versions()?;
|
let all_versions = all_boot_kernel_versions()?;
|
||||||
let image_dir = target_directory(image)?;
|
let image_dir = target_directory(image)?;
|
||||||
let mut remove_paths = Vec::new();
|
let mut remove_paths = Vec::new();
|
||||||
for dirent in fs::read_dir(image_dir)? {
|
util::read_directory(&image_dir, |dent| {
|
||||||
let dirent = dirent?;
|
let path = dent.path();
|
||||||
let path = dirent.path();
|
|
||||||
if is_unused_kernel_image(&path, &all_versions)? {
|
if is_unused_kernel_image(&path, &all_versions)? {
|
||||||
remove_paths.push(path);
|
remove_paths.push(path);
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
for p in remove_paths {
|
for p in remove_paths {
|
||||||
fs::remove_file(p)?;
|
util::remove_file(p)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -216,14 +210,15 @@ fn install_kernel_file(image: &mut ResourceImage, kernel_version: &str) -> Resul
|
|||||||
|
|
||||||
fn all_boot_kernel_versions() -> Result<HashSet<String>> {
|
fn all_boot_kernel_versions() -> Result<HashSet<String>> {
|
||||||
let mut result = HashSet::new();
|
let mut result = HashSet::new();
|
||||||
for dirent in fs::read_dir("/boot")? {
|
util::read_directory("/boot", |dent| {
|
||||||
let dirent = dirent?;
|
if is_kernel_dirent(&dent) {
|
||||||
if is_kernel_dirent(&dirent) {
|
if let Some(kv) = KernelVersion::parse_from_path(&dent.path()) {
|
||||||
if let Some(kv) = KernelVersion::parse_from_path(&dirent.path()) {
|
|
||||||
result.insert(kv.version());
|
result.insert(kv.version());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +237,7 @@ fn install_image_file(image: &ResourceImage, filename: &str) -> Result<()> {
|
|||||||
rotate(&image_dest)?;
|
rotate(&image_dest)?;
|
||||||
}
|
}
|
||||||
info!("installing image file by moving from {} to {}", image.path().display(), image_dest.display());
|
info!("installing image file by moving from {} to {}", image.path().display(), image_dest.display());
|
||||||
fs::rename(image.path(), image_dest)?;
|
util::rename(image.path(), image_dest)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,16 +254,14 @@ fn rotate(path: &Path) -> Result<()> {
|
|||||||
}
|
}
|
||||||
let filename = path.file_name().unwrap();
|
let filename = path.file_name().unwrap();
|
||||||
let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy()));
|
let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy()));
|
||||||
if dot_zero.exists() {
|
util::remove_file(&dot_zero)?;
|
||||||
fs::remove_file(&dot_zero)?;
|
util::rename(path, &dot_zero)?;
|
||||||
}
|
|
||||||
fs::rename(path, &dot_zero)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_channel_name(channel: &str) -> Result<()> {
|
fn validate_channel_name(channel: &str) -> Result<()> {
|
||||||
if !channel.chars().all(|c| c.is_ascii_lowercase()) {
|
if !channel.chars().all(|c| c.is_ascii_lowercase()) {
|
||||||
bail!("Image has invalid channel name '{}'", channel);
|
bail!("image has invalid channel name '{}'", channel);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -334,5 +327,5 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
|||||||
return Ok(p.clone())
|
return Ok(p.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(format_err!("No suitable install partition found"))
|
bail!("no suitable install partition found")
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
nix = "0.17.0"
|
nix = "0.17.0"
|
||||||
failure = "0.1.3"
|
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use std::path::Path;
|
use std::fs::{File,OpenOptions};
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read,Write,Seek,SeekFrom};
|
use std::io::{Read,Write,Seek,SeekFrom};
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::fs::OpenOptions;
|
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use libc;
|
use libc;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
@ -112,7 +112,8 @@ impl BlockDev {
|
|||||||
oo.write(true);
|
oo.write(true);
|
||||||
}
|
}
|
||||||
let file = oo.open(path)
|
let file = oo.open(path)
|
||||||
.map_err(|e| format_err!("Failed to open block device {}: {}", path.display(), e))?;
|
.map_err(context!("failed to open block device {:?}", path))?;
|
||||||
|
|
||||||
Ok(BlockDev{file})
|
Ok(BlockDev{file})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ impl BlockDev {
|
|||||||
let mut sz = 0u64;
|
let mut sz = 0u64;
|
||||||
unsafe {
|
unsafe {
|
||||||
blk_getsize64(self.file.as_raw_fd(), &mut sz)
|
blk_getsize64(self.file.as_raw_fd(), &mut sz)
|
||||||
.map_err(|e| format_err!("Error calling getsize ioctl on block device: {}", e))?;
|
.map_err(context!("error calling getsize ioctl on block device"))?;
|
||||||
}
|
}
|
||||||
Ok(sz)
|
Ok(sz)
|
||||||
}
|
}
|
||||||
@ -138,16 +139,18 @@ impl BlockDev {
|
|||||||
fn setup_io(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
|
fn setup_io(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
|
||||||
let addr = buffer.as_ptr() as usize;
|
let addr = buffer.as_ptr() as usize;
|
||||||
if addr & ALIGNMENT_MASK != 0 {
|
if addr & ALIGNMENT_MASK != 0 {
|
||||||
bail!("block device i/o attempted with incorrectly aligned buffer: {:p}", buffer);
|
bail!("block device I/O attempted with incorrectly aligned buffer address: {:#x}", addr);
|
||||||
}
|
}
|
||||||
if buffer.len() % SECTOR_SIZE != 0 {
|
if buffer.len() % SECTOR_SIZE != 0 {
|
||||||
bail!("buffer length {} is not a multiple of sector size", buffer.len());
|
bail!("buffer length ({}) is not a multiple of sector size", buffer.len());
|
||||||
}
|
}
|
||||||
let count = buffer.len() / SECTOR_SIZE;
|
let count = buffer.len() / SECTOR_SIZE;
|
||||||
if offset + count > self.nsectors()? {
|
if offset + count > self.nsectors()? {
|
||||||
bail!("sector_io({}, {}) is past end of device", offset, buffer.len());
|
bail!("block device sector i/o ({},{}) is past end of device", offset, buffer.len());
|
||||||
}
|
}
|
||||||
self.file.seek(SeekFrom::Start((offset * SECTOR_SIZE) as u64))?;
|
self.file.seek(SeekFrom::Start((offset * SECTOR_SIZE) as u64))
|
||||||
|
.map_err(context!("I/O error accessing block device"))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,16 +158,15 @@ impl BlockDev {
|
|||||||
/// The buffer must be a multiple of sector size (512 bytes).
|
/// The buffer must be a multiple of sector size (512 bytes).
|
||||||
pub fn read_sectors(&mut self, offset: usize, buffer: &mut [u8]) -> Result<()> {
|
pub fn read_sectors(&mut self, offset: usize, buffer: &mut [u8]) -> Result<()> {
|
||||||
self.setup_io(offset, buffer)?;
|
self.setup_io(offset, buffer)?;
|
||||||
self.file.read_exact(buffer)?;
|
self.file.read_exact(buffer)
|
||||||
Ok(())
|
.map_err(context!("I/O error reading from block device"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write sectors from `buffer` to device starting at sector `offset`.
|
/// Write sectors from `buffer` to device starting at sector `offset`.
|
||||||
/// The buffer must be a multiple of sector size (512 bytes).
|
/// The buffer must be a multiple of sector size (512 bytes).
|
||||||
pub fn write_sectors(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
|
pub fn write_sectors(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
|
||||||
self.setup_io(offset, buffer)?;
|
self.setup_io(offset, buffer)?;
|
||||||
self.file.write_all(buffer)?;
|
self.file.write_all(buffer)
|
||||||
Ok(())
|
.map_err(context!("I/O error writing to block device"))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{Result, util};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CMDLINE: CommandLine = match CommandLine::load() {
|
static ref CMDLINE: CommandLine = match CommandLine::load() {
|
||||||
@ -109,7 +108,7 @@ impl CommandLine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn load() -> Result<Self> {
|
fn load() -> Result<Self> {
|
||||||
let s = fs::read_to_string("/proc/cmdline")?;
|
let s = util::read_to_string("/proc/cmdline")?;
|
||||||
let varmap = CommandLineParser::new(s).parse();
|
let varmap = CommandLineParser::new(s).parse();
|
||||||
Ok(CommandLine{varmap})
|
Ok(CommandLine{varmap})
|
||||||
}
|
}
|
||||||
@ -289,7 +288,6 @@ fn foo() {
|
|||||||
let cline = CommandLine::load().unwrap();
|
let cline = CommandLine::load().unwrap();
|
||||||
println!("hello");
|
println!("hello");
|
||||||
println!("cline: {:?}", cline.varmap);
|
println!("cline: {:?}", cline.varmap);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{Result, util};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref OS_RELEASE: Option<OsRelease> = match OsRelease::load() {
|
static ref OS_RELEASE: Option<OsRelease> = match OsRelease::load() {
|
||||||
@ -26,12 +25,13 @@ impl OsRelease {
|
|||||||
return OsRelease::load_file(path);
|
return OsRelease::load_file(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(format_err!("File not found"))
|
bail!("failed to find os-release file")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_file(path: &Path) -> Result<OsRelease> {
|
fn load_file(path: &Path) -> Result<OsRelease> {
|
||||||
let mut vars = HashMap::new();
|
let mut vars = HashMap::new();
|
||||||
for line in fs::read_to_string(path)?.lines() {
|
let content = util::read_to_string(path)?;
|
||||||
|
for line in content.lines() {
|
||||||
let (k,v) = OsRelease::parse_line(line)?;
|
let (k,v) = OsRelease::parse_line(line)?;
|
||||||
vars.insert(k,v);
|
vars.insert(k,v);
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ impl OsRelease {
|
|||||||
for q in &["'", "\""] {
|
for q in &["'", "\""] {
|
||||||
if s.starts_with(q) {
|
if s.starts_with(q) {
|
||||||
if !s.ends_with(q) || s.len() < 2 {
|
if !s.ends_with(q) || s.len() < 2 {
|
||||||
bail!("Unmatched quote character");
|
bail!("unmatched quote character in line: {}", s);
|
||||||
}
|
}
|
||||||
return Ok(s[1..s.len() - 1].to_string());
|
return Ok(s[1..s.len() - 1].to_string());
|
||||||
}
|
}
|
||||||
|
80
libcitadel/src/error.rs
Normal file
80
libcitadel/src/error.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
use std::{result, fmt, error};
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
pub type Result<T> = result::Result<T,Error>;
|
||||||
|
|
||||||
|
/// Return an `Error` from a function.
|
||||||
|
///
|
||||||
|
/// `bail!("something went wrong")` is equivalent to:
|
||||||
|
///
|
||||||
|
/// ```rust.ignore
|
||||||
|
/// return Err(Errror::message(format!("something went wrong", )));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! bail {
|
||||||
|
($e:expr) => {
|
||||||
|
return Err($crate::error::Error::message($e));
|
||||||
|
};
|
||||||
|
($fmt:expr, $($arg:tt)*) => {
|
||||||
|
return Err($crate::error::Error::message(format!($fmt, $($arg)*)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an `Error::Message` instance by formatting a string
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! format_err {
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
$crate::error::Error::message(format!($($arg)*))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// for use in map_err()
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// map_err(context!("something went wrong with {:?}", path))
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// is the same as
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// map_err(|e| format_err!("something went wrong with {:?}: {}", path, e))
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! context {
|
||||||
|
($($arg:tt)*) => { |e|
|
||||||
|
$crate::Error::with_error(format!($($arg)*), e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Message(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn message<S: Into<String>>(msg: S) -> Self {
|
||||||
|
Error::Message(msg.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_error<S,D>(msg: S, err: D) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
D: Display,
|
||||||
|
{
|
||||||
|
let msg = msg.into();
|
||||||
|
Self::message(format!("{}: {}", msg, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error {}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::Message(msg) => msg.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -50,10 +50,11 @@ impl Exec {
|
|||||||
let args: Vec<&str> = args.as_ref().split_whitespace().collect();
|
let args: Vec<&str> = args.as_ref().split_whitespace().collect();
|
||||||
let result = self.cmd
|
let result = self.cmd
|
||||||
.args(args)
|
.args(args)
|
||||||
.output()?;
|
.output()
|
||||||
|
.map_err(context!("failed to execute command {}", self.cmd_name))?;
|
||||||
|
|
||||||
for line in BufReader::new(result.stderr.as_slice()).lines() {
|
for line in BufReader::new(result.stderr.as_slice()).lines() {
|
||||||
verbose!(" {}", line?);
|
verbose!(" {}", line.unwrap());
|
||||||
}
|
}
|
||||||
self.check_cmd_status(result.status)
|
self.check_cmd_status(result.status)
|
||||||
}
|
}
|
||||||
@ -64,7 +65,8 @@ impl Exec {
|
|||||||
let args: Vec<&str> = args.as_ref().split_whitespace().collect();
|
let args: Vec<&str> = args.as_ref().split_whitespace().collect();
|
||||||
let status = self.cmd
|
let status = self.cmd
|
||||||
.args(args)
|
.args(args)
|
||||||
.status()?;
|
.status()
|
||||||
|
.map_err(context!("failed to execute command {}", self.cmd_name))?;
|
||||||
|
|
||||||
Ok(status.success())
|
Ok(status.success())
|
||||||
}
|
}
|
||||||
@ -72,7 +74,9 @@ impl Exec {
|
|||||||
pub fn output(&mut self, args: impl AsRef<str>) -> Result<String> {
|
pub fn output(&mut self, args: impl AsRef<str>) -> Result<String> {
|
||||||
self.ensure_command_exists()?;
|
self.ensure_command_exists()?;
|
||||||
self.add_args(args.as_ref());
|
self.add_args(args.as_ref());
|
||||||
let result = self.cmd.stderr(Stdio::inherit()).output()?;
|
let result = self.cmd.stderr(Stdio::inherit())
|
||||||
|
.output()
|
||||||
|
.map_err(context!("failed to execute command {}", self.cmd_name))?;
|
||||||
self.check_cmd_status(result.status)?;
|
self.check_cmd_status(result.status)?;
|
||||||
Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned())
|
Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned())
|
||||||
}
|
}
|
||||||
@ -90,11 +94,14 @@ impl Exec {
|
|||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.spawn()?;
|
.spawn()
|
||||||
|
.map_err(context!("failed to execute command {}", self.cmd_name))?;
|
||||||
|
|
||||||
let stdin = child.stdin.as_mut().unwrap();
|
let stdin = child.stdin.as_mut().unwrap();
|
||||||
io::copy(&mut r, stdin)?;
|
io::copy(&mut r, stdin)
|
||||||
let output = child.wait_with_output()?;
|
.map_err(context!("error writing to stdin of command {}", self.cmd_name))?;
|
||||||
|
let output = child.wait_with_output()
|
||||||
|
.map_err(context!("error waiting for child command {} to exit", self.cmd_name))?;
|
||||||
Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned())
|
Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,18 +128,18 @@ impl Exec {
|
|||||||
} else if path.exists() {
|
} else if path.exists() {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
Err(format_err!("Cannot execute '{}': command does not exist", self.cmd_name))
|
bail!("cannot execute '{}': command does not exist", self.cmd_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_path(filename: &str) -> Result<PathBuf> {
|
fn search_path(filename: &str) -> Result<PathBuf> {
|
||||||
let path_var = env::var("PATH")?;
|
let path_var = env::var("PATH").unwrap_or(String::new());
|
||||||
for mut path in env::split_paths(&path_var) {
|
for mut path in env::split_paths(&path_var) {
|
||||||
path.push(filename);
|
path.push(filename);
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
return Ok(path);
|
return Ok(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(format_err!("Could not find {} in $PATH", filename))
|
bail!("could not find {} in $PATH", filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,14 +151,16 @@ pub enum FileRange {
|
|||||||
|
|
||||||
|
|
||||||
fn ranged_reader<P: AsRef<Path>>(path: P, range: FileRange) -> Result<Box<dyn Read>> {
|
fn ranged_reader<P: AsRef<Path>>(path: P, range: FileRange) -> Result<Box<dyn Read>> {
|
||||||
let mut f = File::open(path.as_ref())?;
|
let mut f = File::open(path.as_ref())
|
||||||
|
.map_err(context!("failed to open input file {:?}", path.as_ref()))?;
|
||||||
let offset = match range {
|
let offset = match range {
|
||||||
FileRange::All => 0,
|
FileRange::All => 0,
|
||||||
FileRange::Offset(n) => n,
|
FileRange::Offset(n) => n,
|
||||||
FileRange::Range {offset, ..} => offset,
|
FileRange::Range {offset, ..} => offset,
|
||||||
};
|
};
|
||||||
if offset > 0 {
|
if offset > 0 {
|
||||||
f.seek(SeekFrom::Start(offset as u64))?;
|
f.seek(SeekFrom::Start(offset as u64))
|
||||||
|
.map_err(context!("failed to seek to offset {} of input file {:?}", offset, path.as_ref()))?;
|
||||||
}
|
}
|
||||||
let r = BufReader::new(f);
|
let r = BufReader::new(f);
|
||||||
if let FileRange::Range {len, ..} = range {
|
if let FileRange::Range {len, ..} = range {
|
||||||
|
@ -5,10 +5,11 @@ use std::path::Path;
|
|||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
use crate::blockdev::AlignedBuffer;
|
use crate::blockdev::AlignedBuffer;
|
||||||
use crate::{BlockDev,Result,public_key_for_channel,PublicKey};
|
use crate::{Result, BlockDev, public_key_for_channel, PublicKey};
|
||||||
use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use std::sync::atomic::{Ordering,AtomicIsize};
|
use std::sync::atomic::{Ordering,AtomicIsize};
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
/// Expected magic value in header
|
/// Expected magic value in header
|
||||||
const MAGIC: &[u8] = b"SGOS";
|
const MAGIC: &[u8] = b"SGOS";
|
||||||
@ -159,6 +160,9 @@ impl ImageHeader {
|
|||||||
/// Reload header if file has changed on disk
|
/// Reload header if file has changed on disk
|
||||||
pub fn reload_if_stale<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
|
pub fn reload_if_stale<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
if !path.exists() {
|
||||||
|
bail!("cannot reload header because image file {:?} is missing", path);
|
||||||
|
}
|
||||||
let reload = self.is_stale(path)?;
|
let reload = self.is_stale(path)?;
|
||||||
if reload {
|
if reload {
|
||||||
self.reload_file(path)?;
|
self.reload_file(path)?;
|
||||||
@ -185,9 +189,11 @@ impl ImageHeader {
|
|||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let (size,ts) = Self::file_metadata(path)?;
|
let (size,ts) = Self::file_metadata(path)?;
|
||||||
if size < Self::HEADER_SIZE {
|
if size < Self::HEADER_SIZE {
|
||||||
bail!("Cannot load image header because {} has a size of {}", path.display(), size);
|
bail!("cannot load image header from {:?} because file is too short ({})", path, size);
|
||||||
}
|
}
|
||||||
let mut f = File::open(path)?;
|
let mut f = File::open(path)
|
||||||
|
.map_err(context!("failed to load image header from {:?}", path))?;
|
||||||
|
|
||||||
let mut header = Self::from_reader(&mut f)?;
|
let mut header = Self::from_reader(&mut f)?;
|
||||||
*header.timestamp.get_mut() = ts;
|
*header.timestamp.get_mut() = ts;
|
||||||
Ok(header)
|
Ok(header)
|
||||||
@ -195,13 +201,15 @@ impl ImageHeader {
|
|||||||
|
|
||||||
// returns tuple of (size,mtime)
|
// returns tuple of (size,mtime)
|
||||||
fn file_metadata(path: &Path) -> Result<(usize, isize)> {
|
fn file_metadata(path: &Path) -> Result<(usize, isize)> {
|
||||||
let metadata = path.metadata()?;
|
let metadata = path.metadata()
|
||||||
|
.map_err(context!("failed to load image header from {:?}", path))?;
|
||||||
Ok((metadata.len() as usize, metadata.mtime() as isize))
|
Ok((metadata.len() as usize, metadata.mtime() as isize))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_reader<R: Read>(r: &mut R) -> Result<Self> {
|
pub fn from_reader<R: Read>(r: &mut R) -> Result<Self> {
|
||||||
let mut v = vec![0u8; Self::HEADER_SIZE];
|
let mut v = vec![0u8; Self::HEADER_SIZE];
|
||||||
r.read_exact(&mut v)?;
|
r.read_exact(&mut v)
|
||||||
|
.map_err(context!("error reading header bytes"))?;
|
||||||
Self::from_slice(&v)
|
Self::from_slice(&v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,12 +226,9 @@ impl ImageHeader {
|
|||||||
pub fn from_partition<P: AsRef<Path>>(path: P) -> Result<Self> {
|
pub fn from_partition<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
let mut dev = BlockDev::open_ro(path.as_ref())?;
|
let mut dev = BlockDev::open_ro(path.as_ref())?;
|
||||||
let nsectors = dev.nsectors()?;
|
let nsectors = dev.nsectors()?;
|
||||||
ensure!(
|
if nsectors < 8 {
|
||||||
nsectors >= 8,
|
bail!("cannot load/store header from block device {:?} because it's too short ({} sectors)", path.as_ref(), nsectors);
|
||||||
"{} is a block device bit it's too short ({} sectors)",
|
}
|
||||||
path.as_ref().display(),
|
|
||||||
nsectors
|
|
||||||
);
|
|
||||||
let mut buffer = AlignedBuffer::new(Self::HEADER_SIZE);
|
let mut buffer = AlignedBuffer::new(Self::HEADER_SIZE);
|
||||||
dev.read_sectors(nsectors - 8, buffer.as_mut())?;
|
dev.read_sectors(nsectors - 8, buffer.as_mut())?;
|
||||||
Self::from_slice(buffer.as_ref())
|
Self::from_slice(buffer.as_ref())
|
||||||
@ -232,12 +237,9 @@ impl ImageHeader {
|
|||||||
pub fn write_partition<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
pub fn write_partition<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
let mut dev = BlockDev::open_rw(path.as_ref())?;
|
let mut dev = BlockDev::open_rw(path.as_ref())?;
|
||||||
let nsectors = dev.nsectors()?;
|
let nsectors = dev.nsectors()?;
|
||||||
ensure!(
|
if nsectors < 8 {
|
||||||
nsectors >= 8,
|
bail!("cannot load/store header from block device {:?} because it's too short ({} sectors)", path.as_ref(), nsectors);
|
||||||
"{} is a block device bit it's too short ({} sectors)",
|
}
|
||||||
path.as_ref().display(),
|
|
||||||
nsectors
|
|
||||||
);
|
|
||||||
let lock = self.bytes();
|
let lock = self.bytes();
|
||||||
let buffer = AlignedBuffer::from_slice(&lock.0);
|
let buffer = AlignedBuffer::from_slice(&lock.0);
|
||||||
dev.write_sectors(nsectors - 8, buffer.as_ref())?;
|
dev.write_sectors(nsectors - 8, buffer.as_ref())?;
|
||||||
@ -273,7 +275,7 @@ impl ImageHeader {
|
|||||||
let mut lock = self.metainfo.lock().unwrap();
|
let mut lock = self.metainfo.lock().unwrap();
|
||||||
let mb = self.metainfo_bytes();
|
let mb = self.metainfo_bytes();
|
||||||
let metainfo = MetaInfo::parse_bytes(&mb)
|
let metainfo = MetaInfo::parse_bytes(&mb)
|
||||||
.ok_or_else(|| format_err!("ImageHeader has invalid metainfo"))?;
|
.ok_or(format_err!("image header has invalid metainfo"))?;
|
||||||
*lock = Some(Arc::new(metainfo));
|
*lock = Some(Arc::new(metainfo));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -335,13 +337,13 @@ impl ImageHeader {
|
|||||||
|
|
||||||
pub fn update_metainfo<P: AsRef<Path>>(&self, metainfo_bytes: &[u8], signature: &[u8], path: P) -> Result<()> {
|
pub fn update_metainfo<P: AsRef<Path>>(&self, metainfo_bytes: &[u8], signature: &[u8], path: P) -> Result<()> {
|
||||||
self.set_metainfo_bytes(metainfo_bytes)?;
|
self.set_metainfo_bytes(metainfo_bytes)?;
|
||||||
self.set_signature(signature)?;
|
self.set_signature(signature);
|
||||||
self.write_header_to(path)
|
self.write_header_to(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_metainfo_bytes(&self, bytes: &[u8]) -> Result<()> {
|
pub fn set_metainfo_bytes(&self, bytes: &[u8]) -> Result<()> {
|
||||||
let metainfo = MetaInfo::parse_bytes(bytes)
|
let metainfo = MetaInfo::parse_bytes(bytes)
|
||||||
.ok_or_else(|| format_err!("Could not parse metainfo bytes as valid metainfo document"))?;
|
.ok_or(format_err!("cannot parse header metainfo bytes as a valid metainfo document"))?;
|
||||||
|
|
||||||
let mut lock = self.metainfo.lock().unwrap();
|
let mut lock = self.metainfo.lock().unwrap();
|
||||||
self.with_bytes_mut(|bs| {
|
self.with_bytes_mut(|bs| {
|
||||||
@ -369,18 +371,15 @@ impl ImageHeader {
|
|||||||
self.read_bytes(METAINFO_OFFSET + mlen, SIGNATURE_LENGTH)
|
self.read_bytes(METAINFO_OFFSET + mlen, SIGNATURE_LENGTH)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_signature(&self, signature: &[u8]) -> Result<()> {
|
pub fn set_signature(&self, signature: &[u8]) {
|
||||||
if signature.len() != SIGNATURE_LENGTH {
|
assert_eq!(signature.len(), SIGNATURE_LENGTH, "Signature has invalid length");
|
||||||
bail!("Signature has invalid length: {}", signature.len());
|
|
||||||
}
|
|
||||||
let mlen = self.metainfo_len();
|
let mlen = self.metainfo_len();
|
||||||
self.write_bytes(8 + mlen, signature);
|
self.write_bytes(8 + mlen, signature);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_signature(&self) -> Result<()> {
|
pub fn clear_signature(&self) {
|
||||||
let zeros = vec![0u8; SIGNATURE_LENGTH];
|
let zeros = vec![0u8; SIGNATURE_LENGTH];
|
||||||
self.set_signature(&zeros)
|
self.set_signature(&zeros);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn public_key(&self) -> Result<Option<PublicKey>> {
|
pub fn public_key(&self) -> Result<Option<PublicKey>> {
|
||||||
@ -391,13 +390,16 @@ impl ImageHeader {
|
|||||||
pubkey.verify(&self.metainfo_bytes(), &self.signature())
|
pubkey.verify(&self.metainfo_bytes(), &self.signature())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_header<W: Write>(&self, mut writer: W) -> Result<()> {
|
pub fn write_header<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||||
self.with_bytes(|bs| writer.write_all(&bs.0))?;
|
self.with_bytes(|bs| writer.write_all(&bs.0))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_header_to<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
pub fn write_header_to<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
self.write_header(OpenOptions::new().write(true).open(path.as_ref())?)
|
let path = path.as_ref();
|
||||||
|
let w = OpenOptions::new().write(true).open(path)
|
||||||
|
.map_err(context!("failed to open image file {:?} to write header", path))?;
|
||||||
|
self.write_header(w)
|
||||||
|
.map_err(context!("error writing header to image file {:?}", path))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_u8(&self, idx: usize) -> u8 {
|
fn read_u8(&self, idx: usize) -> u8 {
|
||||||
|
@ -38,9 +38,10 @@ impl KeyRing {
|
|||||||
|
|
||||||
pub fn load<P: AsRef<Path>>(path: P, passphrase: &str) -> Result<Self> {
|
pub fn load<P: AsRef<Path>>(path: P, passphrase: &str) -> Result<Self> {
|
||||||
let mut sbox = SecretBox::new(path.as_ref());
|
let mut sbox = SecretBox::new(path.as_ref());
|
||||||
sbox.read().map_err(|e| format_err!("Error reading keyring file: {}", e))?;
|
sbox.read().map_err(context!("error reading keyring file"))?;
|
||||||
let mut bytes = sbox.open(passphrase)?;
|
let mut bytes = sbox.open(passphrase)?;
|
||||||
let keyring = toml::from_slice::<KeyRing>(&bytes)?;
|
let keyring = toml::from_slice::<KeyRing>(&bytes)
|
||||||
|
.map_err(context!("failed to parse keyring file {:?}", path.as_ref()))?;
|
||||||
bytes.iter_mut().for_each(|b| *b = 0);
|
bytes.iter_mut().for_each(|b| *b = 0);
|
||||||
Ok(keyring)
|
Ok(keyring)
|
||||||
}
|
}
|
||||||
@ -73,13 +74,13 @@ impl KeyRing {
|
|||||||
info!("Found {} key with request_key", name);
|
info!("Found {} key with request_key", name);
|
||||||
return Ok(key);
|
return Ok(key);
|
||||||
}
|
}
|
||||||
Err(format_err!("kernel key '{}' not found", name))
|
bail!("kernel key '{}' not found", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_keys_to_kernel(&self) -> Result<()> {
|
pub fn add_keys_to_kernel(&self) -> Result<()> {
|
||||||
for (k,v) in self.keypairs.iter() {
|
for (k,v) in self.keypairs.iter() {
|
||||||
info!("Adding {} to kernel keystore", k.as_str());
|
info!("Adding {} to kernel keystore", k.as_str());
|
||||||
let bytes = hex::decode(v)?;
|
let bytes = hex::decode(v).map_err(|_| format_err!("failed to hex decode ({})", v))?;
|
||||||
let key = KernelKey::add_key("user", k.as_str(), &bytes, KEY_SPEC_USER_KEYRING)?;
|
let key = KernelKey::add_key("user", k.as_str(), &bytes, KEY_SPEC_USER_KEYRING)?;
|
||||||
key.set_perm(0x3f03_0000)?;
|
key.set_perm(0x3f03_0000)?;
|
||||||
}
|
}
|
||||||
@ -96,12 +97,18 @@ impl KeyRing {
|
|||||||
let salt = pwhash::gen_salt();
|
let salt = pwhash::gen_salt();
|
||||||
let nonce = secretbox::gen_nonce();
|
let nonce = secretbox::gen_nonce();
|
||||||
let key = SecretBox::passphrase_to_key(passphrase, &salt)?;
|
let key = SecretBox::passphrase_to_key(passphrase, &salt)?;
|
||||||
let bytes = toml::to_vec(self)?;
|
let bytes = toml::to_vec(self)
|
||||||
|
.map_err(context!("failed to serialize keyring"))?;
|
||||||
let ciphertext = secretbox::seal(&bytes, &nonce, &key);
|
let ciphertext = secretbox::seal(&bytes, &nonce, &key);
|
||||||
|
|
||||||
let mut file = fs::File::create(path.as_ref())?;
|
Self::write_keyring(path.as_ref(), &salt.0, &nonce.0, &ciphertext)
|
||||||
file.write_all(&salt.0)?;
|
.map_err(context!("error writing keyring file {:?}", path.as_ref()))
|
||||||
file.write_all(&nonce.0)?;
|
}
|
||||||
|
|
||||||
|
fn write_keyring(path: &Path, salt: &[u8], nonce: &[u8], ciphertext: &[u8]) -> io::Result<()> {
|
||||||
|
let mut file = fs::File::create(path)?;
|
||||||
|
file.write_all(&salt)?;
|
||||||
|
file.write_all(&nonce)?;
|
||||||
file.write_all(&ciphertext)?;
|
file.write_all(&ciphertext)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -143,6 +150,12 @@ impl SecretBox {
|
|||||||
if !self.data.is_empty() {
|
if !self.data.is_empty() {
|
||||||
self.data.clear();
|
self.data.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.read_keyring_file()
|
||||||
|
.map_err(context!("error reading keyring file {:?}", self.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_keyring_file(&mut self) -> io::Result<()> {
|
||||||
let mut file = fs::File::open(&self.path)?;
|
let mut file = fs::File::open(&self.path)?;
|
||||||
file.read_exact(&mut self.salt.0)?;
|
file.read_exact(&mut self.salt.0)?;
|
||||||
file.read_exact(&mut self.nonce.0)?;
|
file.read_exact(&mut self.nonce.0)?;
|
||||||
@ -153,14 +166,14 @@ impl SecretBox {
|
|||||||
fn open(&self, passphrase: &str) -> Result<Vec<u8>> {
|
fn open(&self, passphrase: &str) -> Result<Vec<u8>> {
|
||||||
let key = Self::passphrase_to_key(passphrase, &self.salt)?;
|
let key = Self::passphrase_to_key(passphrase, &self.salt)?;
|
||||||
let result = secretbox::open(&self.data, &self.nonce, &key)
|
let result = secretbox::open(&self.data, &self.nonce, &key)
|
||||||
.map_err(|_| format_err!("Failed to decrypt {}", self.path.display()))?;
|
.map_err(|_| format_err!("failed to decrypt {:?}", self.path))?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn passphrase_to_key(passphrase: &str, salt: &Salt) -> Result<secretbox::Key> {
|
fn passphrase_to_key(passphrase: &str, salt: &Salt) -> Result<secretbox::Key> {
|
||||||
let mut keybuf = [0; secretbox::KEYBYTES];
|
let mut keybuf = [0; secretbox::KEYBYTES];
|
||||||
pwhash::derive_key(&mut keybuf, passphrase.as_bytes(), salt, pwhash::OPSLIMIT_INTERACTIVE, pwhash::MEMLIMIT_INTERACTIVE)
|
pwhash::derive_key(&mut keybuf, passphrase.as_bytes(), salt, pwhash::OPSLIMIT_INTERACTIVE, pwhash::MEMLIMIT_INTERACTIVE)
|
||||||
.map_err(|_| format_err!("Failed to derive key"))?;
|
.map_err(|_| format_err!("failed to derive key"))?;
|
||||||
Ok(secretbox::Key(keybuf))
|
Ok(secretbox::Key(keybuf))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +225,7 @@ impl KernelKey {
|
|||||||
let mut size = 0;
|
let mut size = 0;
|
||||||
loop {
|
loop {
|
||||||
size = match self.buffer_request(KEYCTL_DESCRIBE, size) {
|
size = match self.buffer_request(KEYCTL_DESCRIBE, size) {
|
||||||
BufferResult::Err(err) => return Err(format_err!("Error calling KEYCTL_DESCRIBE on key: {}", err)),
|
BufferResult::Err(err) => bail!("error calling KEYCTL_DESCRIBE on key: {}", err),
|
||||||
BufferResult::Ok(vec) => return Ok(String::from_utf8(vec).expect("KEYCTL_DESCRIBE returned bad utf8")),
|
BufferResult::Ok(vec) => return Ok(String::from_utf8(vec).expect("KEYCTL_DESCRIBE returned bad utf8")),
|
||||||
BufferResult::TooSmall(sz) => sz,
|
BufferResult::TooSmall(sz) => sz,
|
||||||
}
|
}
|
||||||
@ -231,7 +244,7 @@ impl KernelKey {
|
|||||||
let mut size = 0;
|
let mut size = 0;
|
||||||
loop {
|
loop {
|
||||||
size = match self.buffer_request(KEYCTL_READ, size) {
|
size = match self.buffer_request(KEYCTL_READ, size) {
|
||||||
BufferResult::Err(err) => return Err(format_err!("Error reading key: {}", err)),
|
BufferResult::Err(err) => bail!("Error reading key: {}", err),
|
||||||
BufferResult::Ok(buffer) => return Ok(buffer),
|
BufferResult::Ok(buffer) => return Ok(buffer),
|
||||||
BufferResult::TooSmall(sz) => sz + 1,
|
BufferResult::TooSmall(sz) => sz + 1,
|
||||||
}
|
}
|
||||||
@ -242,14 +255,14 @@ impl KernelKey {
|
|||||||
if size == 0 {
|
if size == 0 {
|
||||||
return match keyctl1(command, self.id()) {
|
return match keyctl1(command, self.id()) {
|
||||||
Err(err) => BufferResult::Err(err),
|
Err(err) => BufferResult::Err(err),
|
||||||
Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctl returned bad size")),
|
Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctl returned bad size").into()),
|
||||||
Ok(n) => BufferResult::TooSmall(n as usize),
|
Ok(n) => BufferResult::TooSmall(n as usize),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let mut buffer = vec![0u8; size];
|
let mut buffer = vec![0u8; size];
|
||||||
match keyctl3(command, self.id(), buffer.as_ptr() as u64, buffer.len() as u64) {
|
match keyctl3(command, self.id(), buffer.as_ptr() as u64, buffer.len() as u64) {
|
||||||
Err(err) => BufferResult::Err(err),
|
Err(err) => BufferResult::Err(err),
|
||||||
Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctrl returned bad size {}", n)),
|
Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctrl returned bad size {}", n).into()),
|
||||||
Ok(sz) if size >= (sz as usize) => {
|
Ok(sz) if size >= (sz as usize) => {
|
||||||
let sz = sz as usize;
|
let sz = sz as usize;
|
||||||
if size > sz {
|
if size > sz {
|
||||||
@ -294,7 +307,7 @@ fn sys_keyctl(command: c_int, arg2: c_ulong, arg3: c_ulong, arg4: c_ulong, arg5:
|
|||||||
unsafe {
|
unsafe {
|
||||||
let r = libc::syscall(libc::SYS_keyctl, command, arg2, arg3, arg4, arg5);
|
let r = libc::syscall(libc::SYS_keyctl, command, arg2, arg3, arg4, arg5);
|
||||||
if r == -1 {
|
if r == -1 {
|
||||||
Err(io::Error::last_os_error().into())
|
bail!("error calling sys_keyctl(): {}", io::Error::last_os_error());
|
||||||
} else {
|
} else {
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
@ -305,7 +318,7 @@ fn _request_key(key_type: *const c_char, description: *const c_char) -> Result<c
|
|||||||
unsafe {
|
unsafe {
|
||||||
let r = libc::syscall(libc::SYS_request_key, key_type, description, 0, 0);
|
let r = libc::syscall(libc::SYS_request_key, key_type, description, 0, 0);
|
||||||
if r == -1 {
|
if r == -1 {
|
||||||
Err(io::Error::last_os_error().into())
|
bail!("error calling sys_request_key(): {}", io::Error::last_os_error());
|
||||||
} else {
|
} else {
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
@ -316,7 +329,7 @@ fn _add_key(key_type: *const c_char, description: *const c_char, payload: *const
|
|||||||
unsafe {
|
unsafe {
|
||||||
let r = libc::syscall(libc::SYS_add_key, key_type, description, payload, plen, ring_id);
|
let r = libc::syscall(libc::SYS_add_key, key_type, description, payload, plen, ring_id);
|
||||||
if r == -1 {
|
if r == -1 {
|
||||||
Err(io::Error::last_os_error().into())
|
bail!("error calling sys_add_key(): {}", io::Error::last_os_error());
|
||||||
} else {
|
} else {
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use crate::Result;
|
|
||||||
|
|
||||||
use sodiumoxide::randombytes::randombytes_into;
|
use sodiumoxide::randombytes::randombytes_into;
|
||||||
use sodiumoxide::crypto::sign::{self,Seed,SEEDBYTES,PUBLICKEYBYTES};
|
use sodiumoxide::crypto::sign::{self,Seed,SEEDBYTES,PUBLICKEYBYTES};
|
||||||
use hex;
|
use hex;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Keys for signing or verifying signatures. Small convenience
|
/// Keys for signing or verifying signatures. Small convenience
|
||||||
/// wrapper around `sodiumoxide::crypto::sign`.
|
/// wrapper around `sodiumoxide::crypto::sign`.
|
||||||
@ -18,10 +18,11 @@ pub struct Signature(sign::Signature);
|
|||||||
impl PublicKey {
|
impl PublicKey {
|
||||||
|
|
||||||
pub fn from_hex(hex: &str) -> Result<PublicKey> {
|
pub fn from_hex(hex: &str) -> Result<PublicKey> {
|
||||||
let bytes = hex::decode(hex)?;
|
let bytes = hex::decode(hex)
|
||||||
|
.map_err(context!("error hex decoding public key"))?;
|
||||||
|
|
||||||
if bytes.len() != PUBLICKEYBYTES {
|
if bytes.len() != PUBLICKEYBYTES {
|
||||||
bail!("Hex encoded public key has invalid length: {}", bytes.len());
|
bail!("hex encoded public key has invalid length: {}", bytes.len());
|
||||||
}
|
}
|
||||||
let pubkey = sign::PublicKey::from_slice(&bytes)
|
let pubkey = sign::PublicKey::from_slice(&bytes)
|
||||||
.expect("PublicKey::from_slice() failed");
|
.expect("PublicKey::from_slice() failed");
|
||||||
@ -49,13 +50,14 @@ impl KeyPair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_hex(hex: &str) -> Result<KeyPair> {
|
pub fn from_hex(hex: &str) -> Result<KeyPair> {
|
||||||
let bytes = hex::decode(hex)?;
|
let bytes = hex::decode(hex)
|
||||||
|
.map_err(context!("Error hex decoding key pair"))?;
|
||||||
KeyPair::from_bytes(&bytes)
|
KeyPair::from_bytes(&bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<KeyPair> {
|
pub fn from_bytes(bytes: &[u8]) -> Result<KeyPair> {
|
||||||
if bytes.len() != SEEDBYTES {
|
if bytes.len() != SEEDBYTES {
|
||||||
bail!("Hex encoded keypair has incorrect length");
|
bail!("hex encoded keypair has incorrect length");
|
||||||
}
|
}
|
||||||
let seed = sign::Seed::from_slice(&bytes).expect("Seed::from_slice() failed");
|
let seed = sign::Seed::from_slice(&bytes).expect("Seed::from_slice() failed");
|
||||||
Ok(KeyPair(seed))
|
Ok(KeyPair(seed))
|
||||||
|
@ -1,22 +1,8 @@
|
|||||||
#[macro_use] extern crate failure;
|
|
||||||
#[macro_use] extern crate nix;
|
#[macro_use] extern crate nix;
|
||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use] extern crate serde_derive;
|
||||||
#[macro_use] extern crate lazy_static;
|
#[macro_use] extern crate lazy_static;
|
||||||
|
|
||||||
use std::result;
|
#[macro_use] pub mod error;
|
||||||
use failure::Error;
|
|
||||||
|
|
||||||
pub fn format_error(err: &Error) -> String {
|
|
||||||
let mut output = err.to_string();
|
|
||||||
let mut prev = err.as_fail();
|
|
||||||
while let Some(next) = prev.cause() {
|
|
||||||
output.push_str(": ");
|
|
||||||
output.push_str(&next.to_string());
|
|
||||||
prev = next;
|
|
||||||
}
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_use] mod log;
|
#[macro_use] mod log;
|
||||||
#[macro_use] mod exec;
|
#[macro_use] mod exec;
|
||||||
mod blockdev;
|
mod blockdev;
|
||||||
@ -87,7 +73,7 @@ pub fn public_key_for_channel(channel: &str) -> Result<Option<PublicKey>> {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T,Error>;
|
pub use error::{Result,Error};
|
||||||
|
|
||||||
pub const BLOCK_SIZE: usize = 4096;
|
pub const BLOCK_SIZE: usize = 4096;
|
||||||
|
|
||||||
|
@ -109,8 +109,10 @@ impl LogOutput for DefaultLogOutput {
|
|||||||
|
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
let mut lock = stdout.lock();
|
let mut lock = stdout.lock();
|
||||||
lock.write_all(line.as_bytes())?;
|
lock.write_all(line.as_bytes())
|
||||||
lock.flush()?;
|
.map_err(context!("error writing log line to stdout"))?;
|
||||||
|
lock.flush()
|
||||||
|
.map_err(context!("error flushing stdout"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use std::path::{Path,PathBuf};
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use crate::{Result,ImageHeader,MetaInfo,Mounts,PublicKey,public_key_for_channel};
|
use std::path::{Path,PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{Result, ImageHeader, MetaInfo, Mounts, PublicKey, public_key_for_channel,util};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Partition {
|
pub struct Partition {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
@ -121,17 +123,21 @@ impl Partition {
|
|||||||
|
|
||||||
pub fn write_status(&mut self, status: u8) -> Result<()> {
|
pub fn write_status(&mut self, status: u8) -> Result<()> {
|
||||||
self.header().set_status(status);
|
self.header().set_status(status);
|
||||||
self.header().write_partition(&self.path)
|
self.write_header()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_flag_and_write(&mut self, flag: u8) -> Result<()> {
|
pub fn set_flag_and_write(&mut self, flag: u8) -> Result<()> {
|
||||||
self.header().set_flag(flag);
|
self.header().set_flag(flag);
|
||||||
self.header().write_partition(&self.path)
|
self.write_header()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_flag_and_write(&mut self, flag: u8) -> Result<()> {
|
pub fn clear_flag_and_write(&mut self, flag: u8) -> Result<()> {
|
||||||
self.header().clear_flag(flag);
|
self.header().clear_flag(flag);
|
||||||
self.header().write_partition(&self.path)
|
self.write_header()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_header(&self) -> Result<()> {
|
||||||
|
self.header().write_partition(&self.path).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called at boot to perform various checks and possibly
|
/// Called at boot to perform various checks and possibly
|
||||||
@ -180,29 +186,36 @@ fn is_in_use(path: &Path) -> Result<bool> {
|
|||||||
//
|
//
|
||||||
fn count_block_holders(path: &Path) -> Result<usize> {
|
fn count_block_holders(path: &Path) -> Result<usize> {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
bail!("Path to rootfs device does not exist: {}", path.display());
|
bail!("path {:?} to rootfs device does not exist", path);
|
||||||
}
|
}
|
||||||
let resolved = fs::canonicalize(path)?;
|
let resolved = fs::canonicalize(path)
|
||||||
|
.map_err(context!("failed to canonicalize path {:?}", path))?;
|
||||||
let fname = match resolved.file_name() {
|
let fname = match resolved.file_name() {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => bail!("path does not have filename?"),
|
None => bail!("path {:?} does not have a filename", resolved),
|
||||||
};
|
};
|
||||||
let holders_dir =
|
let holders_dir =
|
||||||
Path::new("/sys/block")
|
Path::new("/sys/block")
|
||||||
.join(fname)
|
.join(fname)
|
||||||
.join("holders");
|
.join("holders");
|
||||||
let count = fs::read_dir(holders_dir)?.count();
|
|
||||||
|
let count = fs::read_dir(&holders_dir)
|
||||||
|
.map_err(context!("cannot read directory {:?}", holders_dir))?
|
||||||
|
.count();
|
||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rootfs_partition_paths() -> Result<Vec<PathBuf>> {
|
fn rootfs_partition_paths() -> Result<Vec<PathBuf>> {
|
||||||
let mut rootfs_paths = Vec::new();
|
let mut rootfs_paths = Vec::new();
|
||||||
for dent in fs::read_dir("/dev/mapper")? {
|
|
||||||
let path = dent?.path();
|
util::read_directory("/dev/mapper", |dent| {
|
||||||
|
let path = dent.path();
|
||||||
if is_path_rootfs(&path) {
|
if is_path_rootfs(&path) {
|
||||||
rootfs_paths.push(path);
|
rootfs_paths.push(path);
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
|
||||||
|
})?;
|
||||||
Ok(rootfs_paths)
|
Ok(rootfs_paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use toml;
|
use toml;
|
||||||
use crate::{Result, Realms};
|
use crate::{Result, Realms, util};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref GLOBAL_CONFIG: RealmConfig = RealmConfig::load_global_config();
|
pub static ref GLOBAL_CONFIG: RealmConfig = RealmConfig::load_global_config();
|
||||||
@ -147,16 +147,14 @@ impl RealmConfig {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_config<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
pub fn write_to<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
let serialized = toml::to_string(self)?;
|
let serialized = toml::to_string(self)
|
||||||
fs::write(path.as_ref(), serialized)?;
|
.map_err(context!("failed to serialize realm config"))?;
|
||||||
Ok(())
|
util::write_file(path, serialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&self) -> Result<()> {
|
pub fn write(&self) -> Result<()> {
|
||||||
let serialized = toml::to_string(self)?;
|
self.write_to(&self.path)
|
||||||
fs::write(&self.path, serialized)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_mtime(&self) -> i64 {
|
fn read_mtime(&self) -> i64 {
|
||||||
@ -171,8 +169,9 @@ impl RealmConfig {
|
|||||||
let path = self.path.clone();
|
let path = self.path.clone();
|
||||||
|
|
||||||
if self.path.exists() {
|
if self.path.exists() {
|
||||||
let s = fs::read_to_string(&self.path)?;
|
let s = util::read_to_string(&self.path)?;
|
||||||
*self = toml::from_str(&s)?;
|
*self = toml::from_str(&s)
|
||||||
|
.map_err(context!("Failed to parse realm config"))?;
|
||||||
} else {
|
} else {
|
||||||
*self = Self::empty();
|
*self = Self::empty();
|
||||||
}
|
}
|
||||||
|
@ -49,26 +49,22 @@ impl RealmCreateDestroy {
|
|||||||
|
|
||||||
fn create_realm_directory(&self) -> Result<()> {
|
fn create_realm_directory(&self) -> Result<()> {
|
||||||
self.create_home()?;
|
self.create_home()?;
|
||||||
self.move_from_temp()?;
|
self.move_from_temp()
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_home(&self) -> Result<()> {
|
fn create_home(&self) -> Result<()> {
|
||||||
let home = self.temp_basepath().join("home");
|
let home = self.temp_basepath().join("home");
|
||||||
|
|
||||||
fs::create_dir_all(&home)
|
util::create_dir(&home)?;
|
||||||
.map_err(|e| format_err!("failed to create directory {}: {}", home.display(), e))?;
|
util::chown(&home, 1000, 1000)?;
|
||||||
util::chown(&home, 1000, 1000)
|
|
||||||
.map_err(|e| format_err!("failed to change ownership of {} to 1000:1000: {}", home.display(), e))?;
|
|
||||||
|
|
||||||
let skel = Path::new(Realms::BASE_PATH).join("skel");
|
let skel = Path::new(Realms::BASE_PATH).join("skel");
|
||||||
|
|
||||||
if skel.exists() {
|
if skel.exists() {
|
||||||
info!("Populating realm home directory with files from {}", skel.display());
|
info!("Populating realm home directory with files from {}", skel.display());
|
||||||
util::copy_tree(&skel, &home)
|
util::copy_tree(&skel, &home)
|
||||||
.map_err(|e| format_err!("failed to copy tree of files from {} to {}: {}", skel.display(), home.display(), e))?;
|
.map_err(context!("failed to copy tree of files from {:?} to {:?}", skel, home))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,8 +74,7 @@ impl RealmCreateDestroy {
|
|||||||
if to.exists() {
|
if to.exists() {
|
||||||
bail!("Cannot move temporary directory {} to {} because the target already exists", from.display(), to.display());
|
bail!("Cannot move temporary directory {} to {} because the target already exists", from.display(), to.display());
|
||||||
}
|
}
|
||||||
fs::rename(from, to)?;
|
util::rename(&from, &to)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_to_temp(&self) -> Result<()> {
|
fn move_to_temp(&self) -> Result<()> {
|
||||||
@ -89,14 +84,9 @@ impl RealmCreateDestroy {
|
|||||||
bail!("Cannot move realm directory {} to {} because the target already exists", from.display(), to.display());
|
bail!("Cannot move realm directory {} to {} because the target already exists", from.display(), to.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !Self::tmpdir().exists() {
|
let tmpdir = Self::tmpdir();
|
||||||
fs::create_dir_all(Self::tmpdir())?;
|
util::create_dir(&tmpdir)?;
|
||||||
}
|
util::rename(&from, &to)
|
||||||
|
|
||||||
fs::rename(from, to)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_realm(&self, save_home: bool) -> Result<()> {
|
pub fn delete_realm(&self, save_home: bool) -> Result<()> {
|
||||||
@ -106,22 +96,19 @@ impl RealmCreateDestroy {
|
|||||||
self.save_home_for_delete()?;
|
self.save_home_for_delete()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("removing realm directory {}", self.temp_basepath().display());
|
let realmdir = self.temp_basepath();
|
||||||
fs::remove_dir_all(self.temp_basepath())?;
|
info!("removing realm directory {:?}", realmdir);
|
||||||
Ok(())
|
fs::remove_dir_all(&realmdir)
|
||||||
|
.map_err(context!("error removing realm directory {:?}", realmdir))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_home_for_delete(&self) -> Result<()> {
|
fn save_home_for_delete(&self) -> Result<()> {
|
||||||
if !Path::new("/realms/removed").exists() {
|
util::create_dir("/realms/removed")?;
|
||||||
fs::create_dir("/realms/removed")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let target = self.home_save_directory();
|
let target = self.home_save_directory();
|
||||||
let home = self.temp_basepath().join("home");
|
let home = self.temp_basepath().join("home");
|
||||||
fs::rename(&home, &target)
|
|
||||||
.map_err(|e| format_err!("unable to move realm home directory to {}: {}", target.display(), e))?;
|
|
||||||
|
|
||||||
|
util::rename(&home, &target)?;
|
||||||
info!("home directory been moved to {}, delete it at your leisure", target.display());
|
info!("home directory been moved to {}, delete it at your leisure", target.display());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||||||
use std::thread::{self,JoinHandle};
|
use std::thread::{self,JoinHandle};
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
||||||
use crate::{RealmManager, Result, Realm};
|
use crate::{RealmManager, Result, Realm, util};
|
||||||
use super::realms::HasCurrentChanged;
|
use super::realms::HasCurrentChanged;
|
||||||
use dbus::{Connection, BusType, ConnectionItem, Message, Path};
|
use dbus::{Connection, BusType, ConnectionItem, Message, Path};
|
||||||
use inotify::{Inotify, WatchMask, WatchDescriptor, Event};
|
use inotify::{Inotify, WatchMask, WatchDescriptor, Event};
|
||||||
@ -212,8 +212,10 @@ impl DbusEventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dbus_event_loop(&self) -> Result<()> {
|
fn dbus_event_loop(&self) -> Result<()> {
|
||||||
let connection = Connection::get_private(BusType::System)?;
|
let connection = Connection::get_private(BusType::System)
|
||||||
connection.add_match("interface='org.freedesktop.machine1.Manager',type='signal'")?;
|
.map_err(|e| format_err!("Failed to connect to DBUS system bus: {}", e))?;
|
||||||
|
connection.add_match("interface='org.freedesktop.machine1.Manager',type='signal'")
|
||||||
|
.map_err(|e| format_err!("DBUS error adding match rule: {}", e))?;
|
||||||
for item in connection.iter(1000) {
|
for item in connection.iter(1000) {
|
||||||
if self.inner().quit_flag() {
|
if self.inner().quit_flag() {
|
||||||
break;
|
break;
|
||||||
@ -244,7 +246,8 @@ impl DbusEventListener {
|
|||||||
|
|
||||||
let member = message.member()
|
let member = message.member()
|
||||||
.ok_or_else(|| format_err!("invalid signal"))?;
|
.ok_or_else(|| format_err!("invalid signal"))?;
|
||||||
let (name, _path): (String, Path) = message.read2()?;
|
let (name, _path): (String, Path) = message.read2()
|
||||||
|
.map_err(|e| format_err!("Failed to read dbus signal: {}", e))?;
|
||||||
if let (Some(interface),Some(member)) = (message.interface(),message.member()) {
|
if let (Some(interface),Some(member)) = (message.interface(),message.member()) {
|
||||||
verbose!("DBUS: {}:[{}({})]", interface, member,name);
|
verbose!("DBUS: {}:[{}({})]", interface, member,name);
|
||||||
}
|
}
|
||||||
@ -286,18 +289,21 @@ struct InotifyEventListener {
|
|||||||
impl InotifyEventListener {
|
impl InotifyEventListener {
|
||||||
|
|
||||||
fn create(inner: Arc<RwLock<Inner>>) -> Result<Self> {
|
fn create(inner: Arc<RwLock<Inner>>) -> Result<Self> {
|
||||||
let mut inotify = Inotify::init()?;
|
let mut inotify = Inotify::init()
|
||||||
let realms_watch = inotify.add_watch("/realms", WatchMask::MOVED_FROM|WatchMask::MOVED_TO)?;
|
.map_err(context!("inotify initialization failed"))?;
|
||||||
let current_watch = inotify.add_watch("/run/citadel/realms/current", WatchMask::CREATE|WatchMask::MOVED_TO)?;
|
let realms_watch = inotify.add_watch("/realms", WatchMask::MOVED_FROM|WatchMask::MOVED_TO)
|
||||||
|
.map_err(context!("error adding watch for /realms to inotify"))?;
|
||||||
|
let current_watch = inotify.add_watch("/run/citadel/realms/current", WatchMask::CREATE|WatchMask::MOVED_TO)
|
||||||
|
.map_err(context!("error adding watch for /run/citadel/realms/current to inotify"))?;
|
||||||
|
|
||||||
Ok(InotifyEventListener { inner, inotify, realms_watch, current_watch, })
|
Ok(InotifyEventListener { inner, inotify, realms_watch, current_watch, })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wake_inotify() -> Result<()> {
|
fn wake_inotify() -> Result<()> {
|
||||||
let path = "/run/citadel/realms/current/stop-events";
|
let path = "/run/citadel/realms/current/stop-events";
|
||||||
fs::File::create(path)?;
|
fs::File::create(path)
|
||||||
fs::remove_file(path)?;
|
.map_err(context!("error creating {}", path))?;
|
||||||
Ok(())
|
util::remove_file(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn(mut self) -> JoinHandle<Result<()>> {
|
fn spawn(mut self) -> JoinHandle<Result<()>> {
|
||||||
@ -307,7 +313,8 @@ impl InotifyEventListener {
|
|||||||
fn inotify_event_loop(&mut self) -> Result<()> {
|
fn inotify_event_loop(&mut self) -> Result<()> {
|
||||||
let mut buffer = [0; 1024];
|
let mut buffer = [0; 1024];
|
||||||
while !self.inner().quit_flag() {
|
while !self.inner().quit_flag() {
|
||||||
let events = self.inotify.read_events_blocking(&mut buffer)?;
|
let events = self.inotify.read_events_blocking(&mut buffer)
|
||||||
|
.map_err(context!("error reading inotify events"))?;
|
||||||
|
|
||||||
if !self.inner().quit_flag() {
|
if !self.inner().quit_flag() {
|
||||||
for event in events {
|
for event in events {
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
use std::fs;
|
use std::fmt::{self,Write};
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
use crate::{Realm,Result};
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use crate::realm::network::NetworkConfig;
|
|
||||||
|
use crate::{Realm, Result, util, realm::network::NetworkConfig};
|
||||||
|
|
||||||
const NSPAWN_FILE_TEMPLATE: &str = "\
|
const NSPAWN_FILE_TEMPLATE: &str = "\
|
||||||
[Exec]
|
[Exec]
|
||||||
@ -77,15 +75,8 @@ impl <'a> RealmLauncher <'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_launch_config_files(&self) -> Result<()> {
|
pub fn remove_launch_config_files(&self) -> Result<()> {
|
||||||
let nspawn_path = self.realm_nspawn_path();
|
util::remove_file(self.realm_nspawn_path())?;
|
||||||
if nspawn_path.exists() {
|
util::remove_file(self.realm_service_path())
|
||||||
fs::remove_file(&nspawn_path)?;
|
|
||||||
}
|
|
||||||
let service_path = self.realm_service_path();
|
|
||||||
if service_path.exists() {
|
|
||||||
fs::remove_file(&service_path)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_launch_config_files(&mut self, rootfs: &Path, netconfig: &mut NetworkConfig) -> Result<()> {
|
pub fn write_launch_config_files(&mut self, rootfs: &Path, netconfig: &mut NetworkConfig) -> Result<()> {
|
||||||
@ -94,15 +85,11 @@ impl <'a> RealmLauncher <'a> {
|
|||||||
}
|
}
|
||||||
let nspawn_path = self.realm_nspawn_path();
|
let nspawn_path = self.realm_nspawn_path();
|
||||||
let nspawn_content = self.generate_nspawn_file(netconfig)?;
|
let nspawn_content = self.generate_nspawn_file(netconfig)?;
|
||||||
self.write_launch_config_file(&nspawn_path, &nspawn_content)
|
self.write_launch_config_file(&nspawn_path, &nspawn_content)?;
|
||||||
.map_err(|e| format_err!("failed to write nspawn config file {}: {}", nspawn_path.display(), e))?;
|
|
||||||
|
|
||||||
let service_path = self.realm_service_path();
|
let service_path = self.realm_service_path();
|
||||||
let service_content = self.generate_service_file(rootfs);
|
let service_content = self.generate_service_file(rootfs);
|
||||||
self.write_launch_config_file(&service_path, &service_content)
|
self.write_launch_config_file(&service_path, &service_content)
|
||||||
.map_err(|e| format_err!("failed to write service config file {}: {}", service_path.display(), e))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn realm_service_name(&self) -> &str {
|
pub fn realm_service_name(&self) -> &str {
|
||||||
@ -113,15 +100,10 @@ impl <'a> RealmLauncher <'a> {
|
|||||||
/// not already exist, create it.
|
/// not already exist, create it.
|
||||||
fn write_launch_config_file(&self, path: &Path, content: &str) -> Result<()> {
|
fn write_launch_config_file(&self, path: &Path, content: &str) -> Result<()> {
|
||||||
match path.parent() {
|
match path.parent() {
|
||||||
Some(parent) => {
|
Some(parent) => util::create_dir(parent)?,
|
||||||
if !parent.exists() {
|
|
||||||
fs::create_dir_all(parent)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => bail!("config file path {} has no parent?", path.display()),
|
None => bail!("config file path {} has no parent?", path.display()),
|
||||||
};
|
};
|
||||||
fs::write(path, content)?;
|
util::write_file(path, content)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_nspawn_file(&mut self, netconfig: &mut NetworkConfig) -> Result<String> {
|
fn generate_nspawn_file(&mut self, netconfig: &mut NetworkConfig) -> Result<String> {
|
||||||
@ -239,3 +221,9 @@ impl <'a> RealmLauncher <'a> {
|
|||||||
PathBuf::from(SYSTEMD_NSPAWN_PATH).join(format!("{}.nspawn", self.realm.name()))
|
PathBuf::from(SYSTEMD_NSPAWN_PATH).join(format!("{}.nspawn", self.realm.name()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<fmt::Error> for crate::Error {
|
||||||
|
fn from(e: fmt::Error) -> Self {
|
||||||
|
format_err!("Error formatting string: {}", e).into()
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
|
|
||||||
@ -205,7 +204,7 @@ impl RealmManager {
|
|||||||
let home = realm.base_path_file("home");
|
let home = realm.base_path_file("home");
|
||||||
if !home.exists() {
|
if !home.exists() {
|
||||||
warn!("No home directory exists at {}, creating an empty directory", home.display());
|
warn!("No home directory exists at {}, creating an empty directory", home.display());
|
||||||
fs::create_dir_all(&home)?;
|
util::create_dir(&home)?;
|
||||||
util::chown_user(&home)?;
|
util::chown_user(&home)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,10 +225,9 @@ impl RealmManager {
|
|||||||
|
|
||||||
fn create_realm_namefile(&self, realm: &Realm) -> Result<()> {
|
fn create_realm_namefile(&self, realm: &Realm) -> Result<()> {
|
||||||
let namefile = realm.run_path_file("realm-name");
|
let namefile = realm.run_path_file("realm-name");
|
||||||
fs::write(&namefile, realm.name())?;
|
util::write_file(&namefile, realm.name())?;
|
||||||
self.systemd.machinectl_copy_to(realm, &namefile, "/run/realm-name")?;
|
self.systemd.machinectl_copy_to(realm, &namefile, "/run/realm-name")?;
|
||||||
fs::remove_file(&namefile)?;
|
util::remove_file(&namefile)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_realm_dependencies(&self, realm: &Realm, starting: &mut HashSet<String>) -> Result<()> {
|
fn start_realm_dependencies(&self, realm: &Realm, starting: &mut HashSet<String>) -> Result<()> {
|
||||||
@ -331,13 +329,14 @@ impl RealmManager {
|
|||||||
|
|
||||||
// ensure that /proc/pid/root/run and /proc/pid/root/run/realm-name
|
// ensure that /proc/pid/root/run and /proc/pid/root/run/realm-name
|
||||||
// are not symlinks
|
// are not symlinks
|
||||||
let run_meta = run.symlink_metadata()?;
|
let run_meta = run.symlink_metadata()
|
||||||
let name_meta = realm_name.symlink_metadata()?;
|
.map_err(context!("failed reading symlink metadata {:?}", run))?;
|
||||||
|
let name_meta = realm_name.symlink_metadata()
|
||||||
|
.map_err(context!("failed reading symlink metadata {:?}", realm_name))?;
|
||||||
if !run_meta.file_type().is_dir() || !name_meta.file_type().is_file() {
|
if !run_meta.file_type().is_dir() || !name_meta.file_type().is_file() {
|
||||||
bail!("invalid path");
|
bail!("invalid path");
|
||||||
}
|
}
|
||||||
let bytes = fs::read(realm_name)?;
|
util::read_to_string(&realm_name)
|
||||||
Ok(String::from_utf8(bytes)?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {
|
pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {
|
||||||
@ -381,7 +380,6 @@ impl RealmManager {
|
|||||||
}
|
}
|
||||||
self.inner_mut().realmfs_set.remove(realmfs.name());
|
self.inner_mut().realmfs_set.remove(realmfs.name());
|
||||||
info!("Removing RealmFS image file {}", realmfs.path().display());
|
info!("Removing RealmFS image file {}", realmfs.path().display());
|
||||||
fs::remove_file(realmfs.path())?;
|
util::remove_file(realmfs.path())
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ use std::path::{Path,PathBuf};
|
|||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::collections::{HashSet,HashMap};
|
use std::collections::{HashSet,HashMap};
|
||||||
use std::io::{BufReader,BufRead,Write};
|
use std::io::{BufReader,BufRead,Write};
|
||||||
use std::fs::{self,File};
|
use std::fs::File;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{Result, util};
|
||||||
|
|
||||||
const REALMS_RUN_PATH: &str = "/run/citadel/realms";
|
const REALMS_RUN_PATH: &str = "/run/citadel/realms";
|
||||||
|
|
||||||
@ -90,7 +90,8 @@ impl BridgeAllocator {
|
|||||||
let (addr_str, mask_size) = match network.find('/') {
|
let (addr_str, mask_size) = match network.find('/') {
|
||||||
Some(idx) => {
|
Some(idx) => {
|
||||||
let (net,bits) = network.split_at(idx);
|
let (net,bits) = network.split_at(idx);
|
||||||
(net.to_owned(), bits[1..].parse()?)
|
(net.to_owned(), bits[1..].parse()
|
||||||
|
.map_err(|_| format_err!("Failed to parse netmask size ({})", &bits[1..]))?)
|
||||||
},
|
},
|
||||||
None => (network.to_owned(), 24),
|
None => (network.to_owned(), 24),
|
||||||
};
|
};
|
||||||
@ -99,7 +100,7 @@ impl BridgeAllocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mask = (1u32 << (32 - mask_size)) - 1;
|
let mask = (1u32 << (32 - mask_size)) - 1;
|
||||||
let ip = addr_str.parse::<Ipv4Addr>()?;
|
let ip = addr_str.parse::<Ipv4Addr>().map_err(|_| format_err!("Failed to parse IP address ({})", addr_str))?;
|
||||||
|
|
||||||
if (u32::from(ip) & mask) != 0 {
|
if (u32::from(ip) & mask) != 0 {
|
||||||
bail!("network {} has masked bits with netmask /{}", addr_str, mask_size);
|
bail!("network {} has masked bits with netmask /{}", addr_str, mask_size);
|
||||||
@ -199,13 +200,12 @@ impl BridgeAllocator {
|
|||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
let f = File::open(path)?;
|
let f = File::open(&path).map_err(context!("failed opening network state file {:?} for reading", path))?;
|
||||||
let reader = BufReader::new(f);
|
let reader = BufReader::new(f);
|
||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
let line = &line?;
|
let line = &line.map_err(context!("error reading from network state file"))?;
|
||||||
self.parse_state_line(line)?;
|
self.parse_state_line(line)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ impl BridgeAllocator {
|
|||||||
match line.find(':') {
|
match line.find(':') {
|
||||||
Some(idx) => {
|
Some(idx) => {
|
||||||
let (name,addr) = line.split_at(idx);
|
let (name,addr) = line.split_at(idx);
|
||||||
let ip = addr[1..].parse::<Ipv4Addr>()?;
|
let ip = addr[1..].parse::<Ipv4Addr>().map_err(|_| format_err!("Failed to parse IP address ({})", &addr[1..]))?;
|
||||||
self.allocated.insert(ip);
|
self.allocated.insert(ip);
|
||||||
self.allocations.insert(name.to_owned(), ip);
|
self.allocations.insert(name.to_owned(), ip);
|
||||||
},
|
},
|
||||||
@ -225,15 +225,13 @@ impl BridgeAllocator {
|
|||||||
fn write_state(&mut self) -> Result<()> {
|
fn write_state(&mut self) -> Result<()> {
|
||||||
let path = self.state_file_path();
|
let path = self.state_file_path();
|
||||||
let dir = path.parent().unwrap();
|
let dir = path.parent().unwrap();
|
||||||
if !dir.exists() {
|
util::create_dir(dir)?;
|
||||||
fs::create_dir_all(dir)
|
|
||||||
.map_err(|e| format_err!("failed to create directory {} for network allocation state file: {}", dir.display(), e))?;
|
|
||||||
}
|
|
||||||
let mut f = File::create(&path)
|
let mut f = File::create(&path)
|
||||||
.map_err(|e| format_err!("failed to open network state file {} for writing: {}", path.display(), e))?;
|
.map_err(context!("failed to open network state file {:?} for writing", path))?;
|
||||||
|
|
||||||
for (realm,addr) in &self.allocations {
|
for (realm,addr) in &self.allocations {
|
||||||
writeln!(f, "{}:{}", realm, addr)?;
|
writeln!(f, "{}:{}", realm, addr)
|
||||||
|
.map_err(context!("error writing to network allocation state file"))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::os::unix;
|
|
||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path,PathBuf};
|
||||||
|
|
||||||
use crate::{Realm,Result};
|
use crate::{Realm, Result, util};
|
||||||
use crate::Exec;
|
use crate::Exec;
|
||||||
use crate::realm::config::OverlayType;
|
use crate::realm::config::OverlayType;
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ impl RealmOverlay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let lower = base.join("lower").read_link()
|
let lower = base.join("lower").read_link()
|
||||||
.map_err(|e| format_err!("Unable to read link to 'lower' directory of overlay: {}", e));
|
.map_err(context!("unable to read link to 'lower' directory of overlay"));
|
||||||
|
|
||||||
match self.overlay {
|
match self.overlay {
|
||||||
OverlayType::TmpFS => self.remove_tmpfs(&base)?,
|
OverlayType::TmpFS => self.remove_tmpfs(&base)?,
|
||||||
@ -93,14 +92,14 @@ impl RealmOverlay {
|
|||||||
|
|
||||||
fn remove_tmpfs(&self, base: &Path) -> Result<()> {
|
fn remove_tmpfs(&self, base: &Path) -> Result<()> {
|
||||||
fs::remove_dir_all(base)
|
fs::remove_dir_all(base)
|
||||||
.map_err(|e| format_err!("Could not remove overlay directory {}: {}", base.display(), e))
|
.map_err(context!("failed to remove overlay directory {:?}", base))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_btrfs(&self, base: &Path) -> Result<()> {
|
fn remove_btrfs(&self, base: &Path) -> Result<()> {
|
||||||
Exec::new("/usr/bin/btrfs")
|
Exec::new("/usr/bin/btrfs")
|
||||||
.quiet()
|
.quiet()
|
||||||
.run(format!("subvolume delete {}", base.display()))
|
.run(format!("subvolume delete {}", base.display()))
|
||||||
.map_err(|e| format_err!("Could not remove btrfs subvolume {}: {}", base.display(), e))
|
.map_err(context!("failed to remove btrfs subvolume {:?}", base))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_tmpfs(&self, lower: &Path) -> Result<PathBuf> {
|
fn create_tmpfs(&self, lower: &Path) -> Result<PathBuf> {
|
||||||
@ -139,7 +138,8 @@ impl RealmOverlay {
|
|||||||
let upper = self.mkdir(base, "upperdir")?;
|
let upper = self.mkdir(base, "upperdir")?;
|
||||||
let work = self.mkdir(base, "workdir")?;
|
let work = self.mkdir(base, "workdir")?;
|
||||||
let mountpoint = self.mkdir(base, "mountpoint")?;
|
let mountpoint = self.mkdir(base, "mountpoint")?;
|
||||||
unix::fs::symlink(lower, base.join("lower"))?;
|
let baselower = base.join("lower");
|
||||||
|
util::symlink(lower, &baselower)?;
|
||||||
cmd!("/usr/bin/mount",
|
cmd!("/usr/bin/mount",
|
||||||
"-t overlay realm-{}-overlay -olowerdir={},upperdir={},workdir={} {}",
|
"-t overlay realm-{}-overlay -olowerdir={},upperdir={},workdir={} {}",
|
||||||
self.realm,
|
self.realm,
|
||||||
@ -152,8 +152,7 @@ impl RealmOverlay {
|
|||||||
|
|
||||||
fn mkdir(&self, base: &Path, dirname: &str) -> Result<PathBuf> {
|
fn mkdir(&self, base: &Path, dirname: &str) -> Result<PathBuf> {
|
||||||
let path = base.join(dirname);
|
let path = base.join(dirname);
|
||||||
fs::create_dir_all(&path)
|
util::create_dir(&path)?;
|
||||||
.map_err(|e| format_err!("failed to create directory {}: {}", path.display(), e))?;
|
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,11 +187,9 @@ impl Realm {
|
|||||||
/// to order the output when listing realms.
|
/// to order the output when listing realms.
|
||||||
pub fn update_timestamp(&self) -> Result<()> {
|
pub fn update_timestamp(&self) -> Result<()> {
|
||||||
let tstamp = self.base_path().join(".tstamp");
|
let tstamp = self.base_path().join(".tstamp");
|
||||||
if tstamp.exists() {
|
util::remove_file(&tstamp)?;
|
||||||
fs::remove_file(&tstamp)?;
|
|
||||||
}
|
|
||||||
fs::File::create(&tstamp)
|
fs::File::create(&tstamp)
|
||||||
.map_err(|e| format_err!("failed to create timestamp file {}: {}", tstamp.display(), e))?;
|
.map_err(context!("failed to create timestamp file {:?}", tstamp))?;
|
||||||
// also load the new value
|
// also load the new value
|
||||||
self.inner_mut().timestamp = self.load_timestamp();
|
self.inner_mut().timestamp = self.load_timestamp();
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -309,7 +307,7 @@ impl Realm {
|
|||||||
// not exist, create it as a fork of 'base'
|
// not exist, create it as a fork of 'base'
|
||||||
base.fork(default)
|
base.fork(default)
|
||||||
} else {
|
} else {
|
||||||
Err(format_err!("Default RealmFS '{}' does not exist and neither does 'base'", default))
|
bail!("Default RealmFS '{}' does not exist and neither does 'base'", default)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,11 +457,10 @@ impl Realm {
|
|||||||
let path = self.base_path_file("notes");
|
let path = self.base_path_file("notes");
|
||||||
let notes = notes.as_ref();
|
let notes = notes.as_ref();
|
||||||
if path.exists() && notes.is_empty() {
|
if path.exists() && notes.is_empty() {
|
||||||
fs::remove_file(path)?;
|
util::remove_file(&path)
|
||||||
} else {
|
} else {
|
||||||
fs::write(path, notes)?;
|
util::write_file(&path, notes)
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path,PathBuf};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use crate::{Realm, Result, symlink, RealmManager,FileLock};
|
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
|
use crate::{Realm, Result, symlink, RealmManager, FileLock, util};
|
||||||
use super::create::RealmCreateDestroy;
|
use super::create::RealmCreateDestroy;
|
||||||
use crate::realm::systemd::Systemd;
|
use crate::realm::systemd::Systemd;
|
||||||
|
|
||||||
@ -88,12 +88,13 @@ impl Realms {
|
|||||||
|
|
||||||
fn all_realms(mark_active: bool) -> Result<Vec<Realm>> {
|
fn all_realms(mark_active: bool) -> Result<Vec<Realm>> {
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for entry in fs::read_dir(Realms::BASE_PATH)? {
|
util::read_directory(Realms::BASE_PATH, |dent| {
|
||||||
let entry = entry?;
|
if let Some(realm) = Realms::entry_to_realm(dent) {
|
||||||
if let Some(realm) = Realms::entry_to_realm(&entry) {
|
|
||||||
v.push(realm);
|
v.push(realm);
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
if mark_active {
|
if mark_active {
|
||||||
Realms::mark_active_realms(&mut v)?;
|
Realms::mark_active_realms(&mut v)?;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
use std::process::Command;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{Command,Stdio};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{Result,Realm};
|
||||||
|
use crate::realm::{
|
||||||
|
launcher::RealmLauncher,
|
||||||
|
network::NetworkConfig
|
||||||
|
};
|
||||||
|
|
||||||
const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl";
|
const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl";
|
||||||
const MACHINECTL_PATH: &str = "/usr/bin/machinectl";
|
const MACHINECTL_PATH: &str = "/usr/bin/machinectl";
|
||||||
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
use crate::Realm;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::process::Stdio;
|
|
||||||
use crate::realm::network::NetworkConfig;
|
|
||||||
use crate::realm::launcher::RealmLauncher;
|
|
||||||
|
|
||||||
pub struct Systemd {
|
pub struct Systemd {
|
||||||
network: Mutex<NetworkConfig>,
|
network: Mutex<NetworkConfig>,
|
||||||
}
|
}
|
||||||
@ -56,7 +55,8 @@ impl Systemd {
|
|||||||
for dir in realm.config().ephemeral_persistent_dirs() {
|
for dir in realm.config().ephemeral_persistent_dirs() {
|
||||||
let src = home.join(&dir);
|
let src = home.join(&dir);
|
||||||
if src.exists() {
|
if src.exists() {
|
||||||
let src = src.canonicalize()?;
|
let src = src.canonicalize()
|
||||||
|
.map_err(context!("failed to canonicalize {:?}", src))?;
|
||||||
if src.starts_with(&home) && src.exists() {
|
if src.starts_with(&home) && src.exists() {
|
||||||
let dst = Path::new("/home/user").join(&dir);
|
let dst = Path::new("/home/user").join(&dir);
|
||||||
self.machinectl_bind(realm, &src, &dst)?;
|
self.machinectl_bind(realm, &src, &dst)?;
|
||||||
@ -86,12 +86,13 @@ impl Systemd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_systemctl(&self, op: &str, name: &str) -> Result<bool> {
|
fn run_systemctl(&self, op: &str, name: &str) -> Result<bool> {
|
||||||
Command::new(SYSTEMCTL_PATH)
|
let ok = Command::new(SYSTEMCTL_PATH)
|
||||||
.arg(op)
|
.arg(op)
|
||||||
.arg(name)
|
.arg(name)
|
||||||
.status()
|
.status()
|
||||||
.map(|status| status.success())
|
.map(|status| status.success())
|
||||||
.map_err(|e| format_err!("failed to execute {}: {}", MACHINECTL_PATH, e))
|
.map_err(context!("failed to execute {}", MACHINECTL_PATH))?;
|
||||||
|
Ok(ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn machinectl_copy_to(&self, realm: &Realm, from: impl AsRef<Path>, to: &str) -> Result<()> {
|
pub fn machinectl_copy_to(&self, realm: &Realm, from: impl AsRef<Path>, to: &str) -> Result<()> {
|
||||||
@ -100,7 +101,7 @@ impl Systemd {
|
|||||||
Command::new(MACHINECTL_PATH)
|
Command::new(MACHINECTL_PATH)
|
||||||
.args(&["copy-to", realm.name(), from, to ])
|
.args(&["copy-to", realm.name(), from, to ])
|
||||||
.status()
|
.status()
|
||||||
.map_err(|e| format_err!("failed to machinectl copy-to {} {} {}: {}", realm.name(), from, to, e))?;
|
.map_err(context!("failed to machinectl copy-to {} {} {}", realm.name(), from, to))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,17 +111,18 @@ impl Systemd {
|
|||||||
Command::new(MACHINECTL_PATH)
|
Command::new(MACHINECTL_PATH)
|
||||||
.args(&["--mkdir", "bind", realm.name(), from.as_str(), to.as_str() ])
|
.args(&["--mkdir", "bind", realm.name(), from.as_str(), to.as_str() ])
|
||||||
.status()
|
.status()
|
||||||
.map_err(|e| format_err!("failed to machinectl bind {} {} {}: {}", realm.name(), from, to, e))?;
|
.map_err(context!("failed to machinectl bind {} {} {}", realm.name(), from, to))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_active(realm: &Realm) -> Result<bool> {
|
pub fn is_active(realm: &Realm) -> Result<bool> {
|
||||||
Command::new(SYSTEMCTL_PATH)
|
let ok = Command::new(SYSTEMCTL_PATH)
|
||||||
.args(&["--quiet", "is-active"])
|
.args(&["--quiet", "is-active"])
|
||||||
.arg(format!("realm-{}", realm.name()))
|
.arg(format!("realm-{}", realm.name()))
|
||||||
.status()
|
.status()
|
||||||
.map(|status| status.success())
|
.map(|status| status.success())
|
||||||
.map_err(|e| format_err!("failed to execute {}: {}", SYSTEMCTL_PATH, e))
|
.map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?;
|
||||||
|
Ok(ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn are_realms_active(realms: &mut Vec<Realm>) -> Result<String> {
|
pub fn are_realms_active(realms: &mut Vec<Realm>) -> Result<String> {
|
||||||
@ -132,7 +134,8 @@ impl Systemd {
|
|||||||
.arg("is-active")
|
.arg("is-active")
|
||||||
.args(args)
|
.args(args)
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.output()?;
|
.output()
|
||||||
|
.map_err(context!("failed to run /usr/bin/systemctl"))?;
|
||||||
|
|
||||||
Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned())
|
Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned())
|
||||||
}
|
}
|
||||||
@ -175,7 +178,7 @@ impl Systemd {
|
|||||||
cmd.arg(arg.as_ref());
|
cmd.arg(arg.as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.status().map_err(|e| format_err!("failed to execute{}: {}", MACHINECTL_PATH, e))?;
|
cmd.status().map_err(context!("failed to execute {}", MACHINECTL_PATH))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use std::fmt;
|
|||||||
use std::fs::{self, DirEntry};
|
use std::fs::{self, DirEntry};
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{PathBuf, Path};
|
||||||
|
|
||||||
use crate::{Result, RealmFS, CommandLine, ImageHeader};
|
use crate::{Result, RealmFS, CommandLine, ImageHeader, util};
|
||||||
use crate::verity::Verity;
|
use crate::verity::Verity;
|
||||||
|
|
||||||
|
|
||||||
@ -28,7 +28,8 @@ impl Mountpoint {
|
|||||||
/// Read `RealmFS::RUN_DIRECTORY` to collect all current mountpoints
|
/// Read `RealmFS::RUN_DIRECTORY` to collect all current mountpoints
|
||||||
/// and return them.
|
/// and return them.
|
||||||
pub fn all_mountpoints() -> Result<Vec<Mountpoint>> {
|
pub fn all_mountpoints() -> Result<Vec<Mountpoint>> {
|
||||||
let all = fs::read_dir(RealmFS::RUN_DIRECTORY)?
|
let all = fs::read_dir(RealmFS::RUN_DIRECTORY)
|
||||||
|
.map_err(context!("error reading realmfs run directory {}", RealmFS::RUN_DIRECTORY))?
|
||||||
.flat_map(|e| e.ok())
|
.flat_map(|e| e.ok())
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.filter(Mountpoint::is_valid)
|
.filter(Mountpoint::is_valid)
|
||||||
@ -51,11 +52,6 @@ impl Mountpoint {
|
|||||||
self.0.exists()
|
self.0.exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_dir(&self) -> Result<()> {
|
|
||||||
fs::create_dir_all(self.path())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_mounted(&self) -> bool {
|
pub fn is_mounted(&self) -> bool {
|
||||||
// test for an arbitrary expected directory
|
// test for an arbitrary expected directory
|
||||||
self.path().join("etc").exists()
|
self.path().join("etc").exists()
|
||||||
@ -73,9 +69,8 @@ impl Mountpoint {
|
|||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.exists() {
|
util::create_dir(self.path())?;
|
||||||
self.create_dir()?;
|
|
||||||
}
|
|
||||||
let verity_path = self.verity_device_path();
|
let verity_path = self.verity_device_path();
|
||||||
if verity_path.exists() {
|
if verity_path.exists() {
|
||||||
warn!("dm-verity device {:?} already exists which was not expected", verity_path);
|
warn!("dm-verity device {:?} already exists which was not expected", verity_path);
|
||||||
|
@ -130,11 +130,10 @@ impl RealmFS {
|
|||||||
|
|
||||||
/// Return an Error result if name is not valid.
|
/// Return an Error result if name is not valid.
|
||||||
fn validate_name(name: &str) -> Result<()> {
|
fn validate_name(name: &str) -> Result<()> {
|
||||||
if Self::is_valid_name(name) {
|
if !Self::is_valid_name(name) {
|
||||||
Ok(())
|
bail!("Invalid realm name '{}'", name);
|
||||||
} else {
|
|
||||||
Err(format_err!("Invalid realm name '{}'", name))
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if `name` is a valid name for a RealmFS.
|
/// Return `true` if `name` is a valid name for a RealmFS.
|
||||||
@ -211,11 +210,10 @@ impl RealmFS {
|
|||||||
let path = self.path_with_extension("notes");
|
let path = self.path_with_extension("notes");
|
||||||
let notes = notes.as_ref();
|
let notes = notes.as_ref();
|
||||||
if path.exists() && notes.is_empty() {
|
if path.exists() && notes.is_empty() {
|
||||||
fs::remove_file(path)?;
|
util::remove_file(&path)
|
||||||
} else {
|
} else {
|
||||||
fs::write(path, notes)?;
|
util::write_file(&path, notes)
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `MetaInfo` from image header of this RealmFS.
|
/// Return `MetaInfo` from image header of this RealmFS.
|
||||||
@ -353,7 +351,8 @@ impl RealmFS {
|
|||||||
|
|
||||||
// Return the length in blocks of the actual image file on disk
|
// Return the length in blocks of the actual image file on disk
|
||||||
pub fn file_nblocks(&self) -> Result<usize> {
|
pub fn file_nblocks(&self) -> Result<usize> {
|
||||||
let meta = self.path.metadata()?;
|
let meta = self.path.metadata()
|
||||||
|
.map_err(context!("failed to read metadata from realmfs image file {:?}", self.path))?;
|
||||||
let len = meta.len() as usize;
|
let len = meta.len() as usize;
|
||||||
if len % 4096 != 0 {
|
if len % 4096 != 0 {
|
||||||
bail!("realmfs image file '{}' has size which is not a multiple of block size", self.path.display());
|
bail!("realmfs image file '{}' has size which is not a multiple of block size", self.path.display());
|
||||||
@ -397,7 +396,8 @@ impl RealmFS {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocated_size_blocks(&self) -> Result<usize> {
|
pub fn allocated_size_blocks(&self) -> Result<usize> {
|
||||||
let meta = self.path().metadata()?;
|
let meta = self.path().metadata()
|
||||||
|
.map_err(context!("failed to read metadata from realmfs image file {:?}", self.path()))?;
|
||||||
Ok(meta.blocks() as usize / 8)
|
Ok(meta.blocks() as usize / 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{RealmFS, RealmManager, Result};
|
use crate::{RealmFS, RealmManager, Result, util};
|
||||||
|
|
||||||
pub struct RealmFSSet {
|
pub struct RealmFSSet {
|
||||||
realmfs_map: HashMap<String, RealmFS>,
|
realmfs_map: HashMap<String, RealmFS>,
|
||||||
@ -21,12 +21,23 @@ impl RealmFSSet {
|
|||||||
|
|
||||||
fn load_all() -> Result<Vec<RealmFS>> {
|
fn load_all() -> Result<Vec<RealmFS>> {
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for entry in fs::read_dir(RealmFS::BASE_PATH)? {
|
util::read_directory(RealmFS::BASE_PATH, |dent| {
|
||||||
let entry = entry?;
|
if let Some(realmfs) = Self::entry_to_realmfs(dent) {
|
||||||
|
v.push(realmfs)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
/*
|
||||||
|
let entries = fs::read_dir(RealmFS::BASE_PATH)
|
||||||
|
.map_err(context!("error reading realmfs directory {}", RealmFS::BASE_PATH))?;
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry.map_err(context!("error reading directory entry"))?;
|
||||||
if let Some(realmfs) = Self::entry_to_realmfs(&entry) {
|
if let Some(realmfs) = Self::entry_to_realmfs(&entry) {
|
||||||
v.push(realmfs)
|
v.push(realmfs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,10 +80,14 @@ impl Superblock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(path: impl AsRef<Path>, offset: u64) -> Result<Self> {
|
pub fn load(path: impl AsRef<Path>, offset: u64) -> Result<Self> {
|
||||||
|
let path = path.as_ref();
|
||||||
let mut sb = Self::new();
|
let mut sb = Self::new();
|
||||||
let mut file = File::open(path.as_ref())?;
|
let mut file = File::open(path)
|
||||||
file.seek(SeekFrom::Start(1024 + offset))?;
|
.map_err(context!("failed to open image file {:?}", path))?;
|
||||||
file.read_exact(&mut sb.0)?;
|
file.seek(SeekFrom::Start(1024 + offset))
|
||||||
|
.map_err(context!("failed to seek to offset {} of image file {:?}", 1024 + offset, path))?;
|
||||||
|
file.read_exact(&mut sb.0)
|
||||||
|
.map_err(context!("error reading superblock from image file {:?}", path))?;
|
||||||
Ok(sb)
|
Ok(sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use std::process::Command;
|
|||||||
|
|
||||||
use sodiumoxide::randombytes::randombytes;
|
use sodiumoxide::randombytes::randombytes;
|
||||||
|
|
||||||
use crate::{Result, RealmFS, FileLock, ImageHeader, LoopDevice, ResizeSize, util};
|
use crate::{Result, RealmFS, FileLock, ImageHeader, LoopDevice, ResizeSize, util, Error};
|
||||||
use crate::realm::BridgeAllocator;
|
use crate::realm::BridgeAllocator;
|
||||||
use crate::util::is_euid_root;
|
use crate::util::is_euid_root;
|
||||||
use crate::terminal::TerminalRestorer;
|
use crate::terminal::TerminalRestorer;
|
||||||
@ -72,7 +72,7 @@ impl <'a> Update<'a> {
|
|||||||
fn create_update_copy(&self) -> Result<()> {
|
fn create_update_copy(&self) -> Result<()> {
|
||||||
if self.target.exists() {
|
if self.target.exists() {
|
||||||
info!("Update file {} already exists, removing it", self.target.display());
|
info!("Update file {} already exists, removing it", self.target.display());
|
||||||
fs::remove_file(&self.target)?;
|
util::remove_file(&self.target)?;
|
||||||
}
|
}
|
||||||
self.realmfs.copy_image_file(self.target())?;
|
self.realmfs.copy_image_file(self.target())?;
|
||||||
|
|
||||||
@ -108,9 +108,7 @@ impl <'a> Update<'a> {
|
|||||||
if self.resize.is_some() {
|
if self.resize.is_some() {
|
||||||
self.resize_device(loopdev)?;
|
self.resize_device(loopdev)?;
|
||||||
}
|
}
|
||||||
if !self.mountpath.exists() {
|
util::create_dir(&self.mountpath)?;
|
||||||
fs::create_dir_all(&self.mountpath)?;
|
|
||||||
}
|
|
||||||
util::mount(loopdev.device_str(), &self.mountpath, Some("-orw,noatime"))?;
|
util::mount(loopdev.device_str(), &self.mountpath, Some("-orw,noatime"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@ -126,7 +124,6 @@ impl <'a> Update<'a> {
|
|||||||
if self.mountpath.exists() {
|
if self.mountpath.exists() {
|
||||||
if let Err(err) = util::umount(&self.mountpath) {
|
if let Err(err) = util::umount(&self.mountpath) {
|
||||||
warn!("Failed to unmount directory {:?}: {}", self.mountpath, err);
|
warn!("Failed to unmount directory {:?}: {}", self.mountpath, err);
|
||||||
|
|
||||||
}
|
}
|
||||||
if let Err(err) = fs::remove_dir(&self.mountpath) {
|
if let Err(err) = fs::remove_dir(&self.mountpath) {
|
||||||
warn!("Failed to remove mountpoint directory {:?}: {}", self.mountpath, err);
|
warn!("Failed to remove mountpoint directory {:?}: {}", self.mountpath, err);
|
||||||
@ -165,9 +162,11 @@ impl <'a> Update<'a> {
|
|||||||
let len = (nblocks * BLOCK_SIZE) as u64;
|
let len = (nblocks * BLOCK_SIZE) as u64;
|
||||||
let f = fs::OpenOptions::new()
|
let f = fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(&self.target)?;
|
.open(&self.target)
|
||||||
f.set_len(len)?;
|
.map_err(context!("failed to open update image file {:?} to set length", self.target))?;
|
||||||
Ok(())
|
|
||||||
|
f.set_len(len)
|
||||||
|
.map_err(context!("failed setting length of update image file {:?} to {}", self.target, len))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove dm-verity hash tree from update copy of image file.
|
// Remove dm-verity hash tree from update copy of image file.
|
||||||
@ -229,8 +228,10 @@ impl <'a> Update<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let salt = hex::encode(randombytes(32));
|
let salt = hex::encode(randombytes(32));
|
||||||
let verity = Verity::new(&self.target)?;
|
let verity = Verity::new(&self.target)
|
||||||
let output = verity.generate_image_hashtree_with_salt(&salt, nblocks)?;
|
.map_err(context!("failed to create verity context for realmfs update image {:?}", self.target()))?;
|
||||||
|
let output = verity.generate_image_hashtree_with_salt(&salt, nblocks)
|
||||||
|
.map_err(context!("failed to generate dm-verity hashtree for realmfs update image {:?}", self.target()))?;
|
||||||
// XXX passes metainfo for nblocks
|
// XXX passes metainfo for nblocks
|
||||||
//let output = Verity::new(&self.target).generate_image_hashtree_with_salt(&self.realmfs.metainfo(), &salt)?;
|
//let output = Verity::new(&self.target).generate_image_hashtree_with_salt(&self.realmfs.metainfo(), &salt)?;
|
||||||
let root_hash = output.root_hash()
|
let root_hash = output.root_hash()
|
||||||
@ -252,15 +253,21 @@ impl <'a> Update<'a> {
|
|||||||
let header = ImageHeader::new();
|
let header = ImageHeader::new();
|
||||||
header.set_flag(ImageHeader::FLAG_HASH_TREE);
|
header.set_flag(ImageHeader::FLAG_HASH_TREE);
|
||||||
header.update_metainfo(&metainfo_bytes, sig.to_bytes(), &self.target)
|
header.update_metainfo(&metainfo_bytes, sig.to_bytes(), &self.target)
|
||||||
|
.map_err(context!("failed to write header to update image {:?}", self.target()))?;
|
||||||
|
Ok(())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn prompt_user(prompt: &str, default_y: bool) -> Result<bool> {
|
fn prompt_user(prompt: &str, default_y: bool) -> Result<bool> {
|
||||||
let yn = if default_y { "(Y/n)" } else { "(y/N)" };
|
let yn = if default_y { "(Y/n)" } else { "(y/N)" };
|
||||||
print!("{} {} : ", prompt, yn);
|
print!("{} {} : ", prompt, yn);
|
||||||
io::stdout().flush()?;
|
io::stdout().flush()
|
||||||
|
.map_err(context!("failed to flush stdout"))?;
|
||||||
|
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
io::stdin().read_line(&mut line)?;
|
io::stdin().read_line(&mut line)
|
||||||
|
.map_err(context!("failed reading line from stdin"))?;
|
||||||
|
|
||||||
let yes = match line.trim().chars().next() {
|
let yes = match line.trim().chars().next() {
|
||||||
Some(c) => c == 'Y' || c == 'y',
|
Some(c) => c == 'Y' || c == 'y',
|
||||||
@ -319,7 +326,7 @@ impl <'a> Update<'a> {
|
|||||||
.status()
|
.status()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let _ = self.cleanup();
|
let _ = self.cleanup();
|
||||||
e
|
Error::with_error("failed to run systemd-nspawn", e)
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -339,12 +346,13 @@ impl <'a> Update<'a> {
|
|||||||
for i in (1..NUM_BACKUPS).rev() {
|
for i in (1..NUM_BACKUPS).rev() {
|
||||||
let from = backup(i - 1);
|
let from = backup(i - 1);
|
||||||
if from.exists() {
|
if from.exists() {
|
||||||
fs::rename(from, backup(i))?;
|
let to = backup(i);
|
||||||
|
util::rename(&from, &to)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fs::rename(self.realmfs.path(), backup(0))?;
|
let to = backup(0);
|
||||||
fs::rename(self.target(), self.realmfs.path())?;
|
util::rename(self.realmfs.path(), &to)?;
|
||||||
Ok(())
|
util::rename(self.target(), self.realmfs.path())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use std::fs::{self,File,DirEntry};
|
use std::fs::{File,DirEntry};
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::io::{self,Seek,SeekFrom};
|
use std::io::{self,Seek,SeekFrom};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::{CommandLine, OsRelease, ImageHeader, MetaInfo, Result, Partition, Mounts, util, LoopDevice};
|
use crate::{Result, CommandLine, OsRelease, ImageHeader, MetaInfo, Partition, Mounts, util, LoopDevice};
|
||||||
|
|
||||||
use failure::ResultExt;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use crate::UtsName;
|
use crate::UtsName;
|
||||||
use crate::verity::Verity;
|
use crate::verity::Verity;
|
||||||
@ -49,7 +48,7 @@ impl ResourceImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !Self::ensure_storage_mounted()? {
|
if !Self::ensure_storage_mounted()? {
|
||||||
bail!("Unable to mount /storage");
|
bail!("unable to mount /storage");
|
||||||
}
|
}
|
||||||
|
|
||||||
let storage_path = Path::new(STORAGE_BASEDIR).join(&channel);
|
let storage_path = Path::new(STORAGE_BASEDIR).join(&channel);
|
||||||
@ -58,7 +57,7 @@ impl ResourceImage {
|
|||||||
return Ok(image);
|
return Ok(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(format_err!("Failed to find resource image of type: {}", image_type))
|
bail!("failed to find resource image of type: {}", image_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mount_image_type(image_type: &str) -> Result<()> {
|
pub fn mount_image_type(image_type: &str) -> Result<()> {
|
||||||
@ -70,14 +69,14 @@ impl ResourceImage {
|
|||||||
pub fn find_rootfs() -> Result<Self> {
|
pub fn find_rootfs() -> Result<Self> {
|
||||||
match search_directory(RUN_DIRECTORY, "rootfs", None)? {
|
match search_directory(RUN_DIRECTORY, "rootfs", None)? {
|
||||||
Some(image) => Ok(image),
|
Some(image) => Ok(image),
|
||||||
None => Err(format_err!("Failed to find rootfs resource image")),
|
None => bail!("failed to find rootfs resource image"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
|
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
let header = ImageHeader::from_file(path.as_ref())?;
|
let header = ImageHeader::from_file(path.as_ref())?;
|
||||||
if !header.is_magic_valid() {
|
if !header.is_magic_valid() {
|
||||||
bail!("Image file {} does not have a valid header", path.as_ref().display());
|
bail!("image file {:?} does not have a valid header", path.as_ref());
|
||||||
}
|
}
|
||||||
Ok(Self::new(path.as_ref(), header ))
|
Ok(Self::new(path.as_ref(), header ))
|
||||||
}
|
}
|
||||||
@ -143,25 +142,28 @@ impl ResourceImage {
|
|||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
info!("decompressing image file {}", self.path().display());
|
info!("decompressing image file {}", self.path().display());
|
||||||
let mut reader = File::open(self.path())?;
|
let mut reader = File::open(self.path())
|
||||||
reader.seek(SeekFrom::Start(4096))?;
|
.map_err(context!("error opening image file {:?}", self.path()))?;
|
||||||
|
reader.seek(SeekFrom::Start(4096))
|
||||||
|
.map_err(context!("error seeking to offset 4096 in image file {:?}", self.path()))?;
|
||||||
|
|
||||||
let xzfile = self.path.with_extension("tmp.xz");
|
let xzfile = self.path.with_extension("tmp.xz");
|
||||||
let mut out = File::create(&xzfile)?;
|
let mut out = File::create(&xzfile)
|
||||||
io::copy(&mut reader, &mut out)?;
|
.map_err(context!("error creating temporary file {:?}", xzfile))?;
|
||||||
|
io::copy(&mut reader, &mut out)
|
||||||
|
.map_err(context!("error copying image file {:?} to temporary file", self.path()))?;
|
||||||
|
|
||||||
util::xz_decompress(xzfile)?;
|
util::xz_decompress(xzfile)?;
|
||||||
fs::rename(self.path.with_extension("tmp"), self.path())?;
|
let tmpfile = self.path.with_extension("tmp");
|
||||||
|
util::rename(&tmpfile, self.path())?;
|
||||||
|
|
||||||
self.header.clear_flag(ImageHeader::FLAG_DATA_COMPRESSED);
|
self.header.clear_flag(ImageHeader::FLAG_DATA_COMPRESSED);
|
||||||
self.header.write_header_to(self.path())?;
|
self.header.write_header_to(self.path())
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to_partition(&self, partition: &Partition) -> Result<()> {
|
pub fn write_to_partition(&self, partition: &Partition) -> Result<()> {
|
||||||
if self.metainfo().image_type() != "rootfs" {
|
if self.metainfo().image_type() != "rootfs" {
|
||||||
bail!("Cannot write to partition, image type is not rootfs");
|
bail!("cannot write to partition, image type is not rootfs");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.has_verity_hashtree() {
|
if !self.has_verity_hashtree() {
|
||||||
@ -183,7 +185,7 @@ impl ResourceImage {
|
|||||||
|
|
||||||
info!("Mounting dm-verity device to {}", mount_path.display());
|
info!("Mounting dm-verity device to {}", mount_path.display());
|
||||||
|
|
||||||
fs::create_dir_all(mount_path)?;
|
util::create_dir(mount_path)?;
|
||||||
|
|
||||||
util::mount(verity_path, mount_path, None)?;
|
util::mount(verity_path, mount_path, None)?;
|
||||||
Ok(ResourceMount::new_verity(mount_path, verity_dev))
|
Ok(ResourceMount::new_verity(mount_path, verity_dev))
|
||||||
@ -195,11 +197,11 @@ impl ResourceImage {
|
|||||||
match self.header.public_key()? {
|
match self.header.public_key()? {
|
||||||
Some(pubkey) => {
|
Some(pubkey) => {
|
||||||
if !self.header.verify_signature(pubkey) {
|
if !self.header.verify_signature(pubkey) {
|
||||||
bail!("Header signature verification failed");
|
bail!("header signature verification failed");
|
||||||
}
|
}
|
||||||
info!("Image header signature is valid");
|
info!("Image header signature is valid");
|
||||||
}
|
}
|
||||||
None => bail!("Cannot verify header signature because no public key for channel {} is available", self.metainfo().channel())
|
None => bail!("cannot verify header signature because no public key for channel {} is available", self.metainfo().channel())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Setting up dm-verity device for image");
|
info!("Setting up dm-verity device for image");
|
||||||
@ -240,7 +242,7 @@ impl ResourceImage {
|
|||||||
}
|
}
|
||||||
info!("Calculating sha256 of image");
|
info!("Calculating sha256 of image");
|
||||||
let output = util::exec_cmdline_pipe_input("sha256sum", "-", self.path(), util::FileRange::Range{offset: 4096, len: self.metainfo().nblocks() * 4096})
|
let output = util::exec_cmdline_pipe_input("sha256sum", "-", self.path(), util::FileRange::Range{offset: 4096, len: self.metainfo().nblocks() * 4096})
|
||||||
.context(format!("failed to calculate sha256 on {}", self.path().display()))?;
|
.map_err(context!("failed to calculate sha256 on {:?}", self.path()))?;
|
||||||
let v: Vec<&str> = output.split_whitespace().collect();
|
let v: Vec<&str> = output.split_whitespace().collect();
|
||||||
let shasum = v[0].trim().to_owned();
|
let shasum = v[0].trim().to_owned();
|
||||||
Ok(shasum)
|
Ok(shasum)
|
||||||
@ -261,7 +263,7 @@ impl ResourceImage {
|
|||||||
info!("Loop device created: {}", loopdev);
|
info!("Loop device created: {}", loopdev);
|
||||||
info!("Mounting to: {}", mount_path.display());
|
info!("Mounting to: {}", mount_path.display());
|
||||||
|
|
||||||
fs::create_dir_all(mount_path)?;
|
util::create_dir(mount_path)?;
|
||||||
|
|
||||||
util::mount(&loopdev.device_str(), mount_path, Some("-oro"))?;
|
util::mount(&loopdev.device_str(), mount_path, Some("-oro"))?;
|
||||||
|
|
||||||
@ -286,7 +288,8 @@ impl ResourceImage {
|
|||||||
warn!("No manifest file found for resource image: {}", self.path.display());
|
warn!("No manifest file found for resource image: {}", self.path.display());
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
let s = fs::read_to_string(manifest)?;
|
let s = util::read_to_string(manifest)?;
|
||||||
|
|
||||||
for line in s.lines() {
|
for line in s.lines() {
|
||||||
if let Err(e) = self.process_manifest_line(&line) {
|
if let Err(e) = self.process_manifest_line(&line) {
|
||||||
warn!("Processing manifest file for resource image ({}): {}", self.path.display(), e);
|
warn!("Processing manifest file for resource image ({}): {}", self.path.display(), e);
|
||||||
@ -416,7 +419,7 @@ fn parse_timestamp(img: &ResourceImage) -> Result<usize> {
|
|||||||
let ts = img.metainfo()
|
let ts = img.metainfo()
|
||||||
.timestamp()
|
.timestamp()
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.context(format!("Error parsing timestamp for resource image {}", img.path().display()))?;
|
.map_err(|_| format_err!("error parsing timestamp for resource image {:?}", img.path()))?;
|
||||||
Ok(ts)
|
Ok(ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,9 +444,10 @@ fn all_matching_images(dir: &Path, image_type: &str, channel: Option<&str>) -> R
|
|||||||
let kernel_id = OsRelease::citadel_kernel_id();
|
let kernel_id = OsRelease::citadel_kernel_id();
|
||||||
|
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for entry in fs::read_dir(dir)? {
|
util::read_directory(dir, |dent| {
|
||||||
maybe_add_dir_entry(entry?, image_type, channel, kv, kernel_id, &mut v)?;
|
maybe_add_dir_entry(dent, image_type, channel, kv, kernel_id, &mut v)?;
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -454,7 +458,7 @@ fn all_matching_images(dir: &Path, image_type: &str, channel: Option<&str>) -> R
|
|||||||
//
|
//
|
||||||
// If the entry is a match, then instantiate a ResourceImage and add it to
|
// If the entry is a match, then instantiate a ResourceImage and add it to
|
||||||
// the images vector.
|
// the images vector.
|
||||||
fn maybe_add_dir_entry(entry: DirEntry,
|
fn maybe_add_dir_entry(entry: &DirEntry,
|
||||||
image_type: &str,
|
image_type: &str,
|
||||||
channel: Option<&str>,
|
channel: Option<&str>,
|
||||||
kernel_version: Option<&str>,
|
kernel_version: Option<&str>,
|
||||||
@ -465,7 +469,8 @@ fn maybe_add_dir_entry(entry: DirEntry,
|
|||||||
if Some(OsStr::new("img")) != path.extension() {
|
if Some(OsStr::new("img")) != path.extension() {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
let meta = entry.metadata()?;
|
let meta = entry.metadata()
|
||||||
|
.map_err(context!("failed to read metadata for {:?}", entry.path()))?;
|
||||||
if meta.len() < ImageHeader::HEADER_SIZE as u64 {
|
if meta.len() < ImageHeader::HEADER_SIZE as u64 {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path,PathBuf};
|
||||||
use std::os::unix;
|
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{Result, util};
|
||||||
|
|
||||||
pub fn read(path: impl AsRef<Path>) -> Option<PathBuf> {
|
pub fn read(path: impl AsRef<Path>) -> Option<PathBuf> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
@ -29,18 +28,12 @@ pub fn write(target: impl AsRef<Path>, link: impl AsRef<Path>, tmp_in_parent: bo
|
|||||||
let tmp = write_tmp_path(link, tmp_in_parent);
|
let tmp = write_tmp_path(link, tmp_in_parent);
|
||||||
|
|
||||||
if let Some(parent) = link.parent() {
|
if let Some(parent) = link.parent() {
|
||||||
if !parent.exists() {
|
util::create_dir(parent)?;
|
||||||
fs::create_dir_all(parent)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tmp.exists() {
|
util::remove_file(&tmp)?;
|
||||||
fs::remove_file(&tmp)?;
|
util::symlink(target, &tmp)?;
|
||||||
}
|
util::rename(&tmp, link)
|
||||||
|
|
||||||
unix::fs::symlink(target, &tmp)?;
|
|
||||||
fs::rename(&tmp, link)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_tmp_path(link: &Path, tmp_in_parent: bool) -> PathBuf {
|
fn write_tmp_path(link: &Path, tmp_in_parent: bool) -> PathBuf {
|
||||||
@ -60,7 +53,7 @@ fn write_tmp_path(link: &Path, tmp_in_parent: bool) -> PathBuf {
|
|||||||
pub fn remove(path: impl AsRef<Path>) -> Result<()> {
|
pub fn remove(path: impl AsRef<Path>) -> Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
if fs::symlink_metadata(path).is_ok() {
|
if fs::symlink_metadata(path).is_ok() {
|
||||||
fs::remove_file(path)?;
|
util::remove_file(path)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use std::io::{Error,ErrorKind};
|
|||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path,PathBuf};
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{Result, util};
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Create a lockfile and acquire an exclusive lock with flock(2)
|
/// Create a lockfile and acquire an exclusive lock with flock(2)
|
||||||
@ -45,9 +45,7 @@ impl FileLock {
|
|||||||
|
|
||||||
fn open_lockfile(path: &Path) -> Result<File> {
|
fn open_lockfile(path: &Path) -> Result<File> {
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
if !parent.exists() {
|
util::create_dir(parent)?;
|
||||||
fs::create_dir_all(parent)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a few attempts just in case we try to open lockfile
|
// Make a few attempts just in case we try to open lockfile
|
||||||
@ -61,14 +59,14 @@ impl FileLock {
|
|||||||
return Ok(file);
|
return Ok(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(format_err!("unable to open lockfile {}", path.display() ))
|
bail!("unable to open lockfile {:?}", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_create_lockfile(path: &Path) -> Result<Option<File>> {
|
fn try_create_lockfile(path: &Path) -> Result<Option<File>> {
|
||||||
match OpenOptions::new().write(true).create_new(true).open(path) {
|
match OpenOptions::new().write(true).create_new(true).open(path) {
|
||||||
Ok(file) => Ok(Some(file)),
|
Ok(file) => Ok(Some(file)),
|
||||||
Err(ref e) if e.kind() == ErrorKind::AlreadyExists => Ok(None),
|
Err(ref e) if e.kind() == ErrorKind::AlreadyExists => Ok(None),
|
||||||
Err(e) => Err(e.into()),
|
Err(e) => bail!("failed to create lockfile {:?}: {}", path, e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +74,7 @@ impl FileLock {
|
|||||||
match File::open(path) {
|
match File::open(path) {
|
||||||
Ok(file) => Ok(Some(file)),
|
Ok(file) => Ok(Some(file)),
|
||||||
Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(None),
|
Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(None),
|
||||||
Err(e) => Err(e.into()),
|
Err(e) => bail!("failed to open lockfile {:?}: {}", path, e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +97,7 @@ impl FileLock {
|
|||||||
if !block && errno == libc::EWOULDBLOCK {
|
if !block && errno == libc::EWOULDBLOCK {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
return Err(Error::from_raw_os_error(errno).into());
|
bail!("error calling flock(): {}", Error::from_raw_os_error(errno));
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ impl LoopDevice {
|
|||||||
let result = f(&loopdev);
|
let result = f(&loopdev);
|
||||||
let detach_result = loopdev.detach();
|
let detach_result = loopdev.detach();
|
||||||
let r = result?;
|
let r = result?;
|
||||||
detach_result.map_err(|e| format_err!("error detaching loop device: {}", e))?;
|
detach_result.map_err(context!("error detaching loop device"))?;
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,8 +108,7 @@ impl LoopDevice {
|
|||||||
// mount --bind olddir newdir
|
// mount --bind olddir newdir
|
||||||
// mount -o remount,bind,ro olddir newdir
|
// mount -o remount,bind,ro olddir newdir
|
||||||
cmd!(Self::MOUNT, "--bind {} {}", rw.display(), ro.display())?;
|
cmd!(Self::MOUNT, "--bind {} {}", rw.display(), ro.display())?;
|
||||||
cmd!(Self::MOUNT, "-o remount,bind,ro {} {}", rw.display(), ro.display())?;
|
cmd!(Self::MOUNT, "-o remount,bind,ro {} {}", rw.display(), ro.display())
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::fs;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{Result, util};
|
||||||
|
|
||||||
pub struct Mounts {
|
pub struct Mounts {
|
||||||
content: String,
|
content: String,
|
||||||
@ -34,7 +33,7 @@ impl Mounts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn load() -> Result<Mounts> {
|
pub fn load() -> Result<Mounts> {
|
||||||
let content = fs::read_to_string("/proc/mounts")?;
|
let content = util::read_to_string("/proc/mounts")?;
|
||||||
Ok(Mounts { content })
|
Ok(Mounts { content })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::terminal::{RawTerminal, Color, Base16Scheme};
|
use crate::terminal::{RawTerminal, Color, Base16Scheme};
|
||||||
use std::io::{self,Read,Write,Stdout};
|
use std::io::{self,Read,Write,Stdout};
|
||||||
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AnsiControl(String);
|
pub struct AnsiControl(String);
|
||||||
@ -125,10 +126,9 @@ impl AnsiControl {
|
|||||||
io::stdout().flush().unwrap();
|
io::stdout().flush().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to<W: Write>(&self, mut writer: W) -> Result<()> {
|
fn write_to<W: Write>(&self, mut writer: W) -> Result<()> {
|
||||||
writer.write_all(self.as_bytes())?;
|
writer.write_all(self.as_bytes()).map_err(context!("error writing to terminal"))?;
|
||||||
writer.flush()?;
|
writer.flush().map_err(context!("error calling flush() on terminal"))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -137,7 +137,6 @@ pub struct AnsiTerminal {
|
|||||||
raw: RawTerminal<Stdout>,
|
raw: RawTerminal<Stdout>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl AnsiTerminal {
|
impl AnsiTerminal {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let raw = RawTerminal::create(io::stdout())?;
|
let raw = RawTerminal::create(io::stdout())?;
|
||||||
@ -210,18 +209,24 @@ impl AnsiTerminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_code(&mut self, sequence: AnsiControl) -> Result<()> {
|
fn write_code(&mut self, sequence: AnsiControl) -> Result<()> {
|
||||||
|
self._write_code(sequence)
|
||||||
|
.map_err(context!("error writing ansi sequence to terminal"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _write_code(&mut self, sequence: AnsiControl) -> io::Result<()> {
|
||||||
self.raw.write_all(sequence.as_bytes())?;
|
self.raw.write_all(sequence.as_bytes())?;
|
||||||
self.raw.flush()?;
|
self.raw.flush()
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_response(&mut self) -> Result<String> {
|
fn read_response(&mut self) -> Result<String> {
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
let mut input = stdin.lock();
|
let mut input = stdin.lock();
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
input.read_to_end(&mut buffer)?;
|
input.read_to_end(&mut buffer)
|
||||||
let s = String::from_utf8(buffer)?;
|
.map_err(context!("error reading terminal input"))?;
|
||||||
Ok(s)
|
|
||||||
|
String::from_utf8(buffer)
|
||||||
|
.map_err(context!("terminal input is not correct utf-8"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_base16(&mut self, base16: &Base16Scheme) -> Result<()> {
|
pub fn apply_base16(&mut self, base16: &Base16Scheme) -> Result<()> {
|
||||||
@ -235,3 +240,9 @@ impl AnsiTerminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<FromUtf8Error> for crate::Error {
|
||||||
|
fn from(_: FromUtf8Error) -> Self {
|
||||||
|
format_err!("failed to convert string to UTF-8").into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
|||||||
use crate::terminal::{Color, Base16Shell};
|
use crate::terminal::{Color, Base16Shell};
|
||||||
use crate::{Realm, Result, util, RealmManager};
|
use crate::{Realm, Result, util, RealmManager};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs;
|
use std::{fs, io};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@ -123,7 +123,7 @@ impl Base16Scheme {
|
|||||||
|
|
||||||
fn write_ephemeral_realm_files(&self, manager: &RealmManager, realm: &Realm) -> Result<()> {
|
fn write_ephemeral_realm_files(&self, manager: &RealmManager, realm: &Realm) -> Result<()> {
|
||||||
let skel = realm.base_path_file("skel");
|
let skel = realm.base_path_file("skel");
|
||||||
fs::create_dir_all(&skel)?;
|
util::create_dir(&skel)?;
|
||||||
util::chown_user(&skel)?;
|
util::chown_user(&skel)?;
|
||||||
self.write_realm_files(&skel)?;
|
self.write_realm_files(&skel)?;
|
||||||
if realm.is_active() {
|
if realm.is_active() {
|
||||||
@ -144,9 +144,9 @@ impl Base16Scheme {
|
|||||||
pub fn write_realm_files<P: AsRef<Path>>(&self, base: P) -> Result<()> {
|
pub fn write_realm_files<P: AsRef<Path>>(&self, base: P) -> Result<()> {
|
||||||
let base = base.as_ref();
|
let base = base.as_ref();
|
||||||
self.write_shell_file(base)
|
self.write_shell_file(base)
|
||||||
.map_err(|e| format_err!("error writing {} to {}: {}", Self::BASE16_SHELL_FILE, base.display(), e))?;
|
.map_err(context!("error writing {} to {}", Self::BASE16_SHELL_FILE, base.display()))?;
|
||||||
self.write_vim_file(base)
|
self.write_vim_file(base)
|
||||||
.map_err(|e| format_err!("error writing {} to {}: {}", Self::BASE16_VIM_FILE, base.display(), e))?;
|
.map_err(context!("error writing {} to {}", Self::BASE16_VIM_FILE, base.display()))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,16 +160,20 @@ impl Base16Scheme {
|
|||||||
|
|
||||||
fn write_vim_file(&self, dir: &Path) -> Result<()> {
|
fn write_vim_file(&self, dir: &Path) -> Result<()> {
|
||||||
let path = dir.join(Self::BASE16_VIM_FILE);
|
let path = dir.join(Self::BASE16_VIM_FILE);
|
||||||
let mut file = fs::File::create(&path)?;
|
self.write_vim_file_to(&path)
|
||||||
writeln!(&mut file, "if !exists('g:colors_name') || g:colors_name != '{}'", self.slug())?;
|
.map_err(context!("failed to write vim color scheme file to {:?}", path))?;
|
||||||
writeln!(&mut file, " colorscheme base16-{}", self.slug())?;
|
|
||||||
writeln!(&mut file, "endif")?;
|
|
||||||
drop(file);
|
|
||||||
util::chown_user(&path)?;
|
util::chown_user(&path)?;
|
||||||
debug!("Wrote base16 vim config file to {}", path.display());
|
debug!("Wrote base16 vim config file to {}", path.display());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_vim_file_to(&self, path: &Path) -> io::Result<()> {
|
||||||
|
let mut file = fs::File::create(&path)?;
|
||||||
|
writeln!(&mut file, "if !exists('g:colors_name') || g:colors_name != '{}'", self.slug())?;
|
||||||
|
writeln!(&mut file, " colorscheme base16-{}", self.slug())?;
|
||||||
|
writeln!(&mut file, "endif")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::terminal::Base16Scheme;
|
use crate::terminal::Base16Scheme;
|
||||||
use crate::Result;
|
use crate::{Result, util};
|
||||||
|
|
||||||
const TEMPLATE: &str = r##"
|
const TEMPLATE: &str = r##"
|
||||||
if [ -n "$TMUX" ]; then
|
if [ -n "$TMUX" ]; then
|
||||||
@ -70,8 +69,8 @@ impl Base16Shell {
|
|||||||
|
|
||||||
pub fn write_script<P: AsRef<Path>>(path: P, scheme: &Base16Scheme) -> Result<()> {
|
pub fn write_script<P: AsRef<Path>>(path: P, scheme: &Base16Scheme) -> Result<()> {
|
||||||
let output = Base16Shell::new(scheme.clone()).build();
|
let output = Base16Shell::new(scheme.clone()).build();
|
||||||
fs::write(path.as_ref(), output)?;
|
let path = path.as_ref();
|
||||||
Ok(())
|
util::write_file(path, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(scheme: Base16Scheme) -> Self {
|
fn new(scheme: Base16Scheme) -> Self {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{Result, Error};
|
||||||
use crate::terminal::AnsiTerminal;
|
use crate::terminal::AnsiTerminal;
|
||||||
|
|
||||||
#[derive(Copy,Clone,Default,Debug)]
|
#[derive(Copy,Clone,Default,Debug)]
|
||||||
@ -21,7 +21,7 @@ impl Color {
|
|||||||
return Ok(Color(r, g, b))
|
return Ok(Color(r, g, b))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(format_err!("Cannot parse '{}'", s))
|
bail!("Cannot parse '{}'", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rgb(self) -> (u16,u16,u16) {
|
pub fn rgb(self) -> (u16,u16,u16) {
|
||||||
@ -93,3 +93,9 @@ impl TerminalPalette {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<std::num::ParseIntError> for crate::Error {
|
||||||
|
fn from(_: std::num::ParseIntError) -> Self {
|
||||||
|
Error::message("failed to parse integer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -78,7 +78,7 @@ pub fn spawn_citadel_gnome_terminal<S>(command: Option<S>)
|
|||||||
pub fn open_citadel_gnome_terminal<S: AsRef<str>>(command: Option<S>) -> Result<()>
|
pub fn open_citadel_gnome_terminal<S: AsRef<str>>(command: Option<S>) -> Result<()>
|
||||||
{
|
{
|
||||||
let mut cmd = build_open_terminal_command(command);
|
let mut cmd = build_open_terminal_command(command);
|
||||||
let status = cmd.status()?;
|
let status = cmd.status().map_err(context!("error running gnome-terminal"))?;
|
||||||
info!("Gnome terminal exited with: {}", status);
|
info!("Gnome terminal exited with: {}", status);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
@ -1,31 +1,31 @@
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::io::{self,Write};
|
use std::io::{self,Write};
|
||||||
use libc::c_int;
|
|
||||||
|
|
||||||
pub use libc::termios as Termios;
|
pub use libc::termios as Termios;
|
||||||
|
use libc::c_int;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
fn get_terminal_attr() -> io::Result<Termios> {
|
fn get_terminal_attr() -> Result<Termios> {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int;
|
pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int;
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut termios = mem::zeroed();
|
let mut termios = mem::zeroed();
|
||||||
if tcgetattr(0, &mut termios) == -1 {
|
if tcgetattr(0, &mut termios) == -1 {
|
||||||
return Err(io::Error::last_os_error())
|
bail!("error calling tcgetattr() on terminal: {}", io::Error::last_os_error());
|
||||||
}
|
}
|
||||||
Ok(termios)
|
Ok(termios)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_terminal_attr(termios: &Termios) -> io::Result<()> {
|
fn set_terminal_attr(termios: &Termios) -> Result<()> {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int;
|
pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int;
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
if tcsetattr(0, 0, termios) == -1 {
|
if tcsetattr(0, 0, termios) == -1 {
|
||||||
return Err(io::Error::last_os_error())
|
bail!("error calling tcsetattr() on terminal: {}", io::Error::last_os_error());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -54,19 +54,20 @@ impl TerminalRestorer {
|
|||||||
let mut t = self.terminal()?;
|
let mut t = self.terminal()?;
|
||||||
let mut palette = TerminalPalette::default();
|
let mut palette = TerminalPalette::default();
|
||||||
palette.load(&mut t)
|
palette.load(&mut t)
|
||||||
.map_err(|e| format_err!("error reading palette colors from terminal: {}", e))?;
|
.map_err(context!("error reading palette colors from terminal"))?;
|
||||||
Ok(palette)
|
Ok(palette)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_palette(&self, palette: &TerminalPalette) -> Result<()> {
|
fn apply_palette(&self, palette: &TerminalPalette) -> Result<()> {
|
||||||
let mut t = self.terminal()?;
|
let mut t = self.terminal()?;
|
||||||
palette.apply(&mut t)
|
palette.apply(&mut t)
|
||||||
.map_err(|e| format_err!("error setting palette on terminal: {}", e))
|
.map_err(context!("error setting palette on terminal"))?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn terminal(&self) -> Result<AnsiTerminal> {
|
fn terminal(&self) -> Result<AnsiTerminal> {
|
||||||
AnsiTerminal::new()
|
AnsiTerminal::new()
|
||||||
.map_err(|e| format_err!("failed to create AnsiTerminal: {}", e))
|
.map_err(context!("failed to create AnsiTerminal"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_base16_by_slug<S: AsRef<str>>(&self, slug: S) {
|
pub fn apply_base16_by_slug<S: AsRef<str>>(&self, slug: S) {
|
||||||
@ -84,11 +85,10 @@ impl TerminalRestorer {
|
|||||||
fn apply_base16(&self, scheme: &Base16Scheme) -> Result<()> {
|
fn apply_base16(&self, scheme: &Base16Scheme) -> Result<()> {
|
||||||
let mut t = self.terminal()?;
|
let mut t = self.terminal()?;
|
||||||
t.apply_base16(scheme)
|
t.apply_base16(scheme)
|
||||||
.map_err(|e| format_err!("error setting base16 palette colors: {}", e))?;
|
.map_err(context!("error setting base16 palette colors"))?;
|
||||||
t.clear_screen()
|
t.clear_screen()
|
||||||
.map_err(|e| format_err!("error clearing screen: {}", e))
|
.map_err(context!("error clearing screen"))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TerminalRestorer {
|
impl Drop for TerminalRestorer {
|
||||||
|
@ -2,16 +2,16 @@ use std::path::{Path,PathBuf};
|
|||||||
use std::process::{Command,Stdio};
|
use std::process::{Command,Stdio};
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::os::unix::fs as unixfs;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{self,File};
|
use std::fs::{self, File, DirEntry};
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::io::{self, Seek, Read, BufReader, SeekFrom};
|
use std::io::{self, Seek, Read, BufReader, SeekFrom};
|
||||||
|
|
||||||
use failure::ResultExt;
|
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use libc;
|
use libc;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::{Result, util};
|
||||||
|
|
||||||
pub fn is_valid_name(name: &str, maxsize: usize) -> bool {
|
pub fn is_valid_name(name: &str, maxsize: usize) -> bool {
|
||||||
name.len() <= maxsize &&
|
name.len() <= maxsize &&
|
||||||
@ -36,14 +36,14 @@ pub fn is_first_char_alphabetic(s: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_path(filename: &str) -> Result<PathBuf> {
|
fn search_path(filename: &str) -> Result<PathBuf> {
|
||||||
let path_var = env::var("PATH")?;
|
let path_var = env::var("PATH").unwrap_or("".into());
|
||||||
for mut path in env::split_paths(&path_var) {
|
for mut path in env::split_paths(&path_var) {
|
||||||
path.push(filename);
|
path.push(filename);
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
return Ok(path);
|
return Ok(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(format_err!("Could not find {} in $PATH", filename))
|
bail!("could not find {} in $PATH", filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_command_exists(cmd: &str) -> Result<()> {
|
pub fn ensure_command_exists(cmd: &str) -> Result<()> {
|
||||||
@ -54,14 +54,13 @@ pub fn ensure_command_exists(cmd: &str) -> Result<()> {
|
|||||||
} else if path.exists() {
|
} else if path.exists() {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
Err(format_err!("Cannot execute '{}': command does not exist", cmd))
|
bail!("cannot execute '{}': command does not exist", cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn sha256<P: AsRef<Path>>(path: P) -> Result<String> {
|
pub fn sha256<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let output = cmd_with_output!("/usr/bin/sha256sum", "{}", path.display())
|
let output = cmd_with_output!("/usr/bin/sha256sum", "{}", path.display())
|
||||||
.context(format!("failed to calculate sha256 on {}", path.display()))?;
|
.map_err(context!("failed to calculate sha256 on {:?}", path))?;
|
||||||
|
|
||||||
let v: Vec<&str> = output.split_whitespace().collect();
|
let v: Vec<&str> = output.split_whitespace().collect();
|
||||||
Ok(v[0].trim().to_owned())
|
Ok(v[0].trim().to_owned())
|
||||||
@ -75,14 +74,17 @@ pub enum FileRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ranged_reader<P: AsRef<Path>>(path: P, range: FileRange) -> Result<Box<dyn Read>> {
|
fn ranged_reader<P: AsRef<Path>>(path: P, range: FileRange) -> Result<Box<dyn Read>> {
|
||||||
let mut f = File::open(path.as_ref())?;
|
let path = path.as_ref();
|
||||||
|
let mut f = File::open(path)
|
||||||
|
.map_err(context!("error opening input file {:?}", path))?;
|
||||||
let offset = match range {
|
let offset = match range {
|
||||||
FileRange::All => 0,
|
FileRange::All => 0,
|
||||||
FileRange::Offset(n) => n,
|
FileRange::Offset(n) => n,
|
||||||
FileRange::Range {offset, .. } => offset,
|
FileRange::Range {offset, .. } => offset,
|
||||||
};
|
};
|
||||||
if offset > 0 {
|
if offset > 0 {
|
||||||
f.seek(SeekFrom::Start(offset as u64))?;
|
f.seek(SeekFrom::Start(offset as u64))
|
||||||
|
.map_err(context!("error seeking to offset {} in input file {:?}", offset, path))?;
|
||||||
}
|
}
|
||||||
let r = BufReader::new(f);
|
let r = BufReader::new(f);
|
||||||
if let FileRange::Range {len, ..} = range {
|
if let FileRange::Range {len, ..} = range {
|
||||||
@ -107,26 +109,26 @@ pub fn exec_cmdline_pipe_input<S,P>(cmd_path: &str, args: S, input: P, range: Fi
|
|||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.spawn()
|
.spawn()
|
||||||
.context(format!("unable to execute {}", cmd_path))?;
|
.map_err(context!("unable to execute {}", cmd_path))?;
|
||||||
|
|
||||||
let stdin = child.stdin.as_mut().unwrap();
|
let stdin = child.stdin.as_mut().unwrap();
|
||||||
io::copy(&mut r, stdin)?;
|
io::copy(&mut r, stdin)
|
||||||
let output = child.wait_with_output()?;
|
.map_err(context!("error copying input to stdin"))?;
|
||||||
|
let output = child.wait_with_output()
|
||||||
|
.map_err(context!("error waiting for command {} to exit", cmd_path))?;
|
||||||
Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned())
|
Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn xz_compress<P: AsRef<Path>>(path: P) -> Result<()> {
|
pub fn xz_compress<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
cmd!("/usr/bin/xz", "-T0 {}", path.display())
|
cmd!("/usr/bin/xz", "-T0 {}", path.display())
|
||||||
.context(format!("failed to compress {}", path.display()))?;
|
.map_err(context!("failed to compress {:?}", path))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn xz_decompress<P: AsRef<Path>>(path: P) -> Result<()> {
|
pub fn xz_decompress<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
cmd!("/usr/bin/xz", "-d {}", path.display())
|
cmd!("/usr/bin/xz", "-d {}", path.display())
|
||||||
.context(format!("failed to decompress {}", path.display()))?;
|
.map_err(context!("failed to decompress {:?}", path))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mount<P: AsRef<Path>>(source: impl AsRef<str>, target: P, options: Option<&str>) -> Result<()> {
|
pub fn mount<P: AsRef<Path>>(source: impl AsRef<str>, target: P, options: Option<&str>) -> Result<()> {
|
||||||
@ -136,39 +138,145 @@ pub fn mount<P: AsRef<Path>>(source: impl AsRef<str>, target: P, options: Option
|
|||||||
cmd!("/usr/bin/mount", "{} {} {}", options, source, target.display())
|
cmd!("/usr/bin/mount", "{} {} {}", options, source, target.display())
|
||||||
} else {
|
} else {
|
||||||
cmd!("/usr/bin/mount", "{} {}", source, target.display())
|
cmd!("/usr/bin/mount", "{} {}", source, target.display())
|
||||||
}
|
}.map_err(context!("failed to mount {} to {:?}", source, target))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn umount<P: AsRef<Path>>(path: P) -> Result<()> {
|
pub fn umount<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
cmd!("/usr/bin/umount", "{}", path.display())
|
cmd!("/usr/bin/umount", "{}", path.display())
|
||||||
|
.map_err(context!("failed to unmount {:?}", path))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chown_user<P: AsRef<Path>>(path: P) -> io::Result<()> {
|
pub fn chown_user<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||||
chown(path.as_ref(), 1000, 1000)
|
chown(path.as_ref(), 1000, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
|
pub fn chown(path: &Path, uid: u32, gid: u32) -> Result<()> {
|
||||||
let cstr = CString::new(path.as_os_str().as_bytes())?;
|
let cstr = CString::new(path.as_os_str().as_bytes())
|
||||||
|
.expect("path contains null byte");
|
||||||
unsafe {
|
unsafe {
|
||||||
if libc::chown(cstr.as_ptr(), uid, gid) == -1 {
|
if libc::chown(cstr.as_ptr(), uid, gid) == -1 {
|
||||||
return Err(io::Error::last_os_error());
|
let err = io::Error::last_os_error();
|
||||||
|
bail!("failed to chown({},{}) {:?}: {}", uid, gid, path, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rename or move file at `from` to file path `to`
|
||||||
|
///
|
||||||
|
/// A wrapper around `fs::rename()` which on failure returns an error indicating the source and
|
||||||
|
/// destination paths.
|
||||||
|
///
|
||||||
|
pub fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
|
||||||
|
let from = from.as_ref();
|
||||||
|
let to = to.as_ref();
|
||||||
|
fs::rename(from, to)
|
||||||
|
.map_err(context!("error renaming {:?} to {:?}", from, to))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a symlink at path `dst` which points to `src`
|
||||||
|
///
|
||||||
|
/// A wrapper around `fs::symlink()` which on failure returns an error indicating the source and
|
||||||
|
/// destination paths.
|
||||||
|
///
|
||||||
|
pub fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
|
||||||
|
let src = src.as_ref();
|
||||||
|
let dst = dst.as_ref();
|
||||||
|
unixfs::symlink(src, dst)
|
||||||
|
.map_err(context!("failed to create symlink {:?} to {:?}", dst, src))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read directory `dir` and call closure `f` on each `DirEntry`
|
||||||
|
pub fn read_directory<F>(dir: impl AsRef<Path>, mut f: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnMut(&DirEntry) -> Result<()>
|
||||||
|
{
|
||||||
|
let dir = dir.as_ref();
|
||||||
|
let entries = fs::read_dir(dir)
|
||||||
|
.map_err(context!("failed to read directory {:?}", dir))?;
|
||||||
|
for dent in entries {
|
||||||
|
let dent = dent.map_err(context!("error reading entry from directory {:?}", dir))?;
|
||||||
|
f(&dent)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove file at `path` if it exists.
|
||||||
|
///
|
||||||
|
/// A wrapper around `fs::remove_file()` which on failure returns an error indicating the path of
|
||||||
|
/// the file which failed to be removed.
|
||||||
|
///
|
||||||
|
pub fn remove_file(path: impl AsRef<Path>) -> Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
if path.exists() {
|
||||||
|
fs::remove_file(path)
|
||||||
|
.map_err(context!("failed to remove file {:?}", path))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create directory `path` if it does not already exist.
|
||||||
|
///
|
||||||
|
/// A wrapper around `fs::create_dir_all()` which on failure returns an error indicating the path
|
||||||
|
/// of the directory which failed to be created.
|
||||||
|
///
|
||||||
|
pub fn create_dir(path: impl AsRef<Path>) -> Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
if !path.exists() {
|
||||||
|
fs::create_dir_all(path)
|
||||||
|
.map_err(context!("failed to create directory {:?}", path))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write `contents` to file `path`
|
||||||
|
///
|
||||||
|
/// A wrapper around `fs::write()` which on failure returns an error indicating the path
|
||||||
|
/// of the file which failed to be written.
|
||||||
|
///
|
||||||
|
pub fn write_file(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
fs::write(path, contents)
|
||||||
|
.map_err(context!("failed to write to file {:?}", path))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read content of file `path` into a `String`
|
||||||
|
///
|
||||||
|
/// A wrapper around `fs::read_to_string()` which on failure returns an error indicating the path
|
||||||
|
/// of the file which failed to be read.
|
||||||
|
///
|
||||||
|
pub fn read_to_string(path: impl AsRef<Path>) -> Result<String> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
fs::read_to_string(path)
|
||||||
|
.map_err(context!("failed to read file {:?}", path))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy file at path `from` to a new file at path `to`
|
||||||
|
///
|
||||||
|
/// A wrapper around `fs::copy()` which on failure returns an error indicating the source and
|
||||||
|
/// destination paths.
|
||||||
|
///
|
||||||
|
pub fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
|
||||||
|
let from = from.as_ref();
|
||||||
|
let to = to.as_ref();
|
||||||
|
fs::copy(from, to)
|
||||||
|
.map_err(context!("failed to copy file {:?} to {:?}", from, to))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn copy_path(from: &Path, to: &Path, chown_to: Option<(u32,u32)>) -> Result<()> {
|
fn copy_path(from: &Path, to: &Path, chown_to: Option<(u32,u32)>) -> Result<()> {
|
||||||
if to.exists() {
|
if to.exists() {
|
||||||
bail!("destination path {} already exists which is not expected", to.display());
|
bail!("destination path {} already exists which is not expected", to.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
let meta = from.metadata()?;
|
let meta = from.metadata()
|
||||||
|
.map_err(context!("failed to read metadata from source file {:?}", from))?;
|
||||||
|
|
||||||
if from.is_dir() {
|
if from.is_dir() {
|
||||||
fs::create_dir(to)?;
|
util::create_dir(to)?;
|
||||||
} else {
|
} else {
|
||||||
fs::copy(&from, &to)?;
|
util::copy_file(&from, &to)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((uid,gid)) = chown_to {
|
if let Some((uid,gid)) = chown_to {
|
||||||
@ -190,11 +298,14 @@ pub fn copy_tree_with_chown(from_base: &Path, to_base: &Path, chown_to: (u32,u32
|
|||||||
|
|
||||||
fn _copy_tree(from_base: &Path, to_base: &Path, chown_to: Option<(u32,u32)>) -> Result<()> {
|
fn _copy_tree(from_base: &Path, to_base: &Path, chown_to: Option<(u32,u32)>) -> Result<()> {
|
||||||
for entry in WalkDir::new(from_base) {
|
for entry in WalkDir::new(from_base) {
|
||||||
let path = entry?.path().to_owned();
|
let entry = entry.map_err(|e| format_err!("Error walking directory tree: {}", e))?;
|
||||||
let to = to_base.join(path.strip_prefix(from_base)?);
|
let path = entry.path();
|
||||||
if to != to_base {
|
let suffix = path.strip_prefix(from_base)
|
||||||
copy_path(&path, &to, chown_to)
|
.map_err(|_| format_err!("Failed to strip prefix from {:?}", path))?;
|
||||||
.map_err(|e| format_err!("failed to copy {} to {}: {}", path.display(), to.display(), e))?;
|
let to = to_base.join(suffix);
|
||||||
|
if &to != to_base {
|
||||||
|
copy_path(path, &to, chown_to)
|
||||||
|
.map_err(context!("failed to copy {:?} to {:?}", path, to))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -202,9 +313,10 @@ fn _copy_tree(from_base: &Path, to_base: &Path, chown_to: Option<(u32,u32)>) ->
|
|||||||
|
|
||||||
pub fn chown_tree(base: &Path, chown_to: (u32,u32), include_base: bool) -> Result<()> {
|
pub fn chown_tree(base: &Path, chown_to: (u32,u32), include_base: bool) -> Result<()> {
|
||||||
for entry in WalkDir::new(base) {
|
for entry in WalkDir::new(base) {
|
||||||
let entry = entry?;
|
let entry = entry.map_err(|e| format_err!("Error reading directory entry: {}", e))?;
|
||||||
if entry.path() != base || include_base {
|
if entry.path() != base || include_base {
|
||||||
chown(entry.path(), chown_to.0, chown_to.1)?;
|
chown(entry.path(), chown_to.0, chown_to.1)
|
||||||
|
.map_err(context!("failed to chown {:?}", entry.path()))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path,PathBuf};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::{self, OpenOptions,File};
|
use std::fs::{OpenOptions,File};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::{Result, MetaInfo, Partition, LoopDevice, ImageHeader};
|
use crate::{Result, MetaInfo, Partition, LoopDevice, ImageHeader, util};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
||||||
@ -42,21 +42,25 @@ impl Verity {
|
|||||||
let verityfile = self.image.with_extension("verity");
|
let verityfile = self.image.with_extension("verity");
|
||||||
|
|
||||||
// Make sure file size is correct or else verity tree will be appended in wrong place
|
// Make sure file size is correct or else verity tree will be appended in wrong place
|
||||||
let meta = self.image.metadata()?;
|
let meta = self.image.metadata()
|
||||||
|
.map_err(context!("failed to read metadata from image file {:?}", self.image))?;
|
||||||
let len = meta.len() as usize;
|
let len = meta.len() as usize;
|
||||||
let expected = (nblocks + 1) * 4096;
|
let expected = (nblocks + 1) * 4096;
|
||||||
if len != expected {
|
if len != expected {
|
||||||
bail!("Actual file size ({}) does not match expected size ({})", len, expected);
|
bail!("actual file size ({}) does not match expected size ({})", len, expected);
|
||||||
}
|
}
|
||||||
let vout = LoopDevice::with_loop(self.path(), Some(4096), true, |loopdev| {
|
let vout = LoopDevice::with_loop(self.path(), Some(4096), true, |loopdev| {
|
||||||
let output = cmd_with_output!(Self::VERITYSETUP, "--data-blocks={} --salt={} format {} {}",
|
let output = cmd_with_output!(Self::VERITYSETUP, "--data-blocks={} --salt={} format {} {}",
|
||||||
nblocks, salt, loopdev, verityfile.display())?;
|
nblocks, salt, loopdev, verityfile.display())?;
|
||||||
Ok(VerityOutput::parse(&output))
|
Ok(VerityOutput::parse(&output))
|
||||||
})?;
|
})?;
|
||||||
let mut input = File::open(&verityfile)?;
|
let mut input = File::open(&verityfile)
|
||||||
let mut output = OpenOptions::new().append(true).open(self.path())?;
|
.map_err(context!("failed to open temporary verity hashtree file {:?}", verityfile))?;
|
||||||
io::copy(&mut input, &mut output)?;
|
let mut output = OpenOptions::new().append(true).open(self.path())
|
||||||
fs::remove_file(&verityfile)?;
|
.map_err(context!("failed to open image file {:?}", self.path()))?;
|
||||||
|
io::copy(&mut input, &mut output)
|
||||||
|
.map_err(context!("i/o error copying verity hashtree to image file"))?;
|
||||||
|
util::remove_file(&verityfile)?;
|
||||||
Ok(vout)
|
Ok(vout)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,9 +108,7 @@ impl Verity {
|
|||||||
let nblocks = metainfo.nblocks();
|
let nblocks = metainfo.nblocks();
|
||||||
let verity_root = metainfo.verity_root();
|
let verity_root = metainfo.verity_root();
|
||||||
cmd!(Self::VERITYSETUP, "--hash-offset={} --data-blocks={} create {} {} {} {}",
|
cmd!(Self::VERITYSETUP, "--hash-offset={} --data-blocks={} create {} {} {} {}",
|
||||||
nblocks * 4096, nblocks, devname, srcdev, srcdev, verity_root)?;
|
nblocks * 4096, nblocks, devname, srcdev, srcdev, verity_root)
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path(&self) -> &Path {
|
fn path(&self) -> &Path {
|
||||||
|
Loading…
Reference in New Issue
Block a user