Improve error handling by using std lib rust code #4
@ -4,7 +4,7 @@ use std::fs;
|
||||
use std::thread::{self,JoinHandle};
|
||||
use std::time::{self,Instant};
|
||||
|
||||
use libcitadel::{Result, UtsName, util};
|
||||
use libcitadel::{UtsName, util};
|
||||
use libcitadel::ResourceImage;
|
||||
|
||||
use crate::boot::disks;
|
||||
@ -13,34 +13,33 @@ use crate::install::installer::Installer;
|
||||
|
||||
const IMAGE_DIRECTORY: &str = "/run/citadel/images";
|
||||
|
||||
pub fn live_rootfs() -> Result<()> {
|
||||
pub fn live_rootfs() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
copy_artifacts()?;
|
||||
let rootfs = find_rootfs_image()?;
|
||||
setup_rootfs_resource(&rootfs)
|
||||
}
|
||||
|
||||
pub fn live_setup() -> Result<()> {
|
||||
pub fn live_setup() -> Result<(), Box<dyn std::error::Error>> {
|
||||
decompress_images(true)?;
|
||||
info!("Starting live setup");
|
||||
let live = Installer::new_livesetup();
|
||||
live.run()
|
||||
}
|
||||
|
||||
fn copy_artifacts() -> Result<()> {
|
||||
fn copy_artifacts() -> Result<(), Box<dyn std::error::Error>> {
|
||||
for _ in 0..3 {
|
||||
if try_copy_artifacts()? {
|
||||
//decompress_images()?;
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
// Try again after waiting for more devices to be discovered
|
||||
info!("Failed to find partition with images, trying again in 2 seconds");
|
||||
thread::sleep(time::Duration::from_secs(2));
|
||||
}
|
||||
bail!("could not find partition containing resource images")
|
||||
|
||||
Result::Err("could not find partition containing resource images".into())
|
||||
}
|
||||
|
||||
fn try_copy_artifacts() -> Result<bool> {
|
||||
fn try_copy_artifacts() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let rootfs_image = Path::new("/boot/images/citadel-rootfs.img");
|
||||
// Already mounted?
|
||||
if rootfs_image.exists() {
|
||||
@ -60,13 +59,13 @@ fn try_copy_artifacts() -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn kernel_version() -> String {
|
||||
fn kernel_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||
brl
commented
What? Why does this need a What? Why does this need a `Result` return type?
|
||||
let utsname = UtsName::uname();
|
||||
let v = utsname.release().split('-').collect::<Vec<_>>();
|
||||
v[0].to_string()
|
||||
Ok(v[0].to_string())
|
||||
}
|
||||
|
||||
fn deploy_artifacts() -> Result<()> {
|
||||
fn deploy_artifacts() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let run_images = Path::new(IMAGE_DIRECTORY);
|
||||
if !run_images.exists() {
|
||||
util::create_dir(run_images)?;
|
||||
@ -78,7 +77,7 @@ fn deploy_artifacts() -> Result<()> {
|
||||
util::copy_file(dent.path(), run_images.join(dent.file_name()))
|
||||
})?;
|
||||
|
||||
let kv = kernel_version();
|
||||
let kv = kernel_version()?;
|
||||
println!("Copying bzImage-{} to /run/citadel/images", kv);
|
||||
let from = format!("/boot/bzImage-{}", kv);
|
||||
let to = format!("/run/citadel/images/bzImage-{}", kv);
|
||||
@ -92,7 +91,7 @@ fn deploy_artifacts() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deploy_syslinux_artifacts() -> Result<()> {
|
||||
fn deploy_syslinux_artifacts() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let boot_syslinux = Path::new("/boot/syslinux");
|
||||
|
||||
if !boot_syslinux.exists() {
|
||||
@ -112,10 +111,11 @@ fn deploy_syslinux_artifacts() -> Result<()> {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_rootfs_image() -> Result<ResourceImage> {
|
||||
fn find_rootfs_image() -> Result<ResourceImage, Box<dyn std::error::Error>> {
|
||||
let entries = fs::read_dir(IMAGE_DIRECTORY)
|
||||
.map_err(context!("error reading directory {}", IMAGE_DIRECTORY))?;
|
||||
for entry in entries {
|
||||
@ -123,15 +123,21 @@ fn find_rootfs_image() -> Result<ResourceImage> {
|
||||
if entry.path().extension() == Some(OsStr::new("img")) {
|
||||
if let Ok(image) = ResourceImage::from_path(&entry.path()) {
|
||||
if image.metainfo().image_type() == "rootfs" {
|
||||
return Ok(image)
|
||||
return Ok(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bail!("unable to find rootfs resource image in {}", IMAGE_DIRECTORY)
|
||||
Result::Err(
|
||||
brl
commented
This is an improvement over the current error handling API? This is an improvement over the current error handling API?
|
||||
format!(
|
||||
"unable to find rootfs resource image in {}",
|
||||
IMAGE_DIRECTORY
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
fn decompress_images(sync: bool) -> Result<()> {
|
||||
fn decompress_images(sync: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("Decompressing images");
|
||||
let mut threads = Vec::new();
|
||||
util::read_directory("/run/citadel/images", |dent| {
|
||||
@ -140,9 +146,8 @@ fn decompress_images(sync: bool) -> Result<()> {
|
||||
if image.is_compressed() {
|
||||
if sync {
|
||||
if let Err(err) = decompress_one_image_sync(image) {
|
||||
warn!("Error: {}", err);
|
||||
warn!("Error decompressing image: {}", err);
|
||||
}
|
||||
|
||||
} else {
|
||||
threads.push(decompress_one_image(image));
|
||||
}
|
||||
@ -161,9 +166,11 @@ fn decompress_images(sync: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decompress_one_image_sync(image: ResourceImage) -> Result<()> {
|
||||
let start = Instant::now();
|
||||
info!("Decompressing {}", image.path().display());
|
||||
fn decompress_one_image_sync(
|
||||
image: ResourceImage,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let start = Instant::now();
|
||||
info!("Decompressing {}", image.path().display());
|
||||
image.decompress(true)
|
||||
.map_err(|e| format_err!("Failed to decompress image file {}: {}", image.path().display(), e))?;
|
||||
cmd!("/usr/bin/du", "-h {}", image.path().display())?;
|
||||
@ -173,8 +180,7 @@ fn decompress_one_image_sync(image: ResourceImage) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decompress_one_image(image: ResourceImage) -> JoinHandle<Result<()>> {
|
||||
thread::spawn(move || {
|
||||
decompress_one_image_sync(image)
|
||||
})
|
||||
fn decompress_one_image(image: ResourceImage,) ->
|
||||
JoinHandle<Result<(), Box<dyn std::error::Error + Send + Sync>>> {
|
||||
thread::spawn(move || decompress_one_image_sync(image))
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::fs;
|
||||
use std::process::exit;
|
||||
|
||||
use libcitadel::{Result, ResourceImage, CommandLine, KeyRing, LogLevel, Logger, util};
|
||||
use libcitadel::{ResourceImage, CommandLine, KeyRing, LogLevel, Logger, util};
|
||||
use libcitadel::RealmManager;
|
||||
use crate::boot::disks::DiskPartition;
|
||||
use std::path::Path;
|
||||
@ -10,44 +9,40 @@ mod live;
|
||||
mod disks;
|
||||
mod rootfs;
|
||||
|
||||
pub fn main(args: Vec<String>) {
|
||||
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if CommandLine::debug() {
|
||||
Logger::set_log_level(LogLevel::Debug);
|
||||
} else if CommandLine::verbose() {
|
||||
Logger::set_log_level(LogLevel::Info);
|
||||
}
|
||||
|
||||
let result = match args.get(1) {
|
||||
match args.get(1) {
|
||||
Some(s) if s == "rootfs" => do_rootfs(),
|
||||
Some(s) if s == "setup" => do_setup(),
|
||||
Some(s) if s == "boot-automount" => do_boot_automount(),
|
||||
Some(s) if s == "start-realms" => do_start_realms(),
|
||||
_ => Err(format_err!("Bad or missing argument").into()),
|
||||
brl
commented
Ooops, you forgot to replace a Ooops, you forgot to replace a `libcitadel::error::Error` the 'improved' version and everything still works when you return it. Are you still sure rewriting everything was what you needed to do?
|
||||
};
|
||||
}?;
|
||||
|
||||
if let Err(ref e) = result {
|
||||
warn!("Failed: {}", e);
|
||||
exit(1);
|
||||
brl
commented
Is rewriting this to not return a specific exit code the best plan? I don't know if this ever worked as intended to cause the corresponding systemd units to fail and then bring the system into a state where the problem can be understood and diagnosed, but now the utility has an undefined exit code that we may end up relying on. Is rewriting this to not return a specific exit code the best plan? I don't know if this ever worked as intended to cause the corresponding systemd units to fail and then bring the system into a state where the problem can be understood and diagnosed, but now the utility has an undefined exit code that we may end up relying on.
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn do_rootfs() -> Result<()> {
|
||||
fn do_rootfs() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if CommandLine::live_mode() || CommandLine::install_mode() {
|
||||
live::live_rootfs()
|
||||
} else {
|
||||
rootfs::setup_rootfs()
|
||||
Ok(rootfs::setup_rootfs()?)
|
||||
brl
commented
No, don't do that. It misleads reader to think there is a return value from both of these functions. Do this:
No, don't do that. It misleads reader to think there is a return value from both of these functions.
Do this:
```rust
rootfs::setup_rootfs()?;
Ok(())
```
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_keyring() -> Result<()> {
|
||||
fn setup_keyring() -> Result<(), Box<dyn std::error::Error>> {
|
||||
ResourceImage::ensure_storage_mounted()?;
|
||||
let keyring = KeyRing::load_with_cryptsetup_passphrase("/sysroot/storage/keyring")?;
|
||||
keyring.add_keys_to_kernel()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_setup() -> Result<()> {
|
||||
fn do_setup() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if CommandLine::live_mode() || CommandLine::install_mode() {
|
||||
live::live_setup()?;
|
||||
} else if let Err(err) = setup_keyring() {
|
||||
@ -64,8 +59,7 @@ fn do_setup() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn mount_overlay() -> Result<()> {
|
||||
fn mount_overlay() -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("Creating rootfs overlay");
|
||||
|
||||
info!("Moving /sysroot mount to /rootfs.ro");
|
||||
@ -89,13 +83,13 @@ fn mount_overlay() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_start_realms() -> Result<()> {
|
||||
fn do_start_realms() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let manager = RealmManager::load()?;
|
||||
manager.start_boot_realms()
|
||||
Ok(manager.start_boot_realms()?)
|
||||
}
|
||||
|
||||
// Write automount unit for /boot partition
|
||||
fn do_boot_automount() -> Result<()> {
|
||||
fn do_boot_automount() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Logger::set_log_level(LogLevel::Info);
|
||||
|
||||
if CommandLine::live_mode() || CommandLine::install_mode() {
|
||||
@ -105,10 +99,10 @@ fn do_boot_automount() -> Result<()> {
|
||||
|
||||
let boot_partition = find_boot_partition()?;
|
||||
info!("Creating /boot automount units for boot partition {}", boot_partition);
|
||||
cmd!("/usr/bin/systemd-mount", "-A --timeout-idle-sec=300 {} /boot", boot_partition)
|
||||
Ok(cmd!("/usr/bin/systemd-mount", "-A --timeout-idle-sec=300 {} /boot", boot_partition)?)
|
||||
}
|
||||
|
||||
fn find_boot_partition() -> Result<String> {
|
||||
fn find_boot_partition() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let loader_dev = read_loader_dev_efi_var()?;
|
||||
let boot_partitions = DiskPartition::boot_partitions(true)?
|
||||
.into_iter()
|
||||
@ -116,7 +110,7 @@ fn find_boot_partition() -> Result<String> {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if boot_partitions.len() != 1 {
|
||||
return Err(format_err!("Cannot uniquely determine boot partition"));
|
||||
return Result::Err("Cannot uniquely determine boot partition".into());
|
||||
}
|
||||
|
||||
Ok(boot_partitions[0].path().display().to_string())
|
||||
@ -141,7 +135,7 @@ fn matches_loader_dev(partition: &DiskPartition, dev: &Option<String>) -> bool {
|
||||
const LOADER_EFI_VAR_PATH: &str =
|
||||
"/sys/firmware/efi/efivars/LoaderDevicePartUUID-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f";
|
||||
|
||||
fn read_loader_dev_efi_var() -> Result<Option<String>> {
|
||||
fn read_loader_dev_efi_var() -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
let efi_var = Path::new(LOADER_EFI_VAR_PATH);
|
||||
if efi_var.exists() {
|
||||
let s = fs::read(efi_var)
|
||||
|
@ -1,10 +1,10 @@
|
||||
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, LoopDevice};
|
||||
use libcitadel::verity::Verity;
|
||||
|
||||
pub fn setup_rootfs() -> Result<()> {
|
||||
pub fn setup_rootfs() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut p = choose_boot_partiton(true, CommandLine::revert_rootfs())?;
|
||||
if CommandLine::noverity() {
|
||||
setup_partition_unverified(&p)
|
||||
@ -13,7 +13,7 @@ pub fn setup_rootfs() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_rootfs_resource(rootfs: &ResourceImage) -> Result<()> {
|
||||
pub fn setup_rootfs_resource(rootfs: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if CommandLine::noverity() {
|
||||
setup_resource_unverified(&rootfs)
|
||||
} else {
|
||||
@ -21,7 +21,7 @@ pub fn setup_rootfs_resource(rootfs: &ResourceImage) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_resource_unverified(img: &ResourceImage) -> Result<()> {
|
||||
fn setup_resource_unverified(img: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if img.is_compressed() {
|
||||
img.decompress(false)?;
|
||||
}
|
||||
@ -30,25 +30,31 @@ fn setup_resource_unverified(img: &ResourceImage) -> Result<()> {
|
||||
setup_linear_mapping(loopdev.device())
|
||||
}
|
||||
|
||||
fn setup_resource_verified(img: &ResourceImage) -> Result<()> {
|
||||
fn setup_resource_verified(img: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let _ = img.setup_verity_device()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_partition_unverified(p: &Partition) -> Result<()> {
|
||||
fn setup_partition_unverified(p: &Partition) -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("Creating /dev/mapper/rootfs device with linear device mapping of partition (no verity)");
|
||||
setup_linear_mapping(p.path())
|
||||
}
|
||||
|
||||
fn setup_partition_verified(p: &mut Partition) -> Result<()> {
|
||||
fn setup_partition_verified(p: &mut Partition) -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("Creating /dev/mapper/rootfs dm-verity device");
|
||||
if !CommandLine::nosignatures() {
|
||||
if !p.has_public_key() {
|
||||
bail!("no public key available for channel {}", p.metainfo().channel())
|
||||
return Result::Err(
|
||||
format!(
|
||||
"no public key available for channel {}",
|
||||
p.metainfo().channel()
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if !p.is_signature_valid() {
|
||||
p.write_status(ImageHeader::STATUS_BAD_SIG)?;
|
||||
bail!("signature verification failed on partition");
|
||||
return Result::Err("signature verification failed on partition".into());
|
||||
}
|
||||
info!("Image signature is valid for channel {}", p.metainfo().channel());
|
||||
}
|
||||
@ -56,7 +62,7 @@ fn setup_partition_verified(p: &mut Partition) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_linear_mapping(blockdev: &Path) -> Result<()> {
|
||||
fn setup_linear_mapping(blockdev: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let dev = BlockDev::open_ro(blockdev)?;
|
||||
let table = format!("0 {} linear {} 0", dev.nsectors()?, blockdev.display());
|
||||
|
||||
@ -70,7 +76,9 @@ fn setup_linear_mapping(blockdev: &Path) -> Result<()> {
|
||||
.success();
|
||||
|
||||
if !ok {
|
||||
bail!("failed to set up linear identity mapping with /usr/sbin/dmsetup");
|
||||
return Result::Err(
|
||||
"failed to set up linear identity mapping with /usr/sbin/dmsetup".into(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -94,7 +102,8 @@ fn choose_revert_partition(best: Option<Partition>) -> Option<Partition> {
|
||||
best
|
||||
}
|
||||
|
||||
fn choose_boot_partiton(scan: bool, revert_rootfs: bool) -> Result<Partition> {
|
||||
fn choose_boot_partiton(scan: bool, revert_rootfs: bool,
|
||||
) -> Result<Partition, Box<dyn std::error::Error>> {
|
||||
let mut partitions = Partition::rootfs_partitions()?;
|
||||
|
||||
if scan {
|
||||
@ -136,14 +145,17 @@ fn compare_boot_partitions(a: Option<Partition>, b: Partition) -> Option<Partiti
|
||||
}
|
||||
|
||||
// Compare versions and channels
|
||||
let a_v = a.metainfo().version();
|
||||
let b_v = b.metainfo().version();
|
||||
let meta_a = a.metainfo();
|
||||
brl
commented
Why did you rewrite this? Why is this buried in a massive commit described vaguely as "improve error handling"? Why did you rewrite this? Why is this buried in a massive commit described vaguely as "improve error handling"?
|
||||
let meta_b = b.metainfo();
|
||||
|
||||
let ver_a = meta_a.version();
|
||||
let ver_b = meta_b.version();
|
||||
|
||||
// Compare versions only if channels match
|
||||
if a.metainfo().channel() == b.metainfo().channel() {
|
||||
if a_v > b_v {
|
||||
if ver_a > ver_b {
|
||||
return Some(a);
|
||||
} else if b_v > a_v {
|
||||
} else if ver_b > ver_a {
|
||||
return Some(b);
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,10 @@ use std::process::exit;
|
||||
|
||||
use clap::{App,Arg,SubCommand,ArgMatches};
|
||||
use clap::AppSettings::*;
|
||||
use libcitadel::{Result, ResourceImage, Logger, LogLevel, Partition, KeyPair, ImageHeader, util};
|
||||
use libcitadel::{ResourceImage, Logger, LogLevel, Partition, KeyPair, ImageHeader, util};
|
||||
use hex;
|
||||
|
||||
pub fn main(args: Vec<String>) {
|
||||
|
||||
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let app = App::new("citadel-image")
|
||||
.about("Citadel update image builder")
|
||||
.settings(&[ArgRequiredElseHelp,ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder])
|
||||
@ -91,16 +90,18 @@ pub fn main(args: Vec<String>) {
|
||||
println!("Error: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn info(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn info(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = load_image(arg_matches)?;
|
||||
print!("{}",String::from_utf8(img.header().metainfo_bytes())?);
|
||||
print!("{}", String::from_utf8(img.header().metainfo_bytes())?);
|
||||
info_signature(&img)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn info_signature(img: &ResourceImage) -> Result<()> {
|
||||
fn info_signature(img: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if img.header().has_signature() {
|
||||
println!("Signature: {}", hex::encode(&img.header().signature()));
|
||||
} else {
|
||||
@ -116,15 +117,15 @@ fn info_signature(img: &ResourceImage) -> Result<()> {
|
||||
},
|
||||
None => { println!("No public key found for channel '{}'", img.metainfo().channel()) },
|
||||
}
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
fn metainfo(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn metainfo(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = load_image(arg_matches)?;
|
||||
print!("{}",String::from_utf8(img.header().metainfo_bytes())?);
|
||||
print!("{}", String::from_utf8(img.header().metainfo_bytes())?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_verity(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn generate_verity(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = load_image(arg_matches)?;
|
||||
if img.has_verity_hashtree() {
|
||||
info!("Image already has dm-verity hashtree appended, doing nothing.");
|
||||
@ -134,7 +135,7 @@ fn generate_verity(arg_matches: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn verify(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = load_image(arg_matches)?;
|
||||
let ok = img.verify_verity()?;
|
||||
if ok {
|
||||
@ -145,7 +146,7 @@ fn verify(arg_matches: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_shasum(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn verify_shasum(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = load_image(arg_matches)?;
|
||||
let shasum = img.generate_shasum()?;
|
||||
if shasum == img.metainfo().shasum() {
|
||||
@ -158,22 +159,22 @@ fn verify_shasum(arg_matches: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_image(arg_matches: &ArgMatches) -> Result<ResourceImage> {
|
||||
fn load_image(arg_matches: &ArgMatches) -> Result<ResourceImage, Box<dyn std::error::Error>> {
|
||||
let path = arg_matches.value_of("path").expect("path argument missing");
|
||||
if !Path::new(path).exists() {
|
||||
bail!("Cannot load image {}: File does not exist", path);
|
||||
panic!("Cannot load image {}: File does not exist", path);
|
||||
brl
commented
Uh..... you want to panic instead of just exiting cleanly and printing an error message? Uh..... you want to panic instead of just exiting cleanly and printing an error message?
|
||||
}
|
||||
let img = ResourceImage::from_path(path)?;
|
||||
if !img.is_valid_image() {
|
||||
bail!("File {} is not a valid image file", path);
|
||||
panic!("File {} is not a valid image file", path);
|
||||
}
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
fn install_rootfs(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn install_rootfs(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if arg_matches.is_present("choose") {
|
||||
let _ = choose_install_partition(true)?;
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let img = load_image(arg_matches)?;
|
||||
@ -182,7 +183,7 @@ fn install_rootfs(arg_matches: &ArgMatches) -> Result<()> {
|
||||
info!("Verifying sha256 hash of image");
|
||||
let shasum = img.generate_shasum()?;
|
||||
if shasum != img.metainfo().shasum() {
|
||||
bail!("image file does not have expected sha256 value");
|
||||
panic!("image file does not have expected sha256 value");
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,7 +197,7 @@ fn install_rootfs(arg_matches: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_prefer_boot() -> Result<()> {
|
||||
fn clear_prefer_boot() -> Result<(), Box<dyn std::error::Error>> {
|
||||
for mut p in Partition::rootfs_partitions()? {
|
||||
if p.is_initialized() && p.header().has_flag(ImageHeader::FLAG_PREFER_BOOT) {
|
||||
p.clear_flag_and_write(ImageHeader::FLAG_PREFER_BOOT)?;
|
||||
@ -205,13 +206,13 @@ fn clear_prefer_boot() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sign_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn sign_image(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let _img = load_image(arg_matches)?;
|
||||
info!("Not implemented yet");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn install_image(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let source = arg_matches.value_of("path").expect("path argument missing");
|
||||
let img = load_image(arg_matches)?;
|
||||
let _hdr = img.header();
|
||||
@ -220,12 +221,12 @@ fn install_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
// XXX verify signature?
|
||||
|
||||
if !(metainfo.image_type() == "kernel" || metainfo.image_type() == "extra") {
|
||||
bail!("Cannot install image type {}", metainfo.image_type());
|
||||
panic!("Cannot install image type {}", metainfo.image_type());
|
||||
}
|
||||
|
||||
let shasum = img.generate_shasum()?;
|
||||
if shasum != img.metainfo().shasum() {
|
||||
bail!("Image shasum does not match metainfo");
|
||||
panic!("Image shasum does not match metainfo");
|
||||
}
|
||||
|
||||
img.generate_verity_hashtree()?;
|
||||
@ -233,10 +234,10 @@ fn install_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let filename = if metainfo.image_type() == "kernel" {
|
||||
let kernel_version = match metainfo.kernel_version() {
|
||||
Some(version) => version,
|
||||
None => bail!("Kernel image does not have a kernel version field in metainfo"),
|
||||
None => panic!("Kernel image does not have a kernel version field in metainfo"),
|
||||
};
|
||||
if kernel_version.chars().any(|c| c == '/') {
|
||||
bail!("Kernel version field has / char");
|
||||
panic!("Kernel version field has / char");
|
||||
}
|
||||
format!("citadel-kernel-{}-{:03}.img", kernel_version, metainfo.version())
|
||||
} else {
|
||||
@ -244,33 +245,38 @@ fn install_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
};
|
||||
|
||||
if !metainfo.channel().chars().all(|c| c.is_ascii_lowercase()) {
|
||||
bail!("Refusing to build path from strange channel name {}", metainfo.channel());
|
||||
panic!(
|
||||
"Refusing to build path from strange channel name {}",
|
||||
metainfo.channel()
|
||||
);
|
||||
}
|
||||
let image_dir = Path::new("/storage/resources").join(metainfo.channel());
|
||||
let image_dest = image_dir.join(filename);
|
||||
if image_dest.exists() {
|
||||
rotate(&image_dest)?;
|
||||
}
|
||||
util::rename(source, &image_dest)
|
||||
util::rename(source, &image_dest);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rotate(path: &Path) -> Result<()> {
|
||||
fn rotate(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !path.exists() || path.file_name().is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
let filename = path.file_name().unwrap();
|
||||
let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy()));
|
||||
util::remove_file(&dot_zero)?;
|
||||
util::rename(path, &dot_zero)
|
||||
util::rename(path, &dot_zero).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn genkeys() -> Result<()> {
|
||||
fn genkeys() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let keypair = KeyPair::generate();
|
||||
println!("keypair = \"{}\"", keypair.to_hex());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decompress(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn decompress(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = load_image(arg_matches)?;
|
||||
if !img.is_compressed() {
|
||||
info!("Image is not compressed, not decompressing.");
|
||||
@ -280,7 +286,7 @@ fn decompress(arg_matches: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bless() -> Result<()> {
|
||||
fn bless() -> Result<(), Box<dyn std::error::Error>> {
|
||||
for mut p in Partition::rootfs_partitions()? {
|
||||
if p.is_initialized() && p.is_mounted() {
|
||||
p.bless()?;
|
||||
@ -299,7 +305,7 @@ fn bool_to_yesno(val: bool) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
||||
fn choose_install_partition(verbose: bool) -> Result<Partition, Box<dyn std::error::Error>> {
|
||||
let partitions = Partition::rootfs_partitions()?;
|
||||
|
||||
if verbose {
|
||||
@ -314,9 +320,12 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
||||
for p in &partitions {
|
||||
if !p.is_mounted() && !p.is_initialized() {
|
||||
if verbose {
|
||||
info!("Choosing {} because it is empty and not mounted", p.path().display());
|
||||
info!(
|
||||
"Choosing {} because it is empty and not mounted",
|
||||
p.path().display()
|
||||
);
|
||||
}
|
||||
return Ok(p.clone())
|
||||
return Ok(p.clone());
|
||||
}
|
||||
}
|
||||
for p in &partitions {
|
||||
@ -324,10 +333,10 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
||||
if verbose {
|
||||
info!("Choosing {} because it is not mounted", p.path().display());
|
||||
info!("Header metainfo:");
|
||||
print!("{}",String::from_utf8(p.header().metainfo_bytes())?);
|
||||
print!("{}", String::from_utf8(p.header().metainfo_bytes())?);
|
||||
}
|
||||
return Ok(p.clone())
|
||||
return Ok(p.clone());
|
||||
}
|
||||
}
|
||||
bail!("No suitable install partition found")
|
||||
panic!("No suitable install partition found")
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use std::io::{self,Write};
|
||||
use std::path::Path;
|
||||
use libcitadel::Result;
|
||||
use super::disk::Disk;
|
||||
use rpassword;
|
||||
use crate::install::installer::Installer;
|
||||
@ -8,7 +7,7 @@ use crate::install::installer::Installer;
|
||||
const CITADEL_PASSPHRASE_PROMPT: &str = "Enter a password for the Citadel user (or 'q' to quit)";
|
||||
const LUKS_PASSPHRASE_PROMPT: &str = "Enter a disk encryption passphrase (or 'q' to quit";
|
||||
|
||||
pub fn run_cli_install() -> Result<bool> {
|
||||
pub fn run_cli_install() -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let disk = match choose_disk()? {
|
||||
Some(disk) => disk,
|
||||
None => return Ok(false),
|
||||
@ -33,7 +32,7 @@ pub fn run_cli_install() -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
|
||||
pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let disk = find_disk_by_path(target.as_ref())?;
|
||||
display_disk(&disk);
|
||||
|
||||
@ -55,11 +54,15 @@ pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn run_install(disk: Disk, citadel_passphrase: String, passphrase: String) -> Result<()> {
|
||||
fn run_install(
|
||||
disk: Disk,
|
||||
citadel_passphrase: String,
|
||||
passphrase: String,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut install = Installer::new(disk.path(), &citadel_passphrase, &passphrase);
|
||||
install.set_install_syslinux(true);
|
||||
install.verify()?;
|
||||
install.run()
|
||||
Ok(install.run()?)
|
||||
}
|
||||
|
||||
fn display_disk(disk: &Disk) {
|
||||
@ -70,22 +73,22 @@ fn display_disk(disk: &Disk) {
|
||||
println!();
|
||||
}
|
||||
|
||||
fn find_disk_by_path(path: &Path) -> Result<Disk> {
|
||||
fn find_disk_by_path(path: &Path) -> Result<Disk, Box<dyn std::error::Error>> {
|
||||
if !path.exists() {
|
||||
bail!("Target disk path {} does not exist", path.display());
|
||||
panic!("Target disk path {} does not exist", path.display());
|
||||
}
|
||||
for disk in Disk::probe_all()? {
|
||||
if disk.path() == path {
|
||||
return Ok(disk.clone());
|
||||
}
|
||||
}
|
||||
bail!("installation target {} is not a valid disk", path.display())
|
||||
panic!("installation target {} is not a valid disk", path.display())
|
||||
}
|
||||
|
||||
fn choose_disk() -> Result<Option<Disk>> {
|
||||
fn choose_disk() -> Result<Option<Disk>, Box<dyn std::error::Error>> {
|
||||
let disks = Disk::probe_all()?;
|
||||
if disks.is_empty() {
|
||||
bail!("no disks found.");
|
||||
panic!("no disks found.");
|
||||
}
|
||||
|
||||
loop {
|
||||
@ -96,7 +99,7 @@ fn choose_disk() -> Result<Option<Disk>> {
|
||||
}
|
||||
if let Ok(n) = line.parse::<usize>() {
|
||||
if n > 0 && n <= disks.len() {
|
||||
return Ok(Some(disks[n-1].clone()));
|
||||
return Ok(Some(disks[n - 1].clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,7 +114,7 @@ fn prompt_choose_disk(disks: &[Disk]) {
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
|
||||
fn read_line() -> Result<String> {
|
||||
fn read_line() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)
|
||||
.map_err(context!("error reading line from stdin"))?;
|
||||
@ -146,7 +149,7 @@ fn read_passphrase(prompt: &str) -> io::Result<Option<String>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_install(disk: &Disk) -> Result<bool> {
|
||||
fn confirm_install(disk: &Disk) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
println!("Are you sure you want to completely erase this this device?");
|
||||
println!();
|
||||
println!(" Device: {}", disk.path().display());
|
||||
@ -158,4 +161,3 @@ fn confirm_install(disk: &Disk) -> Result<bool> {
|
||||
let answer = read_line()?;
|
||||
Ok(answer == "YES")
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ use pwhash::sha512_crypt;
|
||||
|
||||
use libcitadel::util;
|
||||
use libcitadel::RealmFS;
|
||||
use libcitadel::Result;
|
||||
use libcitadel::OsRelease;
|
||||
use libcitadel::KeyRing;
|
||||
use libcitadel::terminal::Base16Scheme;
|
||||
@ -183,7 +182,7 @@ impl Installer {
|
||||
self.install_syslinux = val;
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<()> {
|
||||
pub fn verify(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let kernel_img = self.kernel_imagename();
|
||||
let bzimage = format!("bzImage-{}", self.kernel_version());
|
||||
let artifacts = vec![
|
||||
@ -192,26 +191,29 @@ impl Installer {
|
||||
];
|
||||
|
||||
if !self.target().exists() {
|
||||
bail!("target device {:?} does not exist", self.target());
|
||||
panic!("target device {:?} does not exist", self.target());
|
||||
}
|
||||
|
||||
for a in artifacts {
|
||||
if !self.artifact_path(a).exists() {
|
||||
bail!("required install artifact {} does not exist in {}", a, self.artifact_directory);
|
||||
panic!(
|
||||
"required install artifact {} does not exist in {}",
|
||||
a, self.artifact_directory
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Result<()> {
|
||||
pub fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match self._type {
|
||||
InstallType::Install => self.run_install(),
|
||||
InstallType::LiveSetup => self.run_live_setup(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_install(&self) -> Result<()> {
|
||||
pub fn run_install(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let start = Instant::now();
|
||||
self.partition_disk()?;
|
||||
self.setup_luks()?;
|
||||
@ -224,7 +226,7 @@ impl Installer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_live_setup(&self) -> Result<()> {
|
||||
pub fn run_live_setup(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.cmd_list(&[
|
||||
"/bin/mount -t tmpfs var-tmpfs /sysroot/var",
|
||||
"/bin/mount -t tmpfs home-tmpfs /sysroot/home",
|
||||
@ -242,8 +244,7 @@ impl Installer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_live_realm(&self) -> Result<()> {
|
||||
|
||||
fn setup_live_realm(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let realmfs_dir = self.storage().join("realms/realmfs-images");
|
||||
let base_realmfs = realmfs_dir.join("base-realmfs.img");
|
||||
|
||||
@ -261,14 +262,14 @@ impl Installer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn partition_disk(&self) -> Result<()> {
|
||||
pub fn partition_disk(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.header("Partitioning target disk")?;
|
||||
self.cmd_list(PARTITION_COMMANDS, &[
|
||||
("$TARGET", self.target_str())
|
||||
])
|
||||
}
|
||||
|
||||
pub fn setup_luks(&self) -> Result<()> {
|
||||
pub fn setup_luks(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.header("Setting up LUKS disk encryption")?;
|
||||
util::create_dir(INSTALL_MOUNT)?;
|
||||
util::write_file(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?;
|
||||
@ -281,15 +282,16 @@ impl Installer {
|
||||
("$LUKS_PASSFILE", LUKS_PASSPHRASE_FILE),
|
||||
])?;
|
||||
|
||||
util::remove_file(LUKS_PASSPHRASE_FILE)
|
||||
util::remove_file(LUKS_PASSPHRASE_FILE)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup_lvm(&self) -> Result<()> {
|
||||
pub fn setup_lvm(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.header("Setting up LVM volumes")?;
|
||||
self.cmd_list(LVM_COMMANDS, &[])
|
||||
}
|
||||
|
||||
pub fn setup_boot(&self) -> Result<()> {
|
||||
pub fn setup_boot(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.header("Setting up /boot partition")?;
|
||||
let boot_partition = self.target_partition(1);
|
||||
self.cmd(format!("/sbin/mkfs.vfat -F 32 {}", boot_partition))?;
|
||||
@ -323,11 +325,11 @@ impl Installer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_syslinux(&self) -> Result<()> {
|
||||
fn setup_syslinux(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.header("Installing syslinux")?;
|
||||
let syslinux_src = self.artifact_path("syslinux");
|
||||
if !syslinux_src.exists() {
|
||||
bail!("no syslinux directory found in artifact directory, cannot install syslinux");
|
||||
panic!("no syslinux directory found in artifact directory, cannot install syslinux");
|
||||
}
|
||||
let dst = Path::new(INSTALL_MOUNT).join("syslinux");
|
||||
util::create_dir(&dst)?;
|
||||
@ -345,17 +347,17 @@ impl Installer {
|
||||
self.cmd(format!("/sbin/extlinux --install {}", dst.display()))
|
||||
}
|
||||
|
||||
fn setup_syslinux_post_umount(&self) -> Result<()> {
|
||||
fn setup_syslinux_post_umount(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mbrbin = self.artifact_path("syslinux/gptmbr.bin");
|
||||
if !mbrbin.exists() {
|
||||
bail!("could not find MBR image: {:?}", mbrbin);
|
||||
panic!("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!("/sbin/parted -s {} set 1 legacy_boot on", self.target_str()))
|
||||
|
||||
}
|
||||
|
||||
pub fn create_storage(&self) -> Result<()> {
|
||||
pub fn create_storage(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.header("Setting up /storage partition")?;
|
||||
|
||||
self.cmd_list(CREATE_STORAGE_COMMANDS,
|
||||
@ -365,7 +367,7 @@ impl Installer {
|
||||
self.cmd(format!("/bin/umount {}", INSTALL_MOUNT))
|
||||
}
|
||||
|
||||
fn setup_storage(&self) -> Result<()> {
|
||||
fn setup_storage(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if self._type == InstallType::Install {
|
||||
self.create_keyring()?;
|
||||
self.setup_storage_resources()?;
|
||||
@ -389,33 +391,33 @@ impl Installer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_keyring(&self) -> Result<()> {
|
||||
fn create_keyring(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.info("Creating initial keyring")?;
|
||||
let keyring = KeyRing::create_new();
|
||||
keyring.write(self.storage().join("keyring"), self.passphrase.as_ref().unwrap())
|
||||
Ok(keyring.write(self.storage().join("keyring"), self.passphrase.as_ref().unwrap())?)
|
||||
}
|
||||
|
||||
|
||||
fn setup_base_realmfs(&self) -> Result<()> {
|
||||
fn setup_base_realmfs(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let realmfs_dir = self.storage().join("realms/realmfs-images");
|
||||
util::create_dir(&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()))
|
||||
}
|
||||
|
||||
fn setup_realm_skel(&self) -> Result<()> {
|
||||
fn setup_realm_skel(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let realm_skel = self.storage().join("realms/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))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_realmlock(&self, dir: &Path) -> Result<()> {
|
||||
fn create_realmlock(&self, dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fs::File::create(dir.join(".realmlock"))
|
||||
.map_err(context!("failed to create {:?}/.realmlock file", dir))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_main_realm(&self) -> Result<()> {
|
||||
fn setup_main_realm(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.header("Creating main realm")?;
|
||||
|
||||
let realm = self.storage().join("realms/realm-main");
|
||||
@ -440,7 +442,7 @@ impl Installer {
|
||||
self.create_realmlock(&realm)
|
||||
}
|
||||
|
||||
fn setup_apt_cacher_realm(&self) -> Result<()> {
|
||||
fn setup_apt_cacher_realm(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.header("Creating apt-cacher realm")?;
|
||||
let realm_base = self.storage().join("realms/realm-apt-cacher");
|
||||
|
||||
@ -460,7 +462,7 @@ impl Installer {
|
||||
self.create_realmlock(&realm_base)
|
||||
}
|
||||
|
||||
fn setup_storage_resources(&self) -> Result<()> {
|
||||
fn setup_storage_resources(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let channel = match OsRelease::citadel_channel() {
|
||||
Some(channel) => channel,
|
||||
None => "dev",
|
||||
@ -474,7 +476,7 @@ impl Installer {
|
||||
self.sparse_copy_artifact(&kernel_img, &resources)
|
||||
}
|
||||
|
||||
fn setup_citadel_passphrase(&self) -> Result<()> {
|
||||
fn setup_citadel_passphrase(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if self._type == InstallType::LiveSetup {
|
||||
self.info("Creating temporary citadel passphrase file for live mode")?;
|
||||
let path = self.storage().join("citadel-state/passwd");
|
||||
@ -497,17 +499,15 @@ impl Installer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_rootfs_partitions(&self) -> Result<()> {
|
||||
pub fn install_rootfs_partitions(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.header("Installing rootfs partitions")?;
|
||||
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 --no-prefer {}", rootfs.display()))
|
||||
}
|
||||
|
||||
pub fn finish_install(&self) -> Result<()> {
|
||||
self.cmd_list(FINISH_COMMANDS, &[
|
||||
("$TARGET", self.target_str())
|
||||
])
|
||||
pub fn finish_install(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.cmd_list(FINISH_COMMANDS, &[("$TARGET", self.target_str())])
|
||||
brl
commented
When you mix reformatting changes in with code changes then I have to read the reformatting changes very carefully to make sure you haven't changed anything. BTW, are you sure this is more readable than the way it was formatted previously? When you mix reformatting changes in with code changes then I have to read the reformatting changes very carefully to make sure you haven't changed anything.
BTW, are you sure this is more readable than the way it was formatted previously?
|
||||
}
|
||||
|
||||
fn global_realm_config(&self) -> &str {
|
||||
@ -546,16 +546,33 @@ impl Installer {
|
||||
Path::new(&self.artifact_directory).join(filename)
|
||||
}
|
||||
|
||||
fn copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P) -> Result<()> {
|
||||
fn copy_artifact<P: AsRef<Path>>(
|
||||
&self,
|
||||
filename: &str,
|
||||
target: P,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self._copy_artifact(filename, target, false)
|
||||
}
|
||||
|
||||
fn sparse_copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P) -> Result<()> {
|
||||
fn sparse_copy_artifact<P: AsRef<Path>>(
|
||||
&self,
|
||||
filename: &str,
|
||||
target: P,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self._copy_artifact(filename, target, true)
|
||||
}
|
||||
|
||||
fn _copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P, sparse: bool) -> Result<()> {
|
||||
self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?;
|
||||
fn _copy_artifact<P: AsRef<Path>>(
|
||||
&self,
|
||||
filename: &str,
|
||||
target: P,
|
||||
sparse: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.info(format!(
|
||||
"Copying {} to {}",
|
||||
filename,
|
||||
target.as_ref().display()
|
||||
))?;
|
||||
let src = self.artifact_path(filename);
|
||||
let target = target.as_ref();
|
||||
util::create_dir(target)?;
|
||||
@ -568,20 +585,19 @@ impl Installer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn header<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
||||
fn header<S: AsRef<str>>(&self, s: S) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.output(format!("\n[+] {}\n", s.as_ref()))
|
||||
}
|
||||
|
||||
fn info<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
||||
fn info<S: AsRef<str>>(&self, s: S) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.output(format!(" [>] {}", s.as_ref()))
|
||||
}
|
||||
|
||||
|
||||
fn output<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
||||
self.write_output(s.as_ref()).map_err(context!("error writing output"))
|
||||
fn output<S: AsRef<str>>(&self, s: S) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.write_output(s.as_ref())
|
||||
}
|
||||
|
||||
fn write_output(&self, s: &str) -> io::Result<()> {
|
||||
fn write_output(&self, s: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("{}", s);
|
||||
io::stdout().flush()?;
|
||||
|
||||
@ -592,7 +608,11 @@ impl Installer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd_list<I: IntoIterator<Item=S>, S: AsRef<str>>(&self, cmd_lines: I, subs: &[(&str,&str)]) -> Result<()> {
|
||||
fn cmd_list<I: IntoIterator<Item = S>, S: AsRef<str>>(
|
||||
&self,
|
||||
cmd_lines: I,
|
||||
subs: &[(&str, &str)],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
for line in cmd_lines {
|
||||
let line = line.as_ref();
|
||||
let line = subs.iter().fold(line.to_string(), |acc, (from,to)| acc.replace(from,to));
|
||||
@ -602,12 +622,12 @@ impl Installer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd<S: AsRef<str>>(&self, args: S) -> Result<()> {
|
||||
fn cmd<S: AsRef<str>>(&self, args: S) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
|
||||
self.run_cmd(args, false)
|
||||
}
|
||||
|
||||
fn run_cmd(&self, args: Vec<&str>, as_user: bool) -> Result<()> {
|
||||
fn run_cmd(&self, args: Vec<&str>, as_user: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.output(format!(" # {}", args.join(" ")))?;
|
||||
|
||||
let mut command = Command::new(args[0]);
|
||||
@ -632,8 +652,8 @@ impl Installer {
|
||||
|
||||
if !result.status.success() {
|
||||
match result.status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", args[0], code),
|
||||
None => bail!("command {} failed with no exit code", args[0]),
|
||||
Some(code) => panic!("command {} failed with exit code: {}", args[0], code),
|
||||
None => panic!("command {} failed with no exit code", args[0]),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -4,7 +4,7 @@ pub(crate) mod installer;
|
||||
mod cli;
|
||||
mod disk;
|
||||
|
||||
pub fn main(args: Vec<String>) {
|
||||
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut args = args.iter().skip(1);
|
||||
let result = if let Some(dev) = args.next() {
|
||||
cli::run_cli_install_with(dev)
|
||||
@ -17,10 +17,11 @@ pub fn main(args: Vec<String>) {
|
||||
Err(ref err) => {
|
||||
println!("Install failed: {}", err);
|
||||
exit(1);
|
||||
},
|
||||
}
|
||||
};
|
||||
if !ok {
|
||||
println!("Install cancelled...");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ use std::sync::mpsc::{Sender};
|
||||
use dbus::tree::{self, Factory, MTFn, MethodResult, Tree};
|
||||
use dbus::{Message};
|
||||
use dbus::blocking::LocalConnection;
|
||||
use libcitadel::{Result};
|
||||
// Use local version of disk.rs since we added some methods
|
||||
use crate::install_backend::disk::*;
|
||||
use crate::install::installer::*;
|
||||
@ -15,7 +14,6 @@ use std::fmt;
|
||||
|
||||
type MethodInfo<'a> = tree::MethodInfo<'a, MTFn<TData>, TData>;
|
||||
|
||||
|
||||
const OBJECT_PATH: &str = "/com/subgraph/installer";
|
||||
const INTERFACE_NAME: &str = "com.subgraph.installer.Manager";
|
||||
const BUS_NAME: &str = "com.subgraph.installer";
|
||||
@ -37,8 +35,7 @@ pub struct DbusServer {
|
||||
}
|
||||
|
||||
impl DbusServer {
|
||||
|
||||
pub fn connect() -> Result<DbusServer> {
|
||||
pub fn connect() -> Result<DbusServer, Box<dyn std::error::Error>> {
|
||||
let connection = LocalConnection::new_system()
|
||||
.map_err(|e| format_err!("Failed to connect to DBUS system bus: {}", e))?;
|
||||
let connection = Arc::new(connection);
|
||||
@ -80,7 +77,7 @@ impl DbusServer {
|
||||
Ok(vec![m.msg.method_return().append1(list)])
|
||||
}
|
||||
|
||||
fn run_install(path: String, citadel_passphrase: String, luks_passphrase: String, sender: Sender<Msg>) -> Result<()> {
|
||||
fn run_install(path: String, citadel_passphrase: String, luks_passphrase: String, sender: Sender<Msg>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut install = Installer::new(path, &citadel_passphrase, &luks_passphrase);
|
||||
install.set_install_syslinux(true);
|
||||
install.verify()?;
|
||||
@ -174,12 +171,12 @@ impl DbusServer {
|
||||
Message::signal(&path, &iface, &member).append1(text)
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<()> {
|
||||
let (sender, receiver) = mpsc::channel::<Msg>();
|
||||
pub fn start(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (sender, receiver) = mpsc::channel::<Msg>();
|
||||
let sender_clone = sender.clone();
|
||||
let tree = self.build_tree(sender);
|
||||
if let Err(_err) = self.connection.request_name(BUS_NAME, false, true, false) {
|
||||
bail!("Failed to request name");
|
||||
panic!("Failed to request name");
|
||||
}
|
||||
|
||||
tree.start_receive(self.connection.as_ref());
|
||||
@ -231,7 +228,6 @@ impl DbusServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -243,7 +239,6 @@ impl TreeData {
|
||||
TreeData {}
|
||||
}
|
||||
|
||||
|
||||
fn disks(&self) -> HashMap<String, Vec<String>> {
|
||||
let disks = Disk::probe_all().unwrap();
|
||||
|
||||
@ -257,7 +252,6 @@ impl TreeData {
|
||||
}
|
||||
disk_map
|
||||
}
|
||||
|
||||
}
|
||||
impl fmt::Debug for TreeData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
|
@ -1,24 +1,20 @@
|
||||
use libcitadel::Result;
|
||||
use std::process::exit;
|
||||
|
||||
mod disk;
|
||||
mod dbus;
|
||||
use libcitadel::CommandLine;
|
||||
|
||||
pub fn main() {
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if CommandLine::install_mode() {
|
||||
if let Err(e) = run_dbus_server() {
|
||||
warn!("Error: {}", e);
|
||||
}
|
||||
run_dbus_server()
|
||||
} else {
|
||||
println!("Citadel installer backend will only run in install or live mode");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_dbus_server() -> Result<()> {
|
||||
fn run_dbus_server() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let server = dbus::DbusServer::connect()?;
|
||||
server.start()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -17,42 +17,37 @@ mod realmfs;
|
||||
mod sync;
|
||||
mod update;
|
||||
|
||||
fn main() {
|
||||
let exe = match env::current_exe() {
|
||||
Ok(path) => path,
|
||||
Err(_e) => {
|
||||
return;
|
||||
},
|
||||
};
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let exe = env::current_exe()?;
|
||||
|
||||
let args = env::args().collect::<Vec<String>>();
|
||||
|
||||
if exe == Path::new("/usr/libexec/citadel-boot") {
|
||||
boot::main(args);
|
||||
boot::main(args)
|
||||
} else if exe == Path::new("/usr/libexec/citadel-install") {
|
||||
install::main(args);
|
||||
install::main(args)
|
||||
} else if exe == Path::new("/usr/libexec/citadel-install-backend") {
|
||||
install_backend::main();
|
||||
install_backend::main()
|
||||
} else if exe == Path::new("/usr/bin/citadel-image") {
|
||||
image::main(args);
|
||||
image::main(args)
|
||||
} else if exe == Path::new("/usr/bin/citadel-realmfs") {
|
||||
realmfs::main(args);
|
||||
realmfs::main(args)
|
||||
} else if exe == Path::new("/usr/bin/citadel-update") {
|
||||
update::main(args);
|
||||
update::main(args)
|
||||
} else if exe == Path::new("/usr/libexec/citadel-desktop-sync") {
|
||||
sync::main(args);
|
||||
sync::main(args)
|
||||
} else if exe == Path::new("/usr/libexec/citadel-run") {
|
||||
do_citadel_run(args);
|
||||
do_citadel_run(args)
|
||||
} else if exe.file_name() == Some(OsStr::new("citadel-mkimage")) {
|
||||
mkimage::main(args);
|
||||
mkimage::main(args)
|
||||
} else if exe.file_name() == Some(OsStr::new("citadel-tool")) {
|
||||
dispatch_command(args);
|
||||
dispatch_command(args)
|
||||
} else {
|
||||
println!("Error: unknown executable {}", exe.display());
|
||||
Result::Err(format!("Error: unknown executable {}", exe.display()).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_command(args: Vec<String>) {
|
||||
fn dispatch_command(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(command) = args.get(1) {
|
||||
match command.as_str() {
|
||||
"boot" => boot::main(rebuild_args("citadel-boot", args)),
|
||||
@ -63,22 +58,33 @@ fn dispatch_command(args: Vec<String>) {
|
||||
"mkimage" => mkimage::main(rebuild_args("citadel-mkimage", args)),
|
||||
"sync" => sync::main(rebuild_args("citadel-desktop-sync", args)),
|
||||
"run" => do_citadel_run(rebuild_args("citadel-run", args)),
|
||||
_ => println!("Error: unknown command {}", command),
|
||||
_ => throw_err(command),
|
||||
}
|
||||
} else {
|
||||
println!("Must provide an argument");
|
||||
Result::Err("Must provide an argument".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn throw_err(command: &String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Result::Err(format!("Error: unknown command {}", command).into())
|
||||
}
|
||||
|
||||
fn rebuild_args(command: &str, args: Vec<String>) -> Vec<String> {
|
||||
iter::once(command.to_string())
|
||||
.chain(args.into_iter().skip(2))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn do_citadel_run(args: Vec<String>) {
|
||||
fn do_citadel_run(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Err(e) = RealmManager::run_in_current(&args[1..], true) {
|
||||
println!("RealmManager::run_in_current({:?}) failed: {}", &args[1..], e);
|
||||
return Result::Err(
|
||||
format!(
|
||||
"RealmManager::run_in_current({:?}) failed: {}",
|
||||
&args[1..],
|
||||
e
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
use libcitadel::Result;
|
||||
|
||||
mod config;
|
||||
mod build;
|
||||
|
||||
pub fn main(args: Vec<String>) {
|
||||
|
||||
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config_path = match args.get(1) {
|
||||
Some(arg) => arg,
|
||||
None => {
|
||||
@ -21,11 +18,11 @@ pub fn main(args: Vec<String>) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_image(config_path: &str) -> Result<()> {
|
||||
fn build_image(config_path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let conf = config::BuildConfig::load(config_path)?;
|
||||
let mut builder = build::UpdateBuilder::new(conf);
|
||||
builder.build()
|
||||
}
|
||||
Ok(builder.build()?)
|
||||
}
|
||||
|
@ -1,16 +1,14 @@
|
||||
use clap::App;
|
||||
use clap::ArgMatches;
|
||||
|
||||
use libcitadel::{Result,RealmFS,Logger,LogLevel};
|
||||
use libcitadel::{RealmFS, Logger, LogLevel};
|
||||
use libcitadel::util::is_euid_root;
|
||||
use clap::SubCommand;
|
||||
use clap::AppSettings::*;
|
||||
use clap::Arg;
|
||||
use libcitadel::ResizeSize;
|
||||
use std::process::exit;
|
||||
|
||||
pub fn main(args: Vec<String>) {
|
||||
|
||||
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Logger::set_log_level(LogLevel::Debug);
|
||||
|
||||
let app = App::new("citadel-realmfs")
|
||||
@ -83,18 +81,15 @@ is the final absolute size of the image.")
|
||||
("activate", Some(m)) => activate(m),
|
||||
("deactivate", Some(m)) => deactivate(m),
|
||||
_ => image_info(&matches),
|
||||
};
|
||||
}?;
|
||||
|
||||
if let Err(ref e) = result {
|
||||
eprintln!("Error: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn realmfs_image(arg_matches: &ArgMatches) -> Result<RealmFS> {
|
||||
fn realmfs_image(arg_matches: &ArgMatches) -> Result<RealmFS, Box<dyn std::error::Error>> {
|
||||
let image = match arg_matches.value_of("image") {
|
||||
Some(s) => s,
|
||||
None => bail!("Image argument required."),
|
||||
None => panic!("Image argument required."),
|
||||
};
|
||||
|
||||
let realmfs = if RealmFS::is_valid_name(image) {
|
||||
@ -102,93 +97,100 @@ fn realmfs_image(arg_matches: &ArgMatches) -> Result<RealmFS> {
|
||||
} else if RealmFS::is_valid_realmfs_image(image) {
|
||||
RealmFS::load_from_path(image)?
|
||||
} else {
|
||||
bail!("Not a valid realmfs name or path to realmfs image file: {}", image);
|
||||
panic!(
|
||||
"Not a valid realmfs name or path to realmfs image file: {}",
|
||||
image
|
||||
);
|
||||
};
|
||||
Ok(realmfs)
|
||||
}
|
||||
|
||||
fn image_info(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn image_info(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = realmfs_image(arg_matches)?;
|
||||
print!("{}", String::from_utf8(img.header().metainfo_bytes())?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_resize_size(s: &str) -> Result<ResizeSize> {
|
||||
fn parse_resize_size(s: &str) -> Result<ResizeSize, Box<dyn std::error::Error>> {
|
||||
let unit = s.chars().last().filter(|c| c.is_alphabetic());
|
||||
|
||||
let skip = if s.starts_with('+') { 1 } else { 0 };
|
||||
let size = s.chars()
|
||||
let size = s
|
||||
.chars()
|
||||
.skip(skip)
|
||||
.take_while(|c| c.is_numeric())
|
||||
.collect::<String>()
|
||||
.parse::<usize>()
|
||||
.map_err(|_| format_err!("Unable to parse size value '{}'",s))?;
|
||||
.map_err(|_| format_err!("Unable to parse size value '{}'", s))?;
|
||||
|
||||
let sz = match unit {
|
||||
Some('g') | Some('G') => ResizeSize::gigs(size),
|
||||
Some('m') | Some('M') => ResizeSize::megs(size),
|
||||
Some(c) => bail!("Unknown size unit '{}'", c),
|
||||
Some(c) => panic!("Unknown size unit '{}'", c),
|
||||
None => ResizeSize::blocks(size),
|
||||
};
|
||||
Ok(sz)
|
||||
}
|
||||
|
||||
fn resize(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn resize(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = realmfs_image(arg_matches)?;
|
||||
info!("image is {}", img.path().display());
|
||||
let size_arg = match arg_matches.value_of("size") {
|
||||
Some(size) => size,
|
||||
None => "No size argument",
|
||||
|
||||
};
|
||||
info!("Size is {}", size_arg);
|
||||
let mode_add = size_arg.starts_with('+');
|
||||
let size = parse_resize_size(size_arg)?;
|
||||
|
||||
if mode_add {
|
||||
img.resize_grow_by(size)
|
||||
img.resize_grow_by(size)?;
|
||||
} else {
|
||||
img.resize_grow_to(size)
|
||||
img.resize_grow_to(size)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn autoresize(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn autoresize(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = realmfs_image(arg_matches)?;
|
||||
|
||||
if let Some(size) = img.auto_resize_size() {
|
||||
img.resize_grow_to(size)
|
||||
img.resize_grow_to(size)?;
|
||||
} else {
|
||||
info!("RealmFS image {} has sufficient free space, doing nothing", img.path().display());
|
||||
Ok(())
|
||||
info!(
|
||||
"RealmFS image {} has sufficient free space, doing nothing",
|
||||
img.path().display()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fork(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn fork(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = realmfs_image(arg_matches)?;
|
||||
let forkname = match arg_matches.value_of("forkname") {
|
||||
Some(name) => name,
|
||||
None => bail!("No fork name argument"),
|
||||
None => panic!("No fork name argument"),
|
||||
};
|
||||
if !RealmFS::is_valid_name(forkname) {
|
||||
bail!("Not a valid RealmFS image name '{}'", forkname);
|
||||
panic!("Not a valid RealmFS image name '{}'", forkname);
|
||||
}
|
||||
if RealmFS::named_image_exists(forkname) {
|
||||
bail!("A RealmFS image named '{}' already exists", forkname);
|
||||
panic!("A RealmFS image named '{}' already exists", forkname);
|
||||
}
|
||||
img.fork(forkname)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn update(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !is_euid_root() {
|
||||
bail!("RealmFS updates must be run as root");
|
||||
panic!("RealmFS updates must be run as root");
|
||||
}
|
||||
let img = realmfs_image(arg_matches)?;
|
||||
img.interactive_update(Some("icy"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn activate(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn activate(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = realmfs_image(arg_matches)?;
|
||||
let img_arg = arg_matches.value_of("image").unwrap();
|
||||
|
||||
@ -201,7 +203,7 @@ fn activate(arg_matches: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deactivate(arg_matches: &ArgMatches) -> Result<()> {
|
||||
fn deactivate(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let img = realmfs_image(arg_matches)?;
|
||||
let img_arg = arg_matches.value_of("image").unwrap();
|
||||
if !img.is_activated() {
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use libcitadel::{Realm, Realms, Result, util};
|
||||
use libcitadel::{Realm, Realms, util};
|
||||
use crate::sync::parser::DesktopFileParser;
|
||||
use std::fs::DirEntry;
|
||||
use crate::sync::desktop_file::DesktopFile;
|
||||
@ -17,14 +17,13 @@ pub struct DesktopFileSync {
|
||||
icons: Option<IconSync>,
|
||||
}
|
||||
|
||||
#[derive(Eq,PartialEq,Hash)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
struct DesktopItem {
|
||||
path: PathBuf,
|
||||
mtime: SystemTime,
|
||||
}
|
||||
|
||||
impl DesktopItem {
|
||||
|
||||
fn new(path: PathBuf, mtime: SystemTime) -> Self {
|
||||
DesktopItem { path, mtime }
|
||||
}
|
||||
@ -46,7 +45,7 @@ impl DesktopItem {
|
||||
impl DesktopFileSync {
|
||||
pub const CITADEL_APPLICATIONS: &'static str = "/home/citadel/.local/share/applications";
|
||||
|
||||
pub fn sync_active_realms() -> Result<()> {
|
||||
pub fn sync_active_realms() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let realms = Realms::load()?;
|
||||
for realm in realms.active(true) {
|
||||
let mut sync = DesktopFileSync::new(realm);
|
||||
@ -72,7 +71,7 @@ impl DesktopFileSync {
|
||||
DesktopFileSync { realm, items: HashSet::new(), icons }
|
||||
}
|
||||
|
||||
pub fn run_sync(&mut self, clear: bool) -> Result<()> {
|
||||
pub fn run_sync(&mut self, clear: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
IconSync::ensure_theme_index_exists()?;
|
||||
|
||||
@ -98,7 +97,7 @@ impl DesktopFileSync {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<()> {
|
||||
fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut directory = Realms::current_realm_symlink().join(directory.as_ref());
|
||||
directory.push("share/applications");
|
||||
if directory.exists() {
|
||||
@ -119,16 +118,16 @@ impl DesktopFileSync {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_target_files() -> Result<()> {
|
||||
util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
|
||||
pub fn clear_target_files() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
|
||||
util::remove_file(dent.path())
|
||||
})
|
||||
})?)
|
||||
}
|
||||
|
||||
fn remove_missing_target_files(&mut self) -> Result<()> {
|
||||
fn remove_missing_target_files(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let sources = self.source_filenames();
|
||||
let prefix = format!("realm-{}.", self.realm.name());
|
||||
util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
|
||||
Ok(util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
|
||||
if let Some(filename) = dent.file_name().to_str() {
|
||||
if filename.starts_with(&prefix) && !sources.contains(filename) {
|
||||
let path = dent.path();
|
||||
@ -137,7 +136,7 @@ impl DesktopFileSync {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
})?)
|
||||
}
|
||||
|
||||
fn mtime(path: &Path) -> Option<SystemTime> {
|
||||
@ -156,7 +155,7 @@ impl DesktopFileSync {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn synchronize_items(&self) -> Result<()> {
|
||||
fn synchronize_items(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
for item in &self.items {
|
||||
let target = Path::new(Self::CITADEL_APPLICATIONS).join(item.filename());
|
||||
if item.is_newer_than(&target) {
|
||||
@ -180,7 +179,7 @@ impl DesktopFileSync {
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_item(&self, item: &DesktopItem) -> Result<()> {
|
||||
fn sync_item(&self, item: &DesktopItem) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut dfp = DesktopFileParser::parse_from_path(&item.path, "/usr/libexec/citadel-run ")?;
|
||||
if dfp.is_showable() {
|
||||
self.sync_item_icon(&mut dfp);
|
||||
|
@ -1,4 +1,4 @@
|
||||
use libcitadel::{Result, Logger, LogLevel};
|
||||
use libcitadel::{Logger, LogLevel};
|
||||
|
||||
mod desktop_file;
|
||||
mod parser;
|
||||
@ -19,8 +19,7 @@ pub const REALM_BASE_PATHS:&[&str] = &[
|
||||
"home/.local/share/flatpak/exports"
|
||||
];
|
||||
|
||||
pub fn main(args: Vec<String>) {
|
||||
|
||||
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if has_arg(&args, "-v") {
|
||||
Logger::set_log_level(LogLevel::Debug);
|
||||
} else {
|
||||
@ -37,12 +36,14 @@ pub fn main(args: Vec<String>) {
|
||||
println!("Desktop file sync failed: {}", e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync(clear: bool) -> Result<()> {
|
||||
fn sync(clear: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(mut sync) = DesktopFileSync::new_current() {
|
||||
sync.run_sync(clear)
|
||||
sync.run_sync(clear)?
|
||||
} else {
|
||||
DesktopFileSync::clear_target_files()
|
||||
DesktopFileSync::clear_target_files()?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use libcitadel::{Result, Partition, ResourceImage, ImageHeader, LogLevel, Logger, util};
|
||||
use libcitadel::{Partition, ResourceImage, ImageHeader, LogLevel, Logger, util};
|
||||
use crate::update::kernel::{KernelInstaller, KernelVersion};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::{DirEntry, File};
|
||||
@ -16,7 +16,7 @@ const FLAG_QUIET: u32 = 0x04;
|
||||
const RESOURCES_DIRECTORY: &str = "/storage/resources";
|
||||
const TEMP_DIRECTORY: &str = "/storage/resources/tmp";
|
||||
|
||||
pub fn main(args: Vec<String>) {
|
||||
pub fn main(args: Vec<String>) -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
let mut args = args.iter().skip(1);
|
||||
let mut flags = 0;
|
||||
|
||||
@ -34,7 +34,7 @@ pub fn main(args: Vec<String>) {
|
||||
Logger::set_log_level(LogLevel::Debug);
|
||||
} else if arg == "--choose-rootfs" {
|
||||
let _ = choose_install_partition(true);
|
||||
return;
|
||||
return Ok(())
|
||||
} else {
|
||||
let path = Path::new(arg);
|
||||
if let Err(e) = install_image(path, flags) {
|
||||
@ -42,12 +42,13 @@ pub fn main(args: Vec<String>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Search directory containing installed image files for an
|
||||
// image file that has an identical shasum and abort the installation
|
||||
// if a duplicate is found.
|
||||
fn detect_duplicates(header: &ImageHeader) -> Result<()> {
|
||||
fn detect_duplicates(header: &ImageHeader) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let metainfo = header.metainfo();
|
||||
let channel = metainfo.channel();
|
||||
let shasum = metainfo.shasum();
|
||||
@ -61,17 +62,20 @@ fn detect_duplicates(header: &ImageHeader) -> Result<()> {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
util::read_directory(&resource_dir, |dent| {
|
||||
Ok(util::read_directory(&resource_dir, |dent| {
|
||||
if let Ok(hdr) = ImageHeader::from_file(dent.path()) {
|
||||
if hdr.metainfo().shasum() == shasum {
|
||||
bail!("A duplicate image file with the same shasum already exists at {}", dent.path().display());
|
||||
panic!(
|
||||
"A duplicate image file with the same shasum already exists at {}",
|
||||
dent.path().display()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
})?)
|
||||
}
|
||||
|
||||
fn create_tmp_copy(path: &Path) -> Result<PathBuf> {
|
||||
fn create_tmp_copy(path: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
if !Path::new(TEMP_DIRECTORY).exists() {
|
||||
util::create_dir(TEMP_DIRECTORY)?;
|
||||
}
|
||||
@ -93,12 +97,12 @@ fn create_tmp_copy(path: &Path) -> Result<PathBuf> {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn install_image(path: &Path, flags: u32) -> Result<()> {
|
||||
fn install_image(path: &Path, flags: u32) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !path.exists() || path.file_name().is_none() {
|
||||
bail!("file path {} does not exist", path.display());
|
||||
panic!("file path {} does not exist", path.display());
|
||||
}
|
||||
if !util::is_euid_root() {
|
||||
bail!("Image updates must be installed by root user");
|
||||
panic!("Image updates must be installed by root user");
|
||||
}
|
||||
|
||||
let header = ImageHeader::from_file(path)?;
|
||||
@ -113,14 +117,14 @@ fn install_image(path: &Path, flags: u32) -> Result<()> {
|
||||
match image.metainfo().image_type() {
|
||||
"kernel" => install_kernel_image(&mut image),
|
||||
"extra" => install_extra_image(&image),
|
||||
"rootfs" => install_rootfs_image(&image, flags),
|
||||
image_type => bail!("Unknown image type: {}", image_type),
|
||||
"rootfs" => install_rootfs_image(&image, flags),
|
||||
image_type => panic!("Unknown image type: {}", image_type),
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the image file for installation by decompressing and generating
|
||||
// dmverity hash tree.
|
||||
fn prepare_image(image: &ResourceImage, flags: u32) -> Result<()> {
|
||||
fn prepare_image(image: &ResourceImage, flags: u32) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if image.is_compressed() {
|
||||
image.decompress(false)?;
|
||||
}
|
||||
@ -129,7 +133,7 @@ fn prepare_image(image: &ResourceImage, flags: u32) -> Result<()> {
|
||||
info!("Verifying sha256 hash of image");
|
||||
let shasum = image.generate_shasum()?;
|
||||
if shasum != image.metainfo().shasum() {
|
||||
bail!("image file does not have expected sha256 value");
|
||||
panic!("image file does not have expected sha256 value");
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,24 +143,28 @@ fn prepare_image(image: &ResourceImage, flags: u32) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_extra_image(image: &ResourceImage) -> Result<()> {
|
||||
fn install_extra_image(image: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let filename = format!("citadel-extra-{:03}.img", image.header().metainfo().version());
|
||||
install_image_file(image, filename.as_str())?;
|
||||
remove_old_extra_images(image)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_old_extra_images(image: &ResourceImage) -> Result<()> {
|
||||
fn remove_old_extra_images(image: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let new_meta = image.header().metainfo();
|
||||
let shasum = new_meta.shasum();
|
||||
let target_dir = target_directory(image)?;
|
||||
util::read_directory(&target_dir, |dent| {
|
||||
Ok(util::read_directory(&target_dir, |dent| {
|
||||
let path = dent.path();
|
||||
maybe_remove_old_extra_image(&path, shasum)
|
||||
})
|
||||
maybe_remove_old_extra_image(&path, shasum).unwrap();
|
||||
Ok(())
|
||||
})?)
|
||||
}
|
||||
|
||||
fn maybe_remove_old_extra_image(path: &Path, shasum: &str) -> Result<()> {
|
||||
fn maybe_remove_old_extra_image(
|
||||
path: &Path,
|
||||
shasum: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let header = ImageHeader::from_file(&path)?;
|
||||
if !header.is_magic_valid() {
|
||||
return Ok(());
|
||||
@ -172,16 +180,16 @@ fn maybe_remove_old_extra_image(path: &Path, shasum: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
|
||||
fn install_kernel_image(image: &mut ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !Path::new("/boot/loader/loader.conf").exists() {
|
||||
bail!("failed to automount /boot partition. Please manually mount correct partition.");
|
||||
panic!("failed to automount /boot partition. Please manually mount correct partition.");
|
||||
}
|
||||
|
||||
let metainfo = image.header().metainfo();
|
||||
let version = metainfo.version();
|
||||
let kernel_version = match metainfo.kernel_version() {
|
||||
Some(kv) => kv,
|
||||
None => bail!("kernel image does not have kernel version field"),
|
||||
None => panic!("kernel image does not have kernel version field"),
|
||||
};
|
||||
info!("kernel version is {}", kernel_version);
|
||||
install_kernel_file(image, &kernel_version)?;
|
||||
@ -194,7 +202,7 @@ fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
|
||||
let mut remove_paths = Vec::new();
|
||||
util::read_directory(&image_dir, |dent| {
|
||||
let path = dent.path();
|
||||
if is_unused_kernel_image(&path, &all_versions)? {
|
||||
if is_unused_kernel_image(&path, &all_versions).unwrap() {
|
||||
remove_paths.push(path);
|
||||
}
|
||||
Ok(())
|
||||
@ -206,7 +214,7 @@ fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_unused_kernel_image(path: &Path, versions: &HashSet<String>) -> Result<bool> {
|
||||
fn is_unused_kernel_image(path: &Path, versions: &HashSet<String>) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let header = ImageHeader::from_file(path)?;
|
||||
if !header.is_magic_valid() {
|
||||
return Ok(false);
|
||||
@ -226,23 +234,25 @@ fn is_unused_kernel_image(path: &Path, versions: &HashSet<String>) -> Result<boo
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn install_kernel_file(image: &mut ResourceImage, kernel_version: &str) -> Result<()> {
|
||||
fn install_kernel_file(
|
||||
image: &mut ResourceImage,
|
||||
kernel_version: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mountpoint = Path::new("/run/citadel/images/kernel-install.mountpoint");
|
||||
info!("Temporarily mounting kernel resource image");
|
||||
let mut handle = image.mount_at(mountpoint)?;
|
||||
let kernel_path = mountpoint.join("kernel/bzImage");
|
||||
if !kernel_path.exists() {
|
||||
handle.unmount()?;
|
||||
bail!("kernel not found in kernel resource image at /kernel/bzImage")
|
||||
panic!("kernel not found in kernel resource image at /kernel/bzImage")
|
||||
}
|
||||
|
||||
let result = KernelInstaller::install_kernel(&kernel_path, kernel_version);
|
||||
KernelInstaller::install_kernel(&kernel_path, kernel_version)?;
|
||||
info!("Unmounting kernel resource image");
|
||||
handle.unmount()?;
|
||||
result
|
||||
Ok(handle.unmount()?)
|
||||
}
|
||||
|
||||
fn all_boot_kernel_versions() -> Result<HashSet<String>> {
|
||||
fn all_boot_kernel_versions() -> Result<HashSet<String>, Box<dyn std::error::Error>> {
|
||||
let mut result = HashSet::new();
|
||||
util::read_directory("/boot", |dent| {
|
||||
if is_kernel_dirent(&dent) {
|
||||
@ -264,7 +274,10 @@ fn is_kernel_dirent(dirent: &DirEntry) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn install_image_file(image: &ResourceImage, filename: &str) -> Result<()> {
|
||||
fn install_image_file(
|
||||
image: &ResourceImage,
|
||||
filename: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let image_dir = target_directory(image)?;
|
||||
let image_dest = image_dir.join(filename);
|
||||
if image_dest.exists() {
|
||||
@ -275,14 +288,14 @@ fn install_image_file(image: &ResourceImage, filename: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn target_directory(image: &ResourceImage) -> Result<PathBuf> {
|
||||
fn target_directory(image: &ResourceImage) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
let metainfo = image.header().metainfo();
|
||||
let channel = metainfo.channel();
|
||||
validate_channel_name(channel)?;
|
||||
Ok(Path::new("/storage/resources").join(channel))
|
||||
}
|
||||
|
||||
fn rotate(path: &Path) -> Result<()> {
|
||||
fn rotate(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !path.exists() || path.file_name().is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
@ -293,14 +306,17 @@ fn rotate(path: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_channel_name(channel: &str) -> Result<()> {
|
||||
fn validate_channel_name(channel: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !channel.chars().all(|c| c.is_ascii_lowercase()) {
|
||||
bail!("image has invalid channel name '{}'", channel);
|
||||
panic!("image has invalid channel name '{}'", channel);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_rootfs_image(image: &ResourceImage, flags: u32) -> Result<()> {
|
||||
fn install_rootfs_image(
|
||||
image: &ResourceImage,
|
||||
flags: u32,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let quiet = flags & FLAG_QUIET != 0;
|
||||
let partition = choose_install_partition(!quiet)?;
|
||||
|
||||
@ -315,7 +331,7 @@ fn install_rootfs_image(image: &ResourceImage, flags: u32) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_prefer_boot() -> Result<()> {
|
||||
fn clear_prefer_boot() -> Result<(), Box<dyn std::error::Error>> {
|
||||
for mut p in Partition::rootfs_partitions()? {
|
||||
if p.is_initialized() && p.header().has_flag(ImageHeader::FLAG_PREFER_BOOT) {
|
||||
p.clear_flag_and_write(ImageHeader::FLAG_PREFER_BOOT)?;
|
||||
@ -332,7 +348,7 @@ fn bool_to_yesno(val: bool) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
||||
fn choose_install_partition(verbose: bool) -> Result<Partition, Box<dyn std::error::Error>> {
|
||||
let partitions = Partition::rootfs_partitions()?;
|
||||
|
||||
if verbose {
|
||||
@ -362,5 +378,5 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
||||
return Ok(p.clone())
|
||||
}
|
||||
}
|
||||
bail!("no suitable install partition found")
|
||||
panic!("no suitable install partition found")
|
||||
}
|
||||
|
There's a common idiom for using
result::Result<T,E>
where you create a new type also calledResult
that specifies the error type you want to use.for example: