From 7ff3855d17c42dddfa3c0e528a88e70d6ed9ae3d Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:09:19 +0400 Subject: [PATCH 001/148] fix: removed obsolete ABI struct parameter Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/setup.rs | 2 -- lib/abi/tests/validate.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/lib/abi/src/setup.rs b/lib/abi/src/setup.rs index a24a3b95..dc80a01c 100644 --- a/lib/abi/src/setup.rs +++ b/lib/abi/src/setup.rs @@ -21,8 +21,6 @@ pub struct CSetupBase { pub mount_point: CSlice, #[non_empty] pub source: CSlice, - #[optional] - pub meta_filename: CSlice, pub empty_config: bool, pub pinned: bool, #[optional] diff --git a/lib/abi/tests/validate.rs b/lib/abi/tests/validate.rs index 693536d2..acb63d2c 100644 --- a/lib/abi/tests/validate.rs +++ b/lib/abi/tests/validate.rs @@ -389,7 +389,6 @@ fn valid_setup_base() -> CSetupBase { base: valid_request_base(), mount_point: CSlice { ptr: null(), len: 0 }, source: CSlice::from_owned(b"/mnt/source".to_vec()), - meta_filename: CSlice { ptr: null(), len: 0 }, empty_config: false, pinned: false, boot_plugin: CSlice { ptr: null(), len: 0 }, From 4a546235cc583409d7c9871c0ae639ec1e13c108 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:09:55 +0400 Subject: [PATCH 002/148] fix: fixed static linking for setup Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/plugin/decoder/manifest.rs | 9 ++++- lib/lib/src/plugin/decoder/unpack.rs | 48 ++++++++++++++++++++------ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/lib/lib/src/plugin/decoder/manifest.rs b/lib/lib/src/plugin/decoder/manifest.rs index 9a4a7e3a..2ae36f5c 100644 --- a/lib/lib/src/plugin/decoder/manifest.rs +++ b/lib/lib/src/plugin/decoder/manifest.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use std::fs; +use std::io::ErrorKind; use std::str::FromStr; use mime::Mime; @@ -26,7 +27,13 @@ pub fn load_decoder_manifests( ) -> Result, DecoderError> { let mut manifests = HashMap::new(); - for entry in fs::read_dir(decoders_dir)? { + let dir = match fs::read_dir(decoders_dir) { + Ok(dir) => dir, + Err(error) if error.kind() == ErrorKind::NotFound => return Ok(manifests), + Err(error) => return Err(error.into()), + }; + + for entry in dir { let path = entry?.path(); if path.extension().and_then(|extension| extension.to_str()) != Some(manifest_extension) { diff --git a/lib/lib/src/plugin/decoder/unpack.rs b/lib/lib/src/plugin/decoder/unpack.rs index b69189d0..d3394c52 100644 --- a/lib/lib/src/plugin/decoder/unpack.rs +++ b/lib/lib/src/plugin/decoder/unpack.rs @@ -30,7 +30,7 @@ use crate::layout::decoders; #[cfg(feature = "dynamic-plugins")] use crate::plugin::decoder::manifest::{DecoderManifest, load_decoder_manifests}; -#[cfg(all(not(feature = "dynamic-plugins"), feature = "builtin-decoders"))] +#[cfg(feature = "builtin-decoders")] use crate::plugin::decoder::static_decoders; pub struct PackageUnpacker { @@ -42,11 +42,14 @@ pub struct PackageUnpacker { #[cfg(all(not(feature = "dynamic-plugins"), feature = "builtin-decoders"))] decoders: Vec<(&'static str, &'static [&'static str], Decoder)>, + + #[cfg(all(feature = "dynamic-plugins", feature = "builtin-decoders"))] + static_decoders: Vec<(&'static str, &'static [&'static str], Decoder)>, } #[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] impl PackageUnpacker { - pub(crate) fn unpack_one( + pub fn unpack_one( &mut self, package_path: &str, index: usize, tmp_path: &str, cancel: &CancelToken, ) -> Result<(PackageTemp, DeclarativeTrigger), DecoderError> { let format = self.format_for(package_path)?; @@ -86,6 +89,9 @@ impl PackageUnpacker { Ok(Self { manifests, decoders: HashMap::new(), + + #[cfg(feature = "builtin-decoders")] + static_decoders: static_decoders(), }) } @@ -95,24 +101,44 @@ impl PackageUnpacker { .and_then(|extension| extension.to_str()) .ok_or_else(|| DecoderError::UnknownFormat(package_path.to_owned()))?; - self.manifests + if let Some(format) = self + .manifests .values() .find(|manifest| manifest.extensions.iter().any(|candidate| candidate == extension)) .map(|manifest| manifest.format.clone()) - .ok_or_else(|| DecoderError::UnknownFormat(package_path.to_owned())) + { + return Ok(format); + } + + #[cfg(feature = "builtin-decoders")] + if let Some((format, ..)) = self + .static_decoders + .iter() + .find(|(_, extensions, _)| extensions.contains(&extension)) + { + return Ok((*format).to_owned()); + } + + Err(DecoderError::UnknownFormat(package_path.to_owned())) } fn decoder_for(&mut self, format: &str) -> Result<&Decoder, DecoderError> { - if !self.decoders.contains_key(format) { - let manifest = self - .manifests - .get(format) - .ok_or_else(|| DecoderError::UnknownFormat(format.to_owned()))?; + if self.decoders.contains_key(format) { + return Ok(&self.decoders[format]); + } + + if let Some(manifest) = self.manifests.get(format) { let decoder = Decoder::load(&manifest.library)?; self.decoders.insert(format.to_owned(), decoder); + return Ok(&self.decoders[format]); + } + + #[cfg(feature = "builtin-decoders")] + if let Some((_, _, decoder)) = self.static_decoders.iter().find(|(name, _, _)| *name == format) { + return Ok(decoder); } - Ok(&self.decoders[format]) + Err(DecoderError::UnknownFormat(format.to_owned())) } } @@ -157,7 +183,7 @@ impl PackageUnpacker { Err(DecoderError::NoDecoders) } - pub(crate) fn unpack_one( + pub fn unpack_one( &mut self, _package_path: &str, _index: usize, _tmp_path: &str, _cancel: &CancelToken, ) -> Result<(PackageTemp, DeclarativeTrigger), DecoderError> { Err(DecoderError::NoDecoders) From 5f4e54b50f0d3df94c6d390738d44cb5ac456cdd Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:11:39 +0400 Subject: [PATCH 003/148] fix: update localization for library structure changes Co-Authored-By: Claude Sonnet 5 --- user/setup-cli/i18n/en/upac-setup-cli.ftl | 8 +++----- user/setup-cli/i18n/ru/upac-setup-cli.ftl | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/user/setup-cli/i18n/en/upac-setup-cli.ftl b/user/setup-cli/i18n/en/upac-setup-cli.ftl index 8f6b7a9e..3a1f177d 100644 --- a/user/setup-cli/i18n/en/upac-setup-cli.ftl +++ b/user/setup-cli/i18n/en/upac-setup-cli.ftl @@ -8,7 +8,6 @@ err-deploy-record = Deploy record operation failed err-boot = Boot entry staging failed err-boot-plugin = Boot plugin operation failed err-io = I/O error -err-meta-malformed = Malformed package metadata err-no-space-left = No space left on device err-not-block-device = Not a block device err-mkfs-failed = Filesystem creation failed @@ -24,10 +23,9 @@ err-invalid-format-params = Invalid filesystem formatting parameters err-reread-failed = Failed to reread the partition table (device busy?) stage-prepare-source = Preparing source -stage-read-meta = Reading package metadata -stage-import-trees = Importing package tree -stage-create-database = Creating package database -stage-insert-file-entry = Recording file entries +stage-enumerate-packages = Enumerating packages +stage-unpack-package = Unpacking package +stage-import-package = Importing package stage-embed-database = Embedding package database stage-write-deploy-record = Writing deploy record stage-stage-boot = Staging boot entry diff --git a/user/setup-cli/i18n/ru/upac-setup-cli.ftl b/user/setup-cli/i18n/ru/upac-setup-cli.ftl index 38117c7a..2c34abda 100644 --- a/user/setup-cli/i18n/ru/upac-setup-cli.ftl +++ b/user/setup-cli/i18n/ru/upac-setup-cli.ftl @@ -8,7 +8,6 @@ err-deploy-record = Ошибка операции с записью деплоя err-boot = Ошибка подготовки загрузочной записи err-boot-plugin = Ошибка загрузочного плагина err-io = Ошибка ввода-вывода -err-meta-malformed = Повреждённые метаданные пакета err-no-space-left = Не осталось места на устройстве err-not-block-device = Не является блочным устройством err-mkfs-failed = Ошибка создания файловой системы @@ -24,10 +23,9 @@ err-invalid-format-params = Некорректные параметры форм err-reread-failed = Не удалось перечитать таблицу разделов (устройство занято?) stage-prepare-source = Подготовка источника -stage-read-meta = Чтение метаданных пакета -stage-import-trees = Импорт дерева пакета -stage-create-database = Создание базы данных пакета -stage-insert-file-entry = Запись файловых записей +stage-enumerate-packages = Перечисление пакетов +stage-unpack-package = Распаковка пакета +stage-import-package = Импорт пакета stage-embed-database = Встраивание базы данных пакета stage-write-deploy-record = Запись записи деплоя stage-stage-boot = Подготовка загрузочной записи From 19396ca512d7919016e2ff49c0b3753a1ea4f0a9 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:12:19 +0400 Subject: [PATCH 004/148] fix: removed obsolete ABI struct field Co-Authored-By: Claude Sonnet 5 --- user/setup-cli/src/commands/manual.rs | 3 --- user/setup-cli/src/commands/whole_disk.rs | 3 --- user/setup-cli/src/errors.rs | 1 - user/setup-cli/tests/inline/errors.rs | 16 ++++++---------- user/setup-cli/tests/inline/progress.rs | 15 ++++++++++----- user/setup-cli/tests/inline/whole_disk.rs | 1 - 6 files changed, 16 insertions(+), 23 deletions(-) diff --git a/user/setup-cli/src/commands/manual.rs b/user/setup-cli/src/commands/manual.rs index 00e013c9..6adb8529 100644 --- a/user/setup-cli/src/commands/manual.rs +++ b/user/setup-cli/src/commands/manual.rs @@ -33,8 +33,6 @@ pub struct Args { #[arg(long)] pub source: String, #[arg(long)] - pub meta_filename: Option, - #[arg(long)] pub empty_config: bool, #[arg(long)] pub pinned: bool, @@ -53,7 +51,6 @@ pub fn run(args: Args, cancel_token: &CancelToken) -> Result<()> { mount_point: args.mount_point.as_deref(), source: &args.source, - meta_filename: args.meta_filename.as_deref(), empty_config: args.empty_config, pinned: args.pinned, boot_plugin: args.boot_plugin.as_deref(), diff --git a/user/setup-cli/src/commands/whole_disk.rs b/user/setup-cli/src/commands/whole_disk.rs index 3b783c4c..73b834ca 100644 --- a/user/setup-cli/src/commands/whole_disk.rs +++ b/user/setup-cli/src/commands/whole_disk.rs @@ -50,8 +50,6 @@ pub struct Args { #[arg(long)] pub source: Option, #[arg(long)] - pub meta_filename: Option, - #[arg(long)] pub empty_config: bool, #[arg(long)] pub pinned: bool, @@ -85,7 +83,6 @@ pub fn run(args: Args, cancel_token: &CancelToken) -> Result<()> { mount_point: args.mount_point.as_deref(), source, - meta_filename: args.meta_filename.as_deref(), empty_config: args.empty_config, pinned: args.pinned, boot_plugin: args.boot_plugin.as_deref(), diff --git a/user/setup-cli/src/errors.rs b/user/setup-cli/src/errors.rs index 5963f9a3..611d6725 100644 --- a/user/setup-cli/src/errors.rs +++ b/user/setup-cli/src/errors.rs @@ -50,7 +50,6 @@ impl Display for LocalizedSetupError { SetupError::Io(kind) => { write!(formatter, "{} ({kind:?})", fl!(LOADER, "err-io")) } - SetupError::MetaMalformed => formatter.write_str(&fl!(LOADER, "err-meta-malformed")), SetupError::NoSpaceLeft => formatter.write_str(&fl!(LOADER, "err-no-space-left")), SetupError::NotBlockDevice => formatter.write_str(&fl!(LOADER, "err-not-block-device")), SetupError::MkfsFailed => formatter.write_str(&fl!(LOADER, "err-mkfs-failed")), diff --git a/user/setup-cli/tests/inline/errors.rs b/user/setup-cli/tests/inline/errors.rs index da68b73d..f9d040f5 100644 --- a/user/setup-cli/tests/inline/errors.rs +++ b/user/setup-cli/tests/inline/errors.rs @@ -27,9 +27,9 @@ fn localized(stage: GenesisStage, error: SetupError) -> String { #[test] fn prefixes_the_message_with_the_localized_failing_stage_name() { - let message = localized(GenesisStage::ImportTrees, SetupError::Unexpected); + let message = localized(GenesisStage::ImportPackage, SetupError::Unexpected); - assert_eq!(message, "Importing package tree: Unexpected error"); + assert_eq!(message, "Importing package: Unexpected error"); } #[test] @@ -49,12 +49,9 @@ fn mount_variant_embeds_the_errno() { #[test] fn repo_variant_embeds_debug_detail() { - let message = localized(GenesisStage::ImportTrees, SetupError::Repo(RepoError::NotFound)); + let message = localized(GenesisStage::ImportPackage, SetupError::Repo(RepoError::NotFound)); - assert_eq!( - message, - "Importing package tree: Repository operation failed (NotFound)" - ); + assert_eq!(message, "Importing package: Repository operation failed (NotFound)"); } #[test] @@ -105,9 +102,9 @@ fn boot_plugin_variant_embeds_debug_detail() { #[test] fn io_variant_embeds_the_error_kind() { - let message = localized(GenesisStage::ReadMeta, SetupError::Io(IoErrorKind::NotFound)); + let message = localized(GenesisStage::UnpackPackage, SetupError::Io(IoErrorKind::NotFound)); - assert_eq!(message, "Reading package metadata: I/O error (NotFound)"); + assert_eq!(message, "Unpacking package: I/O error (NotFound)"); } #[test] @@ -124,7 +121,6 @@ fn reread_failed_variant_embeds_the_errno() { #[test] fn no_payload_variants_use_their_fixed_localized_message() { let cases = [ - (SetupError::MetaMalformed, "Malformed package metadata"), (SetupError::NoSpaceLeft, "No space left on device"), (SetupError::NotBlockDevice, "Not a block device"), (SetupError::MkfsFailed, "Filesystem creation failed"), diff --git a/user/setup-cli/tests/inline/progress.rs b/user/setup-cli/tests/inline/progress.rs index f9be7db1..6c3bda52 100644 --- a/user/setup-cli/tests/inline/progress.rs +++ b/user/setup-cli/tests/inline/progress.rs @@ -45,10 +45,10 @@ fn apply_with_zero_total_stays_on_spinner() { locale::init_for_test(); let mut state = ProgressState::new(); - state.apply(&event(GenesisStage::ReadMeta as u32, 0, 0, empty_slice())); + state.apply(&event(GenesisStage::EnumeratePackages as u32, 0, 0, empty_slice())); assert!(!state.is_bar); - assert_eq!(state.bar.message(), "Reading package metadata"); + assert_eq!(state.bar.message(), "Enumerating packages"); } #[test] @@ -56,7 +56,7 @@ fn apply_with_nonzero_total_switches_to_bar_and_sets_position() { locale::init_for_test(); let mut state = ProgressState::new(); - state.apply(&event(GenesisStage::ImportTrees as u32, 3, 10, empty_slice())); + state.apply(&event(GenesisStage::ImportPackage as u32, 3, 10, empty_slice())); assert!(state.is_bar); assert_eq!(state.bar.length(), Some(10)); @@ -69,9 +69,14 @@ fn apply_includes_subject_in_message_when_present() { let mut state = ProgressState::new(); let subject = CString::new("foo.txt").unwrap(); - state.apply(&event(GenesisStage::ReadMeta as u32, 0, 0, slice_from_cstr(&subject))); + state.apply(&event( + GenesisStage::EnumeratePackages as u32, + 0, + 0, + slice_from_cstr(&subject), + )); - assert_eq!(state.bar.message(), "Reading package metadata: foo.txt"); + assert_eq!(state.bar.message(), "Enumerating packages: foo.txt"); } #[test] diff --git a/user/setup-cli/tests/inline/whole_disk.rs b/user/setup-cli/tests/inline/whole_disk.rs index f1f7068f..5a6a275f 100644 --- a/user/setup-cli/tests/inline/whole_disk.rs +++ b/user/setup-cli/tests/inline/whole_disk.rs @@ -24,7 +24,6 @@ fn valid_args() -> Args { mount_point: None, source: Some("/mnt/source".to_owned()), - meta_filename: None, empty_config: false, pinned: false, boot_plugin: None, From 1026b70fa497fd8fd5d0f5808a47903c3f9b76a1 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:14:59 +0400 Subject: [PATCH 005/148] fix: refactored the library pipeline: installation now creates a proper package structure Co-Authored-By: Claude Sonnet 5 --- lib/setup/Cargo.toml | 3 +- lib/setup/lib.toml | 5 - lib/setup/src/data.rs | 4 - lib/setup/src/error.rs | 9 -- lib/setup/src/genesis/database.rs | 40 -------- lib/setup/src/genesis/entry.rs | 24 +++-- lib/setup/src/genesis/enumerate.rs | 74 +++++++++++++++ lib/setup/src/genesis/files.rs | 66 ------------- lib/setup/src/genesis/import.rs | 104 ++++++++++++++++++++ lib/setup/src/genesis/meta.rs | 41 -------- lib/setup/src/genesis/mod.rs | 61 +++++++----- lib/setup/src/genesis/trees.rs | 125 ------------------------- lib/setup/src/genesis/unpack.rs | 60 ++++++++++++ lib/setup/src/lib.rs | 1 - lib/setup/src/meta.rs | 87 ----------------- lib/setup/src/types.rs | 23 ++--- lib/setup/tests/data.rs | 2 - lib/setup/tests/inline/database.rs | 55 ----------- lib/setup/tests/inline/deploy.rs | 1 - lib/setup/tests/inline/enumerate.rs | 84 +++++++++++++++++ lib/setup/tests/inline/file_entries.rs | 90 ------------------ lib/setup/tests/inline/meta.rs | 74 --------------- lib/setup/tests/inline/trees.rs | 103 -------------------- lib/setup/tests/meta.rs | 90 ------------------ 24 files changed, 387 insertions(+), 839 deletions(-) delete mode 100644 lib/setup/src/genesis/database.rs create mode 100644 lib/setup/src/genesis/enumerate.rs delete mode 100644 lib/setup/src/genesis/files.rs create mode 100644 lib/setup/src/genesis/import.rs delete mode 100644 lib/setup/src/genesis/meta.rs delete mode 100644 lib/setup/src/genesis/trees.rs create mode 100644 lib/setup/src/genesis/unpack.rs delete mode 100644 lib/setup/src/meta.rs delete mode 100644 lib/setup/tests/inline/database.rs create mode 100644 lib/setup/tests/inline/enumerate.rs delete mode 100644 lib/setup/tests/inline/file_entries.rs delete mode 100644 lib/setup/tests/inline/meta.rs delete mode 100644 lib/setup/tests/inline/trees.rs delete mode 100644 lib/setup/tests/meta.rs diff --git a/lib/setup/Cargo.toml b/lib/setup/Cargo.toml index c6a54b60..6b5f70c8 100644 --- a/lib/setup/Cargo.toml +++ b/lib/setup/Cargo.toml @@ -24,7 +24,7 @@ categories.workspace = true workspace = true [dependencies] -upac-lib = { workspace = true, features = ["builtin-all-booters"] } +upac-lib = { workspace = true, features = ["builtin-all-booters", "builtin-all-decoders"] } upac-types = { workspace = true } upac-abi = { workspace = true } upac-macro = { workspace = true } @@ -42,7 +42,6 @@ xz2 = { workspace = true } zstd = { workspace = true } tempfile = { workspace = true } -composefs-setup-root = "0.9.0" sevenz-rust2 = "0.22.2" btrfs-mkfs = "0.13.0" gptman = "3.1.1" diff --git a/lib/setup/lib.toml b/lib/setup/lib.toml index 46b43028..9151ddad 100644 --- a/lib/setup/lib.toml +++ b/lib/setup/lib.toml @@ -3,11 +3,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -# meta.filename is the default name of the per-source_dir package-metadata -# manifest (see meta.rs) — overridable per request via CSetupBase.meta_filename. -[meta] -filename = "meta.toml" - # mount.default_mount_point is where the target deployment partition (and, # under it, the ESP + any extra_mounts) gets mounted when the request's own # mount_point is left empty. diff --git a/lib/setup/src/data.rs b/lib/setup/src/data.rs index 8168fa43..b2645e6d 100644 --- a/lib/setup/src/data.rs +++ b/lib/setup/src/data.rs @@ -22,7 +22,6 @@ pub struct SetupExistingData<'data> { pub mount_point: Option<&'data str>, pub source: &'data str, - pub meta_filename: Option<&'data str>, pub empty_config: bool, pub pinned: bool, pub boot_plugin: Option<&'data str>, @@ -55,7 +54,6 @@ impl<'data> TryFrom<&'data CSetupExistingRequest> for SetupExistingData<'data> { mount_point: (&request.base.mount_point).try_into()?, source: (&request.base.source).try_into()?, - meta_filename: (&request.base.meta_filename).try_into()?, empty_config: request.base.empty_config, pinned: request.base.pinned, boot_plugin: (&request.base.boot_plugin).try_into()?, @@ -81,7 +79,6 @@ pub struct SetupWholeDiskData<'data> { pub mount_point: Option<&'data str>, pub source: &'data str, - pub meta_filename: Option<&'data str>, pub empty_config: bool, pub pinned: bool, pub boot_plugin: Option<&'data str>, @@ -122,7 +119,6 @@ impl<'data> TryFrom<&'data CSetupWholeDiskRequest> for SetupWholeDiskData<'data> mount_point: (&request.base.mount_point).try_into()?, source: (&request.base.source).try_into()?, - meta_filename: (&request.base.meta_filename).try_into()?, empty_config: request.base.empty_config, pinned: request.base.pinned, boot_plugin: (&request.base.boot_plugin).try_into()?, diff --git a/lib/setup/src/error.rs b/lib/setup/src/error.rs index 3c3f5da1..a98edc8f 100644 --- a/lib/setup/src/error.rs +++ b/lib/setup/src/error.rs @@ -12,8 +12,6 @@ use gptman::linux::BlockError as GptBlockError; use nix::errno::Errno; -use toml::de::Error as TomlError; - use upac::boot::error::BootError; use upac::composefs::error::RepoError; use upac::database::error::{DatabaseError, DeployRecordError}; @@ -31,7 +29,6 @@ pub enum SetupError { Boot(BootError), BootPlugin(BootPluginError), Io(IoErrorKind), - MetaMalformed, NoSpaceLeft, NotBlockDevice, MkfsFailed, @@ -97,12 +94,6 @@ impl From for SetupError { } } -impl From for SetupError { - fn from(_: TomlError) -> Self { - SetupError::MetaMalformed - } -} - impl From for SetupError { fn from(error: GptError) -> Self { match error { diff --git a/lib/setup/src/genesis/database.rs b/lib/setup/src/genesis/database.rs deleted file mode 100644 index 98c7b69d..00000000 --- a/lib/setup/src/genesis/database.rs +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use upac::database::meta::MetaStoreMut; -use upac::database::{InMemory, MemoryDatabase}; -use upac::orchestrator::Context; -use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use upac_types::PackageMeta; - -use super::ctx_take; - -use crate::error::SetupError; -use crate::types::{GenesisDatabase, PackageUuid}; - -#[cfg(test)] -#[path = "../../tests/inline/database.rs"] -mod tests; - -pub struct CreateDatabaseStage; - -impl Stage for CreateDatabaseStage { - fn run( - &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, - ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { - let meta = ctx_take!(context, PackageMeta); - - let mut database = MemoryDatabase::new_in_memory()?; - let uuid = database.insert_package_meta(&meta)?; - - context.put(GenesisDatabase(database)); - context.put(PackageUuid(uuid)); - - Ok((progress, StageResult::Advance, Box::new(NoRollback))) - } -} diff --git a/lib/setup/src/genesis/entry.rs b/lib/setup/src/genesis/entry.rs index ebeb342c..ed5b5d00 100644 --- a/lib/setup/src/genesis/entry.rs +++ b/lib/setup/src/genesis/entry.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::fs::{File, copy, create_dir_all}; +use std::fs::{File, create_dir_all, write}; use std::io::Read; use composefs::erofs::reader::erofs_to_filesystem; @@ -12,6 +12,7 @@ use composefs::repository::Repository; use composefs::tree::FileSystem; use upac::boot::write_boot_entry; +use upac::composefs::file::FileHandle; use upac::composefs::repository::ObjectID; use upac::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; use upac::orchestrator::Context; @@ -25,7 +26,7 @@ use super::ctx_get; use crate::error::SetupError; use crate::layout::genesis::{ESP_FALLBACK_LOADER, REFIND_SOURCE, SYSTEMD_BOOT_SOURCE}; use crate::target::TargetSysroot; -use crate::types::{GenesisInput, PrefixDigest, ResolvedSourceDir}; +use crate::types::{GenesisInput, PrefixDigest}; pub struct StageBootStage; @@ -36,11 +37,12 @@ impl Stage for StageBootStage { let target = ctx_get!(context, TargetSysroot); let input = ctx_get!(context, GenesisInput); let prefix_digest = ctx_get!(context, PrefixDigest); - let resolved = ctx_get!(context, ResolvedSourceDir); let repository = target.repository(); let prefix_digest_hex = prefix_digest.0.to_hex(); + let prefix_tree = Self::reopen_tree(repository, &prefix_digest_hex)?; + let candidate = match input.boot_plugin.as_deref() { Some("systemd-boot") => Some(SYSTEMD_BOOT_SOURCE), Some("refind") => Some(REFIND_SOURCE), @@ -48,15 +50,19 @@ impl Stage for StageBootStage { }; if let Some(candidate) = candidate { - let source = resolved.0.join(candidate); - let destination = target.esp_mount_point().join(ESP_FALLBACK_LOADER); - if let Some(parent) = destination.parent() { - create_dir_all(parent)?; + let handle = FileHandle::new(candidate); + if handle.stat_in_tree(&prefix_tree).is_ok() { + let loader_bytes = handle.read_file(repository, &prefix_tree)?; + + let destination = target.esp_mount_point().join(ESP_FALLBACK_LOADER); + if let Some(parent) = destination.parent() { + create_dir_all(parent)?; + } + + write(&destination, &loader_bytes)?; } - copy(&source, &destination)?; } - let prefix_tree = Self::reopen_tree(repository, &prefix_digest_hex)?; let entry_name = write_boot_entry( repository, &prefix_tree, diff --git a/lib/setup/src/genesis/enumerate.rs b/lib/setup/src/genesis/enumerate.rs new file mode 100644 index 00000000..16a2670b --- /dev/null +++ b/lib/setup/src/genesis/enumerate.rs @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::collections::VecDeque; +use std::fs::read_dir; + +use composefs::generic_tree::Stat; +use composefs::repository::ImportContext; +use composefs::tree::FileSystem; + +use tempfile::TempDir; + +use upac::database::{InMemory, MemoryDatabase}; +use upac::errors::CommonError; +use upac::orchestrator::Context; +use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; +use upac::plugin::decoder::unpack::PackageUnpacker; + +use upac_abi::hook::{CancelToken, ProgressEventBuilder}; + +use upac_types::TmpPath; + +use super::ctx_get; + +use crate::error::SetupError; +use crate::types::{ + ConfigTree, GenesisDatabase, PendingPackagePaths, PendingPackages, PrefixTree, ResolvedSourceDir, TotalPackages, + UnpackerState, +}; + +#[cfg(test)] +#[path = "../../tests/inline/enumerate.rs"] +mod tests; + +pub struct EnumeratePackagesStage; + +impl Stage for EnumeratePackagesStage { + fn run( + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, + ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { + let resolved = ctx_get!(context, ResolvedSourceDir); + + let mut package_paths = Vec::new(); + for entry in read_dir(&resolved.0)? { + let entry = entry?; + + if entry.metadata()?.is_file() { + package_paths.push(entry.path().to_string_lossy().into_owned()); + } + } + + let total = package_paths.len() as u64; + + let unpacker = PackageUnpacker::new().map_err(CommonError::Decoder)?; + let scratch = TempDir::new()?; + let tmp_path = TmpPath(scratch.path().to_string_lossy().into_owned()); + let database = MemoryDatabase::new_in_memory()?; + + context.put(PendingPackagePaths(VecDeque::from(package_paths))); + context.put(TotalPackages(total)); + context.put(UnpackerState(unpacker)); + context.put(tmp_path); + context.put(scratch); + context.put(PendingPackages(VecDeque::new())); + context.put(GenesisDatabase(database)); + context.put(PrefixTree(FileSystem::new(Stat::uninitialized()))); + context.put(ConfigTree(FileSystem::new(Stat::uninitialized()))); + context.put(ImportContext::default()); + + Ok((progress, StageResult::Advance, Box::new(NoRollback))) + } +} diff --git a/lib/setup/src/genesis/files.rs b/lib/setup/src/genesis/files.rs deleted file mode 100644 index aac8d95b..00000000 --- a/lib/setup/src/genesis/files.rs +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use upac::database::files::FileStoreMut; -use upac::orchestrator::Context; -use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use upac_types::{FileEntry, FileEntryScope}; - -use super::{ctx_get, ctx_take}; - -use crate::error::SetupError; -use crate::types::{GenesisDatabase, ImportedConfigPaths, ImportedPrefixPaths, PackageUuid}; - -#[cfg(test)] -#[path = "../../tests/inline/file_entries.rs"] -mod tests; - -pub struct InsertFileEntryStage; - -impl Stage for InsertFileEntryStage { - fn run( - &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, - ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { - let mut prefix_paths = ctx_take!(context, ImportedPrefixPaths); - let mut config_paths = ctx_take!(context, ImportedConfigPaths); - let mut database = ctx_take!(context, GenesisDatabase); - - let uuid = ctx_get!(context, PackageUuid); - - let next = if let Some(path) = prefix_paths.0.pop() { - Some((FileEntryScope::Prefix, path)) - } else { - config_paths.0.pop().map(|path| (FileEntryScope::Config, path)) - }; - - if let Some((scope, path)) = next { - database.0.insert_package_file( - uuid.0, - &FileEntry { - path: path.to_string_lossy().into_owned(), - is_user: false, - scope, - }, - )?; - } - - let done = prefix_paths.0.is_empty() && config_paths.0.is_empty(); - - context.put(prefix_paths); - context.put(config_paths); - context.put(database); - - let result = if done { - StageResult::Advance - } else { - StageResult::Repeat - }; - - Ok((progress, result, Box::new(NoRollback))) - } -} diff --git a/lib/setup/src/genesis/import.rs b/lib/setup/src/genesis/import.rs new file mode 100644 index 00000000..bafb3491 --- /dev/null +++ b/lib/setup/src/genesis/import.rs @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::path::Path; + +use composefs::repository::ImportContext; + +use upac::database::files::FileStoreMut; +use upac::database::meta::MetaStoreMut; +use upac::database::triggers::TriggerStoreMut; +use upac::errors::CommonError; +use upac::orchestrator::Context; +use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; + +use upac_abi::hook::{CancelToken, ProgressEventBuilder}; + +use upac_types::{FileEntry, FileEntryScope}; + +use super::{ctx_get, ctx_take, import_if_dir}; + +use crate::error::SetupError; +use crate::target::TargetSysroot; +use crate::types::{ConfigTree, GenesisDatabase, GenesisInput, PendingPackages, PrefixTree, TotalPackages}; + +// No unit test: needs a real decoded package temp dir + a real composefs `Repository`, same +// untestable-in-isolation shape as `up install`'s own `ImportPackageStage` (lib/lib/src/mutated/installer). +pub struct ImportPackageStage; + +impl Stage for ImportPackageStage { + fn run( + &self, context: &mut Context, cancel: &CancelToken, mut progress: ProgressEventBuilder, + ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { + let mut pending_packages = ctx_take!(context, PendingPackages); + let mut prefix_tree = ctx_take!(context, PrefixTree); + let mut config_tree = ctx_take!(context, ConfigTree); + let mut database = ctx_take!(context, GenesisDatabase); + let mut import_ctx = ctx_take!(context, ImportContext); + + let target = ctx_get!(context, TargetSysroot); + let input = ctx_get!(context, GenesisInput); + let total = ctx_get!(context, TotalPackages); + + let repository = target.repository(); + + let (package, trigger) = pending_packages.0.pop_front().ok_or(CommonError::MissingResult)?; + + let source_root = Path::new(&package.temp_package_path); + + let prefix_source = source_root.join("usr"); + let imported = import_if_dir!(repository, &mut prefix_tree.0, &prefix_source, &mut import_ctx, cancel); + + let config_source = source_root.join("etc"); + let imported_config = if input.empty_config { + Vec::new() + } else { + import_if_dir!(repository, &mut config_tree.0, &config_source, &mut import_ctx, cancel) + }; + + let uuid = database.0.insert_package_meta(&package.meta)?; + database.0.set_declarative_triggers(uuid, &trigger)?; + + for path in imported { + database.0.insert_package_file( + uuid, + &FileEntry { + path: path.to_string_lossy().into_owned(), + is_user: false, + scope: FileEntryScope::Prefix, + }, + )?; + } + + for path in imported_config { + database.0.insert_package_file( + uuid, + &FileEntry { + path: path.to_string_lossy().into_owned(), + is_user: false, + scope: FileEntryScope::Config, + }, + )?; + } + + let remaining = pending_packages.0.len() as u64; + let processed = total.0 - remaining; + progress = progress.subject(package.meta.name.clone()).progress(processed, total.0); + + let result = if pending_packages.0.is_empty() { + StageResult::Advance + } else { + StageResult::Repeat + }; + + context.put(pending_packages); + context.put(prefix_tree); + context.put(config_tree); + context.put(database); + context.put(import_ctx); + + Ok((progress, result, Box::new(NoRollback))) + } +} diff --git a/lib/setup/src/genesis/meta.rs b/lib/setup/src/genesis/meta.rs deleted file mode 100644 index 9c8c81a6..00000000 --- a/lib/setup/src/genesis/meta.rs +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use upac::orchestrator::Context; -use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use super::ctx_get; - -use crate::error::SetupError; -use crate::meta::SourceDir; -use crate::types::{GenesisInput, ResolvedSourceDir}; - -#[cfg(test)] -#[path = "../../tests/inline/meta.rs"] -mod tests; - -pub struct ReadMetaStage; - -impl Stage for ReadMetaStage { - fn run( - &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, - ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { - let input = ctx_get!(context, GenesisInput); - let resolved = ctx_get!(context, ResolvedSourceDir); - - let source = SourceDir { path: &resolved.0 }; - - let mut meta = source.read(input.meta_filename.as_deref())?; - let (sha256, installed_size) = source.checksum(!input.empty_config)?; - meta.sha256 = sha256; - meta.installed_size = installed_size; - - context.put(meta); - - Ok((progress, StageResult::Advance, Box::new(NoRollback))) - } -} diff --git a/lib/setup/src/genesis/mod.rs b/lib/setup/src/genesis/mod.rs index 8691f413..969f4194 100644 --- a/lib/setup/src/genesis/mod.rs +++ b/lib/setup/src/genesis/mod.rs @@ -13,28 +13,26 @@ use upac_abi::hook::{Message, MessageHook}; use upac_macro::{FromStageIndex, StageKey}; -use self::database::CreateDatabaseStage; use self::deploy::WriteDeployRecordStage; use self::embed::EmbedDatabaseStage; use self::entry::StageBootStage; -use self::files::InsertFileEntryStage; -use self::meta::ReadMetaStage; +use self::enumerate::EnumeratePackagesStage; +use self::import::ImportPackageStage; use self::source::PrepareSourceStage; -use self::trees::ImportTreesStage; +use self::unpack::UnpackPackageStage; use crate::data::{SetupExistingData, SetupWholeDiskData}; use crate::error::SetupError; use crate::target::TargetSysroot; use crate::types::GenesisInput; -mod database; mod deploy; mod embed; mod entry; -mod files; -mod meta; +mod enumerate; +mod import; mod source; -mod trees; +mod unpack; macro_rules! ctx_get { ($context:expr, $ty:ty) => { @@ -50,18 +48,35 @@ macro_rules! ctx_take { } pub(crate) use ctx_take; +macro_rules! import_if_dir { + ($repository:expr, $tree:expr, $source:expr, $import_ctx:expr, $cancel:expr) => { + if $source.is_dir() { + upac::composefs::file::FileHandle::new(::std::path::PathBuf::new()).import_directory( + $repository, + $tree, + $source, + $import_ctx, + $cancel, + &mut |_| {}, + )? + } else { + Vec::new() + } + }; +} +pub(crate) use import_if_dir; + #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, FromStageIndex, StageKey)] pub enum GenesisStage { PrepareSource = 0, - ReadMeta = 1, - ImportTrees = 2, - CreateDatabase = 3, - InsertFileEntry = 4, - EmbedDatabase = 5, - WriteDeployRecord = 6, - StageBoot = 7, - Setup = 8, + EnumeratePackages = 1, + UnpackPackage = 2, + ImportPackage = 3, + EmbedDatabase = 4, + WriteDeployRecord = 5, + StageBoot = 6, + Setup = 7, } impl SetupExistingData<'_> { @@ -82,10 +97,9 @@ impl SetupExistingData<'_> { let orchestrator = SequentialOrchestrator::new(vec![ Box::new(PrepareSourceStage), - Box::new(ReadMetaStage), - Box::new(ImportTreesStage), - Box::new(CreateDatabaseStage), - Box::new(InsertFileEntryStage), + Box::new(EnumeratePackagesStage), + Box::new(UnpackPackageStage), + Box::new(ImportPackageStage), Box::new(EmbedDatabaseStage), Box::new(WriteDeployRecordStage), Box::new(StageBootStage), @@ -119,10 +133,9 @@ impl SetupWholeDiskData<'_> { let orchestrator = SequentialOrchestrator::new(vec![ Box::new(PrepareSourceStage), - Box::new(ReadMetaStage), - Box::new(ImportTreesStage), - Box::new(CreateDatabaseStage), - Box::new(InsertFileEntryStage), + Box::new(EnumeratePackagesStage), + Box::new(UnpackPackageStage), + Box::new(ImportPackageStage), Box::new(EmbedDatabaseStage), Box::new(WriteDeployRecordStage), Box::new(StageBootStage), diff --git a/lib/setup/src/genesis/trees.rs b/lib/setup/src/genesis/trees.rs deleted file mode 100644 index 7da31900..00000000 --- a/lib/setup/src/genesis/trees.rs +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::read_dir; -use std::io::Result as IoResult; -use std::path::{Path, PathBuf}; - -use composefs::generic_tree::Stat; -use composefs::repository::ImportContext; -use composefs::tree::FileSystem; - -use upac::composefs::file::FileHandle; -use upac::orchestrator::Context; -use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use super::ctx_get; - -use crate::error::SetupError; -use crate::target::TargetSysroot; -use crate::types::{ConfigTree, GenesisInput, ImportedConfigPaths, ImportedPrefixPaths, PrefixTree, ResolvedSourceDir}; - -#[cfg(test)] -#[path = "../../tests/inline/trees.rs"] -mod tests; - -macro_rules! import_with_progress { - ($repository:expr, $tree:expr, $source:expr, $import_ctx:expr, $cancel:expr, $context:expr, $stage:expr) => {{ - let total = ImportTreesStage::count_leaf_entries($source).unwrap_or(0); - let mut current = 0u64; - - FileHandle::new(PathBuf::new()).import_directory( - $repository, - $tree, - $source, - $import_ctx, - $cancel, - &mut |path| { - current += 1; - $context.send_progress( - &ProgressEventBuilder::new($stage) - .subject(path.display().to_string()) - .progress(current, total), - ); - }, - )? - }}; -} - -pub struct ImportTreesStage; - -impl Stage for ImportTreesStage { - fn run( - &self, context: &mut Context, cancel: &CancelToken, progress: ProgressEventBuilder, - ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { - let target = ctx_get!(context, TargetSysroot); - let input = ctx_get!(context, GenesisInput); - let resolved = ctx_get!(context, ResolvedSourceDir); - - let repository = target.repository(); - let mut import_ctx = ImportContext::default(); - let stage = progress.stage(); - - let mut prefix_tree = FileSystem::new(Stat::uninitialized()); - let prefix_source = resolved.0.join("usr"); - let imported = if prefix_source.is_dir() { - import_with_progress!( - repository, - &mut prefix_tree, - &prefix_source, - &mut import_ctx, - cancel, - context, - stage - ) - } else { - Vec::new() - }; - - let mut config_tree = FileSystem::new(Stat::uninitialized()); - let config_source = resolved.0.join("etc"); - let imported_config = if !input.empty_config && config_source.is_dir() { - import_with_progress!( - repository, - &mut config_tree, - &config_source, - &mut import_ctx, - cancel, - context, - stage - ) - } else { - Vec::new() - }; - - context.put(PrefixTree(prefix_tree)); - context.put(ConfigTree(config_tree)); - context.put(ImportedPrefixPaths(imported)); - context.put(ImportedConfigPaths(imported_config)); - context.put(import_ctx); - - Ok((progress, StageResult::Advance, Box::new(NoRollback))) - } -} - -impl ImportTreesStage { - fn count_leaf_entries(dir: &Path) -> IoResult { - let mut count = 0; - - for entry in read_dir(dir)? { - let entry = entry?; - - if entry.metadata()?.is_dir() { - count += Self::count_leaf_entries(&entry.path())?; - } else { - count += 1; - } - } - - Ok(count) - } -} diff --git a/lib/setup/src/genesis/unpack.rs b/lib/setup/src/genesis/unpack.rs new file mode 100644 index 00000000..eda699b5 --- /dev/null +++ b/lib/setup/src/genesis/unpack.rs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac::errors::CommonError; +use upac::orchestrator::Context; +use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; + +use upac_abi::hook::{CancelToken, ProgressEventBuilder}; + +use upac_types::TmpPath; + +use super::{ctx_get, ctx_take}; + +use crate::error::SetupError; +use crate::types::{PendingPackagePaths, PendingPackages, TotalPackages, UnpackerState}; + +// No unit test: needs a real decoder + a real package archive to unpack, same untestable-in- +// isolation shape as `up install`'s own `PreparationStage` (lib/lib/src/mutated/installer). +pub struct UnpackPackageStage; + +impl Stage for UnpackPackageStage { + fn run( + &self, context: &mut Context, cancel: &CancelToken, mut progress: ProgressEventBuilder, + ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { + let mut pending_paths = ctx_take!(context, PendingPackagePaths); + let mut unpacker = ctx_take!(context, UnpackerState); + let mut pending_packages = ctx_take!(context, PendingPackages); + + let tmp_path = ctx_get!(context, TmpPath); + let total = ctx_get!(context, TotalPackages); + + let package_path = pending_paths.0.pop_front().ok_or(CommonError::MissingResult)?; + let index = pending_packages.0.len(); + + let (package, trigger) = unpacker + .0 + .unpack_one(&package_path, index, tmp_path.as_ref(), cancel) + .map_err(CommonError::Decoder)?; + + pending_packages.0.push_back((package, trigger)); + + let remaining = pending_paths.0.len() as u64; + let processed = total.0 - remaining; + progress = progress.subject(package_path).progress(processed, total.0); + + let result = if pending_paths.0.is_empty() { + StageResult::Advance + } else { + StageResult::Repeat + }; + + context.put(pending_paths); + context.put(unpacker); + context.put(pending_packages); + + Ok((progress, result, Box::new(NoRollback))) + } +} diff --git a/lib/setup/src/lib.rs b/lib/setup/src/lib.rs index ad3926b7..914201ed 100644 --- a/lib/setup/src/lib.rs +++ b/lib/setup/src/lib.rs @@ -10,7 +10,6 @@ pub mod genesis; pub mod layout { include!(concat!(env!("OUT_DIR"), "/layout.rs")); } -pub mod meta; pub mod partition; pub mod target; mod types; diff --git a/lib/setup/src/meta.rs b/lib/setup/src/meta.rs deleted file mode 100644 index 7d850bf6..00000000 --- a/lib/setup/src/meta.rs +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::{File, read_dir, read_link, read_to_string}; -use std::io::Read; -use std::path::Path; - -use sha2::{Digest, Sha256}; - -use toml::from_str; - -use upac_types::PackageMeta; - -use crate::error::SetupError; -use crate::layout::meta::FILENAME; - -pub struct SourceDir<'src> { - pub path: &'src Path, -} - -impl SourceDir<'_> { - pub fn read(&self, filename: Option<&str>) -> Result { - let content = read_to_string(self.path.join(filename.unwrap_or(FILENAME)))?; - - Ok(from_str(&content)?) - } - - pub fn checksum(&self, include_config: bool) -> Result<([u8; 32], u64), SetupError> { - let mut accumulator = Accumulator { - hasher: Sha256::new(), - installed_size: 0, - }; - - let sections: &[&str] = if include_config { &["usr", "etc"] } else { &["usr"] }; - - for §ion in sections { - let section_dir = self.path.join(section); - if section_dir.is_dir() { - accumulator.hasher.update(section.as_bytes()); - accumulator.hash_dir(§ion_dir)?; - } - } - - Ok((accumulator.hasher.finalize().into(), accumulator.installed_size)) - } -} - -struct Accumulator { - hasher: Sha256, - installed_size: u64, -} - -impl Accumulator { - fn hash_dir(&mut self, dir: &Path) -> Result<(), SetupError> { - let mut entries = read_dir(dir)?.collect::, _>>()?; - entries.sort_by_key(|entry| entry.file_name()); - - for entry in entries { - let metadata = entry.metadata()?; - self.hasher.update(entry.file_name().as_encoded_bytes()); - - if metadata.is_dir() { - self.hash_dir(&entry.path())?; - } else if metadata.is_symlink() { - self.hasher - .update(read_link(entry.path())?.as_os_str().as_encoded_bytes()); - } else { - let mut file = File::open(entry.path())?; - let mut buffer = [0u8; 65536]; - - loop { - let bytes_read = file.read(&mut buffer)?; - if bytes_read == 0 { - break; - } - self.hasher.update(&buffer[..bytes_read]); - } - - self.installed_size += metadata.len(); - } - } - - Ok(()) - } -} diff --git a/lib/setup/src/types.rs b/lib/setup/src/types.rs index 6a04cde3..60adac8a 100644 --- a/lib/setup/src/types.rs +++ b/lib/setup/src/types.rs @@ -3,20 +3,21 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception +use std::collections::VecDeque; use std::path::PathBuf; use composefs::tree::FileSystem; -use uuid::Uuid; - use upac::composefs::repository::ObjectID; use upac::database::MemoryDatabase; +use upac::plugin::decoder::unpack::PackageUnpacker; + +use upac_types::{DeclarativeTrigger, PackageTemp}; use crate::data::{SetupExistingData, SetupWholeDiskData}; pub(crate) struct GenesisInput { pub source: String, - pub meta_filename: Option, pub empty_config: bool, pub pinned: bool, pub boot_plugin: Option, @@ -24,17 +25,19 @@ pub(crate) struct GenesisInput { pub(crate) struct ResolvedSourceDir(pub PathBuf); -pub(crate) struct PrefixTree(pub FileSystem); +pub(crate) struct PendingPackagePaths(pub VecDeque); -pub(crate) struct ConfigTree(pub FileSystem); +pub(crate) struct TotalPackages(pub u64); -pub(crate) struct ImportedPrefixPaths(pub Vec); +pub(crate) struct UnpackerState(pub PackageUnpacker); -pub(crate) struct ImportedConfigPaths(pub Vec); +pub(crate) struct PendingPackages(pub VecDeque<(PackageTemp, DeclarativeTrigger)>); -pub(crate) struct GenesisDatabase(pub MemoryDatabase); +pub(crate) struct PrefixTree(pub FileSystem); + +pub(crate) struct ConfigTree(pub FileSystem); -pub(crate) struct PackageUuid(pub Uuid); +pub(crate) struct GenesisDatabase(pub MemoryDatabase); pub(crate) struct PrefixDigest(pub ObjectID); @@ -44,7 +47,6 @@ impl From<&SetupExistingData<'_>> for GenesisInput { fn from(data: &SetupExistingData<'_>) -> Self { GenesisInput { source: data.source.to_owned(), - meta_filename: data.meta_filename.map(str::to_owned), empty_config: data.empty_config, pinned: data.pinned, boot_plugin: data.boot_plugin.map(str::to_owned), @@ -56,7 +58,6 @@ impl From<&SetupWholeDiskData<'_>> for GenesisInput { fn from(data: &SetupWholeDiskData<'_>) -> Self { GenesisInput { source: data.source.to_owned(), - meta_filename: data.meta_filename.map(str::to_owned), empty_config: data.empty_config, pinned: data.pinned, boot_plugin: data.boot_plugin.map(str::to_owned), diff --git a/lib/setup/tests/data.rs b/lib/setup/tests/data.rs index d8fd8203..7b55ea88 100644 --- a/lib/setup/tests/data.rs +++ b/lib/setup/tests/data.rs @@ -19,7 +19,6 @@ fn existing_data<'data>(cancel_token: &'data CancelToken, mount_point: Option<&' mount_point, source: "/mnt/source", - meta_filename: None, empty_config: false, pinned: false, boot_plugin: None, @@ -47,7 +46,6 @@ fn whole_disk_data<'data>( mount_point, source: "/mnt/source", - meta_filename: None, empty_config: false, pinned: false, boot_plugin: None, diff --git a/lib/setup/tests/inline/database.rs b/lib/setup/tests/inline/database.rs deleted file mode 100644 index 07931780..00000000 --- a/lib/setup/tests/inline/database.rs +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use upac::database::meta::MetaStore; -use upac::orchestrator::Context; -use upac::orchestrator::stage::{Stage, StageResult}; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use upac_types::PackageMeta; - -use crate::types::{GenesisDatabase, PackageUuid}; - -use super::CreateDatabaseStage; - -#[test] -fn run_inserts_package_meta_and_puts_database_and_uuid() { - let mut context = Context::new(); - context.put(PackageMeta { - name: "test-pkg".to_owned(), - ..PackageMeta::default() - }); - - let cancel = CancelToken::new(); - let progress = ProgressEventBuilder::new(0); - - let (_, result, _) = CreateDatabaseStage.run(&mut context, &cancel, progress).unwrap(); - - assert!(matches!(result, StageResult::Advance)); - - let database = context - .take::() - .expect("stage should put GenesisDatabase"); - let uuid = context.get::().expect("stage should put PackageUuid"); - - let meta = database - .0 - .get_package_meta(uuid.0) - .unwrap() - .expect("meta should be retrievable by the uuid the stage produced"); - assert_eq!(meta.name, "test-pkg"); -} - -#[test] -fn run_fails_when_package_meta_missing_from_context() { - let mut context = Context::new(); - let cancel = CancelToken::new(); - let progress = ProgressEventBuilder::new(0); - - let result = CreateDatabaseStage.run(&mut context, &cancel, progress); - - assert!(result.is_err()); -} diff --git a/lib/setup/tests/inline/deploy.rs b/lib/setup/tests/inline/deploy.rs index 3c473a8f..391e6d42 100644 --- a/lib/setup/tests/inline/deploy.rs +++ b/lib/setup/tests/inline/deploy.rs @@ -22,7 +22,6 @@ use super::WriteDeployRecordStage; fn genesis_input(pinned: bool) -> GenesisInput { GenesisInput { source: String::new(), - meta_filename: None, empty_config: false, pinned, boot_plugin: None, diff --git a/lib/setup/tests/inline/enumerate.rs b/lib/setup/tests/inline/enumerate.rs new file mode 100644 index 00000000..779d039c --- /dev/null +++ b/lib/setup/tests/inline/enumerate.rs @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::fs::{create_dir_all, write}; + +use tempfile::TempDir; + +use upac::orchestrator::Context; +use upac::orchestrator::stage::{Stage, StageResult}; + +use upac_abi::hook::{CancelToken, ProgressEventBuilder}; + +use upac_types::TmpPath; + +use crate::types::{ + ConfigTree, GenesisDatabase, PendingPackagePaths, PendingPackages, PrefixTree, ResolvedSourceDir, TotalPackages, + UnpackerState, +}; + +use super::EnumeratePackagesStage; + +#[test] +fn run_lists_only_files_and_initializes_pipeline_state() { + let source = TempDir::new().unwrap(); + write(source.path().join("a.pkg.tar.zst"), b"a").unwrap(); + write(source.path().join("b.pkg.tar.zst"), b"b").unwrap(); + create_dir_all(source.path().join("not-a-package")).unwrap(); + + let mut context = Context::new(); + context.put(ResolvedSourceDir(source.path().to_path_buf())); + + let cancel = CancelToken::new(); + let progress = ProgressEventBuilder::new(0); + + let (_, result, _guard) = EnumeratePackagesStage.run(&mut context, &cancel, progress).unwrap(); + + assert!(matches!(result, StageResult::Advance)); + + let total = context.get::().unwrap(); + assert_eq!(total.0, 2); + + let pending = context.get::().unwrap(); + assert_eq!(pending.0.len(), 2); + + assert!(context.get::().is_some()); + assert!(context.get::().is_some()); + assert!(context.get::().unwrap().0.is_empty()); + assert!(context.get::().is_some()); + assert!(context.get::().is_some()); + assert!(context.get::().is_some()); +} + +#[test] +fn run_with_empty_directory_sets_total_to_zero() { + let source = TempDir::new().unwrap(); + + let mut context = Context::new(); + context.put(ResolvedSourceDir(source.path().to_path_buf())); + + let cancel = CancelToken::new(); + let progress = ProgressEventBuilder::new(0); + + EnumeratePackagesStage.run(&mut context, &cancel, progress).unwrap(); + + let total = context.get::().unwrap(); + assert_eq!(total.0, 0); + + let pending = context.get::().unwrap(); + assert!(pending.0.is_empty()); +} + +#[test] +fn run_fails_when_source_dir_missing_from_context() { + let mut context = Context::new(); + + let cancel = CancelToken::new(); + let progress = ProgressEventBuilder::new(0); + + let result = EnumeratePackagesStage.run(&mut context, &cancel, progress); + + assert!(result.is_err()); +} diff --git a/lib/setup/tests/inline/file_entries.rs b/lib/setup/tests/inline/file_entries.rs deleted file mode 100644 index cc8b91a4..00000000 --- a/lib/setup/tests/inline/file_entries.rs +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::path::PathBuf; - -use upac::database::files::FileStore; -use upac::database::meta::MetaStoreMut; -use upac::database::{InMemory, MemoryDatabase}; -use upac::orchestrator::Context; -use upac::orchestrator::stage::{Stage, StageResult}; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use upac_types::{FileEntryScope, PackageMeta}; - -use crate::types::{GenesisDatabase, ImportedConfigPaths, ImportedPrefixPaths, PackageUuid}; - -use super::InsertFileEntryStage; - -fn context_with(prefix_paths: Vec, config_paths: Vec) -> Context { - let mut database = MemoryDatabase::new_in_memory().unwrap(); - let uuid = database - .insert_package_meta(&PackageMeta { - name: "test-pkg".to_owned(), - ..PackageMeta::default() - }) - .unwrap(); - - let mut context = Context::new(); - context.put(ImportedPrefixPaths(prefix_paths)); - context.put(ImportedConfigPaths(config_paths)); - context.put(GenesisDatabase(database)); - context.put(PackageUuid(uuid)); - context -} - -#[test] -fn run_inserts_one_entry_per_call_and_repeats_until_all_paths_are_recorded() { - let mut context = context_with( - vec![PathBuf::from("usr/bin/a"), PathBuf::from("usr/bin/b")], - vec![PathBuf::from("etc/conf")], - ); - let cancel = CancelToken::new(); - - let (_, first, _) = InsertFileEntryStage - .run(&mut context, &cancel, ProgressEventBuilder::new(0)) - .unwrap(); - assert!(matches!(first, StageResult::Repeat)); - - let (_, second, _) = InsertFileEntryStage - .run(&mut context, &cancel, ProgressEventBuilder::new(0)) - .unwrap(); - assert!(matches!(second, StageResult::Repeat)); - - let (_, third, _) = InsertFileEntryStage - .run(&mut context, &cancel, ProgressEventBuilder::new(0)) - .unwrap(); - assert!(matches!(third, StageResult::Advance)); - - let uuid = context.get::().unwrap(); - let database = context.get::().unwrap(); - let mut files = database.0.list_package_files(uuid.0).unwrap(); - files.sort_by(|a, b| a.path.cmp(&b.path)); - - assert_eq!(files.len(), 3); - assert_eq!(files[0].path, "etc/conf"); - assert_eq!(files[0].scope, FileEntryScope::Config); - assert_eq!(files[1].path, "usr/bin/a"); - assert_eq!(files[1].scope, FileEntryScope::Prefix); - assert_eq!(files[2].path, "usr/bin/b"); - assert_eq!(files[2].scope, FileEntryScope::Prefix); -} - -#[test] -fn run_advances_immediately_when_both_queues_are_empty() { - let mut context = context_with(Vec::new(), Vec::new()); - let cancel = CancelToken::new(); - - let (_, result, _) = InsertFileEntryStage - .run(&mut context, &cancel, ProgressEventBuilder::new(0)) - .unwrap(); - - assert!(matches!(result, StageResult::Advance)); - - let uuid = context.get::().unwrap(); - let database = context.get::().unwrap(); - assert!(database.0.list_package_files(uuid.0).unwrap().is_empty()); -} diff --git a/lib/setup/tests/inline/meta.rs b/lib/setup/tests/inline/meta.rs deleted file mode 100644 index 1ac2341a..00000000 --- a/lib/setup/tests/inline/meta.rs +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::{create_dir_all, write}; - -use tempfile::TempDir; - -use upac::orchestrator::Context; -use upac::orchestrator::stage::{Stage, StageResult}; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use upac_types::PackageMeta; - -use crate::types::{GenesisInput, ResolvedSourceDir}; - -use super::ReadMetaStage; - -fn genesis_input(empty_config: bool) -> GenesisInput { - GenesisInput { - source: String::new(), - meta_filename: None, - empty_config, - pinned: false, - boot_plugin: None, - } -} - -#[test] -fn run_reads_meta_and_fills_in_sha256_and_installed_size() { - let scratch = TempDir::new().unwrap(); - write( - scratch.path().join("meta.toml"), - "name = \"test-pkg\"\narch = \"x86_64\"\n", - ) - .unwrap(); - create_dir_all(scratch.path().join("usr")).unwrap(); - write(scratch.path().join("usr/a.txt"), b"hello").unwrap(); - - let mut context = Context::new(); - context.put(genesis_input(false)); - context.put(ResolvedSourceDir(scratch.path().to_path_buf())); - - let cancel = CancelToken::new(); - let progress = ProgressEventBuilder::new(0); - - let (_, result, _guard) = ReadMetaStage.run(&mut context, &cancel, progress).unwrap(); - - assert!(matches!(result, StageResult::Advance)); - - let meta = context.get::().unwrap(); - assert_eq!(meta.name, "test-pkg"); - assert_eq!(meta.arch, "x86_64"); - assert_eq!(meta.installed_size, 5); - assert_ne!(meta.sha256, [0u8; 32]); -} - -#[test] -fn run_fails_when_meta_toml_missing() { - let scratch = TempDir::new().unwrap(); - - let mut context = Context::new(); - context.put(genesis_input(false)); - context.put(ResolvedSourceDir(scratch.path().to_path_buf())); - - let cancel = CancelToken::new(); - let progress = ProgressEventBuilder::new(0); - - let result = ReadMetaStage.run(&mut context, &cancel, progress); - - assert!(result.is_err()); -} diff --git a/lib/setup/tests/inline/trees.rs b/lib/setup/tests/inline/trees.rs deleted file mode 100644 index fa66a80d..00000000 --- a/lib/setup/tests/inline/trees.rs +++ /dev/null @@ -1,103 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::{create_dir_all, write}; -use std::path::PathBuf; - -use tempfile::TempDir; - -use upac::orchestrator::Context; -use upac::orchestrator::stage::{Stage, StageResult}; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use crate::target::TargetSysroot; -use crate::types::{ConfigTree, GenesisInput, ImportedConfigPaths, ImportedPrefixPaths, PrefixTree, ResolvedSourceDir}; - -use super::ImportTreesStage; - -fn genesis_input(empty_config: bool) -> GenesisInput { - GenesisInput { - source: String::new(), - meta_filename: None, - empty_config, - pinned: false, - boot_plugin: None, - } -} - -fn context_with(source_dir: PathBuf, empty_config: bool) -> (Context, TempDir) { - let target_scratch = TempDir::new().unwrap(); - let target = TargetSysroot::for_testing(target_scratch.path().to_path_buf()).unwrap(); - - let mut context = Context::new(); - context.put(target); - context.put(genesis_input(empty_config)); - context.put(ResolvedSourceDir(source_dir)); - - (context, target_scratch) -} - -#[test] -fn run_imports_usr_and_etc_and_records_their_paths() { - let source = TempDir::new().unwrap(); - create_dir_all(source.path().join("usr/bin")).unwrap(); - write(source.path().join("usr/bin/tool"), b"binary").unwrap(); - create_dir_all(source.path().join("etc")).unwrap(); - write(source.path().join("etc/conf"), b"config").unwrap(); - - let (mut context, _target_scratch) = context_with(source.path().to_path_buf(), false); - let cancel = CancelToken::new(); - let progress = ProgressEventBuilder::new(0); - - let (_, result, _guard) = ImportTreesStage.run(&mut context, &cancel, progress).unwrap(); - - assert!(matches!(result, StageResult::Advance)); - - let prefix_paths = context.get::().unwrap(); - assert_eq!(prefix_paths.0, vec![PathBuf::from("bin/tool")]); - - let config_paths = context.get::().unwrap(); - assert_eq!(config_paths.0, vec![PathBuf::from("conf")]); - - assert!(context.get::().is_some()); - assert!(context.get::().is_some()); -} - -#[test] -fn run_skips_etc_when_empty_config_is_true() { - let source = TempDir::new().unwrap(); - create_dir_all(source.path().join("usr")).unwrap(); - write(source.path().join("usr/tool"), b"binary").unwrap(); - create_dir_all(source.path().join("etc")).unwrap(); - write(source.path().join("etc/conf"), b"config").unwrap(); - - let (mut context, _target_scratch) = context_with(source.path().to_path_buf(), true); - let cancel = CancelToken::new(); - let progress = ProgressEventBuilder::new(0); - - ImportTreesStage.run(&mut context, &cancel, progress).unwrap(); - - let config_paths = context.get::().unwrap(); - assert!(config_paths.0.is_empty()); - - let prefix_paths = context.get::().unwrap(); - assert_eq!(prefix_paths.0, vec![PathBuf::from("tool")]); -} - -#[test] -fn run_handles_source_with_neither_usr_nor_etc() { - let source = TempDir::new().unwrap(); - - let (mut context, _target_scratch) = context_with(source.path().to_path_buf(), false); - let cancel = CancelToken::new(); - let progress = ProgressEventBuilder::new(0); - - let (_, result, _guard) = ImportTreesStage.run(&mut context, &cancel, progress).unwrap(); - - assert!(matches!(result, StageResult::Advance)); - assert!(context.get::().unwrap().0.is_empty()); - assert!(context.get::().unwrap().0.is_empty()); -} diff --git a/lib/setup/tests/meta.rs b/lib/setup/tests/meta.rs deleted file mode 100644 index 61ed10d5..00000000 --- a/lib/setup/tests/meta.rs +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::{create_dir_all, write}; - -use tempfile::TempDir; - -use upac_setup::meta::SourceDir; - -#[test] -fn read_parses_meta_toml_with_serde_defaults() { - let scratch = TempDir::new().unwrap(); - write( - scratch.path().join("meta.toml"), - "name = \"test-pkg\"\narch = \"x86_64\"\n", - ) - .unwrap(); - - let source = SourceDir { path: scratch.path() }; - let meta = source.read(None).unwrap(); - - assert_eq!(meta.name, "test-pkg"); - assert_eq!(meta.arch, "x86_64"); -} - -#[test] -fn read_honors_explicit_filename_override() { - let scratch = TempDir::new().unwrap(); - write(scratch.path().join("custom.toml"), "name = \"custom-pkg\"\n").unwrap(); - - let source = SourceDir { path: scratch.path() }; - let meta = source.read(Some("custom.toml")).unwrap(); - - assert_eq!(meta.name, "custom-pkg"); -} - -#[test] -fn read_fails_when_file_missing() { - let scratch = TempDir::new().unwrap(); - let source = SourceDir { path: scratch.path() }; - - assert!(source.read(None).is_err()); -} - -#[test] -fn checksum_sums_installed_size_of_usr_files() { - let scratch = TempDir::new().unwrap(); - create_dir_all(scratch.path().join("usr")).unwrap(); - write(scratch.path().join("usr/a.txt"), b"12345").unwrap(); - write(scratch.path().join("usr/b.txt"), b"1234567890").unwrap(); - - let source = SourceDir { path: scratch.path() }; - let (_, installed_size) = source.checksum(false).unwrap(); - - assert_eq!(installed_size, 15); -} - -#[test] -fn checksum_excludes_etc_when_include_config_is_false() { - let scratch = TempDir::new().unwrap(); - create_dir_all(scratch.path().join("usr")).unwrap(); - write(scratch.path().join("usr/a.txt"), b"hello").unwrap(); - create_dir_all(scratch.path().join("etc")).unwrap(); - write(scratch.path().join("etc/b.txt"), b"world").unwrap(); - - let source = SourceDir { path: scratch.path() }; - - let (hash_without_config, size_without_config) = source.checksum(false).unwrap(); - let (hash_with_config, size_with_config) = source.checksum(true).unwrap(); - - assert_ne!(hash_without_config, hash_with_config); - assert_eq!(size_without_config, 5); - assert_eq!(size_with_config, 10); -} - -#[test] -fn checksum_is_deterministic() { - let scratch = TempDir::new().unwrap(); - create_dir_all(scratch.path().join("usr")).unwrap(); - write(scratch.path().join("usr/a.txt"), b"hello").unwrap(); - - let source = SourceDir { path: scratch.path() }; - - let first = source.checksum(false).unwrap(); - let second = source.checksum(false).unwrap(); - - assert_eq!(first, second); -} From 973523fe87724e2c71338a7feaf3bcdcc0ebe0e0 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:36:12 +0400 Subject: [PATCH 006/148] fix: fixed test errors Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/composefs/repository.rs | 8 ++++++++ lib/lib/src/database/files.rs | 14 ++++++++++---- lib/lib/src/database/meta.rs | 14 ++++++++++---- lib/lib/src/database/mod.rs | 22 ++++++++++++++++++++-- lib/lib/src/database/triggers.rs | 6 ++++-- lib/setup/src/target.rs | 5 +---- 6 files changed, 53 insertions(+), 16 deletions(-) diff --git a/lib/lib/src/composefs/repository.rs b/lib/lib/src/composefs/repository.rs index 3eed822a..f6f466c0 100644 --- a/lib/lib/src/composefs/repository.rs +++ b/lib/lib/src/composefs/repository.rs @@ -23,6 +23,14 @@ pub fn init(path: &Path) -> Result<(Repository, bool), RepoError> { Ok(Repository::init_path(AT_FDCWD, path, RepositoryConfig::default())?) } +pub fn init_insecure(path: &Path) -> Result<(Repository, bool), RepoError> { + Ok(Repository::init_path( + AT_FDCWD, + path, + RepositoryConfig::default().set_insecure(), + )?) +} + pub(crate) fn open(path: &Path) -> Result, RepoError> { Ok(Repository::open_path(AT_FDCWD, path)?) } diff --git a/lib/lib/src/database/files.rs b/lib/lib/src/database/files.rs index 34e7e48b..87d09941 100644 --- a/lib/lib/src/database/files.rs +++ b/lib/lib/src/database/files.rs @@ -13,7 +13,7 @@ use upac_types::FileEntry; use upac_types::codec::RedbCodable; use super::error::DatabaseError; -use super::{FILES_UUID_HASH_TABLE, FILES_UUID_TABLE, MemoryDatabase, ReadableSource}; +use super::{FILES_UUID_HASH_TABLE, FILES_UUID_TABLE, MemoryDatabase, ReadTransactionExt, ReadableSource}; use crate::layout::database::FILES_ENTRY_TYPE_NAME; @@ -37,14 +37,18 @@ pub trait FileStoreMut: FileStore { impl FileStore for T { fn find_file_owner(&self, path: &str) -> Result, DatabaseError> { let transaction = self.source().begin_read()?; - let by_path = transaction.open_table(FILES_UUID_HASH_TABLE)?; + let Some(by_path) = transaction.open_table_or_none(FILES_UUID_HASH_TABLE)? else { + return Ok(None); + }; Ok(by_path.get(Self::path_hash(path))?.map(|guard| guard.value())) } fn list_package_files(&self, uuid: Uuid) -> Result, DatabaseError> { let transaction = self.source().begin_read()?; - let files = transaction.open_table(FILES_UUID_TABLE)?; + let Some(files) = transaction.open_table_or_none(FILES_UUID_TABLE)? else { + return Ok(Vec::new()); + }; let mut out = Vec::new(); for entry in files.range((uuid, 0u64)..)? { @@ -63,7 +67,9 @@ impl FileStore for T { fn list_files(&self) -> Result, DatabaseError> { let transaction = self.source().begin_read()?; - let files = transaction.open_table(FILES_UUID_TABLE)?; + let Some(files) = transaction.open_table_or_none(FILES_UUID_TABLE)? else { + return Ok(Vec::new()); + }; let mut out = Vec::new(); for entry in files.iter()? { diff --git a/lib/lib/src/database/meta.rs b/lib/lib/src/database/meta.rs index 875a8039..c5a124cb 100644 --- a/lib/lib/src/database/meta.rs +++ b/lib/lib/src/database/meta.rs @@ -13,7 +13,7 @@ use upac_types::PackageMeta; use upac_types::codec::{RedbCodable, write_len_prefixed, write_opt_str}; use super::error::DatabaseError; -use super::{MemoryDatabase, PACKAGES_HASH_TABLE, PACKAGES_UUID_TABLE, ReadableSource}; +use super::{MemoryDatabase, PACKAGES_HASH_TABLE, PACKAGES_UUID_TABLE, ReadTransactionExt, ReadableSource}; use crate::layout::database::PACKAGES_META_TYPE_NAME; @@ -52,21 +52,27 @@ pub trait MetaStoreMut: MetaStore { impl MetaStore for T { fn find_package_uuid(&self, name: &str, arch: &str, arch_sub: Option<&str>) -> Result, DatabaseError> { let transaction = self.source().begin_read()?; - let by_name = transaction.open_table(PACKAGES_HASH_TABLE)?; + let Some(by_name) = transaction.open_table_or_none(PACKAGES_HASH_TABLE)? else { + return Ok(None); + }; Self::lookup_uuid(&by_name, name, arch, arch_sub) } fn get_package_meta(&self, uuid: Uuid) -> Result, DatabaseError> { let transaction = self.source().begin_read()?; - let packages = transaction.open_table(PACKAGES_UUID_TABLE)?; + let Some(packages) = transaction.open_table_or_none(PACKAGES_UUID_TABLE)? else { + return Ok(None); + }; Ok(packages.get(uuid)?.map(|guard| guard.value().0)) } fn list_packages_metas(&self) -> Result, DatabaseError> { let transaction = self.source().begin_read()?; - let packages = transaction.open_table(PACKAGES_UUID_TABLE)?; + let Some(packages) = transaction.open_table_or_none(PACKAGES_UUID_TABLE)? else { + return Ok(Vec::new()); + }; let mut out = Vec::new(); for entry in packages.iter()? { diff --git a/lib/lib/src/database/mod.rs b/lib/lib/src/database/mod.rs index 5bfcfdf9..6957b321 100644 --- a/lib/lib/src/database/mod.rs +++ b/lib/lib/src/database/mod.rs @@ -8,8 +8,8 @@ use std::path::Path; use std::sync::{Arc, PoisonError, RwLock}; use redb::{ - Builder, Database as RedbDatabase, ReadOnlyDatabase as RedbReadOnlyDatabase, ReadableDatabase, StorageBackend, - TableDefinition, + Builder, Database as RedbDatabase, Key, ReadOnlyDatabase as RedbReadOnlyDatabase, ReadOnlyTable, ReadTransaction, + ReadableDatabase, StorageBackend, TableDefinition, TableError, Value, }; use uuid::Uuid; @@ -122,6 +122,24 @@ impl ReadableSource for ReadOnlyDatabase { } } +pub(crate) trait ReadTransactionExt { + fn open_table_or_none( + &self, definition: TableDefinition, + ) -> Result>, DatabaseError>; +} + +impl ReadTransactionExt for ReadTransaction { + fn open_table_or_none( + &self, definition: TableDefinition, + ) -> Result>, DatabaseError> { + match self.open_table(definition) { + Ok(table) => Ok(Some(table)), + Err(TableError::TableDoesNotExist(_)) => Ok(None), + Err(error) => Err(error.into()), + } + } +} + #[derive(Debug, Clone, Default)] pub struct SharedMemoryBackend(Arc>>); diff --git a/lib/lib/src/database/triggers.rs b/lib/lib/src/database/triggers.rs index dfa033f8..579b0272 100644 --- a/lib/lib/src/database/triggers.rs +++ b/lib/lib/src/database/triggers.rs @@ -11,7 +11,7 @@ use upac_types::DeclarativeTrigger; use upac_types::codec::RedbCodable; use super::error::DatabaseError; -use super::{MemoryDatabase, PACKAGES_TRIGGERS_TABLE, ReadableSource}; +use super::{MemoryDatabase, PACKAGES_TRIGGERS_TABLE, ReadTransactionExt, ReadableSource}; use crate::layout::database::PACKAGES_TRIGGERS_TYPE_NAME; @@ -27,7 +27,9 @@ pub trait TriggerStoreMut: TriggerStore { impl TriggerStore for T { fn get_declarative_triggers(&self, uuid: Uuid) -> Result, DatabaseError> { let transaction = self.source().begin_read()?; - let triggers = transaction.open_table(PACKAGES_TRIGGERS_TABLE)?; + let Some(triggers) = transaction.open_table_or_none(PACKAGES_TRIGGERS_TABLE)? else { + return Ok(None); + }; Ok(triggers.get(uuid)?.map(|guard| guard.value().0)) } diff --git a/lib/setup/src/target.rs b/lib/setup/src/target.rs index ca1510b6..07b05a14 100644 --- a/lib/setup/src/target.rs +++ b/lib/setup/src/target.rs @@ -155,16 +155,13 @@ impl TargetSysroot { #[cfg(test)] impl TargetSysroot { - /// Builds a `TargetSysroot` over a plain directory — no `mount()`, no root required. Only - /// `deploy_dir`/`next_seq_path`/`repository` are meaningful on the result; `Drop` has nothing - /// to unmount since `mounted` stays empty. pub(crate) fn for_testing(mount_point: PathBuf) -> Result { create_dir_all(&mount_point)?; let deploy_dir = mount_point.join(DEPLOYS_DIR); create_dir_all(&deploy_dir)?; - let (repository, _freshly_initialized) = repository::init(&mount_point.join(REPO_DIR))?; + let (repository, _freshly_initialized) = repository::init_insecure(&mount_point.join(REPO_DIR))?; Ok(Self { mount_point, From c3503ef3b9f68c3330adee0181e28b65cf8be8db Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:36:23 +0400 Subject: [PATCH 007/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/TODO.md b/TODO.md index 816c6356..7bea9bcc 100644 --- a/TODO.md +++ b/TODO.md @@ -63,16 +63,3 @@ works after the bootloader-binary fix above). Two separate gaps, both required: upac needs to ship/package this integration itself, or whether it's expected to already exist on the source distro (same assumption as the systemd-boot/rEFInd binary copy above) — needs checking whether Arch/AUR already has a package for this. - -**Genesis tracks the entire bootstrapped system as a single synthetic "rootfs" package**, not -per-package (`ReadMetaStage` reads one `meta.toml`, `ImportTreesStage` imports all of source's -`usr`/`etc` wholesale). Found while reasoning about the `composefs-setup-root` hook: if it needs to -already be installed on the source system (via pacman) for genesis to pick it up, its files still -end up attributed to the one fake "rootfs" package in our database — no real per-package -provenance for anything baked into the source image, unlike a `pacstrap`-then-`up install` flow -would give. Decision made: genesis should eventually be rewritten to install real, individually -decoded packages through the same pipeline `up install` uses, instead of importing a pre-built -directory wholesale — no special-casing even for the kernel package. This is a genesis rewrite, not -a patch; deliberately deferred until after a dedicated code-cleanup/macro-consolidation pass -(reduce duplicated lines, extract shared macros) elsewhere in the codebase first. - From bc53fe31a2a3e456f2e939441f6220457e6ef454 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:48:01 +0400 Subject: [PATCH 008/148] fix: extraction and separation of the 3 ABI levels fix: addition of functions for understanding the booters Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/boot.rs | 6 ++++++ lib/abi/src/lib.rs | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/abi/src/boot.rs b/lib/abi/src/boot.rs index 1483c5bc..0c496b80 100644 --- a/lib/abi/src/boot.rs +++ b/lib/abi/src/boot.rs @@ -16,6 +16,8 @@ pub type SetOneShotFn = unsafe extern "C" fn(request: *const CBootPluginRequest, pub type ConfirmBootFn = unsafe extern "C" fn(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32; +pub type EspLoaderSourceFn = unsafe extern "C" fn() -> CSlice; + pub trait Booter: Sized { type Error; @@ -23,6 +25,10 @@ pub trait Booter: Sized { fn probes() -> bool; fn set_one_shot(&mut self, entry_name: &str) -> Result<(), Self::Error>; fn confirm_boot(&mut self, entry_name: &str) -> Result<(), Self::Error>; + + fn esp_loader_source() -> Option<&'static str> { + None + } } #[repr(C)] diff --git a/lib/abi/src/lib.rs b/lib/abi/src/lib.rs index 3a89c2ce..9e3673a1 100644 --- a/lib/abi/src/lib.rs +++ b/lib/abi/src/lib.rs @@ -16,8 +16,9 @@ pub mod response; pub mod setup; pub mod types; -pub const ABI_VERSION: u32 = 2; -pub const BOOT_ABI_VERSION: u32 = 1; +pub const LIB_ABI_VERSION: u32 = 2; +pub const BOOT_ABI_VERSION: u32 = 2; +pub const DECODER_ABI_VERSION: u32 = 2; #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] From 8cb48123370fb361f82734fe77d85edd07dcb799 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:48:25 +0400 Subject: [PATCH 009/148] fix: fix var for abi in decoders Co-Authored-By: Claude Sonnet 5 --- decoders/alpm/src/lib.rs | 4 ++-- decoders/deb/src/lib.rs | 4 ++-- decoders/rpm/src/lib.rs | 4 ++-- decoders/xbps/src/lib.rs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/decoders/alpm/src/lib.rs b/decoders/alpm/src/lib.rs index 2a0385c9..d69540ed 100644 --- a/decoders/alpm/src/lib.rs +++ b/decoders/alpm/src/lib.rs @@ -5,7 +5,7 @@ use std::str::from_utf8; -use upac_abi::ABI_VERSION; +use upac_abi::DECODER_ABI_VERSION; use upac_abi::decoder::{CDecodeRequest, CDecodeResponse, CDependency, DecodeError}; use upac_abi::memory::{free_cslice, free_cvec_owning}; use upac_abi::package::CPackageMeta; @@ -28,7 +28,7 @@ include!(concat!(env!("OUT_DIR"), "/layout.rs")); /// Touches no pointers. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] pub unsafe extern "C" fn abi_version() -> u32 { - ABI_VERSION + DECODER_ABI_VERSION } /// # Safety diff --git a/decoders/deb/src/lib.rs b/decoders/deb/src/lib.rs index 7be841ba..6c6c29ec 100644 --- a/decoders/deb/src/lib.rs +++ b/decoders/deb/src/lib.rs @@ -5,7 +5,7 @@ use std::str::from_utf8; -use upac_abi::ABI_VERSION; +use upac_abi::DECODER_ABI_VERSION; use upac_abi::decoder::{CDecodeRequest, CDecodeResponse, CDependency, DecodeError}; use upac_abi::memory::{free_cslice, free_cvec_owning}; use upac_abi::package::CPackageMeta; @@ -28,7 +28,7 @@ include!(concat!(env!("OUT_DIR"), "/layout.rs")); /// Touches no pointers. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] pub unsafe extern "C" fn abi_version() -> u32 { - ABI_VERSION + DECODER_ABI_VERSION } /// # Safety diff --git a/decoders/rpm/src/lib.rs b/decoders/rpm/src/lib.rs index 6cd8b29e..10deca54 100644 --- a/decoders/rpm/src/lib.rs +++ b/decoders/rpm/src/lib.rs @@ -6,7 +6,7 @@ use std::fs::File; use std::str::from_utf8; -use upac_abi::ABI_VERSION; +use upac_abi::DECODER_ABI_VERSION; use upac_abi::decoder::{CDecodeRequest, CDecodeResponse, CDependency, DecodeError}; use upac_abi::memory::{free_cslice, free_cvec_owning}; use upac_abi::package::CPackageMeta; @@ -27,7 +27,7 @@ include!(concat!(env!("OUT_DIR"), "/layout.rs")); /// Touches no pointers. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] pub unsafe extern "C" fn abi_version() -> u32 { - ABI_VERSION + DECODER_ABI_VERSION } /// # Safety diff --git a/decoders/xbps/src/lib.rs b/decoders/xbps/src/lib.rs index 5f385374..e501fccf 100644 --- a/decoders/xbps/src/lib.rs +++ b/decoders/xbps/src/lib.rs @@ -5,7 +5,7 @@ use std::str::from_utf8; -use upac_abi::ABI_VERSION; +use upac_abi::DECODER_ABI_VERSION; use upac_abi::decoder::{CDecodeRequest, CDecodeResponse, CDependency, DecodeError}; use upac_abi::memory::{free_cslice, free_cvec_owning}; use upac_abi::package::CPackageMeta; @@ -28,7 +28,7 @@ include!(concat!(env!("OUT_DIR"), "/layout.rs")); /// Touches no pointers. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] pub unsafe extern "C" fn abi_version() -> u32 { - ABI_VERSION + DECODER_ABI_VERSION } /// # Safety From 4f7e4d8ace76ec159573cc40daa9397254ece21c Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:48:46 +0400 Subject: [PATCH 010/148] fix: fix var for lib Co-Authored-By: Claude Sonnet 5 --- user/upac-cli/src/libcore.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/user/upac-cli/src/libcore.rs b/user/upac-cli/src/libcore.rs index a091b154..bc11b76c 100644 --- a/user/upac-cli/src/libcore.rs +++ b/user/upac-cli/src/libcore.rs @@ -10,6 +10,7 @@ use i18n_embed_fl::fl; use nix::unistd::Uid; +use upac_abi::LIB_ABI_VERSION; use upac_abi::error::CError; use upac_abi::hook::CancelToken; use upac_abi::request::{ @@ -170,10 +171,10 @@ impl Lib { }; let abi_version = unsafe { (lib.version_abi)() }; - if abi_version != upac_abi::ABI_VERSION { + if abi_version != LIB_ABI_VERSION { let err = AbiMismatch { got: abi_version, - expected: upac_abi::ABI_VERSION, + expected: LIB_ABI_VERSION, }; return Err(err.into()); From f7e5bf46eb311248d369bc28eb3e696ea2f9a916 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:51:30 +0400 Subject: [PATCH 011/148] fix: fix vars for lib and decode Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/export/mod.rs | 4 ++-- lib/lib/src/plugin/decoder/mod.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/lib/src/export/mod.rs b/lib/lib/src/export/mod.rs index c28aa52f..69661933 100644 --- a/lib/lib/src/export/mod.rs +++ b/lib/lib/src/export/mod.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::ABI_VERSION; +use upac_abi::LIB_ABI_VERSION; use upac_abi::error::{CError, CommandState, ErrorKind}; use upac_abi::hook::CancelToken; use upac_abi::response::CUnmutatedResponse; @@ -15,7 +15,7 @@ pub mod unmutated; /// Touches no pointers — `unsafe extern "C"` only to match the ABI calling convention. #[unsafe(no_mangle)] pub unsafe extern "C" fn version_abi() -> u32 { - ABI_VERSION + LIB_ABI_VERSION } /// # Safety diff --git a/lib/lib/src/plugin/decoder/mod.rs b/lib/lib/src/plugin/decoder/mod.rs index 1add65a9..e77620c0 100644 --- a/lib/lib/src/plugin/decoder/mod.rs +++ b/lib/lib/src/plugin/decoder/mod.rs @@ -14,7 +14,7 @@ use std::str::from_utf8; use libloading::Library; #[cfg(feature = "dynamic-plugins")] -use upac_abi::ABI_VERSION; +use upac_abi::DECODER_ABI_VERSION; #[cfg(feature = "dynamic-plugins")] use upac_abi::decoder::AbiVersionFn; @@ -97,10 +97,10 @@ impl Decoder { let decode: DecodeFn = unsafe { load_symbol(&library, "decode")? }; let got = unsafe { abi_version() }; - if got != ABI_VERSION { + if got != DECODER_ABI_VERSION { return Err(DecoderError::AbiMismatch { got, - expected: ABI_VERSION, + expected: DECODER_ABI_VERSION, }); } @@ -160,7 +160,7 @@ impl Decoder { /// extensions — mirrors `plugin::boot::static_plugins`, adapted for extension-based dispatch /// (a decoder is selected by the package file's extension, not by a `probe()` call). No ABI /// version check: compiled from the same source tree by the same compiler, so the decoder's own -/// `ABI_VERSION` matches by construction. +/// `DECODER_ABI_VERSION` matches by construction. #[cfg(feature = "builtin-decoders")] #[allow( clippy::vec_init_then_push, From 2cd924bad246ec1ba00666d63771b3b079fff329 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:56:21 +0400 Subject: [PATCH 012/148] fix: added binary copying to the image for setup Co-Authored-By: Claude Sonnet 5 --- booters/booter.toml | 10 ++++++ booters/grub/src/lib.rs | 9 +++++- booters/refind/src/backend.rs | 6 +++- booters/refind/src/lib.rs | 9 +++++- booters/systemd-boot/src/backend.rs | 5 +++ booters/systemd-boot/src/lib.rs | 9 +++++- booters/uki/src/lib.rs | 9 +++++- lib/lib/src/plugin/boot/mod.rs | 50 +++++++++++++++++++++++------ 8 files changed, 92 insertions(+), 15 deletions(-) diff --git a/booters/booter.toml b/booters/booter.toml index af32a191..edf98426 100644 --- a/booters/booter.toml +++ b/booters/booter.toml @@ -30,6 +30,13 @@ loader_info_var = "LoaderInfo" loader_entry_one_shot_var = "LoaderEntryOneShot" loader_entry_default_var = "LoaderEntryDefault" +# source is the fixed, package-convention path (source-tree-relative) where systemd's own +# packaging always installs its EFI binary — used by genesis to copy the loader onto a brand-new +# ESP that doesn't have one yet (install/update never need this, the binary is already on the ESP +# from genesis). +[systemd_boot] +source = "usr/lib/systemd/boot/efi/systemd-bootx64.efi" + # grub has no EFI-variable-based one-shot mechanism — it's file-based (grubenv), driven through # grub's own grub-reboot/grub-set-default tools rather than a hand-rolled binary-format writer. # Both the grubenv location and the tool names differ across distro packaging: Debian/Ubuntu/Arch @@ -50,6 +57,9 @@ set_default_bin_fallback = "grub2-set-default" # entry through PreviousBoot only takes effect if refind.conf's `default_selection` starts with # `+` ("remember last boot"); that's a user/deployment-side rEFInd config choice this plugin has # no way to inspect or control. GUID/name per rEFInd's own documented EFI variable, not guessed. +# source is the fixed, package-convention path (source-tree-relative) where rEFInd's own packaging +# always installs its EFI binary — same genesis use as systemd_boot.source above. [refind] previous_boot_var = "PreviousBoot" previous_boot_guid = "36d08fa7-cf0b-42f5-8f14-68df73ed3740" +source = "usr/share/refind/refind_x64.efi" diff --git a/booters/grub/src/lib.rs b/booters/grub/src/lib.rs index 8dfcd1ea..079b67ef 100644 --- a/booters/grub/src/lib.rs +++ b/booters/grub/src/lib.rs @@ -8,7 +8,7 @@ use std::str::from_utf8; use upac_abi::BOOT_ABI_VERSION; use upac_abi::boot::{Booter, CBootPluginRequest}; use upac_abi::error::ErrorKind; -use upac_abi::types::CBorrowed; +use upac_abi::types::{CBorrowed, CSlice}; use crate::backend::Grub; use crate::error::GrubError; @@ -32,6 +32,13 @@ pub unsafe extern "C" fn probe() -> i32 { i32::from(Grub::probes()) } +/// # Safety +/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::EspLoaderSourceFn`. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn esp_loader_source() -> CSlice { + CSlice::from_slice(Grub::esp_loader_source().map(str::as_bytes)) +} + /// # Safety /// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. diff --git a/booters/refind/src/backend.rs b/booters/refind/src/backend.rs index 68745535..6d3c13ea 100644 --- a/booters/refind/src/backend.rs +++ b/booters/refind/src/backend.rs @@ -20,7 +20,7 @@ use upac_abi::boot::Booter; use crate::boot::EFIVARFS_PATH; use crate::error::RefindError; -use crate::refind::{PREVIOUS_BOOT_GUID, PREVIOUS_BOOT_VAR}; +use crate::refind::{PREVIOUS_BOOT_GUID, PREVIOUS_BOOT_VAR, SOURCE}; const FS_IMMUTABLE_FL: c_long = 0x0000_0010; @@ -60,6 +60,10 @@ impl Booter for Refind { fn confirm_boot(&mut self, entry_name: &str) -> Result<(), RefindError> { self.write_previous_boot(entry_name) } + + fn esp_loader_source() -> Option<&'static str> { + Some(SOURCE) + } } impl Refind { diff --git a/booters/refind/src/lib.rs b/booters/refind/src/lib.rs index 26a62fed..623a6334 100644 --- a/booters/refind/src/lib.rs +++ b/booters/refind/src/lib.rs @@ -8,7 +8,7 @@ use std::str::from_utf8; use upac_abi::BOOT_ABI_VERSION; use upac_abi::boot::{Booter, CBootPluginRequest}; use upac_abi::error::ErrorKind; -use upac_abi::types::CBorrowed; +use upac_abi::types::{CBorrowed, CSlice}; use crate::backend::Refind; use crate::error::RefindError; @@ -32,6 +32,13 @@ pub unsafe extern "C" fn probe() -> i32 { i32::from(Refind::probes()) } +/// # Safety +/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::EspLoaderSourceFn`. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn esp_loader_source() -> CSlice { + CSlice::from_slice(Refind::esp_loader_source().map(str::as_bytes)) +} + /// # Safety /// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. diff --git a/booters/systemd-boot/src/backend.rs b/booters/systemd-boot/src/backend.rs index 39b9fab4..8b4a09bd 100644 --- a/booters/systemd-boot/src/backend.rs +++ b/booters/systemd-boot/src/backend.rs @@ -22,6 +22,7 @@ use crate::boot::{ EFIVARFS_PATH, LOADER_ENTRY_DEFAULT_VAR, LOADER_ENTRY_ONE_SHOT_VAR, LOADER_INFO_VAR, SD_BOOT_LOADER_GUID, }; use crate::error::BlsError; +use crate::systemd_boot::SOURCE; const FS_IMMUTABLE_FL: c_long = 0x0000_0010; @@ -61,6 +62,10 @@ impl Booter for Bls { fn confirm_boot(&mut self, entry_name: &str) -> Result<(), BlsError> { self.write_loader_variable(LOADER_ENTRY_DEFAULT_VAR, entry_name) } + + fn esp_loader_source() -> Option<&'static str> { + Some(SOURCE) + } } impl Bls { diff --git a/booters/systemd-boot/src/lib.rs b/booters/systemd-boot/src/lib.rs index b11082c6..385375b0 100644 --- a/booters/systemd-boot/src/lib.rs +++ b/booters/systemd-boot/src/lib.rs @@ -8,7 +8,7 @@ use std::str::from_utf8; use upac_abi::BOOT_ABI_VERSION; use upac_abi::boot::{Booter, CBootPluginRequest}; use upac_abi::error::ErrorKind; -use upac_abi::types::CBorrowed; +use upac_abi::types::{CBorrowed, CSlice}; use crate::backend::Bls; use crate::error::BlsError; @@ -32,6 +32,13 @@ pub unsafe extern "C" fn probe() -> i32 { i32::from(Bls::probes()) } +/// # Safety +/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::EspLoaderSourceFn`. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn esp_loader_source() -> CSlice { + CSlice::from_slice(Bls::esp_loader_source().map(str::as_bytes)) +} + /// # Safety /// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. diff --git a/booters/uki/src/lib.rs b/booters/uki/src/lib.rs index 1140ec7c..e0bbe7e7 100644 --- a/booters/uki/src/lib.rs +++ b/booters/uki/src/lib.rs @@ -8,7 +8,7 @@ use std::str::from_utf8; use upac_abi::BOOT_ABI_VERSION; use upac_abi::boot::{Booter, CBootPluginRequest}; use upac_abi::error::ErrorKind; -use upac_abi::types::CBorrowed; +use upac_abi::types::{CBorrowed, CSlice}; use crate::backend::Uki; use crate::error::UkiError; @@ -32,6 +32,13 @@ pub unsafe extern "C" fn probe() -> i32 { i32::from(Uki::probes()) } +/// # Safety +/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::EspLoaderSourceFn`. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn esp_loader_source() -> CSlice { + CSlice::from_slice(Uki::esp_loader_source().map(str::as_bytes)) +} + /// # Safety /// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. diff --git a/lib/lib/src/plugin/boot/mod.rs b/lib/lib/src/plugin/boot/mod.rs index eaee6821..ca3b2512 100644 --- a/lib/lib/src/plugin/boot/mod.rs +++ b/lib/lib/src/plugin/boot/mod.rs @@ -5,7 +5,7 @@ use std::mem::MaybeUninit; -use upac_abi::boot::{CBootPluginRequest, ConfirmBootFn, ProbeFn, SetOneShotFn}; +use upac_abi::boot::{CBootPluginRequest, ConfirmBootFn, EspLoaderSourceFn, ProbeFn, SetOneShotFn}; use upac_abi::error::ErrorKind; use upac_abi::types::{CBorrowed, CSlice}; @@ -24,19 +24,27 @@ use upac_abi::boot::AbiVersionFn; use crate::plugin::boot::manifest::load_boot_plugin_manifests; #[cfg(feature = "builtin-grub")] -use upac_boot_grub::{confirm_boot as grub_confirm_boot, probe as grub_probe, set_one_shot as grub_set_one_shot}; +use upac_boot_grub::{ + confirm_boot as grub_confirm_boot, esp_loader_source as grub_esp_loader_source, probe as grub_probe, + set_one_shot as grub_set_one_shot, +}; #[cfg(feature = "builtin-systemd-boot")] use upac_boot_systemd_boot::{ - confirm_boot as systemd_boot_confirm_boot, probe as systemd_boot_probe, set_one_shot as systemd_boot_set_one_shot, + confirm_boot as systemd_boot_confirm_boot, esp_loader_source as systemd_boot_esp_loader_source, + probe as systemd_boot_probe, set_one_shot as systemd_boot_set_one_shot, }; #[cfg(feature = "builtin-uki")] -use upac_boot_uki::{confirm_boot as uki_confirm_boot, probe as uki_probe, set_one_shot as uki_set_one_shot}; +use upac_boot_uki::{ + confirm_boot as uki_confirm_boot, esp_loader_source as uki_esp_loader_source, probe as uki_probe, + set_one_shot as uki_set_one_shot, +}; #[cfg(feature = "builtin-refind")] use upac_boot_refind::{ - confirm_boot as refind_confirm_boot, probe as refind_probe, set_one_shot as refind_set_one_shot, + confirm_boot as refind_confirm_boot, esp_loader_source as refind_esp_loader_source, probe as refind_probe, + set_one_shot as refind_set_one_shot, }; pub mod error; @@ -46,11 +54,14 @@ pub mod manifest; #[cfg(feature = "builtin-booters")] impl BootPlugin { - fn from_static(probe: ProbeFn, set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn) -> Self { + fn from_static( + probe: ProbeFn, set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, esp_loader_source: EspLoaderSourceFn, + ) -> Self { BootPlugin { probe, set_one_shot, confirm_boot, + esp_loader_source, #[cfg(feature = "dynamic-plugins")] _library: None, @@ -167,25 +178,35 @@ fn static_plugins() -> Vec<(&'static str, BootPlugin)> { #[cfg(feature = "builtin-uki")] plugins.push(( "uki", - BootPlugin::from_static(uki_probe, uki_set_one_shot, uki_confirm_boot), + BootPlugin::from_static(uki_probe, uki_set_one_shot, uki_confirm_boot, uki_esp_loader_source), )); #[cfg(feature = "builtin-systemd-boot")] plugins.push(( "systemd-boot", - BootPlugin::from_static(systemd_boot_probe, systemd_boot_set_one_shot, systemd_boot_confirm_boot), + BootPlugin::from_static( + systemd_boot_probe, + systemd_boot_set_one_shot, + systemd_boot_confirm_boot, + systemd_boot_esp_loader_source, + ), )); #[cfg(feature = "builtin-grub")] plugins.push(( "grub", - BootPlugin::from_static(grub_probe, grub_set_one_shot, grub_confirm_boot), + BootPlugin::from_static(grub_probe, grub_set_one_shot, grub_confirm_boot, grub_esp_loader_source), )); #[cfg(feature = "builtin-refind")] plugins.push(( "refind", - BootPlugin::from_static(refind_probe, refind_set_one_shot, refind_confirm_boot), + BootPlugin::from_static( + refind_probe, + refind_set_one_shot, + refind_confirm_boot, + refind_esp_loader_source, + ), )); plugins @@ -202,6 +223,7 @@ pub struct BootPlugin { probe: ProbeFn, set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, + esp_loader_source: EspLoaderSourceFn, #[cfg(feature = "dynamic-plugins")] _library: Option, @@ -216,6 +238,7 @@ impl BootPlugin { let probe: ProbeFn = unsafe { load_symbol(&library, "probe")? }; let set_one_shot: SetOneShotFn = unsafe { load_symbol(&library, "set_one_shot")? }; let confirm_boot: ConfirmBootFn = unsafe { load_symbol(&library, "confirm_boot")? }; + let esp_loader_source: EspLoaderSourceFn = unsafe { load_symbol(&library, "esp_loader_source")? }; let got = unsafe { abi_version() }; if got != BOOT_ABI_VERSION { @@ -229,6 +252,7 @@ impl BootPlugin { probe, set_one_shot, confirm_boot, + esp_loader_source, _library: Some(library), }) } @@ -262,4 +286,10 @@ impl BootPlugin { Ok(()) } + + pub fn esp_loader_source(&self) -> Option { + let slice = unsafe { (self.esp_loader_source)() }; + + Option::<&str>::try_from(&slice).ok().flatten().map(str::to_owned) + } } From d490a9ffce2361c084d37236c75643016e3ac9af Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:57:22 +0400 Subject: [PATCH 013/148] fix: switched the mechanism to use a plugin-based approach fix: removed unnecessary constants Co-Authored-By: Claude Sonnet 5 --- lib/setup/lib.toml | 11 +++-------- lib/setup/src/genesis/entry.rs | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/setup/lib.toml b/lib/setup/lib.toml index 9151ddad..c1ee9bac 100644 --- a/lib/setup/lib.toml +++ b/lib/setup/lib.toml @@ -31,15 +31,10 @@ settle_interval_ms = 100 # written (under the OS temp dir) before being embedded into the new /usr # tree — purely transient, read back once right after the write. # -# *_source are the fixed, package-convention paths (source-tree-relative) where each supported -# bootloader ships its own EFI binary — systemd's own packaging always installs to -# lib/systemd/boot/efi/, rEFInd's own packaging always installs to share/refind/. Checked in this -# order (first one found in the source tree wins) since a fresh ESP has no bootloader binary at -# all yet — install/update never need this, the binary is already on the ESP from genesis. # esp_fallback_loader is the fixed "removable media" path UEFI firmware always tries when NVRAM -# has no Boot#### entries at all, which is exactly genesis's situation on a brand-new disk. +# has no Boot#### entries at all, which is exactly genesis's situation on a brand-new disk. The +# resolved boot plugin's own `esp_loader_source()` (see `booters/booter.toml`) says which source +# path — if any — a fresh ESP needs it copied from; genesis only owns the destination. [genesis] scratch_filename = "genesis-packages.redb" -systemd_boot_source = "usr/lib/systemd/boot/efi/systemd-bootx64.efi" -refind_source = "usr/share/refind/refind_x64.efi" esp_fallback_loader = "EFI/BOOT/BOOTX64.EFI" diff --git a/lib/setup/src/genesis/entry.rs b/lib/setup/src/genesis/entry.rs index ed5b5d00..b1a317e6 100644 --- a/lib/setup/src/genesis/entry.rs +++ b/lib/setup/src/genesis/entry.rs @@ -24,7 +24,7 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; use super::ctx_get; use crate::error::SetupError; -use crate::layout::genesis::{ESP_FALLBACK_LOADER, REFIND_SOURCE, SYSTEMD_BOOT_SOURCE}; +use crate::layout::genesis::ESP_FALLBACK_LOADER; use crate::target::TargetSysroot; use crate::types::{GenesisInput, PrefixDigest}; @@ -43,13 +43,9 @@ impl Stage for StageBootStage { let prefix_tree = Self::reopen_tree(repository, &prefix_digest_hex)?; - let candidate = match input.boot_plugin.as_deref() { - Some("systemd-boot") => Some(SYSTEMD_BOOT_SOURCE), - Some("refind") => Some(REFIND_SOURCE), - _ => None, - }; + let plugin = resolve_boot_plugin(BOOT_PLUGINS_DIR, MANIFEST_EXTENSION, input.boot_plugin.as_deref())?; - if let Some(candidate) = candidate { + if let Some(candidate) = plugin.esp_loader_source() { let handle = FileHandle::new(candidate); if handle.stat_in_tree(&prefix_tree).is_ok() { let loader_bytes = handle.read_file(repository, &prefix_tree)?; @@ -71,7 +67,6 @@ impl Stage for StageBootStage { &prefix_digest_hex, )?; - let plugin = resolve_boot_plugin(BOOT_PLUGINS_DIR, MANIFEST_EXTENSION, input.boot_plugin.as_deref())?; plugin.set_one_shot(&entry_name)?; Ok((progress, StageResult::Advance, Box::new(NoRollback))) From 3b64fa689974caaf58d4b6a2d22aca16e0865123 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 12:03:32 +0400 Subject: [PATCH 014/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/TODO.md b/TODO.md index 7bea9bcc..2fa32a5d 100644 --- a/TODO.md +++ b/TODO.md @@ -24,33 +24,11 @@ Real unfinished A/B swap, not just a stale doc — needs a decision (implement t formally drop it and fix the doc to match the single-slot design `lib.toml`'s own comment already argues for). -Genesis (`up-sp`) now installs the actual bootloader binary onto a fresh ESP for systemd-boot and -rEFInd (`StageBootStage::run`, `lib/setup/lib.toml`'s `[genesis]` source paths) — confirmed working -via a live VM test for systemd-boot; rEFInd wired the same way but not yet VM-verified. grub is NOT -handled — a real `grub-install`-equivalent (target-specific generated `grubx64.efi`, not a plain -file copy) is out of scope for now; either shell out to `grub-install` against the mounted ESP, or -explicitly document grub as unsupported for genesis whole-disk mode. - -`StageBootStage` picks which ESP loader binary to copy via a `match input.boot_plugin.as_deref()` -against literal `"systemd-boot"`/`"refind"` strings — this bypasses the actual dynamic boot-plugin -system (`resolve_boot_plugin`/`BootPluginManifest`/`static_plugins`), which is supposed to be the -one place plugin names are known. Adding a 5th booter plugin would require editing this match by -hand instead of just dropping in a new plugin. The correct fix is extending the `Booter` ABI itself -with a 4th function (e.g. `esp_loader_source() -> CSlice`, empty for uki/grub) so genesis asks the -already-resolved plugin for its own install-time source path instead of hardcoding names — but that -means bumping `BOOT_ABI_VERSION` and touching all 4 `booters/*` crates, so deliberately deferred; -the hardcoded match stays as a known, scoped limitation until then. - **Genesis-produced disks don't actually boot into the installed system yet** — found via a live -QEMU/OVMF test (systemd-boot now starts, finds the BLS entry, loads kernel+initramfs — that part -works after the bootloader-binary fix above). Two separate gaps, both required: -1. `partition.rs`'s `LINUX_PARTITION_TYPE_GUID` (`0fc63daf-8483-4772-8e79-3d69d8477de4`, generic - "Linux filesystem data") should be the discoverable-root GUID - (`4f68bce3-e8cd-4db1-96e7-fbcaf984b709`, "Linux root x86-64") so `systemd-gpt-auto-generator` - can find the deploy partition at all instead of hanging on `/dev/gpt-auto-root`. -2. Even with (1) fixed, a plain partition mount isn't how composefs systems boot — nothing in this - project resolves `composefs.digest=` (the kernel cmdline param `write_boot_entry` already - writes) against the on-disk repository, mounts the erofs image with fs-verity, and overlays +QEMU/OVMF test (systemd-boot now starts, finds the BLS entry, loads kernel+initramfs): +1. Still open: a plain partition mount isn't how composefs systems boot — nothing in this project + resolves `composefs.digest=` (the kernel cmdline param `write_boot_entry` already writes) + against the on-disk repository, mounts the erofs image with fs-verity, and overlays `state/deploy//etc/`. **Found a real, existing upstream tool for exactly this**: `composefs-setup-root` (crates.io, same `composefs-rs` project/version as our `composefs`/ `composefs-boot` deps) — a Rust binary, not something we'd write ourselves. Our on-disk layout From 756859f0164b63384f3d19d2409cb3aba6a36652 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 04:04:14 +0400 Subject: [PATCH 015/148] fix: fix search for upac-to.efi Co-Authored-By: Claude Sonnet 5 --- booters/uki/src/backend.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/booters/uki/src/backend.rs b/booters/uki/src/backend.rs index 8b1262ef..dc84d83a 100644 --- a/booters/uki/src/backend.rs +++ b/booters/uki/src/backend.rs @@ -86,14 +86,15 @@ impl Booter for Uki { impl Uki { fn find_boot_id(&self, slot_filename: &str) -> Result { + let slot_file_name = format!("{}.efi", slot_filename.to_lowercase()); + for (entry, _var) in self.manager.get_boot_entries()? { let entry = entry?; - let matches = entry.entry.file_path_list.as_ref().is_some_and(|list| { - list.file_path - .path - .to_lowercase() - .ends_with(&slot_filename.to_lowercase()) - }); + let matches = entry + .entry + .file_path_list + .as_ref() + .is_some_and(|list| list.file_path.path.to_lowercase().ends_with(&slot_file_name)); if matches { return Ok(entry.id); From a59032f172d4632ecb4e3993eb4c7e5271f0385a Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 04:04:40 +0400 Subject: [PATCH 016/148] new: add upac-from slot name Co-Authored-By: Claude Sonnet 5 --- lib/lib/lib.toml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/lib/lib.toml b/lib/lib/lib.toml index 92de957a..f41c4b9a 100644 --- a/lib/lib/lib.toml +++ b/lib/lib/lib.toml @@ -145,17 +145,21 @@ update_mime_database_bin = "update-mime-database" update_desktop_database_bin = "update-desktop-database" shared_mime_info_xmlns = "http://www.freedesktop.org/standards/shared-mime-info" -# ESP discovery and the fixed UKI-direct staging slot name (§5.2/§5.3). esp_mount_primary/ +# ESP discovery and the fixed UKI-direct staging slots (§5.2/§5.3). esp_mount_primary/ # esp_mount_fallback are tried in that order against the running system's mount table (same # rsmount::tables::MountInfo mechanism Deploy::device_path() already uses) — chapter 3 documents -# the ESP as mounted at either. upac_to_slot is the fixed stem a UKI-direct image is always -# staged under (`\EFI\Linux\upac-to.efi`): the corresponding UEFI Boot#### entry is pre-registered -# once, out of scope of this pipeline, and CheckoutStage only ever overwrites that same file's -# content — a content-addressed name would require a new Boot#### NVRAM entry each time, which -# nothing here creates. BLS-style images (systemd-boot/grub/refind) use the prefix_digest itself -# as the entry name instead — content-addressed, no fixed slot needed, since those loaders rescan -# their entries directory fresh every boot. +# the ESP as mounted at either. upac_uki_to_slot is the fixed stem the corresponding UEFI Boot#### +# entry is pre-registered against once, out of scope of this pipeline (`\EFI\Linux\upac-to.efi`) — +# a content-addressed name would require a new Boot#### NVRAM entry each time, which nothing here +# creates. upac_uki_from_slot is NOT a second independently-bootable slot — it's a staging name: +# `write_boot_entry` writes the new image there first, then atomically renames it over +# upac_uki_to_slot, so a crash mid-write never leaves the one file Boot#### actually points at +# truncated (the upstream `composefs-boot` writer itself just does a plain, non-atomic +# `fs::write`). BLS-style images (systemd-boot/grub/refind) use the prefix_digest itself as the +# entry name instead — content-addressed, no staging needed, since those loaders rescan their +# entries directory fresh every boot. [boot] esp_mount_primary = "/efi" esp_mount_fallback = "/boot" upac_uki_to_slot = "upac-to" +upac_uki_from_slot = "upac-from" From bab2c9efda07d030ce6caf3c3248480dc83a78d2 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 04:10:37 +0400 Subject: [PATCH 017/148] fix: added new functions for ABI and API registration Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/boot.rs | 31 +++++++++++++++++++++++++++++++ lib/setup/src/partition.rs | 32 ++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/lib/abi/src/boot.rs b/lib/abi/src/boot.rs index 0c496b80..5fdf6cf2 100644 --- a/lib/abi/src/boot.rs +++ b/lib/abi/src/boot.rs @@ -18,6 +18,8 @@ pub type ConfirmBootFn = unsafe extern "C" fn(request: *const CBootPluginRequest pub type EspLoaderSourceFn = unsafe extern "C" fn() -> CSlice; +pub type RegisterBootSlotsFn = unsafe extern "C" fn(request: *const CBootSlotsRequest, err_out: *mut ErrorKind) -> i32; + pub trait Booter: Sized { type Error; @@ -29,6 +31,22 @@ pub trait Booter: Sized { fn esp_loader_source() -> Option<&'static str> { None } + + fn register_boot_slots( + &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, + ) -> Result<(), Self::Error> { + let _ = ( + esp_partition_number, + esp_starting_lba, + esp_ending_lba, + esp_unique_partition_guid, + to_slot, + from_slot, + ); + + Ok(()) + } } #[repr(C)] @@ -38,3 +56,16 @@ pub struct CBootPluginRequest { pub entry_name: CSlice, } + +#[repr(C)] +#[derive(CNew)] +pub struct CBootSlotsRequest { + pub struct_size: usize, + + pub esp_partition_number: u32, + pub esp_starting_lba: u64, + pub esp_ending_lba: u64, + pub esp_unique_partition_guid: [u8; 16], + pub to_slot: CSlice, + pub from_slot: CSlice, +} diff --git a/lib/setup/src/partition.rs b/lib/setup/src/partition.rs index 38200392..38252fc6 100644 --- a/lib/setup/src/partition.rs +++ b/lib/setup/src/partition.rs @@ -47,11 +47,11 @@ impl GptTable { fn insert_partition( &mut self, number: u32, partition_type: Uuid, name: &str, size_sectors: u64, - ) -> Result<(), SetupError> { + ) -> Result { let starting_lba = self.0.find_first_place(size_sectors).ok_or(SetupError::NoSpaceLeft)?; let ending_lba = starting_lba + size_sectors - 1; - self.0[number] = GPTPartitionEntry { + let entry = GPTPartitionEntry { partition_type_guid: partition_type.to_bytes_le(), unique_partition_guid: Uuid::new_v4().to_bytes_le(), starting_lba, @@ -60,7 +60,9 @@ impl GptTable { partition_name: name.into(), }; - Ok(()) + self.0[number] = entry.clone(); + + Ok(entry) } fn write_into(&mut self, device: &mut File) -> Result<(), SetupError> { @@ -73,6 +75,9 @@ impl GptTable { pub struct DiskLayout { device_path: PathBuf, esp_partition: u32, + esp_starting_lba: u64, + esp_ending_lba: u64, + esp_unique_partition_guid: Uuid, deploy_partition: u32, extra_partitions: Vec, } @@ -98,7 +103,7 @@ impl DiskLayout { let mut next_number = 1; let esp_partition = next_number; - gpt.insert_partition( + let esp_entry = gpt.insert_partition( esp_partition, ESP_PARTITION_TYPE_GUID, "ESP", @@ -134,6 +139,9 @@ impl DiskLayout { let layout = DiskLayout { device_path: device_path.to_owned(), esp_partition, + esp_starting_lba: esp_entry.starting_lba, + esp_ending_lba: esp_entry.ending_lba, + esp_unique_partition_guid: Uuid::from_bytes_le(esp_entry.unique_partition_guid), deploy_partition, extra_partitions: extras, }; @@ -169,6 +177,22 @@ impl DiskLayout { self.partition_path(self.esp_partition) } + pub fn esp_partition_number(&self) -> u32 { + self.esp_partition + } + + pub fn esp_starting_lba(&self) -> u64 { + self.esp_starting_lba + } + + pub fn esp_ending_lba(&self) -> u64 { + self.esp_ending_lba + } + + pub fn esp_unique_partition_guid(&self) -> Uuid { + self.esp_unique_partition_guid + } + pub fn deploy_path(&self) -> PathBuf { self.partition_path(self.deploy_partition) } From fa222c403f5c58b416bbe15b28e03959634286bc Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 04:19:27 +0400 Subject: [PATCH 018/148] fix: fix test Co-Authored-By: Claude Sonnet 5 --- lib/setup/tests/inline/partition.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/setup/tests/inline/partition.rs b/lib/setup/tests/inline/partition.rs index 29fa7005..925a3fc5 100644 --- a/lib/setup/tests/inline/partition.rs +++ b/lib/setup/tests/inline/partition.rs @@ -5,12 +5,17 @@ use std::path::PathBuf; +use uuid::Uuid; + use super::DiskLayout; fn layout(device_path: &str, extra_partitions: Vec) -> DiskLayout { DiskLayout { device_path: PathBuf::from(device_path), esp_partition: 1, + esp_starting_lba: 2048, + esp_ending_lba: 2048 + 1024 * 1024 / 512 - 1, + esp_unique_partition_guid: Uuid::new_v4(), deploy_partition: 2, extra_partitions, } From d3b3c7fb9c003cc599f5ed11413834e9369dfdaf Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 04:19:57 +0400 Subject: [PATCH 019/148] fix: simplified the default function for the trait Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/boot.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/abi/src/boot.rs b/lib/abi/src/boot.rs index 5fdf6cf2..d1320401 100644 --- a/lib/abi/src/boot.rs +++ b/lib/abi/src/boot.rs @@ -35,18 +35,7 @@ pub trait Booter: Sized { fn register_boot_slots( &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, - ) -> Result<(), Self::Error> { - let _ = ( - esp_partition_number, - esp_starting_lba, - esp_ending_lba, - esp_unique_partition_guid, - to_slot, - from_slot, - ); - - Ok(()) - } + ) -> Result<(), Self::Error>; } #[repr(C)] From 5e9ba0d8bde94a536c3255a38945dbf738221b14 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 04:20:24 +0400 Subject: [PATCH 020/148] fix: implemented creation of to and from for uki Co-Authored-By: Claude Sonnet 5 --- booters/booter.toml | 8 +++++ booters/uki/src/backend.rs | 73 ++++++++++++++++++++++++++++++++++++++ booters/uki/src/error.rs | 2 ++ booters/uki/src/lib.rs | 50 +++++++++++++++++++++++++- 4 files changed, 132 insertions(+), 1 deletion(-) diff --git a/booters/booter.toml b/booters/booter.toml index edf98426..366bd028 100644 --- a/booters/booter.toml +++ b/booters/booter.toml @@ -30,6 +30,14 @@ loader_info_var = "LoaderInfo" loader_entry_one_shot_var = "LoaderEntryOneShot" loader_entry_default_var = "LoaderEntryDefault" +# efi_linux_dir is the fixed UEFI-style (backslash) directory composefs-boot's own +# `write_t2_simple` always writes UKI images under (`EFI/Linux/`, hardcoded upstream, not +# configurable) — used to build the file-path device-path segment when genesis registers a +# UEFI Boot#### entry for a UKI slot, so the entry's path always matches where the image +# actually lands. +[uki] +efi_linux_dir = "\\EFI\\Linux\\" + # source is the fixed, package-convention path (source-tree-relative) where systemd's own # packaging always installs its EFI binary — used by genesis to copy the loader onto a brand-new # ESP that doesn't have one yet (install/update never need this, the binary is already on the ESP diff --git a/booters/uki/src/backend.rs b/booters/uki/src/backend.rs index dc84d83a..a10b53fb 100644 --- a/booters/uki/src/backend.rs +++ b/booters/uki/src/backend.rs @@ -11,6 +11,9 @@ use std::path::Path; use std::str::FromStr; use efivar::VarManager; +use efivar::boot::{ + BootEntry, BootEntryAttributes, BootVarName, EFIHardDrive, EFIHardDriveType, FilePath, FilePathList, +}; use efivar::efi::{Variable, VariableFlags}; use nix::{ioctl_read, ioctl_write_ptr}; @@ -23,6 +26,7 @@ use crate::boot::{BOOT_NEXT_VAR, BOOT_ORDER_VAR, EFI_SYSFS_PATH, EFIVARFS_PATH, use crate::error::UkiError; use crate::grub::{GRUBENV_FALLBACK, GRUBENV_PRIMARY}; use crate::refind::{PREVIOUS_BOOT_GUID, PREVIOUS_BOOT_VAR}; +use crate::uki::EFI_LINUX_DIR; const FS_IMMUTABLE_FL: c_long = 0x0000_0010; @@ -82,6 +86,39 @@ impl Booter for Uki { Ok(()) } + + fn register_boot_slots( + &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, + ) -> Result<(), UkiError> { + let partition_sig = Uuid::from_bytes_le(esp_unique_partition_guid); + let partition_size = esp_ending_lba - esp_starting_lba + 1; + + let to_id = self.register_slot( + esp_partition_number, + esp_starting_lba, + partition_size, + partition_sig, + to_slot, + )?; + let from_id = self.register_slot( + esp_partition_number, + esp_starting_lba, + partition_size, + partition_sig, + from_slot, + )?; + + let mut order = self.manager.get_boot_order().unwrap_or_default(); + order.retain(|&existing| existing != to_id && existing != from_id); + order.insert(0, from_id); + order.insert(0, to_id); + + Self::clear_immutable(&Variable::new(BOOT_ORDER_VAR)); + self.manager.set_boot_order(order)?; + + Ok(()) + } } impl Uki { @@ -104,6 +141,42 @@ impl Uki { Err(UkiError::EntryNotFound) } + fn register_slot( + &mut self, partition_number: u32, partition_start: u64, partition_size: u64, partition_sig: Uuid, + slot_filename: &str, + ) -> Result { + let id = self.free_boot_id()?; + + let entry = BootEntry { + attributes: BootEntryAttributes::LOAD_OPTION_ACTIVE, + description: slot_filename.to_owned(), + file_path_list: Some(FilePathList { + file_path: FilePath { + path: format!("{EFI_LINUX_DIR}{slot_filename}.efi"), + }, + hard_drive: EFIHardDrive { + partition_number, + partition_start, + partition_size, + partition_sig, + format: 0x02, + sig_type: EFIHardDriveType::Gpt, + }, + }), + optional_data: Vec::new(), + }; + + self.manager.add_boot_entry(id, entry)?; + + Ok(id) + } + + fn free_boot_id(&self) -> Result { + (0..u16::MAX) + .find(|id| !self.manager.exists(&Variable::new(&id.boot_var_name())).unwrap_or(true)) + .ok_or(UkiError::NoFreeBootId) + } + fn clear_immutable(variable: &Variable) { let Ok(file) = OpenOptions::new() .read(true) diff --git a/booters/uki/src/error.rs b/booters/uki/src/error.rs index ea030a01..75dd7ac9 100644 --- a/booters/uki/src/error.rs +++ b/booters/uki/src/error.rs @@ -14,6 +14,7 @@ pub enum UkiError { EfiUnavailable, PermissionDenied, EntryNotFound, + NoFreeBootId, InvalidRequest, Unexpected, } @@ -39,6 +40,7 @@ impl From for ErrorKind { UkiError::EfiUnavailable => ErrorKind::NotInitialized, UkiError::PermissionDenied => ErrorKind::PermissionDenied, UkiError::EntryNotFound => ErrorKind::NotFound, + UkiError::NoFreeBootId => ErrorKind::OutOfMemory, UkiError::InvalidRequest => ErrorKind::InvalidEntry, UkiError::Unexpected => ErrorKind::Unexpected, } diff --git a/booters/uki/src/lib.rs b/booters/uki/src/lib.rs index e0bbe7e7..7d0ea2c3 100644 --- a/booters/uki/src/lib.rs +++ b/booters/uki/src/lib.rs @@ -6,7 +6,7 @@ use std::str::from_utf8; use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{Booter, CBootPluginRequest}; +use upac_abi::boot::{Booter, CBootPluginRequest, CBootSlotsRequest}; use upac_abi::error::ErrorKind; use upac_abi::types::{CBorrowed, CSlice}; @@ -83,6 +83,40 @@ pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_ou } } +/// # Safety +/// `request`, if non-null, must point to a valid, initialized `CBootSlotsRequest` for the +/// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn register_boot_slots(request: *const CBootSlotsRequest, err_out: *mut ErrorKind) -> i32 { + if request.is_null() { + write_error(err_out, UkiError::InvalidRequest); + return -1; + } + + let request = unsafe { &*request }; + + let result = slots_from_request(request).and_then(|(to_slot, from_slot)| { + Uki::new().and_then(|mut uki| { + uki.register_boot_slots( + request.esp_partition_number, + request.esp_starting_lba, + request.esp_ending_lba, + request.esp_unique_partition_guid, + &to_slot, + &from_slot, + ) + }) + }); + + match result { + Ok(()) => 0, + Err(error) => { + write_error(err_out, error); + -1 + } + } +} + fn entry_name_from_request(request: &CBootPluginRequest) -> Result { let bytes = unsafe { request.entry_name.as_borrowed() }; @@ -91,6 +125,20 @@ fn entry_name_from_request(request: &CBootPluginRequest) -> Result Result<(String, String), UkiError> { + let to_bytes = unsafe { request.to_slot.as_borrowed() }; + let from_bytes = unsafe { request.from_slot.as_borrowed() }; + + let to_slot = from_utf8(to_bytes) + .map(str::to_owned) + .map_err(|_| UkiError::InvalidRequest)?; + let from_slot = from_utf8(from_bytes) + .map(str::to_owned) + .map_err(|_| UkiError::InvalidRequest)?; + + Ok((to_slot, from_slot)) +} + fn write_error(err_out: *mut ErrorKind, error: UkiError) { if !err_out.is_null() { unsafe { *err_out = error.into() }; From 2e12931f8ceb0377cf5b65e978ea986a162b2743 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 04:22:31 +0400 Subject: [PATCH 021/148] fix: added a stub to all remaining booters Co-Authored-By: Claude Sonnet 5 --- booters/grub/src/backend.rs | 16 ++++++++++++++++ booters/grub/src/lib.rs | 9 ++++++++- booters/refind/src/backend.rs | 16 ++++++++++++++++ booters/systemd-boot/src/backend.rs | 16 ++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/booters/grub/src/backend.rs b/booters/grub/src/backend.rs index dc1ce59d..9e29b361 100644 --- a/booters/grub/src/backend.rs +++ b/booters/grub/src/backend.rs @@ -35,6 +35,22 @@ impl Booter for Grub { fn confirm_boot(&mut self, entry_name: &str) -> Result<(), GrubError> { self.run_first_available([SET_DEFAULT_BIN_PRIMARY, SET_DEFAULT_BIN_FALLBACK], entry_name) } + + fn register_boot_slots( + &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, + ) -> Result<(), GrubError> { + let _ = ( + esp_partition_number, + esp_starting_lba, + esp_ending_lba, + esp_unique_partition_guid, + to_slot, + from_slot, + ); + + Ok(()) + } } impl Grub { diff --git a/booters/grub/src/lib.rs b/booters/grub/src/lib.rs index 079b67ef..0bb7930d 100644 --- a/booters/grub/src/lib.rs +++ b/booters/grub/src/lib.rs @@ -6,7 +6,7 @@ use std::str::from_utf8; use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{Booter, CBootPluginRequest}; +use upac_abi::boot::{Booter, CBootPluginRequest, CBootSlotsRequest}; use upac_abi::error::ErrorKind; use upac_abi::types::{CBorrowed, CSlice}; @@ -83,6 +83,13 @@ pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_ou } } +/// # Safety +/// Touches no pointers — grub has no Boot#### entries to register, always succeeds. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, _err_out: *mut ErrorKind) -> i32 { + 0 +} + fn entry_name_from_request(request: &CBootPluginRequest) -> Result { let bytes = unsafe { request.entry_name.as_borrowed() }; diff --git a/booters/refind/src/backend.rs b/booters/refind/src/backend.rs index 6d3c13ea..3b8dd748 100644 --- a/booters/refind/src/backend.rs +++ b/booters/refind/src/backend.rs @@ -64,6 +64,22 @@ impl Booter for Refind { fn esp_loader_source() -> Option<&'static str> { Some(SOURCE) } + + fn register_boot_slots( + &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, + ) -> Result<(), RefindError> { + let _ = ( + esp_partition_number, + esp_starting_lba, + esp_ending_lba, + esp_unique_partition_guid, + to_slot, + from_slot, + ); + + Ok(()) + } } impl Refind { diff --git a/booters/systemd-boot/src/backend.rs b/booters/systemd-boot/src/backend.rs index 8b4a09bd..1b4750f5 100644 --- a/booters/systemd-boot/src/backend.rs +++ b/booters/systemd-boot/src/backend.rs @@ -66,6 +66,22 @@ impl Booter for Bls { fn esp_loader_source() -> Option<&'static str> { Some(SOURCE) } + + fn register_boot_slots( + &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, + ) -> Result<(), BlsError> { + let _ = ( + esp_partition_number, + esp_starting_lba, + esp_ending_lba, + esp_unique_partition_guid, + to_slot, + from_slot, + ); + + Ok(()) + } } impl Bls { From b0d0212279d7cd406f659169b723d8fa8254d951 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 04:25:01 +0400 Subject: [PATCH 022/148] fix: removed an extra exported function Co-Authored-By: Claude Sonnet 5 --- booters/grub/src/lib.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/booters/grub/src/lib.rs b/booters/grub/src/lib.rs index 0bb7930d..079b67ef 100644 --- a/booters/grub/src/lib.rs +++ b/booters/grub/src/lib.rs @@ -6,7 +6,7 @@ use std::str::from_utf8; use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{Booter, CBootPluginRequest, CBootSlotsRequest}; +use upac_abi::boot::{Booter, CBootPluginRequest}; use upac_abi::error::ErrorKind; use upac_abi::types::{CBorrowed, CSlice}; @@ -83,13 +83,6 @@ pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_ou } } -/// # Safety -/// Touches no pointers — grub has no Boot#### entries to register, always succeeds. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, _err_out: *mut ErrorKind) -> i32 { - 0 -} - fn entry_name_from_request(request: &CBootPluginRequest) -> Result { let bytes = unsafe { request.entry_name.as_borrowed() }; From e91cf950fcc1599aa57c5e76fb5cd459c8dceca1 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 04:38:42 +0400 Subject: [PATCH 023/148] fix: added changes for static linking in setup Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/plugin/boot/mod.rs | 58 ++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/lib/lib/src/plugin/boot/mod.rs b/lib/lib/src/plugin/boot/mod.rs index ca3b2512..22fd43f9 100644 --- a/lib/lib/src/plugin/boot/mod.rs +++ b/lib/lib/src/plugin/boot/mod.rs @@ -5,7 +5,9 @@ use std::mem::MaybeUninit; -use upac_abi::boot::{CBootPluginRequest, ConfirmBootFn, EspLoaderSourceFn, ProbeFn, SetOneShotFn}; +use upac_abi::boot::{ + CBootPluginRequest, CBootSlotsRequest, ConfirmBootFn, EspLoaderSourceFn, ProbeFn, RegisterBootSlotsFn, SetOneShotFn, +}; use upac_abi::error::ErrorKind; use upac_abi::types::{CBorrowed, CSlice}; @@ -26,25 +28,26 @@ use crate::plugin::boot::manifest::load_boot_plugin_manifests; #[cfg(feature = "builtin-grub")] use upac_boot_grub::{ confirm_boot as grub_confirm_boot, esp_loader_source as grub_esp_loader_source, probe as grub_probe, - set_one_shot as grub_set_one_shot, + register_boot_slots as grub_register_boot_slots, set_one_shot as grub_set_one_shot, }; #[cfg(feature = "builtin-systemd-boot")] use upac_boot_systemd_boot::{ confirm_boot as systemd_boot_confirm_boot, esp_loader_source as systemd_boot_esp_loader_source, - probe as systemd_boot_probe, set_one_shot as systemd_boot_set_one_shot, + probe as systemd_boot_probe, register_boot_slots as systemd_boot_register_boot_slots, + set_one_shot as systemd_boot_set_one_shot, }; #[cfg(feature = "builtin-uki")] use upac_boot_uki::{ confirm_boot as uki_confirm_boot, esp_loader_source as uki_esp_loader_source, probe as uki_probe, - set_one_shot as uki_set_one_shot, + register_boot_slots as uki_register_boot_slots, set_one_shot as uki_set_one_shot, }; #[cfg(feature = "builtin-refind")] use upac_boot_refind::{ confirm_boot as refind_confirm_boot, esp_loader_source as refind_esp_loader_source, probe as refind_probe, - set_one_shot as refind_set_one_shot, + register_boot_slots as refind_register_boot_slots, set_one_shot as refind_set_one_shot, }; pub mod error; @@ -56,12 +59,14 @@ pub mod manifest; impl BootPlugin { fn from_static( probe: ProbeFn, set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, esp_loader_source: EspLoaderSourceFn, + register_boot_slots: RegisterBootSlotsFn, ) -> Self { BootPlugin { probe, set_one_shot, confirm_boot, esp_loader_source, + register_boot_slots, #[cfg(feature = "dynamic-plugins")] _library: None, @@ -178,7 +183,13 @@ fn static_plugins() -> Vec<(&'static str, BootPlugin)> { #[cfg(feature = "builtin-uki")] plugins.push(( "uki", - BootPlugin::from_static(uki_probe, uki_set_one_shot, uki_confirm_boot, uki_esp_loader_source), + BootPlugin::from_static( + uki_probe, + uki_set_one_shot, + uki_confirm_boot, + uki_esp_loader_source, + uki_register_boot_slots, + ), )); #[cfg(feature = "builtin-systemd-boot")] @@ -189,13 +200,20 @@ fn static_plugins() -> Vec<(&'static str, BootPlugin)> { systemd_boot_set_one_shot, systemd_boot_confirm_boot, systemd_boot_esp_loader_source, + systemd_boot_register_boot_slots, ), )); #[cfg(feature = "builtin-grub")] plugins.push(( "grub", - BootPlugin::from_static(grub_probe, grub_set_one_shot, grub_confirm_boot, grub_esp_loader_source), + BootPlugin::from_static( + grub_probe, + grub_set_one_shot, + grub_confirm_boot, + grub_esp_loader_source, + grub_register_boot_slots, + ), )); #[cfg(feature = "builtin-refind")] @@ -206,6 +224,7 @@ fn static_plugins() -> Vec<(&'static str, BootPlugin)> { refind_set_one_shot, refind_confirm_boot, refind_esp_loader_source, + refind_register_boot_slots, ), )); @@ -224,6 +243,7 @@ pub struct BootPlugin { set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, esp_loader_source: EspLoaderSourceFn, + register_boot_slots: RegisterBootSlotsFn, #[cfg(feature = "dynamic-plugins")] _library: Option, @@ -239,6 +259,7 @@ impl BootPlugin { let set_one_shot: SetOneShotFn = unsafe { load_symbol(&library, "set_one_shot")? }; let confirm_boot: ConfirmBootFn = unsafe { load_symbol(&library, "confirm_boot")? }; let esp_loader_source: EspLoaderSourceFn = unsafe { load_symbol(&library, "esp_loader_source")? }; + let register_boot_slots: RegisterBootSlotsFn = unsafe { load_symbol(&library, "register_boot_slots")? }; let got = unsafe { abi_version() }; if got != BOOT_ABI_VERSION { @@ -253,6 +274,7 @@ impl BootPlugin { set_one_shot, confirm_boot, esp_loader_source, + register_boot_slots, _library: Some(library), }) } @@ -292,4 +314,26 @@ impl BootPlugin { Option::<&str>::try_from(&slice).ok().flatten().map(str::to_owned) } + + pub fn register_boot_slots( + &self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, + ) -> Result<(), BootPluginError> { + let request = CBootSlotsRequest::new( + esp_partition_number, + esp_starting_lba, + esp_ending_lba, + esp_unique_partition_guid, + CSlice::from_borrowed(to_slot.as_bytes()), + CSlice::from_borrowed(from_slot.as_bytes()), + ); + let mut error = MaybeUninit::::uninit(); + + let code = unsafe { (self.register_boot_slots)(&request, error.as_mut_ptr()) }; + if code != 0 { + return Err(BootPluginError::Reported(unsafe { error.assume_init() })); + } + + Ok(()) + } } From 97495d0071bdc591462ad2d6773f523fe505291a Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 04:39:06 +0400 Subject: [PATCH 024/148] fix: restored symbols for booters Co-Authored-By: Claude Sonnet 5 --- booters/grub/src/lib.rs | 9 ++++++++- booters/refind/src/lib.rs | 9 ++++++++- booters/systemd-boot/src/lib.rs | 9 ++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/booters/grub/src/lib.rs b/booters/grub/src/lib.rs index 079b67ef..0bb7930d 100644 --- a/booters/grub/src/lib.rs +++ b/booters/grub/src/lib.rs @@ -6,7 +6,7 @@ use std::str::from_utf8; use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{Booter, CBootPluginRequest}; +use upac_abi::boot::{Booter, CBootPluginRequest, CBootSlotsRequest}; use upac_abi::error::ErrorKind; use upac_abi::types::{CBorrowed, CSlice}; @@ -83,6 +83,13 @@ pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_ou } } +/// # Safety +/// Touches no pointers — grub has no Boot#### entries to register, always succeeds. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, _err_out: *mut ErrorKind) -> i32 { + 0 +} + fn entry_name_from_request(request: &CBootPluginRequest) -> Result { let bytes = unsafe { request.entry_name.as_borrowed() }; diff --git a/booters/refind/src/lib.rs b/booters/refind/src/lib.rs index 623a6334..fcf0f5cd 100644 --- a/booters/refind/src/lib.rs +++ b/booters/refind/src/lib.rs @@ -6,7 +6,7 @@ use std::str::from_utf8; use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{Booter, CBootPluginRequest}; +use upac_abi::boot::{Booter, CBootPluginRequest, CBootSlotsRequest}; use upac_abi::error::ErrorKind; use upac_abi::types::{CBorrowed, CSlice}; @@ -83,6 +83,13 @@ pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_ou } } +/// # Safety +/// Touches no pointers — grub has no Boot#### entries to register, always succeeds. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, _err_out: *mut ErrorKind) -> i32 { + 0 +} + fn entry_name_from_request(request: &CBootPluginRequest) -> Result { let bytes = unsafe { request.entry_name.as_borrowed() }; diff --git a/booters/systemd-boot/src/lib.rs b/booters/systemd-boot/src/lib.rs index 385375b0..77f219d3 100644 --- a/booters/systemd-boot/src/lib.rs +++ b/booters/systemd-boot/src/lib.rs @@ -6,7 +6,7 @@ use std::str::from_utf8; use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{Booter, CBootPluginRequest}; +use upac_abi::boot::{Booter, CBootPluginRequest, CBootSlotsRequest}; use upac_abi::error::ErrorKind; use upac_abi::types::{CBorrowed, CSlice}; @@ -83,6 +83,13 @@ pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_ou } } +/// # Safety +/// Touches no pointers — grub has no Boot#### entries to register, always succeeds. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, _err_out: *mut ErrorKind) -> i32 { + 0 +} + fn entry_name_from_request(request: &CBootPluginRequest) -> Result { let bytes = unsafe { request.entry_name.as_borrowed() }; From 5276486f3e25a3c173df614124590afeaf1ac85f Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 20:57:44 +0400 Subject: [PATCH 025/148] fix: fixed description for SAFTY section Co-Authored-By: Claude Sonnet 5 --- booters/refind/src/lib.rs | 2 +- booters/systemd-boot/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/booters/refind/src/lib.rs b/booters/refind/src/lib.rs index fcf0f5cd..3022c9ea 100644 --- a/booters/refind/src/lib.rs +++ b/booters/refind/src/lib.rs @@ -84,7 +84,7 @@ pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_ou } /// # Safety -/// Touches no pointers — grub has no Boot#### entries to register, always succeeds. +/// Touches no pointers — rEFInd has no Boot#### entries to register, always succeeds. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, _err_out: *mut ErrorKind) -> i32 { 0 diff --git a/booters/systemd-boot/src/lib.rs b/booters/systemd-boot/src/lib.rs index 77f219d3..fe0b4a8d 100644 --- a/booters/systemd-boot/src/lib.rs +++ b/booters/systemd-boot/src/lib.rs @@ -84,7 +84,7 @@ pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_ou } /// # Safety -/// Touches no pointers — grub has no Boot#### entries to register, always succeeds. +/// Touches no pointers — systemd-boot has no Boot#### entries to register, always succeeds. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, _err_out: *mut ErrorKind) -> i32 { 0 From 45dbf3ef4125493d40c588f8ac818e230b818c10 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 20:58:10 +0400 Subject: [PATCH 026/148] fix: added generation of everything required for the UKI format to work Co-Authored-By: Claude Sonnet 5 --- lib/setup/lib.toml | 16 ++++++++++++- lib/setup/src/genesis/entry.rs | 39 ++++++++++++++++++++++++++++-- lib/setup/src/genesis/mod.rs | 4 ++++ lib/setup/src/partition.rs | 6 ++--- lib/setup/src/target.rs | 43 +++++++++++++++++++++++++++++++--- 5 files changed, 99 insertions(+), 9 deletions(-) diff --git a/lib/setup/lib.toml b/lib/setup/lib.toml index c1ee9bac..1edb62df 100644 --- a/lib/setup/lib.toml +++ b/lib/setup/lib.toml @@ -22,10 +22,16 @@ wipefs_bin = "wipefs" # partition.settle_attempts/settle_interval_ms bound how long DiskLayout::create waits for the # kernel (via udev) to create the new partition device nodes after BLKRRPART, before giving up — # the ioctl itself is synchronous, but the /dev/vdaN nodes it triggers are created by udev -# asynchronously afterward. +# asynchronously afterward. esp_label/deploy_label name both the GPT partition entry itself +# (DiskLayout::create) and the filesystem volume label written at format time +# (TargetSysroot::create_whole_disk) — purely informational (`lsblk -f`/`blkid`/other OS +# installers), nothing in this codebase looks a partition up by either. Uppercase by the same +# convention real-world ESP volume labels always use. [partition] settle_attempts = 50 settle_interval_ms = 100 +esp_label = "ESP" +deploy_label = "UPAC-DEPLOY" # genesis.scratch_filename is where the freshly-built package database is # written (under the OS temp dir) before being embedded into the new /usr @@ -35,6 +41,14 @@ settle_interval_ms = 100 # has no Boot#### entries at all, which is exactly genesis's situation on a brand-new disk. The # resolved boot plugin's own `esp_loader_source()` (see `booters/booter.toml`) says which source # path — if any — a fresh ESP needs it copied from; genesis only owns the destination. +# efi_linux_dir is a real, mounted-filesystem-relative path (forward slashes, joined via +# `Path::join`) to where `composefs-boot`'s own UKI writer always lands `upac-to.efi`/ +# `upac-from.efi` (`EFI/Linux/`, hardcoded upstream) — used only to seed `upac-from.efi` with the +# same first image on genesis's very first deploy. Distinct from `booters/booter.toml`'s +# `uki.efi_linux_dir`, which is the same directory spelled as a backslash UEFI device-path string +# for NVRAM Boot#### entries, not a filesystem path — the two can't share a definition across the +# crate boundary. [genesis] scratch_filename = "genesis-packages.redb" esp_fallback_loader = "EFI/BOOT/BOOTX64.EFI" +efi_linux_dir = "EFI/Linux" diff --git a/lib/setup/src/genesis/entry.rs b/lib/setup/src/genesis/entry.rs index b1a317e6..c726405c 100644 --- a/lib/setup/src/genesis/entry.rs +++ b/lib/setup/src/genesis/entry.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::fs::{File, create_dir_all, write}; +use std::fs::{File, copy, create_dir_all, write}; use std::io::Read; use composefs::erofs::reader::erofs_to_filesystem; @@ -14,6 +14,7 @@ use composefs::tree::FileSystem; use upac::boot::write_boot_entry; use upac::composefs::file::FileHandle; use upac::composefs::repository::ObjectID; +use upac::layout::boot::{UPAC_UKI_FROM_SLOT, UPAC_UKI_TO_SLOT}; use upac::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; use upac::orchestrator::Context; use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; @@ -24,7 +25,7 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; use super::ctx_get; use crate::error::SetupError; -use crate::layout::genesis::ESP_FALLBACK_LOADER; +use crate::layout::genesis::{EFI_LINUX_DIR, ESP_FALLBACK_LOADER}; use crate::target::TargetSysroot; use crate::types::{GenesisInput, PrefixDigest}; @@ -67,6 +68,40 @@ impl Stage for StageBootStage { &prefix_digest_hex, )?; + // UKI-direct: genesis is the very first deploy, so `upac-from.efi` (the fallback slot) + // has nothing real to hold yet — seed it with the exact same image `write_boot_entry` just + // wrote to `upac-to.efi`. `up install`/`update` never do this: only genesis creates both + // files and both Boot#### entries; every later deploy only ever touches `upac-to.efi`. + if entry_name == UPAC_UKI_TO_SLOT { + let efi_linux = target.esp_mount_point().join(EFI_LINUX_DIR); + let to_path = efi_linux.join(format!("{UPAC_UKI_TO_SLOT}.efi")); + let from_path = efi_linux.join(format!("{UPAC_UKI_FROM_SLOT}.efi")); + copy(&to_path, &from_path)?; + + let geometry = ( + target.esp_partition_number(), + target.esp_starting_lba(), + target.esp_ending_lba(), + target.esp_unique_partition_guid(), + ); + + // Manual mode (pre-existing partitions) has nowhere to read GPT geometry back from — + // registering the two Boot#### entries is skipped there, same as the existing + // "pre-registered once, out of scope of this pipeline" assumption everywhere else. + if let (Some(partition_number), Some(starting_lba), Some(ending_lba), Some(unique_partition_guid)) = + geometry + { + plugin.register_boot_slots( + partition_number, + starting_lba, + ending_lba, + unique_partition_guid.to_bytes_le(), + UPAC_UKI_TO_SLOT, + UPAC_UKI_FROM_SLOT, + )?; + } + } + plugin.set_one_shot(&entry_name)?; Ok((progress, StageResult::Advance, Box::new(NoRollback))) diff --git a/lib/setup/src/genesis/mod.rs b/lib/setup/src/genesis/mod.rs index 969f4194..9e631831 100644 --- a/lib/setup/src/genesis/mod.rs +++ b/lib/setup/src/genesis/mod.rs @@ -87,6 +87,10 @@ impl SetupExistingData<'_> { Path::new(self.esp_device), PathBuf::from(self.mount_point()), &self.extra_mounts, + None, + None, + None, + None, ) .map_err(|error| (GenesisStage::Setup, error))?; diff --git a/lib/setup/src/partition.rs b/lib/setup/src/partition.rs index 38252fc6..bf406ec4 100644 --- a/lib/setup/src/partition.rs +++ b/lib/setup/src/partition.rs @@ -17,7 +17,7 @@ use upac_types::PartitionSpec; use crate::error::SetupError; use crate::format::FormatTarget; -use crate::layout::partition::{SETTLE_ATTEMPTS, SETTLE_INTERVAL_MS}; +use crate::layout::partition::{DEPLOY_LABEL, ESP_LABEL, SETTLE_ATTEMPTS, SETTLE_INTERVAL_MS}; #[cfg(test)] #[path = "../tests/inline/partition.rs"] @@ -106,7 +106,7 @@ impl DiskLayout { let esp_entry = gpt.insert_partition( esp_partition, ESP_PARTITION_TYPE_GUID, - "ESP", + ESP_LABEL, mib_to_sectors!(esp_size_mib, sector_size), )?; next_number += 1; @@ -115,7 +115,7 @@ impl DiskLayout { gpt.insert_partition( deploy_partition, LINUX_ROOT_X86_64_GUID, - "upac-deploy", + DEPLOY_LABEL, mib_to_sectors!(deploy_size_mib, sector_size), )?; next_number += 1; diff --git a/lib/setup/src/target.rs b/lib/setup/src/target.rs index 07b05a14..799a764c 100644 --- a/lib/setup/src/target.rs +++ b/lib/setup/src/target.rs @@ -17,11 +17,14 @@ use upac::layout::deployment::{DEPLOYS_DIR, NEXT_SEQ_PATH, REPO_DIR}; use upac_abi::FsKind; +use uuid::Uuid; + use upac_types::PartitionMount; use crate::data::SetupWholeDiskData; use crate::error::SetupError; use crate::format::FormatTarget; +use crate::layout::partition::{DEPLOY_LABEL, ESP_LABEL}; use crate::partition::DiskLayout; pub struct TargetSysroot { @@ -29,12 +32,17 @@ pub struct TargetSysroot { deploy_dir: PathBuf, repository: ManuallyDrop>, mounted: Vec, + esp_partition_number: Option, + esp_starting_lba: Option, + esp_ending_lba: Option, + esp_unique_partition_guid: Option, } impl TargetSysroot { pub fn new( deploy_device: &Path, deploy_fs: FsKind, esp_device: &Path, mount_point: PathBuf, - extra_mounts: &[PartitionMount], + extra_mounts: &[PartitionMount], esp_partition_number: Option, esp_starting_lba: Option, + esp_ending_lba: Option, esp_unique_partition_guid: Option, ) -> Result { create_dir_all(&mount_point)?; @@ -84,6 +92,10 @@ impl TargetSysroot { deploy_dir, repository: ManuallyDrop::new(repository), mounted, + esp_partition_number, + esp_starting_lba, + esp_ending_lba, + esp_unique_partition_guid, }) } @@ -97,16 +109,17 @@ impl TargetSysroot { )?; let esp_path = layout.esp_path(); + FormatTarget { device_path: &esp_path, - label: Some("ESP"), + label: Some(ESP_LABEL), } .format_esp()?; let deploy_path = layout.deploy_path(); FormatTarget { device_path: &deploy_path, - label: Some("upac-deploy"), + label: Some(DEPLOY_LABEL), } .format(data.deploy_fs, data.node_size, data.sector_size, data.force_wipe)?; @@ -133,6 +146,10 @@ impl TargetSysroot { &esp_path, PathBuf::from(data.mount_point()), &extra_mounts, + Some(layout.esp_partition_number()), + Some(layout.esp_starting_lba()), + Some(layout.esp_ending_lba()), + Some(layout.esp_unique_partition_guid()), ) } @@ -151,6 +168,22 @@ impl TargetSysroot { pub fn esp_mount_point(&self) -> PathBuf { self.mount_point.join(ESP_MOUNT_PRIMARY.trim_start_matches('/')) } + + pub fn esp_partition_number(&self) -> Option { + self.esp_partition_number + } + + pub fn esp_starting_lba(&self) -> Option { + self.esp_starting_lba + } + + pub fn esp_ending_lba(&self) -> Option { + self.esp_ending_lba + } + + pub fn esp_unique_partition_guid(&self) -> Option { + self.esp_unique_partition_guid + } } #[cfg(test)] @@ -168,6 +201,10 @@ impl TargetSysroot { deploy_dir, repository: ManuallyDrop::new(repository), mounted: Vec::new(), + esp_partition_number: None, + esp_starting_lba: None, + esp_ending_lba: None, + esp_unique_partition_guid: None, }) } } From 7588ca69717d811bc5705a38855af2c9dd9ebd2a Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 20:58:18 +0400 Subject: [PATCH 027/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/TODO.md b/TODO.md index 2fa32a5d..ca663574 100644 --- a/TODO.md +++ b/TODO.md @@ -17,27 +17,26 @@ Test-coverage pass in progress, going file by file through the non-command core `plugin/decoder/{error,unpack,mod}.rs`, `plugin/boot/{error,manifest,mod}.rs`, `composefs/{diff,error,mod}.rs`, `config/mod.rs`, `boot/{error,mod}.rs`. -`boot/mod.rs`'s UKI staging only ever writes the single fixed `upac-to` slot -(`layout::boot::UPAC_TO_SLOT`) — doc chapter 3's disk-layout map still describes a two-slot -`upac-from.efi`/`upac-to.efi` A/B scheme, but no code anywhere writes/reads an `upac-from` slot. -Real unfinished A/B swap, not just a stale doc — needs a decision (implement the second slot, or -formally drop it and fix the doc to match the single-slot design `lib.toml`'s own comment already -argues for). - -**Genesis-produced disks don't actually boot into the installed system yet** — found via a live -QEMU/OVMF test (systemd-boot now starts, finds the BLS entry, loads kernel+initramfs): -1. Still open: a plain partition mount isn't how composefs systems boot — nothing in this project - resolves `composefs.digest=` (the kernel cmdline param `write_boot_entry` already writes) - against the on-disk repository, mounts the erofs image with fs-verity, and overlays - `state/deploy//etc/`. **Found a real, existing upstream tool for exactly this**: - `composefs-setup-root` (crates.io, same `composefs-rs` project/version as our `composefs`/ - `composefs-boot` deps) — a Rust binary, not something we'd write ourselves. Our on-disk layout - already matches its hardcoded expectations (`composefs/`, `state/deploy//`) after - renaming `etc-upper` → `etc` (done, `lib.toml`'s `config_dir_name`). What's still missing: the - actual boot-time integration — the live VM's initramfs is systemd-based (mkinitcpio's `systemd` - hook, not classic busybox-style hooks), so this needs a systemd unit ordered between - `sysroot.mount` and `initrd-switch-root.target` (same role as ostree's - `ostree-prepare-root.service`), not a classic mkinitcpio hook script. Also unresolved: whether - upac needs to ship/package this integration itself, or whether it's expected to already exist - on the source distro (same assumption as the systemd-boot/rEFInd binary copy above) — needs - checking whether Arch/AUR already has a package for this. +**UKI A/B boot (`upac-from.efi`/`upac-to.efi`) confirm-boot service not designed yet**: after a +successful boot, something needs to confirm once, swap `to`↔`from`, and set the normal persistent +boot order. Nothing calls `Booter::confirm_boot` anywhere yet; this belongs to a not-yet-designed +"confirm boot" systemd service, not genesis or the ordinary install/update pipeline. + +**grub genesis support still not handled**: unlike systemd-boot/rEFInd (binary-copy via +`esp_loader_source`), grub needs a real `grub-install`-equivalent (target-specific generated +`grubx64.efi`, not a plain file copy) — out of scope for now; either shell out to `grub-install` +against the mounted ESP, or explicitly document grub as unsupported for genesis whole-disk mode. + +**Genesis-produced disks don't actually boot into the installed system yet**: a plain partition +mount isn't how composefs systems boot — nothing in this project resolves `composefs.digest=` +(the kernel cmdline param `write_boot_entry` already writes) against the on-disk repository, mounts +the erofs image with fs-verity, and overlays `state/deploy//etc/`. **Found a real, existing +upstream tool for exactly this**: `composefs-setup-root` (crates.io, same `composefs-rs` +project/version as our `composefs`/`composefs-boot` deps) — a Rust binary, not something we'd write +ourselves. What's still missing: the actual boot-time integration — the live VM's initramfs is +systemd-based (mkinitcpio's `systemd` hook, not classic busybox-style hooks), so this needs a +systemd unit ordered between `sysroot.mount` and `initrd-switch-root.target` (same role as ostree's +`ostree-prepare-root.service`), not a classic mkinitcpio hook script. Also unresolved: whether upac +needs to ship/package this integration itself, or whether it's expected to already exist on the +source distro (same assumption as the systemd-boot/rEFInd binary copy above) — needs checking +whether Arch/AUR already has a package for this. From 66149976131acec886b31b507b4ce735dd1bf240 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 21:12:37 +0400 Subject: [PATCH 028/148] fix: fixed test placement Co-Authored-By: Claude Sonnet 5 --- lib/setup/src/target.rs | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/setup/src/target.rs b/lib/setup/src/target.rs index 799a764c..f10e526f 100644 --- a/lib/setup/src/target.rs +++ b/lib/setup/src/target.rs @@ -39,6 +39,10 @@ pub struct TargetSysroot { } impl TargetSysroot { + #[allow( + clippy::too_many_arguments, + reason = "flat ESP-geometry params by design, not grouped into a struct — see partition::DiskLayout" + )] pub fn new( deploy_device: &Path, deploy_fs: FsKind, esp_device: &Path, mount_point: PathBuf, extra_mounts: &[PartitionMount], esp_partition_number: Option, esp_starting_lba: Option, @@ -186,6 +190,24 @@ impl TargetSysroot { } } +impl Drop for TargetSysroot { + fn drop(&mut self) { + // SAFETY: `self` is being dropped and `repository` is never accessed again. + unsafe { ManuallyDrop::drop(&mut self.repository) }; + + let Some((base, nested)) = self.mounted.split_first() else { + return; + }; + + for mount_point in nested.iter().rev() { + let _ = umount(mount_point); + let _ = remove_dir(mount_point); + } + + let _ = umount(base); + } +} + #[cfg(test)] impl TargetSysroot { pub(crate) fn for_testing(mount_point: PathBuf) -> Result { @@ -208,21 +230,3 @@ impl TargetSysroot { }) } } - -impl Drop for TargetSysroot { - fn drop(&mut self) { - // SAFETY: `self` is being dropped and `repository` is never accessed again. - unsafe { ManuallyDrop::drop(&mut self.repository) }; - - let Some((base, nested)) = self.mounted.split_first() else { - return; - }; - - for mount_point in nested.iter().rev() { - let _ = umount(mount_point); - let _ = remove_dir(mount_point); - } - - let _ = umount(base); - } -} From ef3be2a8672e81f7368cbe401cdb6fb67c82ccc9 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 21:24:40 +0400 Subject: [PATCH 029/148] fix: add more tests Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/deploy/mod.rs | 15 +++++ lib/lib/tests/deploy_error.rs | 79 ++++++++++++++++++++++++ lib/lib/tests/inline/deploy.rs | 86 ++++++++++++++++++++++++++ lib/lib/tests/plugin_boot_error.rs | 56 +++++++++++++++++ lib/lib/tests/plugin_boot_manifest.rs | 89 +++++++++++++++++++++++++++ lib/lib/tests/plugin_decoder.rs | 9 +++ lib/lib/tests/plugin_decoder_error.rs | 64 +++++++++++++++++++ lib/lib/tests/scripts_error.rs | 66 ++++++++++++++++++++ lib/lib/tests/scripts_hook.rs | 77 +++++++++++++++++++++++ 9 files changed, 541 insertions(+) create mode 100644 lib/lib/tests/deploy_error.rs create mode 100644 lib/lib/tests/inline/deploy.rs create mode 100644 lib/lib/tests/plugin_boot_error.rs create mode 100644 lib/lib/tests/plugin_boot_manifest.rs create mode 100644 lib/lib/tests/plugin_decoder_error.rs create mode 100644 lib/lib/tests/scripts_error.rs diff --git a/lib/lib/src/deploy/mod.rs b/lib/lib/src/deploy/mod.rs index d4df4582..0ce96831 100644 --- a/lib/lib/src/deploy/mod.rs +++ b/lib/lib/src/deploy/mod.rs @@ -36,6 +36,10 @@ pub mod error; pub mod esp; pub mod retention; +#[cfg(test)] +#[path = "../../tests/inline/deploy.rs"] +mod tests; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DeployMode { ReadOnly, @@ -214,3 +218,14 @@ impl Drop for Deploy { let _ = remove_dir(&self.sysroot); } } + +#[cfg(test)] +impl Deploy { + pub(crate) fn for_testing(deploy_dir: PathBuf) -> Self { + Deploy { + sysroot: deploy_dir.clone(), + deploy: deploy_dir, + repo: PathBuf::new(), + } + } +} diff --git a/lib/lib/tests/deploy_error.rs b/lib/lib/tests/deploy_error.rs new file mode 100644 index 00000000..47c2f513 --- /dev/null +++ b/lib/lib/tests/deploy_error.rs @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; + +use anyhow::anyhow; + +use nix::errno::Errno; + +use rsblkid::probe::{ProbeBuilderError, ProbeError}; + +use rsmount::errors::MountInfoError; + +use upac::deploy::error::SysrootError; + +use upac_abi::error::ErrorKind; + +#[test] +fn mount_info_error_maps_to_mount_info_unavailable() { + let error = MountInfoError::Creation("boom".to_owned()); + + assert_eq!(SysrootError::from(error), SysrootError::MountInfoUnavailable); +} + +#[test] +fn probe_builder_error_maps_to_probe_unavailable() { + let error = ProbeBuilderError::Required("scan_device".to_owned()); + + assert_eq!(SysrootError::from(error), SysrootError::ProbeUnavailable); +} + +#[test] +fn probe_error_maps_to_probe_unavailable() { + let error = ProbeError::Config("bad config".to_owned()); + + assert_eq!(SysrootError::from(error), SysrootError::ProbeUnavailable); +} + +#[test] +fn io_error_maps_to_sysroot_dir_unavailable() { + let error = IoError::new(IoErrorKind::PermissionDenied, "denied"); + + assert_eq!(SysrootError::from(error), SysrootError::SysrootDirUnavailable); +} + +#[test] +fn errno_maps_to_the_system_variant_with_the_same_errno() { + assert_eq!(SysrootError::from(Errno::ENOSPC), SysrootError::System(Errno::ENOSPC)); +} + +#[test] +fn anyhow_error_maps_to_current_prefix_digest_not_found() { + let error = anyhow!("no current prefix digest"); + + assert_eq!(SysrootError::from(error), SysrootError::CurrentPrefixDigestNotFound); +} + +#[test] +fn every_variant_maps_to_the_documented_error_kind() { + let cases = [ + (SysrootError::MountInfoUnavailable, ErrorKind::Unexpected), + (SysrootError::RootDeviceNotFound, ErrorKind::NotFound), + (SysrootError::CanonicalDeviceNotFound, ErrorKind::NotFound), + (SysrootError::SysrootDirUnavailable, ErrorKind::NotFound), + (SysrootError::DeploysDirNotFound, ErrorKind::NotFound), + (SysrootError::RepoDirNotFound, ErrorKind::NotFound), + (SysrootError::ProbeUnavailable, ErrorKind::Unexpected), + (SysrootError::FilesystemTypeNotFound, ErrorKind::NotFound), + (SysrootError::CurrentPrefixDigestNotFound, ErrorKind::NotFound), + (SysrootError::EspNotFound, ErrorKind::NotFound), + (SysrootError::System(Errno::EIO), ErrorKind::Unexpected), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/inline/deploy.rs b/lib/lib/tests/inline/deploy.rs new file mode 100644 index 00000000..9c0134b6 --- /dev/null +++ b/lib/lib/tests/inline/deploy.rs @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::fs::create_dir_all; +use std::path::Path; + +use tempfile::TempDir; + +use crate::database::record::DeployRecord; + +use super::Deploy; + +fn write_record(deploy_dir: &Path, digest: &str, seq: u64, pinned: bool) { + let record_dir = deploy_dir.join(digest); + create_dir_all(&record_dir).unwrap(); + + let record = DeployRecord { + prefix_digest: digest.to_owned(), + subject: "test".to_owned(), + message: None, + seq, + timestamp: DeployRecord::now_secs(), + config_history: Vec::new(), + working_config: String::new(), + pinned, + }; + record.write(&record_dir).unwrap(); +} + +#[test] +fn prune_deploys_removes_nothing_when_total_is_within_retention_depth() { + let scratch = TempDir::new().unwrap(); + write_record(scratch.path(), "digest-0", 0, false); + write_record(scratch.path(), "digest-1", 1, false); + write_record(scratch.path(), "digest-2", 2, false); + + let deploy = Deploy::for_testing(scratch.path().to_path_buf()); + let removed = deploy.prune_deploys().unwrap(); + + assert!(removed.is_empty()); + assert!(scratch.path().join("digest-0").is_dir()); + assert!(scratch.path().join("digest-1").is_dir()); + assert!(scratch.path().join("digest-2").is_dir()); +} + +#[test] +fn prune_deploys_never_removes_a_pinned_deploy_regardless_of_age() { + let scratch = TempDir::new().unwrap(); + + // Oldest of the bunch, would be beyond any realistic retention depth on its own — + // `pinned: true` must save it anyway. + write_record(scratch.path(), "digest-oldest-pinned", 0, true); + + for seq in 1..=6 { + write_record(scratch.path(), &format!("digest-{seq}"), seq, false); + } + + let deploy = Deploy::for_testing(scratch.path().to_path_buf()); + let removed = deploy.prune_deploys().unwrap(); + + assert!(!removed.contains(&"digest-oldest-pinned".to_owned())); + assert!(scratch.path().join("digest-oldest-pinned").is_dir()); +} + +#[test] +fn prune_deploys_removes_the_oldest_unpinned_deploy_when_the_total_is_large() { + let scratch = TempDir::new().unwrap(); + + // Comfortably more entries than any sane retention depth would keep, so the single oldest, + // unpinned deploy is guaranteed to fall outside it regardless of the real (environment-read) + // `RuntimeSettings::load().gc.retention_depth` value. + for seq in 0..64 { + write_record(scratch.path(), &format!("digest-{seq}"), seq, false); + } + + let deploy = Deploy::for_testing(scratch.path().to_path_buf()); + let removed = deploy.prune_deploys().unwrap(); + + assert!(removed.contains(&"digest-0".to_owned())); + assert!(!scratch.path().join("digest-0").is_dir()); + + // The most recent one is always within any positive retention depth. + assert!(scratch.path().join("digest-63").is_dir()); +} diff --git a/lib/lib/tests/plugin_boot_error.rs b/lib/lib/tests/plugin_boot_error.rs new file mode 100644 index 00000000..6a931ac4 --- /dev/null +++ b/lib/lib/tests/plugin_boot_error.rs @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; + +use upac::plugin::boot::error::BootPluginError; + +use upac_abi::error::ErrorKind; + +#[test] +fn io_error_maps_to_io_with_the_same_kind() { + let error = IoError::new(IoErrorKind::PermissionDenied, "denied"); + + assert_eq!( + BootPluginError::from(error), + BootPluginError::Io(IoErrorKind::PermissionDenied) + ); +} + +#[test] +fn toml_error_maps_to_manifest() { + let error = toml::from_str::("not valid toml [[[").unwrap_err(); + + assert_eq!(BootPluginError::from(error), BootPluginError::Manifest); +} + +#[test] +fn every_variant_maps_to_the_documented_error_kind() { + let cases = [ + (BootPluginError::Load, ErrorKind::NotFound), + (BootPluginError::Symbol, ErrorKind::AbiMismatch), + ( + BootPluginError::AbiMismatch { got: 1, expected: 2 }, + ErrorKind::AbiMismatch, + ), + ( + BootPluginError::Reported(ErrorKind::PermissionDenied), + ErrorKind::PermissionDenied, + ), + (BootPluginError::Io(IoErrorKind::NotFound), ErrorKind::ReadFailed), + (BootPluginError::Manifest, ErrorKind::InvalidEntry), + ( + BootPluginError::DuplicateName("uki".to_owned()), + ErrorKind::InvalidEntry, + ), + (BootPluginError::UnknownName("uki".to_owned()), ErrorKind::NotFound), + (BootPluginError::NoClaimant, ErrorKind::NotFound), + (BootPluginError::AmbiguousClaim, ErrorKind::InvalidEntry), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/plugin_boot_manifest.rs b/lib/lib/tests/plugin_boot_manifest.rs new file mode 100644 index 00000000..e661da30 --- /dev/null +++ b/lib/lib/tests/plugin_boot_manifest.rs @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::fs::write; + +use tempfile::{Builder, TempDir}; + +use upac::plugin::boot::error::BootPluginError; +use upac::plugin::boot::manifest::load_boot_plugin_manifests; + +fn scratch_dir(name: &str) -> TempDir { + Builder::new().prefix(name).tempdir().unwrap() +} + +#[test] +fn load_boot_plugin_manifests_collects_distinct_names() { + let dir = scratch_dir("distinct-names"); + write( + dir.path().join("uki.boot"), + "name = \"uki\"\nlibrary = \"libupac-uki.so\"\n", + ) + .unwrap(); + write( + dir.path().join("grub.boot"), + "name = \"grub\"\nlibrary = \"libupac-grub.so\"\n", + ) + .unwrap(); + + let manifests = load_boot_plugin_manifests(dir.path().to_str().unwrap(), "boot").unwrap(); + + assert_eq!(manifests.len(), 2); + assert_eq!(manifests["uki"].library, "libupac-uki.so"); + assert_eq!(manifests["grub"].library, "libupac-grub.so"); +} + +#[test] +fn load_boot_plugin_manifests_ignores_non_matching_extension() { + let dir = scratch_dir("ignore-extension"); + write( + dir.path().join("uki.boot"), + "name = \"uki\"\nlibrary = \"libupac-uki.so\"\n", + ) + .unwrap(); + write(dir.path().join("README.md"), b"not a manifest").unwrap(); + + let manifests = load_boot_plugin_manifests(dir.path().to_str().unwrap(), "boot").unwrap(); + + assert_eq!(manifests.len(), 1); +} + +#[test] +fn load_boot_plugin_manifests_fails_on_duplicate_name() { + let dir = scratch_dir("duplicate-name"); + write( + dir.path().join("a.boot"), + "name = \"uki\"\nlibrary = \"libupac-uki-a.so\"\n", + ) + .unwrap(); + write( + dir.path().join("b.boot"), + "name = \"uki\"\nlibrary = \"libupac-uki-b.so\"\n", + ) + .unwrap(); + + let result = load_boot_plugin_manifests(dir.path().to_str().unwrap(), "boot"); + + assert_eq!(result.unwrap_err(), BootPluginError::DuplicateName("uki".to_owned())); +} + +#[test] +fn load_boot_plugin_manifests_fails_on_malformed_toml() { + let dir = scratch_dir("malformed-toml"); + write(dir.path().join("broken.boot"), "not valid toml [[[").unwrap(); + + let result = load_boot_plugin_manifests(dir.path().to_str().unwrap(), "boot"); + + assert_eq!(result.unwrap_err(), BootPluginError::Manifest); +} + +#[test] +fn load_boot_plugin_manifests_treats_a_missing_directory_as_no_manifests() { + let dir = scratch_dir("missing-dir").path().join("does-not-exist"); + + let manifests = load_boot_plugin_manifests(dir.to_str().unwrap(), "boot").unwrap(); + + assert!(manifests.is_empty()); +} diff --git a/lib/lib/tests/plugin_decoder.rs b/lib/lib/tests/plugin_decoder.rs index 6ffae21f..77ddd3e9 100644 --- a/lib/lib/tests/plugin_decoder.rs +++ b/lib/lib/tests/plugin_decoder.rs @@ -144,6 +144,15 @@ fn load_decoder_manifests_fails_on_duplicate_format() { assert_eq!(result.unwrap_err(), DecoderError::DuplicateFormat("deb".to_owned())); } +#[test] +fn load_decoder_manifests_treats_a_missing_directory_as_no_manifests() { + let dir = scratch_dir("missing-dir").path().join("does-not-exist"); + + let manifests = load_decoder_manifests(dir.to_str().unwrap(), "decoder").unwrap(); + + assert!(manifests.is_empty()); +} + #[test] fn load_decoder_manifests_fails_on_malformed_toml() { let dir = scratch_dir("malformed-toml"); diff --git a/lib/lib/tests/plugin_decoder_error.rs b/lib/lib/tests/plugin_decoder_error.rs new file mode 100644 index 00000000..a4fd413f --- /dev/null +++ b/lib/lib/tests/plugin_decoder_error.rs @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; + +use mime::Mime; + +use upac::plugin::decoder::error::DecoderError; + +use upac_abi::error::ErrorKind; + +#[test] +fn abi_error_kind_maps_to_invalid_response() { + let error = ErrorKind::AbiMismatch; + + assert_eq!(DecoderError::from(error), DecoderError::InvalidResponse); +} + +#[test] +fn io_error_maps_to_io_with_the_same_kind() { + let error = IoError::new(IoErrorKind::NotFound, "missing"); + + assert_eq!(DecoderError::from(error), DecoderError::Io(IoErrorKind::NotFound)); +} + +#[test] +fn toml_error_maps_to_manifest() { + let error = toml::from_str::("not valid toml [[[").unwrap_err(); + + assert_eq!(DecoderError::from(error), DecoderError::Manifest); +} + +#[test] +fn mime_parse_error_maps_to_invalid_mime_type() { + let error = "".parse::().unwrap_err(); + + assert_eq!(DecoderError::from(error), DecoderError::InvalidMimeType); +} + +#[test] +fn every_variant_maps_to_the_documented_error_kind() { + let cases = [ + (DecoderError::Load, ErrorKind::NotFound), + (DecoderError::Symbol, ErrorKind::AbiMismatch), + ( + DecoderError::AbiMismatch { got: 1, expected: 2 }, + ErrorKind::AbiMismatch, + ), + (DecoderError::Failed(-1), ErrorKind::Unexpected), + (DecoderError::InvalidResponse, ErrorKind::InvalidEntry), + (DecoderError::Io(IoErrorKind::NotFound), ErrorKind::ReadFailed), + (DecoderError::Manifest, ErrorKind::InvalidEntry), + (DecoderError::DuplicateFormat("deb".to_owned()), ErrorKind::InvalidEntry), + (DecoderError::UnknownFormat("zst".to_owned()), ErrorKind::NotFound), + (DecoderError::InvalidMimeType, ErrorKind::InvalidEntry), + (DecoderError::NoDecoders, ErrorKind::NotFound), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/scripts_error.rs b/lib/lib/tests/scripts_error.rs new file mode 100644 index 00000000..b39b7ec9 --- /dev/null +++ b/lib/lib/tests/scripts_error.rs @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; +use std::str::from_utf8; + +use upac::scripts::error::HookError; + +use upac_abi::error::ErrorKind; + +use upac_pki::error::PkiError; + +#[test] +fn toml_error_maps_to_parse() { + let error = toml::from_str::("not valid toml [[[").unwrap_err(); + + assert_eq!(HookError::from(error), HookError::Parse); +} + +#[test] +fn io_error_maps_to_io_with_the_same_kind() { + let error = IoError::new(IoErrorKind::PermissionDenied, "denied"); + + assert_eq!(HookError::from(error), HookError::Io(IoErrorKind::PermissionDenied)); +} + +#[test] +fn utf8_error_maps_to_encoding() { + let bytes: Vec = vec![0xff, 0xfe]; + let error = from_utf8(&bytes).unwrap_err(); + + assert_eq!(HookError::from(error), HookError::Encoding); +} + +#[test] +fn pki_error_maps_each_variant_directly() { + let cases = [ + (PkiError::Malformed, HookError::MalformedSignature), + (PkiError::InvalidSignature, HookError::InvalidSignature), + (PkiError::Generation, HookError::Parse), + ]; + + for (error, expected) in cases { + assert_eq!(HookError::from(error), expected); + } +} + +#[test] +fn every_variant_maps_to_the_documented_error_kind() { + let cases = [ + (HookError::Parse, ErrorKind::InvalidEntry), + (HookError::InvalidTrigger, ErrorKind::InvalidEntry), + (HookError::NoTrigger, ErrorKind::InvalidEntry), + (HookError::Io(IoErrorKind::NotFound), ErrorKind::ReadFailed), + (HookError::Encoding, ErrorKind::InvalidEntry), + (HookError::MalformedSignature, ErrorKind::InvalidEntry), + (HookError::InvalidSignature, ErrorKind::InvalidEntry), + (HookError::TriggerConflict("deb".to_owned()), ErrorKind::InvalidEntry), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/scripts_hook.rs b/lib/lib/tests/scripts_hook.rs index 2b431bb3..28968b0c 100644 --- a/lib/lib/tests/scripts_hook.rs +++ b/lib/lib/tests/scripts_hook.rs @@ -7,11 +7,14 @@ use std::fs::{read_link, write}; use std::path::{Path, PathBuf}; use tempfile::{Builder, TempDir}; +use upac::errors::CommonError; +use upac::orchestrator::stage::{ConcurrentStage, StageResult}; use upac::scripts::error::HookError; use upac::scripts::file::HookFile; use upac::scripts::load::load_hooks; use upac::scripts::pipeline::{Operation, PipelineTrigger, Timing}; use upac::scripts::primitive::Step; +use upac_abi::hook::ProgressEventBuilder; use upac_pki::generate::{Identity, SigningIdentity, generate_root, generate_signing_cert}; use upac_pki::signature::HookSignature; @@ -216,6 +219,80 @@ fn primitive_vec_rollback_guard_unwinds_in_reverse_order() { assert!(!c.exists()); } +#[test] +fn hook_file_run_executes_all_steps_and_returns_advance() { + let dir = scratch_dir("run-advance"); + let a = dir.path().join("a"); + let b = dir.path().join("b"); + + let hook_file = HookFile::parse(&format!( + concat!( + "operation = \"install\"\ntiming = \"pre\"\n\n", + "[[steps]]\ntype = \"touch_file\"\npath = {:?}\n\n", + "[[steps]]\ntype = \"touch_file\"\npath = {:?}\n", + ), + a, b + )) + .unwrap(); + + let (_, result, _guard) = + ConcurrentStage::::run(Box::new(hook_file), ProgressEventBuilder::new(0)).unwrap(); + + assert!(matches!(result, StageResult::Advance)); + assert!(a.exists()); + assert!(b.exists()); +} + +#[test] +fn hook_file_run_rolls_back_and_errors_when_a_critical_step_fails() { + let dir = scratch_dir("run-critical-failure"); + let touched = dir.path().join("touched"); + let missing_from = dir.path().join("does-not-exist"); + let move_to = dir.path().join("move-to"); + + let hook_file = HookFile::parse(&format!( + concat!( + "operation = \"install\"\ntiming = \"pre\"\ncritical = true\n\n", + "[[steps]]\ntype = \"touch_file\"\npath = {:?}\n\n", + "[[steps]]\ntype = \"move_file\"\nfrom = {:?}\nto = {:?}\n", + ), + touched, missing_from, move_to + )) + .unwrap(); + + let result = ConcurrentStage::::run(Box::new(hook_file), ProgressEventBuilder::new(0)); + + assert!(result.is_err()); + assert!(!touched.exists(), "the already-executed step must be rolled back"); +} + +#[test] +fn hook_file_run_stops_early_without_error_when_a_non_critical_step_fails() { + let dir = scratch_dir("run-non-critical-failure"); + let touched = dir.path().join("touched"); + let missing_from = dir.path().join("does-not-exist"); + let move_to = dir.path().join("move-to"); + + let hook_file = HookFile::parse(&format!( + concat!( + "operation = \"install\"\ntiming = \"pre\"\ncritical = false\n\n", + "[[steps]]\ntype = \"touch_file\"\npath = {:?}\n\n", + "[[steps]]\ntype = \"move_file\"\nfrom = {:?}\nto = {:?}\n", + ), + touched, missing_from, move_to + )) + .unwrap(); + + let (_, result, _guard) = + ConcurrentStage::::run(Box::new(hook_file), ProgressEventBuilder::new(0)).unwrap(); + + assert!(matches!(result, StageResult::Advance)); + assert!( + touched.exists(), + "a non-critical failure must not roll back prior steps" + ); +} + #[test] fn load_hooks_returns_matching_hook_for_signed_valid_file() { let hooks_dir = scratch_dir("load-valid"); From 91cdd14948a77d7cde9d28ebb3bc6d1971856676 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 6 Sep 2026 21:25:35 +0400 Subject: [PATCH 030/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index ca663574..2c541a4e 100644 --- a/TODO.md +++ b/TODO.md @@ -11,11 +11,11 @@ Near-term, concrete items. See `ROADMAP.md` for the bigger picture. ## upac-lib Test-coverage pass in progress, going file by file through the non-command core first -(`errors.rs`/`lock.rs`/`search.rs`/`fs.rs`/`orchestrator/*`/`database/*` done), commands -(`mutated`/`unmutated`) last. Remaining core files not yet visited: `deploy/{error,retention,mod}.rs` -(`esp.rs` skipped — real mount), `scripts/{error,file,load,pipeline,primitive}.rs`, -`plugin/decoder/{error,unpack,mod}.rs`, `plugin/boot/{error,manifest,mod}.rs`, -`composefs/{diff,error,mod}.rs`, `config/mod.rs`, `boot/{error,mod}.rs`. +(`errors.rs`/`lock.rs`/`search.rs`/`fs.rs`/`orchestrator/*`/`database/*`/`deploy/*`/`scripts/*`/ +`plugin/decoder/{error,manifest,triggers}.rs`/`plugin/boot/{error,manifest}.rs` done — +`plugin/decoder/unpack.rs`/`plugin/decoder/mod.rs`/`plugin/boot/mod.rs` skipped, need a real +dlopen'd/`builtin-*` plugin), commands (`mutated`/`unmutated`) last. Remaining core files not yet +visited: `composefs/{diff,error,mod}.rs`, `config/mod.rs`, `boot/{error,mod}.rs`. **UKI A/B boot (`upac-from.efi`/`upac-to.efi`) confirm-boot service not designed yet**: after a successful boot, something needs to confirm once, swap `to`↔`from`, and set the normal persistent From e171b7f5342120d6af5099133e55aff1e2c71850 Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 02:37:04 +0400 Subject: [PATCH 031/148] fix: add more tests Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/mutated/files/error.rs | 4 + lib/lib/src/mutated/mime/error.rs | 4 + lib/lib/src/mutated/rollback/error.rs | 4 + lib/lib/src/mutated/uninstaller/error.rs | 4 + lib/lib/src/mutated/update/error.rs | 4 + lib/lib/src/unmutated/diff/error.rs | 4 + lib/lib/src/unmutated/diff_config/error.rs | 4 + lib/lib/src/unmutated/search_files/error.rs | 4 + lib/lib/src/unmutated/search_in_meta/error.rs | 4 + .../search_in_package_files/error.rs | 4 + lib/lib/src/unmutated/search_meta/error.rs | 4 + lib/lib/tests/boot.rs | 97 ++++++++++ lib/lib/tests/boot_error.rs | 29 +++ lib/lib/tests/composefs_diff.rs | 166 ++++++++++++++++++ lib/lib/tests/composefs_error.rs | 138 +++++++++++++++ lib/lib/tests/inline/mutated_files_error.rs | 20 +++ lib/lib/tests/inline/mutated_mime_error.rs | 33 ++++ .../tests/inline/mutated_rollback_error.rs | 51 ++++++ .../tests/inline/mutated_uninstaller_error.rs | 20 +++ lib/lib/tests/inline/mutated_update_error.rs | 21 +++ .../inline/unmutated_diff_config_error.rs | 46 +++++ lib/lib/tests/inline/unmutated_diff_error.rs | 23 +++ .../inline/unmutated_search_files_error.rs | 37 ++++ .../inline/unmutated_search_in_meta_error.rs | 37 ++++ ...unmutated_search_in_package_files_error.rs | 37 ++++ .../inline/unmutated_search_meta_error.rs | 37 ++++ 26 files changed, 836 insertions(+) create mode 100644 lib/lib/tests/boot.rs create mode 100644 lib/lib/tests/boot_error.rs create mode 100644 lib/lib/tests/composefs_diff.rs create mode 100644 lib/lib/tests/composefs_error.rs create mode 100644 lib/lib/tests/inline/mutated_files_error.rs create mode 100644 lib/lib/tests/inline/mutated_mime_error.rs create mode 100644 lib/lib/tests/inline/mutated_rollback_error.rs create mode 100644 lib/lib/tests/inline/mutated_uninstaller_error.rs create mode 100644 lib/lib/tests/inline/mutated_update_error.rs create mode 100644 lib/lib/tests/inline/unmutated_diff_config_error.rs create mode 100644 lib/lib/tests/inline/unmutated_diff_error.rs create mode 100644 lib/lib/tests/inline/unmutated_search_files_error.rs create mode 100644 lib/lib/tests/inline/unmutated_search_in_meta_error.rs create mode 100644 lib/lib/tests/inline/unmutated_search_in_package_files_error.rs create mode 100644 lib/lib/tests/inline/unmutated_search_meta_error.rs diff --git a/lib/lib/src/mutated/files/error.rs b/lib/lib/src/mutated/files/error.rs index 49bf8919..0f5a6c64 100644 --- a/lib/lib/src/mutated/files/error.rs +++ b/lib/lib/src/mutated/files/error.rs @@ -16,6 +16,10 @@ use crate::errors::{ use crate::lock::LockError; use crate::plugin::boot::error::BootPluginError; +#[cfg(test)] +#[path = "../../../tests/inline/mutated_files_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum FilesError { PackageNotFound, diff --git a/lib/lib/src/mutated/mime/error.rs b/lib/lib/src/mutated/mime/error.rs index a410613a..b297164c 100644 --- a/lib/lib/src/mutated/mime/error.rs +++ b/lib/lib/src/mutated/mime/error.rs @@ -11,6 +11,10 @@ use upac_abi::error::ErrorKind; use crate::errors::{CommonError, common_error_from, lock_error_from}; use crate::lock::LockError; +#[cfg(test)] +#[path = "../../../tests/inline/mutated_mime_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum MimeError { Common(CommonError), diff --git a/lib/lib/src/mutated/rollback/error.rs b/lib/lib/src/mutated/rollback/error.rs index 1b5aa059..ccd778b9 100644 --- a/lib/lib/src/mutated/rollback/error.rs +++ b/lib/lib/src/mutated/rollback/error.rs @@ -17,6 +17,10 @@ use crate::errors::{ use crate::lock::LockError; use crate::plugin::boot::error::BootPluginError; +#[cfg(test)] +#[path = "../../../tests/inline/mutated_rollback_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum RollbackError { Common(CommonError), diff --git a/lib/lib/src/mutated/uninstaller/error.rs b/lib/lib/src/mutated/uninstaller/error.rs index d84f5237..e61c5a42 100644 --- a/lib/lib/src/mutated/uninstaller/error.rs +++ b/lib/lib/src/mutated/uninstaller/error.rs @@ -16,6 +16,10 @@ use crate::errors::{ use crate::lock::LockError; use crate::plugin::boot::error::BootPluginError; +#[cfg(test)] +#[path = "../../../tests/inline/mutated_uninstaller_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum UninstallError { PackageNotFound, diff --git a/lib/lib/src/mutated/update/error.rs b/lib/lib/src/mutated/update/error.rs index e7db861f..fe023562 100644 --- a/lib/lib/src/mutated/update/error.rs +++ b/lib/lib/src/mutated/update/error.rs @@ -16,6 +16,10 @@ use crate::errors::{ use crate::lock::LockError; use crate::plugin::boot::error::BootPluginError; +#[cfg(test)] +#[path = "../../../tests/inline/mutated_update_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum UpdateError { PackageNotFound, diff --git a/lib/lib/src/unmutated/diff/error.rs b/lib/lib/src/unmutated/diff/error.rs index b0af6592..e6dfb3c3 100644 --- a/lib/lib/src/unmutated/diff/error.rs +++ b/lib/lib/src/unmutated/diff/error.rs @@ -14,6 +14,10 @@ use crate::errors::{ }; use crate::lock::LockError; +#[cfg(test)] +#[path = "../../../tests/inline/unmutated_diff_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum DiffError { Common(CommonError), diff --git a/lib/lib/src/unmutated/diff_config/error.rs b/lib/lib/src/unmutated/diff_config/error.rs index 639655bb..71b1b220 100644 --- a/lib/lib/src/unmutated/diff_config/error.rs +++ b/lib/lib/src/unmutated/diff_config/error.rs @@ -14,6 +14,10 @@ use crate::errors::{ }; use crate::lock::LockError; +#[cfg(test)] +#[path = "../../../tests/inline/unmutated_diff_config_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum DiffConfigError { Common(CommonError), diff --git a/lib/lib/src/unmutated/search_files/error.rs b/lib/lib/src/unmutated/search_files/error.rs index 327879ae..6fefb3c7 100644 --- a/lib/lib/src/unmutated/search_files/error.rs +++ b/lib/lib/src/unmutated/search_files/error.rs @@ -14,6 +14,10 @@ use crate::errors::{ }; use crate::lock::LockError; +#[cfg(test)] +#[path = "../../../tests/inline/unmutated_search_files_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum SearchFilesError { Common(CommonError), diff --git a/lib/lib/src/unmutated/search_in_meta/error.rs b/lib/lib/src/unmutated/search_in_meta/error.rs index cd588325..a928244e 100644 --- a/lib/lib/src/unmutated/search_in_meta/error.rs +++ b/lib/lib/src/unmutated/search_in_meta/error.rs @@ -14,6 +14,10 @@ use crate::errors::{ }; use crate::lock::LockError; +#[cfg(test)] +#[path = "../../../tests/inline/unmutated_search_in_meta_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum SearchInMetaError { Common(CommonError), diff --git a/lib/lib/src/unmutated/search_in_package_files/error.rs b/lib/lib/src/unmutated/search_in_package_files/error.rs index 9a5fe780..58dfe107 100644 --- a/lib/lib/src/unmutated/search_in_package_files/error.rs +++ b/lib/lib/src/unmutated/search_in_package_files/error.rs @@ -14,6 +14,10 @@ use crate::errors::{ }; use crate::lock::LockError; +#[cfg(test)] +#[path = "../../../tests/inline/unmutated_search_in_package_files_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum SearchInPackageFilesError { Common(CommonError), diff --git a/lib/lib/src/unmutated/search_meta/error.rs b/lib/lib/src/unmutated/search_meta/error.rs index b0ae8d82..931e207c 100644 --- a/lib/lib/src/unmutated/search_meta/error.rs +++ b/lib/lib/src/unmutated/search_meta/error.rs @@ -14,6 +14,10 @@ use crate::errors::{ }; use crate::lock::LockError; +#[cfg(test)] +#[path = "../../../tests/inline/unmutated_search_meta_error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum SearchMetaError { Common(CommonError), diff --git a/lib/lib/tests/boot.rs b/lib/lib/tests/boot.rs new file mode 100644 index 00000000..77143339 --- /dev/null +++ b/lib/lib/tests/boot.rs @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::fs::{File, write}; + +use composefs::fsverity::FsVerityHashValue; +use composefs::generic_tree::Stat; +use composefs::repository::{ImportContext, Repository, RepositoryConfig}; +use composefs::tree::FileSystem; +use nix::fcntl::AT_FDCWD; +use tempfile::{Builder, TempDir}; + +use upac::boot::error::BootError; +use upac::boot::write_boot_entry; +use upac::composefs::file::FileHandle; +use upac::composefs::repository::ObjectID; + +fn scratch_dir(name: &str) -> TempDir { + Builder::new().prefix(name).tempdir().unwrap() +} + +fn empty_tree() -> FileSystem { + FileSystem::new(Stat::uninitialized()) +} + +fn open_repository(name: &str) -> (TempDir, Repository) { + let dir = scratch_dir(name); + let (repository, _created) = + Repository::init_path(AT_FDCWD, dir.path(), RepositoryConfig::default().set_insecure()).unwrap(); + + (dir, repository) +} + +fn source_file(dir_name: &str, content: &[u8]) -> File { + let dir = scratch_dir(dir_name); + let path = dir.path().join("source"); + write(&path, content).unwrap(); + + File::open(&path).unwrap() +} + +fn ensure_modules_dir(tree: &mut FileSystem) { + if FileHandle::from_tree(tree, "lib/modules").is_ok() { + return; + } + FileHandle::new("lib") + .insert_in_tree(tree, Stat::uninitialized()) + .unwrap(); + FileHandle::new("lib/modules") + .insert_in_tree(tree, Stat::uninitialized()) + .unwrap(); +} + +fn insert_kernel( + repository: &Repository, tree: &mut FileSystem, ctx: &mut ImportContext, kver: &str, +) { + ensure_modules_dir(tree); + FileHandle::new(format!("lib/modules/{kver}")) + .insert_in_tree(tree, Stat::uninitialized()) + .unwrap(); + FileHandle::new(format!("lib/modules/{kver}/vmlinuz")) + .insert_file( + repository, + tree, + &source_file(&format!("kernel-{kver}"), b"kernel"), + Stat::uninitialized(), + ctx, + ) + .unwrap(); +} + +#[test] +fn write_boot_entry_fails_when_the_tree_has_no_boot_resource() { + let (_scratch, repository) = open_repository("boot-none"); + let tree = empty_tree(); + let esp = scratch_dir("boot-none-esp"); + + let result = write_boot_entry(&repository, &tree, ObjectID::EMPTY, esp.path(), "deadbeef"); + + assert_eq!(result.unwrap_err(), BootError::NoBootResource); +} + +#[test] +fn write_boot_entry_fails_when_the_tree_has_more_than_one_boot_resource() { + let (_scratch, repository) = open_repository("boot-ambiguous"); + let mut ctx = ImportContext::default(); + let mut tree = empty_tree(); + insert_kernel(&repository, &mut tree, &mut ctx, "6.6.0"); + insert_kernel(&repository, &mut tree, &mut ctx, "6.7.0"); + let esp = scratch_dir("boot-ambiguous-esp"); + + let result = write_boot_entry(&repository, &tree, ObjectID::EMPTY, esp.path(), "deadbeef"); + + assert_eq!(result.unwrap_err(), BootError::AmbiguousBootResource); +} diff --git a/lib/lib/tests/boot_error.rs b/lib/lib/tests/boot_error.rs new file mode 100644 index 00000000..b90f8b13 --- /dev/null +++ b/lib/lib/tests/boot_error.rs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use anyhow::anyhow; + +use upac::boot::error::BootError; + +use upac_abi::error::ErrorKind; + +#[test] +fn anyhow_error_maps_to_unexpected() { + assert_eq!(BootError::from(anyhow!("boom")), BootError::Unexpected); +} + +#[test] +fn every_variant_maps_to_the_documented_error_kind() { + let cases = [ + (BootError::NoBootResource, ErrorKind::NotFound), + (BootError::AmbiguousBootResource, ErrorKind::InvalidEntry), + (BootError::UnsupportedBootResource, ErrorKind::InvalidEntry), + (BootError::Unexpected, ErrorKind::Unexpected), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/composefs_diff.rs b/lib/lib/tests/composefs_diff.rs new file mode 100644 index 00000000..ffbdd29a --- /dev/null +++ b/lib/lib/tests/composefs_diff.rs @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::fs::{File, write}; + +use composefs::generic_tree::Stat; +use composefs::repository::{ImportContext, Repository, RepositoryConfig}; +use composefs::tree::FileSystem; +use nix::fcntl::AT_FDCWD; +use tempfile::{Builder, TempDir}; + +use upac::composefs::diff::TreeDiff; +use upac::composefs::file::FileHandle; +use upac::composefs::repository::ObjectID; + +use upac_abi::FileDiffKind; + +fn scratch_dir(name: &str) -> TempDir { + Builder::new().prefix(name).tempdir().unwrap() +} + +fn empty_tree() -> FileSystem { + FileSystem::new(Stat::uninitialized()) +} + +fn open_repository(name: &str) -> (TempDir, Repository) { + let dir = scratch_dir(name); + let (repository, _created) = + Repository::init_path(AT_FDCWD, dir.path(), RepositoryConfig::default().set_insecure()).unwrap(); + + (dir, repository) +} + +fn source_file(dir_name: &str, content: &[u8]) -> File { + let dir = scratch_dir(dir_name); + let path = dir.path().join("source"); + write(&path, content).unwrap(); + + File::open(&path).unwrap() +} + +fn insert( + repository: &Repository, tree: &mut FileSystem, ctx: &mut ImportContext, path: &str, + content: &[u8], +) { + FileHandle::new(path) + .insert_file( + repository, + tree, + &source_file(&path.replace('/', "-"), content), + Stat::uninitialized(), + ctx, + ) + .unwrap(); +} + +#[test] +fn run_reports_no_changes_for_identical_trees() { + let (_scratch, repository) = open_repository("diff-unchanged"); + let mut ctx = ImportContext::default(); + let mut from = empty_tree(); + let mut to = empty_tree(); + insert(&repository, &mut from, &mut ctx, "file.txt", b"same"); + insert(&repository, &mut to, &mut ctx, "file.txt", b"same"); + + let changes = TreeDiff::run(&from, &to); + + assert!(changes.is_empty()); +} + +#[test] +fn run_reports_added_for_a_file_only_in_to() { + let (_scratch, repository) = open_repository("diff-added"); + let mut ctx = ImportContext::default(); + let from = empty_tree(); + let mut to = empty_tree(); + insert(&repository, &mut to, &mut ctx, "new.txt", b"content"); + + let changes = TreeDiff::run(&from, &to); + + assert_eq!(changes, vec![("new.txt".to_owned(), FileDiffKind::Added)]); +} + +#[test] +fn run_reports_removed_for_a_file_only_in_from() { + let (_scratch, repository) = open_repository("diff-removed"); + let mut ctx = ImportContext::default(); + let mut from = empty_tree(); + let to = empty_tree(); + insert(&repository, &mut from, &mut ctx, "old.txt", b"content"); + + let changes = TreeDiff::run(&from, &to); + + assert_eq!(changes, vec![("old.txt".to_owned(), FileDiffKind::Removed)]); +} + +#[test] +fn run_reports_modified_for_a_file_with_different_content_in_each_tree() { + let (_scratch, repository) = open_repository("diff-modified"); + let mut ctx = ImportContext::default(); + let mut from = empty_tree(); + let mut to = empty_tree(); + insert(&repository, &mut from, &mut ctx, "file.txt", b"first"); + insert(&repository, &mut to, &mut ctx, "file.txt", b"second"); + + let changes = TreeDiff::run(&from, &to); + + assert_eq!(changes, vec![("file.txt".to_owned(), FileDiffKind::Modified)]); +} + +#[test] +fn run_recurses_into_matched_subdirectories() { + let (_scratch, repository) = open_repository("diff-nested"); + let mut ctx = ImportContext::default(); + let mut from = empty_tree(); + let mut to = empty_tree(); + FileHandle::new("dir") + .insert_in_tree(&mut from, Stat::uninitialized()) + .unwrap(); + FileHandle::new("dir") + .insert_in_tree(&mut to, Stat::uninitialized()) + .unwrap(); + insert(&repository, &mut to, &mut ctx, "dir/new.txt", b"content"); + + let changes = TreeDiff::run(&from, &to); + + assert_eq!(changes, vec![("dir/new.txt".to_owned(), FileDiffKind::Added)]); +} + +#[test] +fn run_marks_both_sides_when_a_directory_is_replaced_by_a_regular_file() { + let (_scratch, repository) = open_repository("diff-type-change"); + let mut ctx = ImportContext::default(); + let mut from = empty_tree(); + let mut to = empty_tree(); + FileHandle::new("thing") + .insert_in_tree(&mut from, Stat::uninitialized()) + .unwrap(); + insert(&repository, &mut from, &mut ctx, "thing/child", b"content"); + insert(&repository, &mut to, &mut ctx, "thing", b"content"); + + let changes = TreeDiff::run(&from, &to); + + assert_eq!(changes.len(), 2); + assert!(changes.contains(&("thing".to_owned(), FileDiffKind::Added))); + assert!(changes.contains(&("thing/child".to_owned(), FileDiffKind::Removed))); +} + +#[test] +fn run_ignores_a_bare_directory_present_on_only_one_side() { + let (_scratch, repository) = open_repository("diff-dir-only-side"); + let mut ctx = ImportContext::default(); + let mut from = empty_tree(); + let mut to = empty_tree(); + FileHandle::new("empty-dir") + .insert_in_tree(&mut to, Stat::uninitialized()) + .unwrap(); + insert(&repository, &mut from, &mut ctx, "file.txt", b"content"); + insert(&repository, &mut to, &mut ctx, "file.txt", b"content"); + + let changes = TreeDiff::run(&from, &to); + + assert!(changes.is_empty()); +} diff --git a/lib/lib/tests/composefs_error.rs b/lib/lib/tests/composefs_error.rs new file mode 100644 index 00000000..e3ba4232 --- /dev/null +++ b/lib/lib/tests/composefs_error.rs @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::ffi::OsStr; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; + +use anyhow::anyhow; + +use composefs::fsverity::Algorithm; +use composefs::generic_tree::ImageError; +use composefs::repository::RepositoryOpenError; + +use hex::FromHexError; + +use upac::composefs::error::RepoError; + +use upac_abi::error::ErrorKind; + +#[test] +fn hex_error_maps_to_invalid_digest() { + let error = FromHexError::OddLength; + + assert_eq!(RepoError::from(error), RepoError::InvalidDigest); +} + +#[test] +fn io_error_maps_by_kind() { + assert_eq!( + RepoError::from(IoError::new(IoErrorKind::NotFound, "missing")), + RepoError::NotFound + ); + assert_eq!( + RepoError::from(IoError::new(IoErrorKind::PermissionDenied, "denied")), + RepoError::AccessDenied + ); + assert_eq!(RepoError::from(IoError::other("other")), RepoError::Unexpected); +} + +#[test] +fn repository_open_error_maps_every_variant() { + assert_eq!( + RepoError::from(RepositoryOpenError::MetadataMissing), + RepoError::NotInitialized + ); + assert_eq!( + RepoError::from(RepositoryOpenError::OldFormatRepository), + RepoError::NotInitialized + ); + assert_eq!( + RepoError::from(RepositoryOpenError::MetadataInvalid( + serde_json::from_str::("not json").unwrap_err() + )), + RepoError::Corrupted + ); + assert_eq!( + RepoError::from(RepositoryOpenError::AlgorithmMismatch { + found: Algorithm::Sha256 { lg_blocksize: 12 }, + expected: Algorithm::Sha512 { lg_blocksize: 12 }, + }), + RepoError::AlgorithmMismatch + ); + assert_eq!( + RepoError::from(RepositoryOpenError::UnsupportedVersion { found: 99 }), + RepoError::UnsupportedVersion + ); + assert_eq!( + RepoError::from(RepositoryOpenError::IncompatibleFeatures(vec!["unknown".to_owned()])), + RepoError::IncompatibleFeatures + ); + assert_eq!( + RepoError::from(RepositoryOpenError::Io(IoError::new(IoErrorKind::NotFound, "missing"))), + RepoError::NotFound + ); +} + +#[test] +fn image_error_maps_every_variant() { + assert_eq!( + RepoError::from(ImageError::InvalidFilename(Box::::from(OsStr::new("..")))), + RepoError::InvalidPath + ); + assert_eq!( + RepoError::from(ImageError::NotFound(Box::::from(OsStr::new("missing")))), + RepoError::NotFound + ); + assert_eq!( + RepoError::from(ImageError::NotADirectory(Box::::from(OsStr::new("file")))), + RepoError::NotADirectory + ); + assert_eq!( + RepoError::from(ImageError::IsADirectory(Box::::from(OsStr::new("dir")))), + RepoError::IsADirectory + ); + assert_eq!( + RepoError::from(ImageError::IsNotRegular(Box::::from(OsStr::new("special")))), + RepoError::NotRegularFile + ); + assert_eq!( + RepoError::from(ImageError::LeafIdOutOfBounds(1, 0)), + RepoError::Unexpected + ); + assert_eq!( + RepoError::from(ImageError::OrphanedLeaves(vec![1])), + RepoError::Unexpected + ); +} + +#[test] +fn anyhow_error_maps_to_unexpected() { + assert_eq!(RepoError::from(anyhow!("boom")), RepoError::Unexpected); +} + +#[test] +fn every_variant_maps_to_the_documented_error_kind() { + let cases = [ + (RepoError::NotInitialized, ErrorKind::NotInitialized), + (RepoError::Corrupted, ErrorKind::ReadFailed), + (RepoError::AlgorithmMismatch, ErrorKind::Unexpected), + (RepoError::UnsupportedVersion, ErrorKind::Unexpected), + (RepoError::IncompatibleFeatures, ErrorKind::Unexpected), + (RepoError::NotFound, ErrorKind::NotFound), + (RepoError::AccessDenied, ErrorKind::PermissionDenied), + (RepoError::InvalidPath, ErrorKind::InvalidPath), + (RepoError::InvalidDigest, ErrorKind::InvalidPath), + (RepoError::NotADirectory, ErrorKind::InvalidEntry), + (RepoError::IsADirectory, ErrorKind::InvalidEntry), + (RepoError::NotRegularFile, ErrorKind::InvalidEntry), + (RepoError::NotASymlink, ErrorKind::InvalidEntry), + (RepoError::Cancelled, ErrorKind::Cancelled), + (RepoError::Unexpected, ErrorKind::Unexpected), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/inline/mutated_files_error.rs b/lib/lib/tests/inline/mutated_files_error.rs new file mode 100644 index 00000000..fb4b5268 --- /dev/null +++ b/lib/lib/tests/inline/mutated_files_error.rs @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{CommonError, FilesError}; + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + (FilesError::PackageNotFound, ErrorKind::NotFound), + (FilesError::Common(CommonError::OutOfMemory), ErrorKind::OutOfMemory), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/inline/mutated_mime_error.rs b/lib/lib/tests/inline/mutated_mime_error.rs new file mode 100644 index 00000000..8c977387 --- /dev/null +++ b/lib/lib/tests/inline/mutated_mime_error.rs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{CommonError, IoError, IoErrorKind, MimeError}; + +#[test] +fn io_error_maps_to_io_with_the_same_kind() { + let error = IoError::new(IoErrorKind::PermissionDenied, "denied"); + + assert_eq!(MimeError::from(error), MimeError::Io(IoErrorKind::PermissionDenied)); +} + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + (MimeError::Common(CommonError::OutOfMemory), ErrorKind::OutOfMemory), + (MimeError::Io(IoErrorKind::NotFound), ErrorKind::NotFound), + ( + MimeError::Io(IoErrorKind::PermissionDenied), + ErrorKind::PermissionDenied, + ), + (MimeError::Io(IoErrorKind::Other), ErrorKind::Unexpected), + (MimeError::DesktopFileMalformed, ErrorKind::InvalidEntry), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/inline/mutated_rollback_error.rs b/lib/lib/tests/inline/mutated_rollback_error.rs new file mode 100644 index 00000000..ebce8c79 --- /dev/null +++ b/lib/lib/tests/inline/mutated_rollback_error.rs @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{ + CommonError, ConfigDigestResolveError, DeployRecordError, DeployRecordsError, RollbackError, SysrootError, +}; + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + (RollbackError::Common(CommonError::OutOfMemory), ErrorKind::OutOfMemory), + ( + RollbackError::ConfigDigestNotFound("deadbeef".to_owned()), + ErrorKind::NotFound, + ), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} + +#[test] +fn config_digest_resolve_error_not_found_maps_to_config_digest_not_found() { + let error = ConfigDigestResolveError::NotFound("deadbeef".to_owned()); + + assert_eq!( + RollbackError::from(error), + RollbackError::ConfigDigestNotFound("deadbeef".to_owned()) + ); +} + +#[test] +fn config_digest_resolve_error_records_delegates_to_the_inner_error() { + let sysroot = ConfigDigestResolveError::Records(DeployRecordsError::Sysroot(SysrootError::MountInfoUnavailable)); + let deploy_record = + ConfigDigestResolveError::Records(DeployRecordsError::DeployRecord(DeployRecordError::NotFound)); + + assert_eq!( + RollbackError::from(sysroot), + RollbackError::Common(CommonError::Sysroot(SysrootError::MountInfoUnavailable)) + ); + assert_eq!( + RollbackError::from(deploy_record), + RollbackError::Common(CommonError::DeployRecord(DeployRecordError::NotFound)) + ); +} diff --git a/lib/lib/tests/inline/mutated_uninstaller_error.rs b/lib/lib/tests/inline/mutated_uninstaller_error.rs new file mode 100644 index 00000000..2a5f35e4 --- /dev/null +++ b/lib/lib/tests/inline/mutated_uninstaller_error.rs @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{CommonError, UninstallError}; + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + (UninstallError::PackageNotFound, ErrorKind::NotFound), + (UninstallError::Common(CommonError::OutOfMemory), ErrorKind::OutOfMemory), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/inline/mutated_update_error.rs b/lib/lib/tests/inline/mutated_update_error.rs new file mode 100644 index 00000000..8ea26983 --- /dev/null +++ b/lib/lib/tests/inline/mutated_update_error.rs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{CommonError, UpdateError}; + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + (UpdateError::PackageNotFound, ErrorKind::NotFound), + (UpdateError::DowngradeNotAllowed, ErrorKind::InvalidEntry), + (UpdateError::Common(CommonError::OutOfMemory), ErrorKind::OutOfMemory), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/inline/unmutated_diff_config_error.rs b/lib/lib/tests/inline/unmutated_diff_config_error.rs new file mode 100644 index 00000000..4bc6f73b --- /dev/null +++ b/lib/lib/tests/inline/unmutated_diff_config_error.rs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{CommonError, ConfigDigestResolveError, DeployRecordsError, DiffConfigError, SysrootError}; + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + ( + DiffConfigError::Common(CommonError::OutOfMemory), + ErrorKind::OutOfMemory, + ), + ( + DiffConfigError::ConfigDigestNotFound("deadbeef".to_owned()), + ErrorKind::NotFound, + ), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} + +#[test] +fn config_digest_resolve_error_not_found_maps_to_config_digest_not_found() { + let error = ConfigDigestResolveError::NotFound("deadbeef".to_owned()); + + assert_eq!( + DiffConfigError::from(error), + DiffConfigError::ConfigDigestNotFound("deadbeef".to_owned()) + ); +} + +#[test] +fn config_digest_resolve_error_records_delegates_to_the_inner_error() { + let error = ConfigDigestResolveError::Records(DeployRecordsError::Sysroot(SysrootError::MountInfoUnavailable)); + + assert_eq!( + DiffConfigError::from(error), + DiffConfigError::Common(CommonError::Sysroot(SysrootError::MountInfoUnavailable)) + ); +} diff --git a/lib/lib/tests/inline/unmutated_diff_error.rs b/lib/lib/tests/inline/unmutated_diff_error.rs new file mode 100644 index 00000000..39870114 --- /dev/null +++ b/lib/lib/tests/inline/unmutated_diff_error.rs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{CommonError, DiffError}; + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + (DiffError::Common(CommonError::OutOfMemory), ErrorKind::OutOfMemory), + ( + DiffError::ConfigDigestNotFound("deadbeef".to_owned()), + ErrorKind::NotFound, + ), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/inline/unmutated_search_files_error.rs b/lib/lib/tests/inline/unmutated_search_files_error.rs new file mode 100644 index 00000000..0199a101 --- /dev/null +++ b/lib/lib/tests/inline/unmutated_search_files_error.rs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{CommonError, SearchFilesError}; + +#[test] +fn regex_error_maps_to_invalid_search_pattern() { + let invalid_pattern = "("; + let error = regex::Regex::new(invalid_pattern).unwrap_err(); + + assert!(matches!( + SearchFilesError::from(error), + SearchFilesError::InvalidSearchPattern(_) + )); +} + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + ( + SearchFilesError::Common(CommonError::OutOfMemory), + ErrorKind::OutOfMemory, + ), + ( + SearchFilesError::InvalidSearchPattern("(".to_owned()), + ErrorKind::InvalidEntry, + ), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/inline/unmutated_search_in_meta_error.rs b/lib/lib/tests/inline/unmutated_search_in_meta_error.rs new file mode 100644 index 00000000..097ba98d --- /dev/null +++ b/lib/lib/tests/inline/unmutated_search_in_meta_error.rs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{CommonError, SearchInMetaError}; + +#[test] +fn regex_error_maps_to_invalid_search_pattern() { + let invalid_pattern = "("; + let error = regex::Regex::new(invalid_pattern).unwrap_err(); + + assert!(matches!( + SearchInMetaError::from(error), + SearchInMetaError::InvalidSearchPattern(_) + )); +} + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + ( + SearchInMetaError::Common(CommonError::OutOfMemory), + ErrorKind::OutOfMemory, + ), + ( + SearchInMetaError::InvalidSearchPattern("(".to_owned()), + ErrorKind::InvalidEntry, + ), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/inline/unmutated_search_in_package_files_error.rs b/lib/lib/tests/inline/unmutated_search_in_package_files_error.rs new file mode 100644 index 00000000..e8912241 --- /dev/null +++ b/lib/lib/tests/inline/unmutated_search_in_package_files_error.rs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{CommonError, SearchInPackageFilesError}; + +#[test] +fn regex_error_maps_to_invalid_search_pattern() { + let invalid_pattern = "("; + let error = regex::Regex::new(invalid_pattern).unwrap_err(); + + assert!(matches!( + SearchInPackageFilesError::from(error), + SearchInPackageFilesError::InvalidSearchPattern(_) + )); +} + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + ( + SearchInPackageFilesError::Common(CommonError::OutOfMemory), + ErrorKind::OutOfMemory, + ), + ( + SearchInPackageFilesError::InvalidSearchPattern("(".to_owned()), + ErrorKind::InvalidEntry, + ), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} diff --git a/lib/lib/tests/inline/unmutated_search_meta_error.rs b/lib/lib/tests/inline/unmutated_search_meta_error.rs new file mode 100644 index 00000000..591bcc4e --- /dev/null +++ b/lib/lib/tests/inline/unmutated_search_meta_error.rs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; + +use super::{CommonError, SearchMetaError}; + +#[test] +fn regex_error_maps_to_invalid_search_pattern() { + let invalid_pattern = "("; + let error = regex::Regex::new(invalid_pattern).unwrap_err(); + + assert!(matches!( + SearchMetaError::from(error), + SearchMetaError::InvalidSearchPattern(_) + )); +} + +#[test] +fn every_own_variant_maps_to_the_documented_error_kind() { + let cases = [ + ( + SearchMetaError::Common(CommonError::OutOfMemory), + ErrorKind::OutOfMemory, + ), + ( + SearchMetaError::InvalidSearchPattern("(".to_owned()), + ErrorKind::InvalidEntry, + ), + ]; + + for (error, expected) in cases { + assert_eq!(ErrorKind::from(error), expected); + } +} From 0787e9d3e20bd33f5993744a9cefe58f629bc75b Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 02:39:39 +0400 Subject: [PATCH 032/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/TODO.md b/TODO.md index 2c541a4e..6f9c4819 100644 --- a/TODO.md +++ b/TODO.md @@ -10,12 +10,17 @@ Near-term, concrete items. See `ROADMAP.md` for the bigger picture. ## upac-lib -Test-coverage pass in progress, going file by file through the non-command core first -(`errors.rs`/`lock.rs`/`search.rs`/`fs.rs`/`orchestrator/*`/`database/*`/`deploy/*`/`scripts/*`/ -`plugin/decoder/{error,manifest,triggers}.rs`/`plugin/boot/{error,manifest}.rs` done — -`plugin/decoder/unpack.rs`/`plugin/decoder/mod.rs`/`plugin/boot/mod.rs` skipped, need a real -dlopen'd/`builtin-*` plugin), commands (`mutated`/`unmutated`) last. Remaining core files not yet -visited: `composefs/{diff,error,mod}.rs`, `config/mod.rs`, `boot/{error,mod}.rs`. +Test-coverage pass in progress. The entire non-command core is covered (`errors.rs`/`lock.rs`/ +`search.rs`/`fs.rs`/`orchestrator/*`/`database/*`/`deploy/*`/`scripts/*`/`composefs/*`/`config/*`/ +`boot/*`/`plugin/decoder/{error,manifest,triggers}.rs`/`plugin/boot/{error,manifest}.rs`), except +`plugin/decoder/unpack.rs`/`plugin/decoder/mod.rs`/`plugin/boot/mod.rs` (need a real dlopen'd/ +`builtin-*` plugin) and `deploy/esp.rs` (real mount table) — both explicit, justified skips. Every +`mutated`/`unmutated` command's own `Error` enum is also now covered (inline tests next to +each `error.rs`, since `mutated`/`unmutated` aren't `pub`) — only each variant's own logic, not the +macro-generated `Common(...)` delegation shared with `errors.rs`'s already-tested `CommonError`. +Remaining: the `Stage::run()` bodies themselves — each needs a real composefs `Repository`/`Deploy`/ +database in context, likely out of scope for unit tests unless a pure-logic helper turns out to be +extractable. **UKI A/B boot (`upac-from.efi`/`upac-to.efi`) confirm-boot service not designed yet**: after a successful boot, something needs to confirm once, swap `to`↔`from`, and set the normal persistent @@ -26,17 +31,3 @@ boot order. Nothing calls `Booter::confirm_boot` anywhere yet; this belongs to a `esp_loader_source`), grub needs a real `grub-install`-equivalent (target-specific generated `grubx64.efi`, not a plain file copy) — out of scope for now; either shell out to `grub-install` against the mounted ESP, or explicitly document grub as unsupported for genesis whole-disk mode. - -**Genesis-produced disks don't actually boot into the installed system yet**: a plain partition -mount isn't how composefs systems boot — nothing in this project resolves `composefs.digest=` -(the kernel cmdline param `write_boot_entry` already writes) against the on-disk repository, mounts -the erofs image with fs-verity, and overlays `state/deploy//etc/`. **Found a real, existing -upstream tool for exactly this**: `composefs-setup-root` (crates.io, same `composefs-rs` -project/version as our `composefs`/`composefs-boot` deps) — a Rust binary, not something we'd write -ourselves. What's still missing: the actual boot-time integration — the live VM's initramfs is -systemd-based (mkinitcpio's `systemd` hook, not classic busybox-style hooks), so this needs a -systemd unit ordered between `sysroot.mount` and `initrd-switch-root.target` (same role as ostree's -`ostree-prepare-root.service`), not a classic mkinitcpio hook script. Also unresolved: whether upac -needs to ship/package this integration itself, or whether it's expected to already exist on the -source distro (same assumption as the systemd-boot/rEFInd binary copy above) — needs checking -whether Arch/AUR already has a package for this. From 2193620ded3da4239c13ee0a22bca6eb16b77b66 Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 02:52:39 +0400 Subject: [PATCH 033/148] fix: updated Booter structure and trait Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/boot.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/abi/src/boot.rs b/lib/abi/src/boot.rs index d1320401..53676c2b 100644 --- a/lib/abi/src/boot.rs +++ b/lib/abi/src/boot.rs @@ -20,6 +20,8 @@ pub type EspLoaderSourceFn = unsafe extern "C" fn() -> CSlice; pub type RegisterBootSlotsFn = unsafe extern "C" fn(request: *const CBootSlotsRequest, err_out: *mut ErrorKind) -> i32; +pub type InstallFn = unsafe extern "C" fn(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32; + pub trait Booter: Sized { type Error; @@ -36,6 +38,8 @@ pub trait Booter: Sized { &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, ) -> Result<(), Self::Error>; + + fn install(&mut self, esp_mount_point: &str) -> Result<(), Self::Error>; } #[repr(C)] @@ -43,7 +47,7 @@ pub trait Booter: Sized { pub struct CBootPluginRequest { pub struct_size: usize, - pub entry_name: CSlice, + pub value: CSlice, } #[repr(C)] From c882d56d7f659988f421f0f9136a4c02a3e4aa32 Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 02:55:52 +0400 Subject: [PATCH 034/148] fix: added plugins for installation fix: added plugins for installation Co-Authored-By: Claude Sonnet 5 --- booters/booter.toml | 13 ++++++++++ booters/grub/src/backend.rs | 37 ++++++++++++++++++++++++----- booters/grub/src/lib.rs | 30 +++++++++++++++++++---- booters/refind/src/backend.rs | 6 +++++ booters/refind/src/lib.rs | 10 +++++++- booters/systemd-boot/src/backend.rs | 6 +++++ booters/systemd-boot/src/lib.rs | 10 +++++++- booters/uki/src/backend.rs | 6 +++++ booters/uki/src/lib.rs | 10 +++++++- 9 files changed, 115 insertions(+), 13 deletions(-) diff --git a/booters/booter.toml b/booters/booter.toml index 366bd028..88c055f0 100644 --- a/booters/booter.toml +++ b/booters/booter.toml @@ -51,6 +51,15 @@ source = "usr/lib/systemd/boot/efi/systemd-bootx64.efi" # ship grub-reboot/grub-set-default writing /boot/grub/grubenv, while Fedora/RHEL keep the # "grub-*" names reserved for legacy GRUB Legacy and ship grub2-reboot/grub2-set-default writing # /boot/grub2/grubenv instead. *_primary/*_fallback are tried in that order. +# +# install_bin_primary/install_bin_fallback: same Debian/Arch-vs-Fedora naming split as the +# reboot/set-default tools above, for the one-time `grub-install` genesis runs against a freshly +# partitioned ESP. install_target is grub's own `--target` platform name (this project is +# UEFI/x86_64-only throughout, matching systemd_boot/refind's own *_x64 binary names below). +# install_bootloader_id is grub's `--bootloader-id`, used only to name the install's own +# NVRAM-independent EFI/BOOT/BOOTX64.EFI fallback copy (see backend.rs's `--removable --no-nvram` +# — genesis relies on the same firmware fallback path uki/systemd-boot/refind already use via +# esp_loader_source, not a Boot#### entry, so a fresh disk boots without any NVRAM setup). [grub] grubenv_primary = "/boot/grub/grubenv" grubenv_fallback = "/boot/grub2/grubenv" @@ -58,6 +67,10 @@ reboot_bin_primary = "grub-reboot" reboot_bin_fallback = "grub2-reboot" set_default_bin_primary = "grub-set-default" set_default_bin_fallback = "grub2-set-default" +install_bin_primary = "grub-install" +install_bin_fallback = "grub2-install" +install_target = "x86_64-efi" +install_bootloader_id = "upac" # rEFInd has no separate one-shot/persistent pair of variables like systemd-boot's # LoaderEntryOneShot/LoaderEntryDefault — it has exactly one, PreviousBoot, which it writes itself diff --git a/booters/grub/src/backend.rs b/booters/grub/src/backend.rs index 9e29b361..507622af 100644 --- a/booters/grub/src/backend.rs +++ b/booters/grub/src/backend.rs @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception +use std::fs::{create_dir_all, write}; use std::io::ErrorKind as IoErrorKind; use std::path::Path; use std::process::Command; @@ -11,10 +12,12 @@ use upac_abi::boot::Booter; use crate::error::GrubError; use crate::grub::{ - GRUBENV_FALLBACK, GRUBENV_PRIMARY, REBOOT_BIN_FALLBACK, REBOOT_BIN_PRIMARY, SET_DEFAULT_BIN_FALLBACK, - SET_DEFAULT_BIN_PRIMARY, + GRUBENV_FALLBACK, GRUBENV_PRIMARY, INSTALL_BIN_FALLBACK, INSTALL_BIN_PRIMARY, INSTALL_BOOTLOADER_ID, + INSTALL_TARGET, REBOOT_BIN_FALLBACK, REBOOT_BIN_PRIMARY, SET_DEFAULT_BIN_FALLBACK, SET_DEFAULT_BIN_PRIMARY, }; +const GRUB_CFG_CONTENTS: &str = "insmod blscfg\nblscfg\n"; + pub struct Grub; impl Booter for Grub { @@ -29,11 +32,11 @@ impl Booter for Grub { } fn set_one_shot(&mut self, entry_name: &str) -> Result<(), GrubError> { - self.run_first_available([REBOOT_BIN_PRIMARY, REBOOT_BIN_FALLBACK], entry_name) + self.run_first_available([REBOOT_BIN_PRIMARY, REBOOT_BIN_FALLBACK], &[entry_name]) } fn confirm_boot(&mut self, entry_name: &str) -> Result<(), GrubError> { - self.run_first_available([SET_DEFAULT_BIN_PRIMARY, SET_DEFAULT_BIN_FALLBACK], entry_name) + self.run_first_available([SET_DEFAULT_BIN_PRIMARY, SET_DEFAULT_BIN_FALLBACK], &[entry_name]) } fn register_boot_slots( @@ -51,12 +54,34 @@ impl Booter for Grub { Ok(()) } + + fn install(&mut self, esp_mount_point: &str) -> Result<(), GrubError> { + self.run_first_available( + [INSTALL_BIN_PRIMARY, INSTALL_BIN_FALLBACK], + &[ + &format!("--target={INSTALL_TARGET}"), + &format!("--efi-directory={esp_mount_point}"), + &format!("--boot-directory={esp_mount_point}"), + &format!("--bootloader-id={INSTALL_BOOTLOADER_ID}"), + "--removable", + "--no-nvram", + ], + )?; + + let grub_cfg = Path::new(esp_mount_point).join("grub").join("grub.cfg"); + if let Some(parent) = grub_cfg.parent() { + create_dir_all(parent)?; + } + write(&grub_cfg, GRUB_CFG_CONTENTS)?; + + Ok(()) + } } impl Grub { - fn run_first_available(&self, candidates: [&str; 2], entry_name: &str) -> Result<(), GrubError> { + fn run_first_available(&self, candidates: [&str; 2], args: &[&str]) -> Result<(), GrubError> { for candidate in candidates { - match Command::new(candidate).arg(entry_name).status() { + match Command::new(candidate).args(args).status() { Ok(status) if status.success() => return Ok(()), Ok(_) => return Err(GrubError::Unexpected), Err(error) if error.kind() == IoErrorKind::NotFound => continue, diff --git a/booters/grub/src/lib.rs b/booters/grub/src/lib.rs index 0bb7930d..a685aa41 100644 --- a/booters/grub/src/lib.rs +++ b/booters/grub/src/lib.rs @@ -49,7 +49,7 @@ pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginRequest, err_ou return -1; } - let result = entry_name_from_request(unsafe { &*request }) + let result = string_from_request(unsafe { &*request }) .and_then(|entry_name| Grub::new().and_then(|mut grub| grub.set_one_shot(&entry_name))); match result { @@ -71,7 +71,7 @@ pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_ou return -1; } - let result = entry_name_from_request(unsafe { &*request }) + let result = string_from_request(unsafe { &*request }) .and_then(|entry_name| Grub::new().and_then(|mut grub| grub.confirm_boot(&entry_name))); match result { @@ -90,8 +90,30 @@ pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, 0 } -fn entry_name_from_request(request: &CBootPluginRequest) -> Result { - let bytes = unsafe { request.entry_name.as_borrowed() }; +/// # Safety +/// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the +/// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn install(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32 { + if request.is_null() { + write_error(err_out, GrubError::InvalidRequest); + return -1; + } + + let result = string_from_request(unsafe { &*request }) + .and_then(|esp_mount_point| Grub::new().and_then(|mut grub| grub.install(&esp_mount_point))); + + match result { + Ok(()) => 0, + Err(error) => { + write_error(err_out, error); + -1 + } + } +} + +fn string_from_request(request: &CBootPluginRequest) -> Result { + let bytes = unsafe { request.value.as_borrowed() }; from_utf8(bytes) .map(str::to_owned) diff --git a/booters/refind/src/backend.rs b/booters/refind/src/backend.rs index 3b8dd748..8fdb78dc 100644 --- a/booters/refind/src/backend.rs +++ b/booters/refind/src/backend.rs @@ -80,6 +80,12 @@ impl Booter for Refind { Ok(()) } + + fn install(&mut self, esp_mount_point: &str) -> Result<(), RefindError> { + let _ = esp_mount_point; + + Ok(()) + } } impl Refind { diff --git a/booters/refind/src/lib.rs b/booters/refind/src/lib.rs index 3022c9ea..616630f3 100644 --- a/booters/refind/src/lib.rs +++ b/booters/refind/src/lib.rs @@ -90,8 +90,16 @@ pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, 0 } +/// # Safety +/// Touches no pointers — rEFInd has nothing to install onto a pre-existing ESP, always succeeds +/// (its binary is copied from the source package tree via `esp_loader_source` instead). +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn install(_request: *const CBootPluginRequest, _err_out: *mut ErrorKind) -> i32 { + 0 +} + fn entry_name_from_request(request: &CBootPluginRequest) -> Result { - let bytes = unsafe { request.entry_name.as_borrowed() }; + let bytes = unsafe { request.value.as_borrowed() }; from_utf8(bytes) .map(str::to_owned) diff --git a/booters/systemd-boot/src/backend.rs b/booters/systemd-boot/src/backend.rs index 1b4750f5..994b0ae0 100644 --- a/booters/systemd-boot/src/backend.rs +++ b/booters/systemd-boot/src/backend.rs @@ -82,6 +82,12 @@ impl Booter for Bls { Ok(()) } + + fn install(&mut self, esp_mount_point: &str) -> Result<(), BlsError> { + let _ = esp_mount_point; + + Ok(()) + } } impl Bls { diff --git a/booters/systemd-boot/src/lib.rs b/booters/systemd-boot/src/lib.rs index fe0b4a8d..96529a94 100644 --- a/booters/systemd-boot/src/lib.rs +++ b/booters/systemd-boot/src/lib.rs @@ -90,8 +90,16 @@ pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, 0 } +/// # Safety +/// Touches no pointers — systemd-boot has nothing to install onto a pre-existing ESP, always +/// succeeds (its binary is copied from the source package tree via `esp_loader_source` instead). +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn install(_request: *const CBootPluginRequest, _err_out: *mut ErrorKind) -> i32 { + 0 +} + fn entry_name_from_request(request: &CBootPluginRequest) -> Result { - let bytes = unsafe { request.entry_name.as_borrowed() }; + let bytes = unsafe { request.value.as_borrowed() }; from_utf8(bytes) .map(str::to_owned) diff --git a/booters/uki/src/backend.rs b/booters/uki/src/backend.rs index a10b53fb..3e494c9f 100644 --- a/booters/uki/src/backend.rs +++ b/booters/uki/src/backend.rs @@ -119,6 +119,12 @@ impl Booter for Uki { Ok(()) } + + fn install(&mut self, esp_mount_point: &str) -> Result<(), UkiError> { + let _ = esp_mount_point; + + Ok(()) + } } impl Uki { diff --git a/booters/uki/src/lib.rs b/booters/uki/src/lib.rs index 7d0ea2c3..b888bd81 100644 --- a/booters/uki/src/lib.rs +++ b/booters/uki/src/lib.rs @@ -117,8 +117,16 @@ pub unsafe extern "C" fn register_boot_slots(request: *const CBootSlotsRequest, } } +/// # Safety +/// Touches no pointers — uki has nothing to install onto a pre-existing ESP, always succeeds +/// (its binary is copied from the source package tree via `esp_loader_source` instead). +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn install(_request: *const CBootPluginRequest, _err_out: *mut ErrorKind) -> i32 { + 0 +} + fn entry_name_from_request(request: &CBootPluginRequest) -> Result { - let bytes = unsafe { request.entry_name.as_borrowed() }; + let bytes = unsafe { request.value.as_borrowed() }; from_utf8(bytes) .map(str::to_owned) From 6d966a74cc1b0de39d9a6f9b19802d795fe1d55d Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 02:56:16 +0400 Subject: [PATCH 035/148] fix: updated boot plugin contracts Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/plugin/boot/mod.rs | 41 +++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/lib/src/plugin/boot/mod.rs b/lib/lib/src/plugin/boot/mod.rs index 22fd43f9..90ce2854 100644 --- a/lib/lib/src/plugin/boot/mod.rs +++ b/lib/lib/src/plugin/boot/mod.rs @@ -6,7 +6,8 @@ use std::mem::MaybeUninit; use upac_abi::boot::{ - CBootPluginRequest, CBootSlotsRequest, ConfirmBootFn, EspLoaderSourceFn, ProbeFn, RegisterBootSlotsFn, SetOneShotFn, + CBootPluginRequest, CBootSlotsRequest, ConfirmBootFn, EspLoaderSourceFn, InstallFn, ProbeFn, RegisterBootSlotsFn, + SetOneShotFn, }; use upac_abi::error::ErrorKind; use upac_abi::types::{CBorrowed, CSlice}; @@ -27,27 +28,27 @@ use crate::plugin::boot::manifest::load_boot_plugin_manifests; #[cfg(feature = "builtin-grub")] use upac_boot_grub::{ - confirm_boot as grub_confirm_boot, esp_loader_source as grub_esp_loader_source, probe as grub_probe, - register_boot_slots as grub_register_boot_slots, set_one_shot as grub_set_one_shot, + confirm_boot as grub_confirm_boot, esp_loader_source as grub_esp_loader_source, install as grub_install, + probe as grub_probe, register_boot_slots as grub_register_boot_slots, set_one_shot as grub_set_one_shot, }; #[cfg(feature = "builtin-systemd-boot")] use upac_boot_systemd_boot::{ confirm_boot as systemd_boot_confirm_boot, esp_loader_source as systemd_boot_esp_loader_source, - probe as systemd_boot_probe, register_boot_slots as systemd_boot_register_boot_slots, - set_one_shot as systemd_boot_set_one_shot, + install as systemd_boot_install, probe as systemd_boot_probe, + register_boot_slots as systemd_boot_register_boot_slots, set_one_shot as systemd_boot_set_one_shot, }; #[cfg(feature = "builtin-uki")] use upac_boot_uki::{ - confirm_boot as uki_confirm_boot, esp_loader_source as uki_esp_loader_source, probe as uki_probe, - register_boot_slots as uki_register_boot_slots, set_one_shot as uki_set_one_shot, + confirm_boot as uki_confirm_boot, esp_loader_source as uki_esp_loader_source, install as uki_install, + probe as uki_probe, register_boot_slots as uki_register_boot_slots, set_one_shot as uki_set_one_shot, }; #[cfg(feature = "builtin-refind")] use upac_boot_refind::{ - confirm_boot as refind_confirm_boot, esp_loader_source as refind_esp_loader_source, probe as refind_probe, - register_boot_slots as refind_register_boot_slots, set_one_shot as refind_set_one_shot, + confirm_boot as refind_confirm_boot, esp_loader_source as refind_esp_loader_source, install as refind_install, + probe as refind_probe, register_boot_slots as refind_register_boot_slots, set_one_shot as refind_set_one_shot, }; pub mod error; @@ -59,7 +60,7 @@ pub mod manifest; impl BootPlugin { fn from_static( probe: ProbeFn, set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, esp_loader_source: EspLoaderSourceFn, - register_boot_slots: RegisterBootSlotsFn, + register_boot_slots: RegisterBootSlotsFn, install: InstallFn, ) -> Self { BootPlugin { probe, @@ -67,6 +68,7 @@ impl BootPlugin { confirm_boot, esp_loader_source, register_boot_slots, + install, #[cfg(feature = "dynamic-plugins")] _library: None, @@ -189,6 +191,7 @@ fn static_plugins() -> Vec<(&'static str, BootPlugin)> { uki_confirm_boot, uki_esp_loader_source, uki_register_boot_slots, + uki_install, ), )); @@ -201,6 +204,7 @@ fn static_plugins() -> Vec<(&'static str, BootPlugin)> { systemd_boot_confirm_boot, systemd_boot_esp_loader_source, systemd_boot_register_boot_slots, + systemd_boot_install, ), )); @@ -213,6 +217,7 @@ fn static_plugins() -> Vec<(&'static str, BootPlugin)> { grub_confirm_boot, grub_esp_loader_source, grub_register_boot_slots, + grub_install, ), )); @@ -225,6 +230,7 @@ fn static_plugins() -> Vec<(&'static str, BootPlugin)> { refind_confirm_boot, refind_esp_loader_source, refind_register_boot_slots, + refind_install, ), )); @@ -244,6 +250,7 @@ pub struct BootPlugin { confirm_boot: ConfirmBootFn, esp_loader_source: EspLoaderSourceFn, register_boot_slots: RegisterBootSlotsFn, + install: InstallFn, #[cfg(feature = "dynamic-plugins")] _library: Option, @@ -260,6 +267,7 @@ impl BootPlugin { let confirm_boot: ConfirmBootFn = unsafe { load_symbol(&library, "confirm_boot")? }; let esp_loader_source: EspLoaderSourceFn = unsafe { load_symbol(&library, "esp_loader_source")? }; let register_boot_slots: RegisterBootSlotsFn = unsafe { load_symbol(&library, "register_boot_slots")? }; + let install: InstallFn = unsafe { load_symbol(&library, "install")? }; let got = unsafe { abi_version() }; if got != BOOT_ABI_VERSION { @@ -275,6 +283,7 @@ impl BootPlugin { confirm_boot, esp_loader_source, register_boot_slots, + install, _library: Some(library), }) } @@ -336,4 +345,16 @@ impl BootPlugin { Ok(()) } + + pub fn install(&self, esp_mount_point: &str) -> Result<(), BootPluginError> { + let request = CBootPluginRequest::new(CSlice::from_borrowed(esp_mount_point.as_bytes())); + let mut error = MaybeUninit::::uninit(); + + let code = unsafe { (self.install)(&request, error.as_mut_ptr()) }; + if code != 0 { + return Err(BootPluginError::Reported(unsafe { error.assume_init() })); + } + + Ok(()) + } } From 233f0b92bf8c5fea81347eca589316000b6dc471 Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 02:57:25 +0400 Subject: [PATCH 036/148] fix: update install for grub Co-Authored-By: Claude Sonnet 5 --- lib/setup/src/genesis/entry.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/setup/src/genesis/entry.rs b/lib/setup/src/genesis/entry.rs index c726405c..1e7c8eb0 100644 --- a/lib/setup/src/genesis/entry.rs +++ b/lib/setup/src/genesis/entry.rs @@ -46,6 +46,9 @@ impl Stage for StageBootStage { let plugin = resolve_boot_plugin(BOOT_PLUGINS_DIR, MANIFEST_EXTENSION, input.boot_plugin.as_deref())?; + let esp_mount_point = target.esp_mount_point().to_string_lossy().into_owned(); + plugin.install(&esp_mount_point)?; + if let Some(candidate) = plugin.esp_loader_source() { let handle = FileHandle::new(candidate); if handle.stat_in_tree(&prefix_tree).is_ok() { From 4cef362dd6a30d8a33dc75048df86001cf2575e8 Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 02:58:24 +0400 Subject: [PATCH 037/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/TODO.md b/TODO.md index 6f9c4819..72b55b8b 100644 --- a/TODO.md +++ b/TODO.md @@ -26,8 +26,3 @@ extractable. successful boot, something needs to confirm once, swap `to`↔`from`, and set the normal persistent boot order. Nothing calls `Booter::confirm_boot` anywhere yet; this belongs to a not-yet-designed "confirm boot" systemd service, not genesis or the ordinary install/update pipeline. - -**grub genesis support still not handled**: unlike systemd-boot/rEFInd (binary-copy via -`esp_loader_source`), grub needs a real `grub-install`-equivalent (target-specific generated -`grubx64.efi`, not a plain file copy) — out of scope for now; either shell out to `grub-install` -against the mounted ESP, or explicitly document grub as unsupported for genesis whole-disk mode. From 17ac1c540245ecb6cfbb0d9d434e70d83cb49c92 Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 03:05:01 +0400 Subject: [PATCH 038/148] fix: removed the 'config' folder as it was unnecessary Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/{config/merge.rs => config.rs} | 0 lib/lib/src/config/mod.rs | 6 ------ lib/lib/src/mutated/installer/merge.rs | 2 +- lib/lib/src/mutated/uninstaller/merge.rs | 2 +- lib/lib/src/mutated/update/merge.rs | 2 +- lib/lib/tests/config_merge.rs | 2 +- 6 files changed, 4 insertions(+), 10 deletions(-) rename lib/lib/src/{config/merge.rs => config.rs} (100%) delete mode 100644 lib/lib/src/config/mod.rs diff --git a/lib/lib/src/config/merge.rs b/lib/lib/src/config.rs similarity index 100% rename from lib/lib/src/config/merge.rs rename to lib/lib/src/config.rs diff --git a/lib/lib/src/config/mod.rs b/lib/lib/src/config/mod.rs deleted file mode 100644 index 177dfc90..00000000 --- a/lib/lib/src/config/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -pub mod merge; diff --git a/lib/lib/src/mutated/installer/merge.rs b/lib/lib/src/mutated/installer/merge.rs index 906fcaf2..0976a69e 100644 --- a/lib/lib/src/mutated/installer/merge.rs +++ b/lib/lib/src/mutated/installer/merge.rs @@ -12,7 +12,7 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; use crate::composefs::overlay::{apply_overlay_upper, apply_tree_overlay}; use crate::composefs::repository::commit_tree; -use crate::config::merge::merge_config; +use crate::config::merge_config; use crate::database::error::DeployRecordError; use crate::database::record::DeployRecord; use crate::deploy::Deploy; diff --git a/lib/lib/src/mutated/uninstaller/merge.rs b/lib/lib/src/mutated/uninstaller/merge.rs index baf84bc1..4a14086c 100644 --- a/lib/lib/src/mutated/uninstaller/merge.rs +++ b/lib/lib/src/mutated/uninstaller/merge.rs @@ -13,7 +13,7 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; use crate::composefs::file::FileHandle; use crate::composefs::overlay::apply_overlay_upper; use crate::composefs::repository::commit_tree; -use crate::config::merge::merge_config; +use crate::config::merge_config; use crate::database::error::DeployRecordError; use crate::database::record::DeployRecord; use crate::deploy::Deploy; diff --git a/lib/lib/src/mutated/update/merge.rs b/lib/lib/src/mutated/update/merge.rs index 76c1efd0..3a2791b1 100644 --- a/lib/lib/src/mutated/update/merge.rs +++ b/lib/lib/src/mutated/update/merge.rs @@ -13,7 +13,7 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; use crate::composefs::file::FileHandle; use crate::composefs::overlay::{apply_overlay_upper, apply_tree_overlay}; use crate::composefs::repository::commit_tree; -use crate::config::merge::merge_config; +use crate::config::merge_config; use crate::database::error::DeployRecordError; use crate::database::record::DeployRecord; use crate::deploy::Deploy; diff --git a/lib/lib/tests/config_merge.rs b/lib/lib/tests/config_merge.rs index c8383bb7..1769b883 100644 --- a/lib/lib/tests/config_merge.rs +++ b/lib/lib/tests/config_merge.rs @@ -12,7 +12,7 @@ use nix::fcntl::AT_FDCWD; use tempfile::{Builder, TempDir}; use upac::composefs::file::FileHandle; use upac::composefs::repository::ObjectID; -use upac::config::merge::merge_config; +use upac::config::merge_config; fn scratch_dir(name: &str) -> TempDir { Builder::new().prefix(name).tempdir().unwrap() From 85de292354eb5e4ad15114b7154439350b97d12e Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 03:05:13 +0400 Subject: [PATCH 039/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index 72b55b8b..40dab58f 100644 --- a/TODO.md +++ b/TODO.md @@ -22,7 +22,25 @@ Remaining: the `Stage::run()` bodies themselves — each needs a real composefs database in context, likely out of scope for unit tests unless a pure-logic helper turns out to be extractable. -**UKI A/B boot (`upac-from.efi`/`upac-to.efi`) confirm-boot service not designed yet**: after a -successful boot, something needs to confirm once, swap `to`↔`from`, and set the normal persistent -boot order. Nothing calls `Booter::confirm_boot` anywhere yet; this belongs to a not-yet-designed -"confirm boot" systemd service, not genesis or the ordinary install/update pipeline. +**Two standalone boot-time services still need to be built** — neither is upac-lib/upac-cli code, +both run on the installed system itself, outside anything `up`/`up-sp` invokes: + +- **composefs-mount boot hook**: nothing yet resolves `composefs.digest=` (the kernel cmdline + param `write_boot_entry` already writes) against the on-disk repository, mounts the erofs image + with fs-verity, and overlays `state/deploy//etc/` — without this, a genesis-produced disk's + firmware boots the kernel, but the initramfs has no way to actually assemble the root. The upstream + tool for this already exists (`composefs-setup-root`, crates.io, same `composefs-rs` project as + our `composefs`/`composefs-boot` deps) — what's missing is the systemd-unit integration (ordered + between `sysroot.mount` and `initrd-switch-root.target`, same role as ostree's + `ostree-prepare-root.service`; the live VM's initramfs is systemd-based, not classic mkinitcpio + hooks). Also unresolved: whether upac ships/packages this integration itself or expects it to + already exist on the source distro. +- **UKI A/B confirm-boot service**: after a successful boot, something needs to confirm once, swap + `to`↔`from`, and set the normal persistent boot order. Nothing calls `Booter::confirm_boot` + anywhere yet. + +**`lib/lib/src/plugin/` needs a readability pass**: grown too dense to follow, `plugin/boot/mod.rs` +in particular (360 lines — static-link wiring for all 4 plugins, dynamic dlopen loading, and the +`BootPlugin` public API all crammed into one file). Split by concern (mirror the existing +folder-per-logical-unit convention used elsewhere in this crate) and simplify — right now it takes +real effort to follow what calls what. From 05057eef5ddc5c496ee016cefdca7165df9dec5d Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 03:14:46 +0400 Subject: [PATCH 040/148] fix: split up the large mod.rs for the plugin fix: updated lib.toml in accordance with xtask guidelines Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/plugin/boot/dynamic_link.rs | 52 ++++++++ lib/lib/src/plugin/boot/mod.rs | 169 ++---------------------- lib/lib/src/plugin/boot/static_link.rs | 118 +++++++++++++++++ lib/setup/lib.toml | 5 +- 4 files changed, 182 insertions(+), 162 deletions(-) create mode 100644 lib/lib/src/plugin/boot/dynamic_link.rs create mode 100644 lib/lib/src/plugin/boot/static_link.rs diff --git a/lib/lib/src/plugin/boot/dynamic_link.rs b/lib/lib/src/plugin/boot/dynamic_link.rs new file mode 100644 index 00000000..081428ee --- /dev/null +++ b/lib/lib/src/plugin/boot/dynamic_link.rs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use libloading::Library; + +use upac_abi::BOOT_ABI_VERSION; +use upac_abi::boot::{ + AbiVersionFn, ConfirmBootFn, EspLoaderSourceFn, InstallFn, ProbeFn, RegisterBootSlotsFn, SetOneShotFn, +}; + +use super::BootPlugin; +use super::error::BootPluginError; + +unsafe fn load_symbol(library: &Library, name: &str) -> Result { + unsafe { library.get::(name.as_bytes()) } + .map(|symbol| *symbol) + .map_err(|_| BootPluginError::Symbol) +} + +impl BootPlugin { + pub(super) fn load(library_name: &str) -> Result { + let library = unsafe { Library::new(library_name) }.map_err(|_| BootPluginError::Load)?; + + let abi_version: AbiVersionFn = unsafe { load_symbol(&library, "abi_version")? }; + let probe: ProbeFn = unsafe { load_symbol(&library, "probe")? }; + let set_one_shot: SetOneShotFn = unsafe { load_symbol(&library, "set_one_shot")? }; + let confirm_boot: ConfirmBootFn = unsafe { load_symbol(&library, "confirm_boot")? }; + let esp_loader_source: EspLoaderSourceFn = unsafe { load_symbol(&library, "esp_loader_source")? }; + let register_boot_slots: RegisterBootSlotsFn = unsafe { load_symbol(&library, "register_boot_slots")? }; + let install: InstallFn = unsafe { load_symbol(&library, "install")? }; + + let got = unsafe { abi_version() }; + if got != BOOT_ABI_VERSION { + return Err(BootPluginError::AbiMismatch { + got, + expected: BOOT_ABI_VERSION, + }); + } + + Ok(BootPlugin { + probe, + set_one_shot, + confirm_boot, + esp_loader_source, + register_boot_slots, + install, + _library: Some(library), + }) + } +} diff --git a/lib/lib/src/plugin/boot/mod.rs b/lib/lib/src/plugin/boot/mod.rs index 90ce2854..50fd5a76 100644 --- a/lib/lib/src/plugin/boot/mod.rs +++ b/lib/lib/src/plugin/boot/mod.rs @@ -17,70 +17,26 @@ use crate::plugin::boot::error::BootPluginError; #[cfg(feature = "dynamic-plugins")] use libloading::Library; -#[cfg(feature = "dynamic-plugins")] -use upac_abi::BOOT_ABI_VERSION; - -#[cfg(feature = "dynamic-plugins")] -use upac_abi::boot::AbiVersionFn; - #[cfg(feature = "dynamic-plugins")] use crate::plugin::boot::manifest::load_boot_plugin_manifests; -#[cfg(feature = "builtin-grub")] -use upac_boot_grub::{ - confirm_boot as grub_confirm_boot, esp_loader_source as grub_esp_loader_source, install as grub_install, - probe as grub_probe, register_boot_slots as grub_register_boot_slots, set_one_shot as grub_set_one_shot, -}; - -#[cfg(feature = "builtin-systemd-boot")] -use upac_boot_systemd_boot::{ - confirm_boot as systemd_boot_confirm_boot, esp_loader_source as systemd_boot_esp_loader_source, - install as systemd_boot_install, probe as systemd_boot_probe, - register_boot_slots as systemd_boot_register_boot_slots, set_one_shot as systemd_boot_set_one_shot, -}; - -#[cfg(feature = "builtin-uki")] -use upac_boot_uki::{ - confirm_boot as uki_confirm_boot, esp_loader_source as uki_esp_loader_source, install as uki_install, - probe as uki_probe, register_boot_slots as uki_register_boot_slots, set_one_shot as uki_set_one_shot, -}; - -#[cfg(feature = "builtin-refind")] -use upac_boot_refind::{ - confirm_boot as refind_confirm_boot, esp_loader_source as refind_esp_loader_source, install as refind_install, - probe as refind_probe, register_boot_slots as refind_register_boot_slots, set_one_shot as refind_set_one_shot, -}; - pub mod error; #[cfg(feature = "dynamic-plugins")] pub mod manifest; +#[cfg(feature = "dynamic-plugins")] +mod dynamic_link; + #[cfg(feature = "builtin-booters")] -impl BootPlugin { - fn from_static( - probe: ProbeFn, set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, esp_loader_source: EspLoaderSourceFn, - register_boot_slots: RegisterBootSlotsFn, install: InstallFn, - ) -> Self { - BootPlugin { - probe, - set_one_shot, - confirm_boot, - esp_loader_source, - register_boot_slots, - install, - - #[cfg(feature = "dynamic-plugins")] - _library: None, - } - } -} +mod static_link; /// Resolves a boot plugin by loading shared objects described by on-disk manifests. /// /// Built with `dynamic-plugins`: plugins are discovered at runtime from /// `boot_plugins_dir`. Any plugin compiled in via `builtin-*` is still reachable -/// through [`static_plugins`], but on-disk manifests take part in the same search. +/// through [`static_link::static_plugins`], but on-disk manifests take part in the +/// same search. #[cfg(feature = "dynamic-plugins")] pub fn resolve_boot_plugin( boot_plugins_dir: &str, manifest_extension: &str, requested: Option<&str>, @@ -94,7 +50,7 @@ pub fn resolve_boot_plugin( } #[cfg(feature = "builtin-booters")] - if let Some((_, plugin)) = static_plugins() + if let Some((_, plugin)) = static_link::static_plugins() .into_iter() .find(|(plugin_name, _)| *plugin_name == name) { @@ -113,7 +69,7 @@ pub fn resolve_boot_plugin( } #[cfg(feature = "builtin-booters")] - for (_, plugin) in static_plugins() { + for (_, plugin) in static_link::static_plugins() { if plugin.probes() { claimants.push(plugin); } @@ -149,7 +105,7 @@ pub fn resolve_boot_plugin( #[cfg(feature = "builtin-booters")] { - let plugins = static_plugins(); + let plugins = static_link::static_plugins(); match requested { Some(name) => plugins @@ -170,80 +126,6 @@ pub fn resolve_boot_plugin( } } -/// The boot plugins linked into this build, in probe order. -/// -/// No ABI version check is performed here: these are compiled from the same source -/// tree by the same compiler, so [`BOOT_ABI_VERSION`] matches by construction. -#[cfg(feature = "builtin-booters")] -#[allow( - clippy::vec_init_then_push, - reason = "each push is independently cfg-gated, vec![] can't express that" -)] -fn static_plugins() -> Vec<(&'static str, BootPlugin)> { - let mut plugins = Vec::new(); - - #[cfg(feature = "builtin-uki")] - plugins.push(( - "uki", - BootPlugin::from_static( - uki_probe, - uki_set_one_shot, - uki_confirm_boot, - uki_esp_loader_source, - uki_register_boot_slots, - uki_install, - ), - )); - - #[cfg(feature = "builtin-systemd-boot")] - plugins.push(( - "systemd-boot", - BootPlugin::from_static( - systemd_boot_probe, - systemd_boot_set_one_shot, - systemd_boot_confirm_boot, - systemd_boot_esp_loader_source, - systemd_boot_register_boot_slots, - systemd_boot_install, - ), - )); - - #[cfg(feature = "builtin-grub")] - plugins.push(( - "grub", - BootPlugin::from_static( - grub_probe, - grub_set_one_shot, - grub_confirm_boot, - grub_esp_loader_source, - grub_register_boot_slots, - grub_install, - ), - )); - - #[cfg(feature = "builtin-refind")] - plugins.push(( - "refind", - BootPlugin::from_static( - refind_probe, - refind_set_one_shot, - refind_confirm_boot, - refind_esp_loader_source, - refind_register_boot_slots, - refind_install, - ), - )); - - plugins -} - -#[cfg(feature = "dynamic-plugins")] -unsafe fn load_symbol(library: &Library, name: &str) -> Result { - unsafe { library.get::(name.as_bytes()) } - .map(|symbol| *symbol) - .map_err(|_| BootPluginError::Symbol) -} - pub struct BootPlugin { probe: ProbeFn, set_one_shot: SetOneShotFn, @@ -256,39 +138,6 @@ pub struct BootPlugin { _library: Option, } -#[cfg(feature = "dynamic-plugins")] -impl BootPlugin { - pub fn load(library_name: &str) -> Result { - let library = unsafe { Library::new(library_name) }.map_err(|_| BootPluginError::Load)?; - - let abi_version: AbiVersionFn = unsafe { load_symbol(&library, "abi_version")? }; - let probe: ProbeFn = unsafe { load_symbol(&library, "probe")? }; - let set_one_shot: SetOneShotFn = unsafe { load_symbol(&library, "set_one_shot")? }; - let confirm_boot: ConfirmBootFn = unsafe { load_symbol(&library, "confirm_boot")? }; - let esp_loader_source: EspLoaderSourceFn = unsafe { load_symbol(&library, "esp_loader_source")? }; - let register_boot_slots: RegisterBootSlotsFn = unsafe { load_symbol(&library, "register_boot_slots")? }; - let install: InstallFn = unsafe { load_symbol(&library, "install")? }; - - let got = unsafe { abi_version() }; - if got != BOOT_ABI_VERSION { - return Err(BootPluginError::AbiMismatch { - got, - expected: BOOT_ABI_VERSION, - }); - } - - Ok(BootPlugin { - probe, - set_one_shot, - confirm_boot, - esp_loader_source, - register_boot_slots, - install, - _library: Some(library), - }) - } -} - impl BootPlugin { pub fn probes(&self) -> bool { unsafe { (self.probe)() == 1 } diff --git a/lib/lib/src/plugin/boot/static_link.rs b/lib/lib/src/plugin/boot/static_link.rs new file mode 100644 index 00000000..433ec20c --- /dev/null +++ b/lib/lib/src/plugin/boot/static_link.rs @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::boot::{ConfirmBootFn, EspLoaderSourceFn, InstallFn, ProbeFn, RegisterBootSlotsFn, SetOneShotFn}; + +use super::BootPlugin; + +#[cfg(feature = "builtin-grub")] +use upac_boot_grub::{ + confirm_boot as grub_confirm_boot, esp_loader_source as grub_esp_loader_source, install as grub_install, + probe as grub_probe, register_boot_slots as grub_register_boot_slots, set_one_shot as grub_set_one_shot, +}; + +#[cfg(feature = "builtin-systemd-boot")] +use upac_boot_systemd_boot::{ + confirm_boot as systemd_boot_confirm_boot, esp_loader_source as systemd_boot_esp_loader_source, + install as systemd_boot_install, probe as systemd_boot_probe, + register_boot_slots as systemd_boot_register_boot_slots, set_one_shot as systemd_boot_set_one_shot, +}; + +#[cfg(feature = "builtin-uki")] +use upac_boot_uki::{ + confirm_boot as uki_confirm_boot, esp_loader_source as uki_esp_loader_source, install as uki_install, + probe as uki_probe, register_boot_slots as uki_register_boot_slots, set_one_shot as uki_set_one_shot, +}; + +#[cfg(feature = "builtin-refind")] +use upac_boot_refind::{ + confirm_boot as refind_confirm_boot, esp_loader_source as refind_esp_loader_source, install as refind_install, + probe as refind_probe, register_boot_slots as refind_register_boot_slots, set_one_shot as refind_set_one_shot, +}; + +impl BootPlugin { + fn from_static( + probe: ProbeFn, set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, esp_loader_source: EspLoaderSourceFn, + register_boot_slots: RegisterBootSlotsFn, install: InstallFn, + ) -> Self { + BootPlugin { + probe, + set_one_shot, + confirm_boot, + esp_loader_source, + register_boot_slots, + install, + + #[cfg(feature = "dynamic-plugins")] + _library: None, + } + } +} + +/// The boot plugins linked into this build, in probe order. +/// +/// No ABI version check is performed here: these are compiled from the same source +/// tree by the same compiler, so `BOOT_ABI_VERSION` matches by construction. +#[allow( + clippy::vec_init_then_push, + reason = "each push is independently cfg-gated, vec![] can't express that" +)] +pub(super) fn static_plugins() -> Vec<(&'static str, BootPlugin)> { + let mut plugins = Vec::new(); + + #[cfg(feature = "builtin-uki")] + plugins.push(( + "uki", + BootPlugin::from_static( + uki_probe, + uki_set_one_shot, + uki_confirm_boot, + uki_esp_loader_source, + uki_register_boot_slots, + uki_install, + ), + )); + + #[cfg(feature = "builtin-systemd-boot")] + plugins.push(( + "systemd-boot", + BootPlugin::from_static( + systemd_boot_probe, + systemd_boot_set_one_shot, + systemd_boot_confirm_boot, + systemd_boot_esp_loader_source, + systemd_boot_register_boot_slots, + systemd_boot_install, + ), + )); + + #[cfg(feature = "builtin-grub")] + plugins.push(( + "grub", + BootPlugin::from_static( + grub_probe, + grub_set_one_shot, + grub_confirm_boot, + grub_esp_loader_source, + grub_register_boot_slots, + grub_install, + ), + )); + + #[cfg(feature = "builtin-refind")] + plugins.push(( + "refind", + BootPlugin::from_static( + refind_probe, + refind_set_one_shot, + refind_confirm_boot, + refind_esp_loader_source, + refind_register_boot_slots, + refind_install, + ), + )); + + plugins +} diff --git a/lib/setup/lib.toml b/lib/setup/lib.toml index 1edb62df..79203e45 100644 --- a/lib/setup/lib.toml +++ b/lib/setup/lib.toml @@ -28,11 +28,12 @@ wipefs_bin = "wipefs" # installers), nothing in this codebase looks a partition up by either. Uppercase by the same # convention real-world ESP volume labels always use. [partition] -settle_attempts = 50 -settle_interval_ms = 100 esp_label = "ESP" deploy_label = "UPAC-DEPLOY" +settle_attempts = 50 +settle_interval_ms = 100 + # genesis.scratch_filename is where the freshly-built package database is # written (under the OS temp dir) before being embedded into the new /usr # tree — purely transient, read back once right after the write. From 1f2c76e44d154332d3afc65cd9cfed67484ea2fc Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 21:27:26 +0400 Subject: [PATCH 041/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/TODO.md b/TODO.md index 40dab58f..fa7323bf 100644 --- a/TODO.md +++ b/TODO.md @@ -25,22 +25,28 @@ extractable. **Two standalone boot-time services still need to be built** — neither is upac-lib/upac-cli code, both run on the installed system itself, outside anything `up`/`up-sp` invokes: -- **composefs-mount boot hook**: nothing yet resolves `composefs.digest=` (the kernel cmdline - param `write_boot_entry` already writes) against the on-disk repository, mounts the erofs image - with fs-verity, and overlays `state/deploy//etc/` — without this, a genesis-produced disk's - firmware boots the kernel, but the initramfs has no way to actually assemble the root. The upstream - tool for this already exists (`composefs-setup-root`, crates.io, same `composefs-rs` project as - our `composefs`/`composefs-boot` deps) — what's missing is the systemd-unit integration (ordered - between `sysroot.mount` and `initrd-switch-root.target`, same role as ostree's - `ostree-prepare-root.service`; the live VM's initramfs is systemd-based, not classic mkinitcpio - hooks). Also unresolved: whether upac ships/packages this integration itself or expects it to - already exist on the source distro. +- **composefs-mount boot hook**: nothing yet resolves `composefs=` (the kernel cmdline param + `write_boot_entry` already writes via `ComposefsCmdline::new_v2`) against the on-disk repository, + mounts the erofs image with fs-verity, and overlays `state/deploy//etc/` — without this, a + genesis-produced disk's firmware boots the kernel, but the initramfs has no way to actually + assemble the root. The upstream tool for this already exists (`composefs-setup-root`, crates.io, + same `composefs-rs` project as our `composefs`/`composefs-boot` deps) — confirmed by reading its + `main.rs`: it does NOT ship any systemd unit itself (only the binary — `Makefile`'s + `install-setup-root` target installs nothing else), so the unit is ours to write. Confirmed its + hardcoded expectations already match our on-disk layout exactly, no restructuring needed: + `Repository::open_path(sysroot, "composefs")` ↔ `lib.toml`'s `repo_dir = "composefs"`; + `state/deploy//{etc,var}` ↔ `deploys_dir = "state/deploy"` + + `TargetSysroot::deploy_dir(prefix_digest)`; `composefs=` karg ↔ + `ComposefsCmdline::new_v2(...).to_cmdline_arg()`. Remaining work: (1) write the actual `.service` + unit (`After=sysroot.mount`, `Before=initrd-root-fs.target`/`initrd-switch-root.target`, same role + as ostree's `ostree-prepare-root.service`), (2) have genesis embed it directly into `PrefixTree` + before `commit_tree()` — same mechanism `EmbedDatabaseStage` already uses to insert the database + file, not a new one — plus create its `*.wants/` enablement symlink since there's no live systemd + to `systemctl enable` against on an unbooted target. Still unresolved: whether upac ships/packages + the `composefs-setup-root` binary itself or expects it to already exist on the source distro (same + open question as systemd-boot/rEFInd's own binaries). - **UKI A/B confirm-boot service**: after a successful boot, something needs to confirm once, swap `to`↔`from`, and set the normal persistent boot order. Nothing calls `Booter::confirm_boot` - anywhere yet. - -**`lib/lib/src/plugin/` needs a readability pass**: grown too dense to follow, `plugin/boot/mod.rs` -in particular (360 lines — static-link wiring for all 4 plugins, dynamic dlopen loading, and the -`BootPlugin` public API all crammed into one file). Split by concern (mirror the existing -folder-per-logical-unit convention used elsewhere in this crate) and simplify — right now it takes -real effort to follow what calls what. + anywhere yet. Open design question: how does the service know it just booted the `to` slot + specifically (from `/proc/cmdline`? from the loaded UKI's own filename?) — needs deciding before + writing any code. From 53b6165843a3c82f7f428cb1c25e9c1c1c70ab47 Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 22:22:39 +0400 Subject: [PATCH 042/148] fix: added system installation of upac and a hook to load composefs Co-Authored-By: Claude Sonnet 5 --- lib/setup/lib.toml | 22 +++++++++ lib/setup/src/error.rs | 1 + lib/setup/src/genesis/mod.rs | 13 ++++-- lib/setup/src/genesis/system.rs | 82 +++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 lib/setup/src/genesis/system.rs diff --git a/lib/setup/lib.toml b/lib/setup/lib.toml index 79203e45..6d454eb2 100644 --- a/lib/setup/lib.toml +++ b/lib/setup/lib.toml @@ -49,7 +49,29 @@ settle_interval_ms = 100 # `uki.efi_linux_dir`, which is the same directory spelled as a backslash UEFI device-path string # for NVRAM Boot#### entries, not a filesystem path — the two can't share a definition across the # crate boundary. +# +# system_dir is a source-relative directory, sibling to the package archives `EnumeratePackagesStage` +# scans (skipped by it automatically — it only looks at files, never directories) — a literal, +# 1:1 mirror of the target's real `/usr` layout (matches `PrefixTree`, see `database_path` in +# `lib/lib/lib.toml`), imported wholesale by `ImportSystemStage`. This is how a built `up`/ +# `upac-lib`/booters (or anything else that must exist outside the normal per-package pipeline) +# gets onto a genesis'd disk — genesis never resolves or installs itself automatically, whoever +# assembles `--source` has to put it there, same assumption already made for the systemd-boot/ +# rEFInd binaries. +# +# composefs_setup_root_unit_path (relative to `system_dir`, i.e. also relative to real `/usr`) is +# where `ImportSystemStage` requires to find a `composefs-setup-root.service` unit — hard error +# (`SetupError::ComposefsSetupRootUnitNotFound`) if missing, since a genesis'd disk cannot boot at +# all without it. composefs_setup_root_wants_path/composefs_setup_root_wants_target: the stage +# also creates the unit's `*.target.wants/` enablement symlink itself (pure systemd mechanics, no +# reason to burden `--source` with it) — see TODO.md for why this specific set of paths matches +# `composefs-setup-root`'s own hardcoded expectations (confirmed by reading its upstream source, +# not guessed). [genesis] scratch_filename = "genesis-packages.redb" esp_fallback_loader = "EFI/BOOT/BOOTX64.EFI" efi_linux_dir = "EFI/Linux" +system_dir = "system" +composefs_setup_root_unit_path = "lib/systemd/system/composefs-setup-root.service" +composefs_setup_root_wants_path = "lib/systemd/system/initrd-root-fs.target.wants/composefs-setup-root.service" +composefs_setup_root_wants_target = "../composefs-setup-root.service" diff --git a/lib/setup/src/error.rs b/lib/setup/src/error.rs index a98edc8f..2f9930c1 100644 --- a/lib/setup/src/error.rs +++ b/lib/setup/src/error.rs @@ -37,6 +37,7 @@ pub enum SetupError { InvalidPartitionLayout, InvalidFormatParams, RereadFailed(Errno), + ComposefsSetupRootUnitNotFound, Unexpected, } diff --git a/lib/setup/src/genesis/mod.rs b/lib/setup/src/genesis/mod.rs index 9e631831..991c3036 100644 --- a/lib/setup/src/genesis/mod.rs +++ b/lib/setup/src/genesis/mod.rs @@ -19,6 +19,7 @@ use self::entry::StageBootStage; use self::enumerate::EnumeratePackagesStage; use self::import::ImportPackageStage; use self::source::PrepareSourceStage; +use self::system::ImportSystemStage; use self::unpack::UnpackPackageStage; use crate::data::{SetupExistingData, SetupWholeDiskData}; @@ -32,6 +33,7 @@ mod entry; mod enumerate; mod import; mod source; +mod system; mod unpack; macro_rules! ctx_get { @@ -73,10 +75,11 @@ pub enum GenesisStage { EnumeratePackages = 1, UnpackPackage = 2, ImportPackage = 3, - EmbedDatabase = 4, - WriteDeployRecord = 5, - StageBoot = 6, - Setup = 7, + ImportSystem = 4, + EmbedDatabase = 5, + WriteDeployRecord = 6, + StageBoot = 7, + Setup = 8, } impl SetupExistingData<'_> { @@ -104,6 +107,7 @@ impl SetupExistingData<'_> { Box::new(EnumeratePackagesStage), Box::new(UnpackPackageStage), Box::new(ImportPackageStage), + Box::new(ImportSystemStage), Box::new(EmbedDatabaseStage), Box::new(WriteDeployRecordStage), Box::new(StageBootStage), @@ -140,6 +144,7 @@ impl SetupWholeDiskData<'_> { Box::new(EnumeratePackagesStage), Box::new(UnpackPackageStage), Box::new(ImportPackageStage), + Box::new(ImportSystemStage), Box::new(EmbedDatabaseStage), Box::new(WriteDeployRecordStage), Box::new(StageBootStage), diff --git a/lib/setup/src/genesis/system.rs b/lib/setup/src/genesis/system.rs new file mode 100644 index 00000000..1615e08e --- /dev/null +++ b/lib/setup/src/genesis/system.rs @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::path::Path; + +use composefs::generic_tree::Stat; +use composefs::repository::ImportContext; +use composefs::tree::FileSystem; + +use upac::composefs::file::FileHandle; +use upac::composefs::repository::ObjectID; +use upac::orchestrator::Context; +use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; + +use upac_abi::hook::{CancelToken, ProgressEventBuilder}; + +use super::{ctx_get, ctx_take, import_if_dir}; + +use crate::error::SetupError; +use crate::layout::genesis::{ + COMPOSEFS_SETUP_ROOT_UNIT_PATH, COMPOSEFS_SETUP_ROOT_WANTS_PATH, COMPOSEFS_SETUP_ROOT_WANTS_TARGET, SYSTEM_DIR, +}; +use crate::target::TargetSysroot; +use crate::types::{PrefixTree, ResolvedSourceDir}; + +// No unit test: needs a real filesystem tree to import + a real composefs `Repository`, same +// untestable-in-isolation shape as `ImportPackageStage`. +pub struct ImportSystemStage; + +impl Stage for ImportSystemStage { + fn run( + &self, context: &mut Context, cancel: &CancelToken, progress: ProgressEventBuilder, + ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { + let mut prefix_tree = ctx_take!(context, PrefixTree); + let mut import_ctx = ctx_take!(context, ImportContext); + + let resolved = ctx_get!(context, ResolvedSourceDir); + let target = ctx_get!(context, TargetSysroot); + + let repository = target.repository(); + + let system_dir = resolved.0.join(SYSTEM_DIR); + let unit_source = system_dir.join(COMPOSEFS_SETUP_ROOT_UNIT_PATH); + if !unit_source.is_file() { + return Err(SetupError::ComposefsSetupRootUnitNotFound); + } + + import_if_dir!(repository, &mut prefix_tree.0, &system_dir, &mut import_ctx, cancel); + + ensure_ancestor_dirs(COMPOSEFS_SETUP_ROOT_WANTS_PATH, &mut prefix_tree.0)?; + FileHandle::new(COMPOSEFS_SETUP_ROOT_WANTS_PATH).symlink_in_tree( + &mut prefix_tree.0, + COMPOSEFS_SETUP_ROOT_WANTS_TARGET, + Stat::uninitialized(), + )?; + + context.put(prefix_tree); + context.put(import_ctx); + + Ok((progress, StageResult::Advance, Box::new(NoRollback))) + } +} + +fn ensure_ancestor_dirs(path: &str, tree: &mut FileSystem) -> Result<(), SetupError> { + let mut ancestors: Vec<&Path> = Path::new(path) + .ancestors() + .skip(1) + .filter(|ancestor| !ancestor.as_os_str().is_empty()) + .collect(); + ancestors.reverse(); + + for ancestor in ancestors { + let handle = FileHandle::new(ancestor); + if handle.stat_in_tree(tree).is_err() { + handle.insert_in_tree(tree, Stat::uninitialized())?; + } + } + + Ok(()) +} From 1afda8439fbbabde0a0d4ddebc87b11ee4dd8f10 Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 22:22:50 +0400 Subject: [PATCH 043/148] fix: propagated library setup changes to the installer CLI Co-Authored-By: Claude Sonnet 5 --- user/setup-cli/i18n/en/upac-setup-cli.ftl | 2 ++ user/setup-cli/i18n/ru/upac-setup-cli.ftl | 2 ++ user/setup-cli/src/errors.rs | 3 +++ user/setup-cli/tests/inline/errors.rs | 4 ++++ 4 files changed, 11 insertions(+) diff --git a/user/setup-cli/i18n/en/upac-setup-cli.ftl b/user/setup-cli/i18n/en/upac-setup-cli.ftl index 3a1f177d..fc924160 100644 --- a/user/setup-cli/i18n/en/upac-setup-cli.ftl +++ b/user/setup-cli/i18n/en/upac-setup-cli.ftl @@ -21,11 +21,13 @@ err-missing-source = Missing required argument: --source err-invalid-partition-layout = Requested partition sizes don't fit on the disk err-invalid-format-params = Invalid filesystem formatting parameters err-reread-failed = Failed to reread the partition table (device busy?) +err-composefs-setup-root-unit-not-found = composefs-setup-root.service not found under source's system/ directory stage-prepare-source = Preparing source stage-enumerate-packages = Enumerating packages stage-unpack-package = Unpacking package stage-import-package = Importing package +stage-import-system = Importing system files stage-embed-database = Embedding package database stage-write-deploy-record = Writing deploy record stage-stage-boot = Staging boot entry diff --git a/user/setup-cli/i18n/ru/upac-setup-cli.ftl b/user/setup-cli/i18n/ru/upac-setup-cli.ftl index 2c34abda..cd6ca91d 100644 --- a/user/setup-cli/i18n/ru/upac-setup-cli.ftl +++ b/user/setup-cli/i18n/ru/upac-setup-cli.ftl @@ -21,11 +21,13 @@ err-missing-source = Отсутствует обязательный аргум err-invalid-partition-layout = Запрошенные размеры разделов не помещаются на диск err-invalid-format-params = Некорректные параметры форматирования файловой системы err-reread-failed = Не удалось перечитать таблицу разделов (устройство занято?) +err-composefs-setup-root-unit-not-found = composefs-setup-root.service не найден в директории system/ источника stage-prepare-source = Подготовка источника stage-enumerate-packages = Перечисление пакетов stage-unpack-package = Распаковка пакета stage-import-package = Импорт пакета +stage-import-system = Импорт системных файлов stage-embed-database = Встраивание базы данных пакета stage-write-deploy-record = Запись записи деплоя stage-stage-boot = Подготовка загрузочной записи diff --git a/user/setup-cli/src/errors.rs b/user/setup-cli/src/errors.rs index 611d6725..90be2507 100644 --- a/user/setup-cli/src/errors.rs +++ b/user/setup-cli/src/errors.rs @@ -60,6 +60,9 @@ impl Display for LocalizedSetupError { SetupError::RereadFailed(errno) => { write!(formatter, "{} ({errno})", fl!(LOADER, "err-reread-failed")) } + SetupError::ComposefsSetupRootUnitNotFound => { + formatter.write_str(&fl!(LOADER, "err-composefs-setup-root-unit-not-found")) + } SetupError::Unexpected => formatter.write_str(&fl!(LOADER, "err-unexpected")), } } diff --git a/user/setup-cli/tests/inline/errors.rs b/user/setup-cli/tests/inline/errors.rs index f9d040f5..f3dede54 100644 --- a/user/setup-cli/tests/inline/errors.rs +++ b/user/setup-cli/tests/inline/errors.rs @@ -140,6 +140,10 @@ fn no_payload_variants_use_their_fixed_localized_message() { SetupError::InvalidFormatParams, "Invalid filesystem formatting parameters", ), + ( + SetupError::ComposefsSetupRootUnitNotFound, + "composefs-setup-root.service not found under source's system/ directory", + ), (SetupError::Unexpected, "Unexpected error"), ]; From 682e0f1c8d08a7a08bc7322640844d489160091d Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 7 Sep 2026 22:23:08 +0400 Subject: [PATCH 044/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 51 ++++++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/TODO.md b/TODO.md index fa7323bf..d92834dc 100644 --- a/TODO.md +++ b/TODO.md @@ -22,31 +22,28 @@ Remaining: the `Stage::run()` bodies themselves — each needs a real composefs database in context, likely out of scope for unit tests unless a pure-logic helper turns out to be extractable. -**Two standalone boot-time services still need to be built** — neither is upac-lib/upac-cli code, -both run on the installed system itself, outside anything `up`/`up-sp` invokes: +**`genesis`'s `system/` mechanism is done**: `ImportSystemStage` requires `/system/` (a +literal 1:1 mirror of the target's real `/usr`, sibling to the package archives — +`EnumeratePackagesStage` already skips it, it only looks at files) to contain +`lib/systemd/system/composefs-setup-root.service` (hard error, `SetupError:: +ComposefsSetupRootUnitNotFound`, if missing), imports the whole tree into `PrefixTree`, and creates +the unit's `*.target.wants/` enablement symlink itself. This is also how a built `up`/`upac-lib`/ +booters gets onto a genesis'd disk at all — genesis never installs itself automatically, whoever +assembles `--source` has to place it under `system/` too, same assumption already made for the +systemd-boot/rEFInd binaries. Confirmed `composefs-setup-root`'s own hardcoded expectations already +match upac's on-disk layout exactly (repo at `composefs/`, per-deploy state at `state/deploy//`, +`composefs=` cmdline karg) — no restructuring was needed, only the unit + the `system/` plumbing. +Still unresolved: whether upac ships/packages the `composefs-setup-root` binary itself or expects it +to already exist on the source distro (same open question as the systemd-boot/rEFInd binaries). -- **composefs-mount boot hook**: nothing yet resolves `composefs=` (the kernel cmdline param - `write_boot_entry` already writes via `ComposefsCmdline::new_v2`) against the on-disk repository, - mounts the erofs image with fs-verity, and overlays `state/deploy//etc/` — without this, a - genesis-produced disk's firmware boots the kernel, but the initramfs has no way to actually - assemble the root. The upstream tool for this already exists (`composefs-setup-root`, crates.io, - same `composefs-rs` project as our `composefs`/`composefs-boot` deps) — confirmed by reading its - `main.rs`: it does NOT ship any systemd unit itself (only the binary — `Makefile`'s - `install-setup-root` target installs nothing else), so the unit is ours to write. Confirmed its - hardcoded expectations already match our on-disk layout exactly, no restructuring needed: - `Repository::open_path(sysroot, "composefs")` ↔ `lib.toml`'s `repo_dir = "composefs"`; - `state/deploy//{etc,var}` ↔ `deploys_dir = "state/deploy"` + - `TargetSysroot::deploy_dir(prefix_digest)`; `composefs=` karg ↔ - `ComposefsCmdline::new_v2(...).to_cmdline_arg()`. Remaining work: (1) write the actual `.service` - unit (`After=sysroot.mount`, `Before=initrd-root-fs.target`/`initrd-switch-root.target`, same role - as ostree's `ostree-prepare-root.service`), (2) have genesis embed it directly into `PrefixTree` - before `commit_tree()` — same mechanism `EmbedDatabaseStage` already uses to insert the database - file, not a new one — plus create its `*.wants/` enablement symlink since there's no live systemd - to `systemctl enable` against on an unbooted target. Still unresolved: whether upac ships/packages - the `composefs-setup-root` binary itself or expects it to already exist on the source distro (same - open question as systemd-boot/rEFInd's own binaries). -- **UKI A/B confirm-boot service**: after a successful boot, something needs to confirm once, swap - `to`↔`from`, and set the normal persistent boot order. Nothing calls `Booter::confirm_boot` - anywhere yet. Open design question: how does the service know it just booted the `to` slot - specifically (from `/proc/cmdline`? from the loaded UKI's own filename?) — needs deciding before - writing any code. +**Boot confirmation service, generalized to all 4 plugins (not just UKI)**: `Booter::confirm_boot +(entry_name)` is already implemented for every plugin — grub (`grub-set-default`, promotes the +one-shot `grub-reboot` selection to persistent default), systemd-boot (writes `LoaderEntryDefault`), +rEFInd (writes `PreviousBoot`) all already do the right thing for their own one-shot mechanism; uki +still needs its to/from swap + persistent NVRAM boot order designed. But nothing anywhere calls +`confirm_boot` for any of them after a successful boot. Needs its own small service + unit, shipped +the same way as `composefs-setup-root.service` — via `system/`, built and dropped in by whoever +assembles `--source`, not embedded in upac itself. Open design question, now needed generically +(not just for UKI's to/from case): how does the service determine which `entry_name` was actually +booted (`/proc/cmdline`? the loaded image's own filename? grubenv's own state?) — needs deciding +before writing any code. From 6d379628ac3b18ad72d2d7582bb8530ec7f776f7 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 02:57:58 +0400 Subject: [PATCH 045/148] fix: reorganize ABI crate around C-ABI-only structs - delete boot.rs/decoder.rs/setup.rs: boot- and decode-request/response structs move into request.rs/response.rs, dependency struct into package.rs, fn-pointer typedefs and shared enums into lib.rs - CDependency -> CPackageDependency - delete dead C-ABI setup structs (CSetupBaseStruct, CPartitionMount, CPartitionSpec, CGptLayout, CBtrfsOptions, CSetupExistingRequest, CSetupWholeDiskRequest) - never constructed anywhere, genesis/up-sp builds its Rust request types directly with no C-ABI boundary - drop misplaced CTryToRust off CDecodePackageResponse (belongs on a Rust-side mirror type, not the C struct itself) - fix abi tests to match: validate.rs renames, hook.rs trimmed to CancelToken only (ProgressEventBuilder/Message tests now live with their code in upac_types) Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/boot.rs | 64 ---------------- lib/abi/src/decoder.rs | 107 --------------------------- lib/abi/src/error.rs | 7 -- lib/abi/src/hook.rs | 93 ----------------------- lib/abi/src/lib.rs | 52 ++++++++----- lib/abi/src/package.rs | 10 +++ lib/abi/src/request.rs | 47 +++++++++++- lib/abi/src/response.rs | 23 ++++-- lib/abi/src/setup.rs | 100 ------------------------- lib/abi/tests/hook.rs | 63 +--------------- lib/abi/tests/validate.rs | 152 +------------------------------------- 11 files changed, 109 insertions(+), 609 deletions(-) delete mode 100644 lib/abi/src/boot.rs delete mode 100644 lib/abi/src/decoder.rs delete mode 100644 lib/abi/src/setup.rs diff --git a/lib/abi/src/boot.rs b/lib/abi/src/boot.rs deleted file mode 100644 index 53676c2b..00000000 --- a/lib/abi/src/boot.rs +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use upac_macro::CNew; - -use crate::error::ErrorKind; -use crate::types::CSlice; - -pub type AbiVersionFn = unsafe extern "C" fn() -> u32; - -pub type ProbeFn = unsafe extern "C" fn() -> i32; - -pub type SetOneShotFn = unsafe extern "C" fn(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32; - -pub type ConfirmBootFn = unsafe extern "C" fn(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32; - -pub type EspLoaderSourceFn = unsafe extern "C" fn() -> CSlice; - -pub type RegisterBootSlotsFn = unsafe extern "C" fn(request: *const CBootSlotsRequest, err_out: *mut ErrorKind) -> i32; - -pub type InstallFn = unsafe extern "C" fn(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32; - -pub trait Booter: Sized { - type Error; - - fn new() -> Result; - fn probes() -> bool; - fn set_one_shot(&mut self, entry_name: &str) -> Result<(), Self::Error>; - fn confirm_boot(&mut self, entry_name: &str) -> Result<(), Self::Error>; - - fn esp_loader_source() -> Option<&'static str> { - None - } - - fn register_boot_slots( - &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, - esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, - ) -> Result<(), Self::Error>; - - fn install(&mut self, esp_mount_point: &str) -> Result<(), Self::Error>; -} - -#[repr(C)] -#[derive(CNew)] -pub struct CBootPluginRequest { - pub struct_size: usize, - - pub value: CSlice, -} - -#[repr(C)] -#[derive(CNew)] -pub struct CBootSlotsRequest { - pub struct_size: usize, - - pub esp_partition_number: u32, - pub esp_starting_lba: u64, - pub esp_ending_lba: u64, - pub esp_unique_partition_guid: [u8; 16], - pub to_slot: CSlice, - pub from_slot: CSlice, -} diff --git a/lib/abi/src/decoder.rs b/lib/abi/src/decoder.rs deleted file mode 100644 index 5ea575db..00000000 --- a/lib/abi/src/decoder.rs +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::io::Error as IoError; -use std::io::ErrorKind as IoErrorKind; - -use upac_macro::{CNew, CValidate}; - -use crate::error::ErrorKind; -use crate::hook::CancelToken; -use crate::package::{CPackageMeta, CVersion}; -use crate::types::{CSlice, CVec, check_size}; - -pub const CONSTRAINT_LESS: u8 = 0b001; -pub const CONSTRAINT_EQUAL: u8 = 0b010; -pub const CONSTRAINT_GREATER: u8 = 0b100; -pub const CONSTRAINT_ANY: u8 = CONSTRAINT_LESS | CONSTRAINT_EQUAL | CONSTRAINT_GREATER; - -pub type AbiVersionFn = unsafe extern "C" fn() -> u32; - -pub type DecodeFn = unsafe extern "C" fn(request: *const CDecodeRequest, response_out: *mut CDecodeResponse) -> i32; - -pub type FreeDecodeResponseFn = unsafe extern "C" fn(response: *mut CDecodeResponse); - -pub fn parse_constraint_prefix(token: &[u8], operators: &[(&[u8], u8)]) -> Option<(u8, usize)> { - operators - .iter() - .find(|(operator, _)| token.starts_with(operator)) - .map(|(operator, constraint)| (*constraint, operator.len())) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DecodeError { - InvalidRequest, - Io(IoErrorKind), - ChecksumMismatch, - UnsupportedFormat, - MissingMetadata, - MalformedMetadata, - InvalidUtf8, - Cancelled, -} - -impl From for DecodeError { - fn from(error: IoError) -> Self { - DecodeError::Io(error.kind()) - } -} - -impl DecodeError { - pub fn code(self) -> i32 { - match self { - DecodeError::InvalidRequest => -1, - DecodeError::Io(_) => -2, - DecodeError::ChecksumMismatch => -3, - DecodeError::UnsupportedFormat => -4, - DecodeError::MissingMetadata => -5, - DecodeError::MalformedMetadata => -6, - DecodeError::InvalidUtf8 => -7, - DecodeError::Cancelled => -8, - } - } -} - -#[repr(C)] -#[derive(CNew)] -pub struct CDecodeRequest { - pub struct_size: usize, - - pub package_path: CSlice, - pub output_dir: CSlice, - - pub checksum: [u8; 32], - - pub cancel_token: *mut CancelToken, -} - -#[repr(C)] -#[derive(CValidate)] -pub struct CDecodeResponse { - pub struct_size: usize, - - pub meta: CPackageMeta, - - pub dependencies: CVec, - pub declarative_triggers: CVec, - - pub free: FreeDecodeResponseFn, -} - -impl Drop for CDecodeResponse { - fn drop(&mut self) { - unsafe { (self.free)(self) }; - } -} - -#[repr(C)] -#[derive(CValidate)] -pub struct CDependency { - pub struct_size: usize, - - pub name: CSlice, - pub constraint: u8, - pub version: CVersion, -} diff --git a/lib/abi/src/error.rs b/lib/abi/src/error.rs index 53ec0d75..80f9e942 100644 --- a/lib/abi/src/error.rs +++ b/lib/abi/src/error.rs @@ -69,10 +69,3 @@ pub struct CError { pub state: u32, pub error: ErrorKind, } - -pub trait CommandState: Copy { - const DOMAIN: ErrorDomain; - const VALIDATION: Self; - - fn as_u32(self) -> u32; -} diff --git a/lib/abi/src/hook.rs b/lib/abi/src/hook.rs index 0fda2595..9fc48a10 100644 --- a/lib/abi/src/hook.rs +++ b/lib/abi/src/hook.rs @@ -3,16 +3,10 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::ffi::CString; -use std::mem::size_of; -use std::os::raw::c_void; -use std::ptr::null; use std::sync::atomic::{AtomicU8, Ordering}; use crate::types::CSlice; -pub type HookMessageFn = unsafe extern "C" fn(event: *const CProgressEvent, ctx: *mut c_void) -> HookAck; - #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HookAck { @@ -30,93 +24,6 @@ pub struct CProgressEvent { pub total: u64, } -pub struct ProgressEventBuilder { - stage: u32, - phase: u32, - subject: Option, - current: u64, - total: u64, -} - -impl ProgressEventBuilder { - pub fn new(stage: u32) -> Self { - Self { - stage, - phase: 0, - subject: None, - current: 0, - total: 0, - } - } - - pub fn stage(&self) -> u32 { - self.stage - } - - pub fn phase(mut self, phase: u32) -> Self { - self.phase = phase; - self - } - - pub fn subject(mut self, subject: impl Into) -> Self { - self.subject = CString::new(subject.into()).ok(); - self - } - - pub fn progress(mut self, current: u64, total: u64) -> Self { - self.current = current; - self.total = total; - self - } - - pub fn build(&self) -> CProgressEvent { - let subject = match &self.subject { - Some(subject) => CSlice { - ptr: subject.as_ptr().cast(), - len: subject.as_bytes().len(), - }, - None => CSlice { ptr: null(), len: 0 }, - }; - - CProgressEvent { - struct_size: size_of::(), - stage: self.stage, - phase: self.phase, - subject, - current: self.current, - total: self.total, - } - } -} - -pub trait MessageHook { - fn send(&self, event: &CProgressEvent) -> HookAck; -} - -pub struct Message { - hook_message: Option, - hook_message_context: *mut c_void, -} - -impl Message { - pub fn new(hook_message: Option, hook_message_context: *mut c_void) -> Self { - Self { - hook_message, - hook_message_context, - } - } -} - -impl MessageHook for Message { - fn send(&self, event: &CProgressEvent) -> HookAck { - let Some(hook_message) = self.hook_message else { - return HookAck::Delivered; - }; - - unsafe { hook_message(event as *const CProgressEvent, self.hook_message_context) } - } -} - #[repr(C)] pub struct CancelToken { cancelled: AtomicU8, diff --git a/lib/abi/src/lib.rs b/lib/abi/src/lib.rs index 9e3673a1..bf44bf80 100644 --- a/lib/abi/src/lib.rs +++ b/lib/abi/src/lib.rs @@ -3,23 +3,51 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use self::error::ErrorKind; +use std::ffi::c_void; + +use crate::error::ErrorKind; +use crate::hook::{CProgressEvent, HookAck}; +use crate::request::{ + CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, CDecodeRequest, +}; +use crate::response::CDecodePackageResponse; -pub mod boot; -pub mod decoder; pub mod error; pub mod hook; pub mod memory; pub mod package; pub mod request; pub mod response; -pub mod setup; pub mod types; pub const LIB_ABI_VERSION: u32 = 2; pub const BOOT_ABI_VERSION: u32 = 2; pub const DECODER_ABI_VERSION: u32 = 2; +pub const CONSTRAINT_LESS: u8 = 0b001; +pub const CONSTRAINT_EQUAL: u8 = 0b010; +pub const CONSTRAINT_GREATER: u8 = 0b100; +pub const CONSTRAINT_ANY: u8 = CONSTRAINT_LESS | CONSTRAINT_EQUAL | CONSTRAINT_GREATER; + +pub type BootPluginAbiVersionFn = unsafe extern "C" fn() -> u32; + +pub type DecodePluginAbiVersionFn = unsafe extern "C" fn() -> u32; + +pub type HookMessageFn = unsafe extern "C" fn(event: *const CProgressEvent, ctx: *mut c_void) -> HookAck; + +pub type SetOneShotFn = + unsafe extern "C" fn(request: *const CBootPluginSetOneShotRequest, err_out: *mut ErrorKind) -> i32; + +pub type ConfirmBootFn = + unsafe extern "C" fn(request: *const CBootPluginConfirmSuccsesBootRequest, err_out: *mut ErrorKind) -> i32; + +pub type InstallFn = unsafe extern "C" fn(request: *const CBootPluginInstallRequest, err_out: *mut ErrorKind) -> i32; + +pub type DecodeFn = + unsafe extern "C" fn(request: *const CDecodeRequest, response_out: *mut CDecodePackageResponse) -> i32; + +pub type FreeDecodeResponseFn = unsafe extern "C" fn(response: *mut CDecodePackageResponse); + #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FileDiffKind { @@ -39,13 +67,6 @@ impl FileDiffKind { } } -// A package's own metadata can be Added/Removed/Modified — or unchanged while -// one of its own files changed underneath it (e.g. a hand-edited is_user file), -// which FileDiffKind's three variants can't represent. Kept separate rather -// than adding a fourth variant to FileDiffKind, since every file-level -// consumer (DiffPrefixFileEntry/DiffConfigFileEntry/DiffUntrackedFileEntry) is -// already a complete, correct 3-way split — a package-only concept doesn't -// belong there. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PackageDiffKind { @@ -67,11 +88,6 @@ impl PackageDiffKind { } } -// Distinguishes which tree a DiffPrefixFileEntry/DiffUntrackedFileEntry came -// from when both /usr and /etc changes are folded into one list (the combined -// diff command). Standalone diff_prefix/diff_config don't need it to -// disambiguate (the command itself already implies the axis), but reuse the -// same entry types and set it to a fixed value. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DiffFileSource { @@ -89,10 +105,6 @@ impl DiffFileSource { } } -// Filesystem chosen for the deployment partition (needs fs-verity support, see -// doc chapter 3 §(4)) or any extra mount upac-setup formats/mounts. Appending -// a new variant later (e.g. bcachefs) is a plain additive change here — every -// consumer already goes through from_u8, so nothing needs pre-reserving. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FsKind { diff --git a/lib/abi/src/package.rs b/lib/abi/src/package.rs index 2c594f70..c7985999 100644 --- a/lib/abi/src/package.rs +++ b/lib/abi/src/package.rs @@ -48,3 +48,13 @@ pub struct CPackageInfo { #[optional] pub arch_sub: CSlice, } + +#[repr(C)] +#[derive(CValidate)] +pub struct CPackageDependency { + pub struct_size: usize, + + pub name: CSlice, + pub constraint: u8, + pub version: CVersion, +} diff --git a/lib/abi/src/request.rs b/lib/abi/src/request.rs index 6c2518df..aaa726f1 100644 --- a/lib/abi/src/request.rs +++ b/lib/abi/src/request.rs @@ -7,8 +7,9 @@ use std::os::raw::c_void; use upac_macro::{CNew, CValidate}; +use super::HookMessageFn; use crate::error::ErrorKind; -use crate::hook::{CancelToken, HookMessageFn}; +use crate::hook::CancelToken; use crate::package::CPackageInfo; use crate::types::{CSlice, CVec, check_size}; use crate::{DiffFileSource, FileDiffKind}; @@ -268,3 +269,47 @@ pub struct CSearchInPackageFilesRequest { pub search: CSlice, pub is_regex: bool, } + +#[repr(C)] +#[derive(CNew, CValidate)] +pub struct CDecodeRequest { + pub struct_size: usize, + + pub package_path: CSlice, + pub output_dir: CSlice, + + pub checksum: [u8; 32], + + pub cancel_token: *mut CancelToken, +} + +#[repr(C)] +#[derive(CNew, CValidate)] +pub struct CBootPluginSetOneShotRequest { + pub struct_size: usize, + + pub entry_name: CSlice, +} + +#[repr(C)] +#[derive(CNew, CValidate)] +pub struct CBootPluginConfirmSuccsesBootRequest { + pub struct_size: usize, + + pub entry_name: CSlice, + pub esp_mount_point: CSlice, +} + +#[repr(C)] +#[derive(CNew, CValidate)] +pub struct CBootPluginInstallRequest { + pub struct_size: usize, + + pub esp_mount_point: CSlice, + pub esp_partition_number: u32, + pub esp_starting_lba: u64, + pub esp_ending_lba: u64, + pub esp_unique_partition_guid: [u8; 16], + pub to_slot: CSlice, + pub from_slot: CSlice, +} diff --git a/lib/abi/src/response.rs b/lib/abi/src/response.rs index 762b4fb2..4b2ba363 100644 --- a/lib/abi/src/response.rs +++ b/lib/abi/src/response.rs @@ -5,9 +5,10 @@ use upac_macro::{CFree, CNew, CValidate}; +use crate::FreeDecodeResponseFn; use crate::error::ErrorKind; use crate::memory::{free_cslice, free_cvec_owning}; -use crate::package::{CPackageMeta, CVersion}; +use crate::package::{CPackageDependency, CPackageMeta, CVersion}; use crate::types::{CSlice, CVec, check_size}; use crate::{DiffFileSource, FileDiffKind, PackageDiffKind}; @@ -196,12 +197,20 @@ pub struct CDiffResponse { } #[repr(C)] -#[derive(CFree)] -pub struct CUnmutatedResponse { +#[derive(CValidate)] +pub struct CDecodePackageResponse { pub struct_size: usize, - pub metas: CVec, - pub files: CVec, - pub commits: CVec, - pub diff_packages: CVec, + pub meta: CPackageMeta, + + pub dependencies: CVec, + pub declarative_triggers: CVec, + + pub free: FreeDecodeResponseFn, +} + +impl Drop for CDecodePackageResponse { + fn drop(&mut self) { + unsafe { (self.free)(self) }; + } } diff --git a/lib/abi/src/setup.rs b/lib/abi/src/setup.rs deleted file mode 100644 index dc80a01c..00000000 --- a/lib/abi/src/setup.rs +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use upac_macro::{CNew, CValidate}; - -use crate::FsKind; -use crate::error::ErrorKind; -use crate::request::CRequestBase; -use crate::types::{CSlice, CVec, check_size}; - -#[repr(C)] -#[derive(CNew, CValidate)] -pub struct CSetupBase { - pub struct_size: usize, - - pub base: CRequestBase, - - #[optional] - pub mount_point: CSlice, - #[non_empty] - pub source: CSlice, - pub empty_config: bool, - pub pinned: bool, - #[optional] - pub boot_plugin: CSlice, -} - -#[repr(C)] -#[derive(CValidate)] -pub struct CPartitionMount { - pub struct_size: usize, - - #[non_empty] - pub mount_path: CSlice, - #[non_empty] - pub device_path: CSlice, - pub fs_kind: FsKind, -} - -#[repr(C)] -#[derive(CValidate)] -pub struct CPartitionSpec { - pub struct_size: usize, - - #[non_empty] - pub mount_path: CSlice, - pub size_mib: u64, - pub fs_kind: FsKind, -} - -#[repr(C)] -#[derive(CNew, CValidate)] -pub struct CSetupExistingRequest { - pub struct_size: usize, - - pub base: CSetupBase, - - #[non_empty] - pub esp_device: CSlice, - #[non_empty] - pub deploy_device: CSlice, - pub deploy_fs: FsKind, - pub extra_mounts: CVec, -} - -#[repr(C)] -#[derive(CValidate)] -pub struct CGptLayout { - pub struct_size: usize, - - pub esp_size_mib: u64, - pub deploy_fs: FsKind, - pub deploy_size_mib: u64, - pub extra_partitions: CVec, - pub force_wipe: bool, -} - -#[repr(C)] -#[derive(CValidate)] -pub struct CBtrfsOptions { - pub struct_size: usize, - - pub node_size: u32, - pub sector_size: u32, -} - -#[repr(C)] -#[derive(CNew, CValidate)] -pub struct CSetupWholeDiskRequest { - pub struct_size: usize, - - pub base: CSetupBase, - - #[non_empty] - pub device_path: CSlice, - pub gpt: CGptLayout, - pub btrfs: CBtrfsOptions, -} diff --git a/lib/abi/tests/hook.rs b/lib/abi/tests/hook.rs index 3f8ae6f1..a0536ade 100644 --- a/lib/abi/tests/hook.rs +++ b/lib/abi/tests/hook.rs @@ -3,18 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::mem::size_of; -use std::os::raw::c_void; -use std::ptr::{addr_of_mut, null_mut}; - -use upac_abi::hook::{CProgressEvent, CancelToken, HookAck, Message, MessageHook, ProgressEventBuilder}; - -unsafe extern "C" fn record_stage_and_retry(event: *const CProgressEvent, ctx: *mut c_void) -> HookAck { - unsafe { - *ctx.cast::() = (*event).stage; - } - HookAck::Retry -} +use upac_abi::hook::CancelToken; #[test] fn cancel_token_starts_not_cancelled() { @@ -48,53 +37,3 @@ fn cancel_token_reset_clears_a_cancellation() { assert!(!token.is_cancelled()); } - -#[test] -fn progress_event_builder_defaults() { - let event = ProgressEventBuilder::new(3).build(); - - assert_eq!(event.struct_size, size_of::()); - assert_eq!(event.stage, 3); - assert_eq!(event.phase, 0); - assert_eq!(event.current, 0); - assert_eq!(event.total, 0); - assert!(event.subject.ptr.is_null()); -} - -#[test] -fn progress_event_builder_stage_accessor_matches_the_constructor() { - let builder = ProgressEventBuilder::new(7); - - assert_eq!(builder.stage(), 7); -} - -#[test] -fn progress_event_builder_applies_phase_subject_and_progress() { - let builder = ProgressEventBuilder::new(1).phase(2).subject("foo.txt").progress(3, 10); - let event = builder.build(); - - assert_eq!(event.phase, 2); - assert_eq!(event.current, 3); - assert_eq!(event.total, 10); - assert_eq!(<&str>::try_from(&event.subject).unwrap(), "foo.txt"); -} - -#[test] -fn message_send_with_no_hook_returns_delivered() { - let message = Message::new(None, null_mut()); - let event = ProgressEventBuilder::new(0).build(); - - assert_eq!(message.send(&event), HookAck::Delivered); -} - -#[test] -fn message_send_with_a_hook_forwards_the_event_and_context() { - let mut recorded_stage: u32 = 0; - let message = Message::new(Some(record_stage_and_retry), addr_of_mut!(recorded_stage).cast()); - let event = ProgressEventBuilder::new(9).build(); - - let ack = message.send(&event); - - assert_eq!(ack, HookAck::Retry); - assert_eq!(recorded_stage, 9); -} diff --git a/lib/abi/tests/validate.rs b/lib/abi/tests/validate.rs index acb63d2c..efeddc3c 100644 --- a/lib/abi/tests/validate.rs +++ b/lib/abi/tests/validate.rs @@ -6,18 +6,16 @@ use std::mem::size_of; use std::ptr::{null, null_mut}; -use upac_abi::decoder::CDependency; use upac_abi::error::ErrorKind; use upac_abi::memory::free_cslice; -use upac_abi::package::{CPackageInfo, CPackageMeta, CVersion}; +use upac_abi::package::{CPackageDependency, CPackageInfo, CPackageMeta, CVersion}; use upac_abi::request::CRequestBase; use upac_abi::response::{ CConfigCommitEntry, CDiffConfigFileEntry, CDiffFileEntryCommon, CDiffPrefixFileEntry, CDiffUntrackedFileEntry, CHistoryEntry, CPrefixEntry, CSearchFileEntry, }; -use upac_abi::setup::{CBtrfsOptions, CGptLayout, CPartitionMount, CPartitionSpec, CSetupBase}; use upac_abi::types::{COwned, CSlice, CVec}; -use upac_abi::{DiffFileSource, FileDiffKind, FsKind}; +use upac_abi::{DiffFileSource, FileDiffKind}; fn valid_version() -> CVersion { CVersion { @@ -103,8 +101,8 @@ fn package_info_validate_rejects_missing_required_field() { #[test] fn dependency_validate_rejects_invalid_nested_version() { - let mut dependency = CDependency { - struct_size: size_of::(), + let mut dependency = CPackageDependency { + struct_size: size_of::(), name: CSlice::from_owned(b"glibc".to_vec()), constraint: 0b010, version: valid_version(), @@ -382,145 +380,3 @@ fn request_base_validate_rejects_wrong_struct_size() { assert_eq!(unsafe { base.validate() }, Err(ErrorKind::AbiMismatch)); } - -fn valid_setup_base() -> CSetupBase { - CSetupBase { - struct_size: size_of::(), - base: valid_request_base(), - mount_point: CSlice { ptr: null(), len: 0 }, - source: CSlice::from_owned(b"/mnt/source".to_vec()), - empty_config: false, - pinned: false, - boot_plugin: CSlice { ptr: null(), len: 0 }, - } -} - -#[test] -fn setup_base_validate_ok_with_all_optionals_absent() { - let base = valid_setup_base(); - - assert!(unsafe { base.validate() }.is_ok()); - unsafe { free_cslice(&base.source) }; -} - -#[test] -fn setup_base_validate_rejects_missing_required_source() { - let mut base = valid_setup_base(); - unsafe { free_cslice(&base.source) }; - base.source = CSlice { ptr: null(), len: 0 }; - - assert_eq!(unsafe { base.validate() }, Err(ErrorKind::InvalidEntry)); -} - -fn valid_partition_mount() -> CPartitionMount { - CPartitionMount { - struct_size: size_of::(), - mount_path: CSlice::from_owned(b"/boot".to_vec()), - device_path: CSlice::from_owned(b"/dev/sda1".to_vec()), - fs_kind: FsKind::Ext4, - } -} - -#[test] -fn partition_mount_validate_ok_for_well_formed() { - let mount = valid_partition_mount(); - - assert!(unsafe { mount.validate() }.is_ok()); - unsafe { - free_cslice(&mount.mount_path); - free_cslice(&mount.device_path); - } -} - -#[test] -fn partition_mount_validate_rejects_missing_mount_path() { - let mut mount = valid_partition_mount(); - unsafe { free_cslice(&mount.mount_path) }; - mount.mount_path = CSlice { ptr: null(), len: 0 }; - - assert_eq!(unsafe { mount.validate() }, Err(ErrorKind::InvalidEntry)); - unsafe { free_cslice(&mount.device_path) }; -} - -fn valid_partition_spec() -> CPartitionSpec { - CPartitionSpec { - struct_size: size_of::(), - mount_path: CSlice::from_owned(b"/boot".to_vec()), - size_mib: 512, - fs_kind: FsKind::Ext4, - } -} - -#[test] -fn partition_spec_validate_ok_for_well_formed() { - let spec = valid_partition_spec(); - - assert!(unsafe { spec.validate() }.is_ok()); - unsafe { free_cslice(&spec.mount_path) }; -} - -#[test] -fn partition_spec_validate_rejects_missing_mount_path() { - let mut spec = valid_partition_spec(); - unsafe { free_cslice(&spec.mount_path) }; - spec.mount_path = CSlice { ptr: null(), len: 0 }; - - assert_eq!(unsafe { spec.validate() }, Err(ErrorKind::InvalidEntry)); -} - -fn valid_gpt_layout() -> CGptLayout { - CGptLayout { - struct_size: size_of::(), - esp_size_mib: 512, - deploy_fs: FsKind::Ext4, - deploy_size_mib: 8192, - extra_partitions: CVec { - ptr: null_mut(), - len: 0, - }, - force_wipe: false, - } -} - -#[test] -fn gpt_layout_validate_ok_with_no_extra_partitions() { - assert!(unsafe { valid_gpt_layout().validate() }.is_ok()); -} - -#[test] -fn gpt_layout_validate_rejects_an_invalid_extra_partition() { - let mut bad_spec = valid_partition_spec(); - bad_spec.struct_size = 0; - let mut specs = vec![bad_spec]; - - let mut layout = valid_gpt_layout(); - layout.extra_partitions = CVec { - ptr: specs.as_mut_ptr(), - len: specs.len(), - }; - - assert_eq!(unsafe { layout.validate() }, Err(ErrorKind::AbiMismatch)); - - unsafe { free_cslice(&specs[0].mount_path) }; -} - -fn valid_btrfs_options() -> CBtrfsOptions { - CBtrfsOptions { - struct_size: size_of::(), - node_size: 0, - sector_size: 0, - } -} - -#[test] -fn btrfs_options_validate_ok_for_well_formed() { - assert!(unsafe { valid_btrfs_options().validate() }.is_ok()); -} - -#[test] -fn btrfs_options_validate_rejects_wrong_struct_size() { - let mut options = valid_btrfs_options(); - options.struct_size = 0; - - assert_eq!(unsafe { options.validate() }, Err(ErrorKind::AbiMismatch)); -} From 9026bf29b43e84f3928b5395fe5ef779bf8fe9a9 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 02:58:11 +0400 Subject: [PATCH 046/148] fix: sync VALIDATABLE_COMPOSITES with current abi structs - CDependency -> CPackageDependency - drop entries for structs deleted from abi (CSetupBase and friends) and CBootPluginInstallRequest (never nested, doesn't derive CValidate) Co-Authored-By: Claude Sonnet 5 --- lib/macro/src/common.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/macro/src/common.rs b/lib/macro/src/common.rs index 9bf290c2..7feb428c 100644 --- a/lib/macro/src/common.rs +++ b/lib/macro/src/common.rs @@ -28,12 +28,7 @@ pub(crate) const VALIDATABLE_COMPOSITES: &[&str] = &[ "CPrefixEntry", "CHistoryEntry", "CRequestBase", - "CDependency", - "CSetupBase", - "CPartitionMount", - "CPartitionSpec", - "CGptLayout", - "CBtrfsOptions", + "CPackageDependency", ]; pub(crate) fn generic_arg(segment: &PathSegment) -> Option<&Type> { From 010dd4085f27dc093ec8445513fddb103a52ba0e Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 03:40:17 +0400 Subject: [PATCH 047/148] fix: split upac_types::lib.rs into per-domain modules, fix broken imports - lib.rs (one 400+ line file) split into entry.rs (file/diff/history/ search entry types), error.rs (DecodeError, CommandState), hook.rs (ProgressEventBuilder, Message), package.rs (Version, PackageMeta, PackageEntry, PackageDependency, DecodedPackageMeta), traits.rs (Booter, DecodeMeta, MessageHook) - fix imports broken by the abi-crate reorg: CommandState/DecodeError now pulled from crate::error (not upac_abi::error/upac_abi::decoder), each module imports only the C-struct counterparts it actually needs instead of one giant crate-root use block - delete lib/types/src/tests.rs (superseded by external tests/*.rs) - fix tests/conversions.rs: PackageMeta/Version now import via upac_types::package, not the crate root (no pub use re-export) Co-Authored-By: Claude Sonnet 5 --- lib/types/src/decoder.rs | 48 +++-- lib/types/src/entry.rs | 135 +++++++++++++ lib/types/src/error.rs | 49 +++++ lib/types/src/hook.rs | 97 ++++++++++ lib/types/src/lib.rs | 339 ++------------------------------- lib/types/src/package.rs | 171 +++++++++++++++++ lib/types/src/states.rs | 4 +- lib/types/src/tests.rs | 157 --------------- lib/types/src/traits.rs | 25 +++ lib/types/tests/conversions.rs | 2 +- 10 files changed, 530 insertions(+), 497 deletions(-) create mode 100644 lib/types/src/entry.rs create mode 100644 lib/types/src/error.rs create mode 100644 lib/types/src/hook.rs create mode 100644 lib/types/src/package.rs delete mode 100644 lib/types/src/tests.rs create mode 100644 lib/types/src/traits.rs diff --git a/lib/types/src/decoder.rs b/lib/types/src/decoder.rs index 7f7da1ce..64b74d65 100644 --- a/lib/types/src/decoder.rs +++ b/lib/types/src/decoder.rs @@ -5,23 +5,47 @@ use std::io::Read; -use upac_abi::decoder::DecodeError; +use upac_macro::RedbCodec; -use crate::{Dependency, PackageMeta}; +use crate::error::DecodeError; -pub fn read_to_string(reader: &mut R) -> Result { - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes)?; +#[derive(Debug, Clone, RedbCodec)] +pub struct DeclarativeTrigger { + pub format: String, + pub triggers: Vec, +} - String::from_utf8(bytes).map_err(|_| DecodeError::InvalidUtf8) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DecoderTrigger { + PreInstall, + PostInstall, + PreUpgrade, + PostUpgrade, + PreRemove, + PostRemove, +} + +impl DecoderTrigger { + pub const ALL: [DecoderTrigger; 6] = [ + DecoderTrigger::PreInstall, + DecoderTrigger::PostInstall, + DecoderTrigger::PreUpgrade, + DecoderTrigger::PostUpgrade, + DecoderTrigger::PreRemove, + DecoderTrigger::PostRemove, + ]; } -#[derive(Debug)] -pub struct DecodedMeta { - pub meta: PackageMeta, - pub dependencies: Vec, +pub fn parse_constraint_prefix(token: &[u8], operators: &[(&[u8], u8)]) -> Option<(u8, usize)> { + operators + .iter() + .find(|(operator, _)| token.starts_with(operator)) + .map(|(operator, constraint)| (*constraint, operator.len())) } -pub trait DecodeMeta { - fn decode(&self, sha256: [u8; 32]) -> Result; +pub fn read_to_string(reader: &mut R) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes)?; + + String::from_utf8(bytes).map_err(|_| DecodeError::InvalidUtf8) } diff --git a/lib/types/src/entry.rs b/lib/types/src/entry.rs new file mode 100644 index 00000000..6c34f6f9 --- /dev/null +++ b/lib/types/src/entry.rs @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::mem::size_of; + +use upac_abi::package::CVersion; +use upac_abi::response::{ + CConfigCommitEntry, CDiffConfigFileEntry, CDiffFileEntryCommon, CDiffPackageEntry, CDiffPrefixFileEntry, + CDiffUntrackedFileEntry, CHistoryEntry, CPrefixEntry, CSearchFileEntry, +}; +use upac_abi::types::{COwned, CSlice, CVec}; +use upac_abi::{DiffFileSource, FileDiffKind, PackageDiffKind}; + +use upac_macro::{RedbCodec, RustToC}; + +use crate::codec::RedbCodable; +use crate::package::Version; + +// ── FileEntryScope ────────────────────────────────────────────────────────── +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FileEntryScope { + Prefix = 0, + Config = 1, +} + +impl RedbCodable for FileEntryScope { + fn redb_encode(&self, buf: &mut Vec) { + buf.push(*self as u8); + } + + fn redb_decode(data: &[u8], offset: &mut usize) -> FileEntryScope { + let value = data[*offset]; + *offset += 1; + + match value { + 1 => FileEntryScope::Config, + _ => FileEntryScope::Prefix, + } + } +} + +// ── FileEntry ─────────────────────────────────────────────────────────────── +#[derive(Debug, Clone, RedbCodec)] +pub struct FileEntry { + pub path: String, + pub is_user: bool, + pub scope: FileEntryScope, +} + +// ── SearchFileEntry ───────────────────────────────────────────────────────── +#[derive(Debug, Clone, RustToC)] +pub struct SearchFileEntry { + pub path: String, + pub package_name: String, + pub is_user: bool, +} + +// ── PrefixEntry ───────────────────────────────────────────────────────────── +#[derive(Debug, Clone, RustToC)] +pub struct PrefixEntry { + pub prefix_digest: String, + + pub subject: String, + pub message: Option, + + pub timestamp: u64, + + pub working_config: Option, +} + +// ── ConfigCommitEntry ───────────────────────────────────────────────────────────── +#[derive(Debug, Clone, RustToC)] +pub struct ConfigCommitEntry { + pub config_digest: String, + + pub subject: String, + pub message: Option, +} + +// ── HistoryEntry ──────────────────────────────────────────────────────────── +#[derive(Debug, Clone, RustToC)] +pub struct HistoryEntry { + pub prefix_digest: String, + + pub subject: String, + pub message: Option, + + pub timestamp: u64, + + pub working_config: Option, + pub config_history: Vec, +} + +// ── DiffFileEntryCommon ────────────────────────────────────────────────────── +#[derive(Debug, Clone, RustToC)] +pub struct DiffFileEntryCommon { + pub path: String, + pub kind: FileDiffKind, +} + +// ── DiffPrefixFileEntry ───────────────────────────────────────────────────── +#[derive(Debug, Clone, RustToC)] +pub struct DiffPrefixFileEntry { + pub common: DiffFileEntryCommon, + pub source: DiffFileSource, + pub package_name: String, + pub is_user: bool, +} + +// ── DiffConfigFileEntry ───────────────────────────────────────────────────── +#[derive(Debug, Clone, RustToC)] +pub struct DiffConfigFileEntry { + pub common: DiffFileEntryCommon, + pub package_name: Option, +} + +// ── DiffPackageEntry ──────────────────────────────────────────────────────── +#[derive(Debug, Clone, RustToC)] +pub struct DiffPackageEntry { + pub name: String, + pub kind: PackageDiffKind, + pub version: Version, + + pub files: Vec, +} + +// ── DiffUntrackedFileEntry ────────────────────────────────────────────────── +#[derive(Debug, Clone, RustToC)] +pub struct DiffUntrackedFileEntry { + pub common: DiffFileEntryCommon, + pub source: DiffFileSource, +} diff --git a/lib/types/src/error.rs b/lib/types/src/error.rs new file mode 100644 index 00000000..758348a6 --- /dev/null +++ b/lib/types/src/error.rs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::io::Error as IoError; +use std::io::ErrorKind as IoErrorKind; + +use upac_abi::error::ErrorDomain; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DecodeError { + InvalidRequest, + Io(IoErrorKind), + ChecksumMismatch, + UnsupportedFormat, + MissingMetadata, + MalformedMetadata, + InvalidUtf8, + Cancelled, +} + +impl From for DecodeError { + fn from(error: IoError) -> Self { + DecodeError::Io(error.kind()) + } +} + +impl DecodeError { + pub fn code(self) -> i32 { + match self { + DecodeError::InvalidRequest => -1, + DecodeError::Io(_) => -2, + DecodeError::ChecksumMismatch => -3, + DecodeError::UnsupportedFormat => -4, + DecodeError::MissingMetadata => -5, + DecodeError::MalformedMetadata => -6, + DecodeError::InvalidUtf8 => -7, + DecodeError::Cancelled => -8, + } + } +} + +pub trait CommandState: Copy { + const DOMAIN: ErrorDomain; + const VALIDATION: Self; + + fn as_u32(self) -> u32; +} diff --git a/lib/types/src/hook.rs b/lib/types/src/hook.rs new file mode 100644 index 00000000..64bad63a --- /dev/null +++ b/lib/types/src/hook.rs @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::ffi::{CString, c_void}; +use std::mem::size_of; +use std::ptr::null; + +use upac_abi::HookMessageFn; +use upac_abi::hook::{CProgressEvent, HookAck}; +use upac_abi::types::CSlice; + +use crate::traits::MessageHook; + +pub struct ProgressEventBuilder { + stage: u32, + phase: u32, + subject: Option, + current: u64, + total: u64, +} + +impl ProgressEventBuilder { + pub fn new(stage: u32) -> Self { + Self { + stage, + phase: 0, + subject: None, + current: 0, + total: 0, + } + } + + pub fn stage(&self) -> u32 { + self.stage + } + + pub fn phase(mut self, phase: u32) -> Self { + self.phase = phase; + self + } + + pub fn subject(mut self, subject: impl Into) -> Self { + self.subject = CString::new(subject.into()).ok(); + self + } + + pub fn progress(mut self, current: u64, total: u64) -> Self { + self.current = current; + self.total = total; + self + } + + pub fn build(&self) -> CProgressEvent { + let subject = match &self.subject { + Some(subject) => CSlice { + ptr: subject.as_ptr().cast(), + len: subject.as_bytes().len(), + }, + None => CSlice { ptr: null(), len: 0 }, + }; + + CProgressEvent { + struct_size: size_of::(), + stage: self.stage, + phase: self.phase, + subject, + current: self.current, + total: self.total, + } + } +} + +pub struct Message { + hook_message: Option, + hook_message_context: *mut c_void, +} + +impl Message { + pub fn new(hook_message: Option, hook_message_context: *mut c_void) -> Self { + Self { + hook_message, + hook_message_context, + } + } +} + +impl MessageHook for Message { + fn send(&self, event: &CProgressEvent) -> HookAck { + let Some(hook_message) = self.hook_message else { + return HookAck::Delivered; + }; + + unsafe { hook_message(event as *const CProgressEvent, self.hook_message_context) } + } +} diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 4e9682f7..211a5a9d 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -3,32 +3,19 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::cmp::Ordering; +use upac_abi::FsKind; -use serde::{Deserialize, Deserializer}; - -use upac_abi::decoder::CDependency; -use upac_abi::error::ErrorKind; -use upac_abi::package::{CPackageMeta, CVersion}; -use upac_abi::response::{ - CConfigCommitEntry, CDiffConfigFileEntry, CDiffFileEntryCommon, CDiffPackageEntry, CDiffPrefixFileEntry, - CDiffUntrackedFileEntry, CHistoryEntry, CPrefixEntry, CSearchFileEntry, -}; -use upac_abi::setup::{CBtrfsOptions, CGptLayout, CPartitionMount, CPartitionSpec}; -use upac_abi::types::{COwned, CSlice, CVec}; -use upac_abi::{DiffFileSource, FileDiffKind, FsKind, PackageDiffKind}; - -use upac_macro::{CTryToRust, RedbCodec, RustToC}; - -use crate::codec::RedbCodable; +use crate::package::{PackageEntry, PackageMeta}; pub mod codec; pub mod decoder; +pub mod entry; +pub mod error; +pub mod hook; +pub mod package; pub mod settings; pub mod states; - -#[cfg(test)] -mod tests; +pub mod traits; macro_rules! as_str_method { ($name:ty) => { @@ -40,309 +27,9 @@ macro_rules! as_str_method { }; } -// ── Version ───────────────────────────────────────────────────────────────── -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -enum VersionToken<'a> { - Alpha(&'a str), - Numeric(u64), -} - -#[derive(Debug, Clone, PartialEq, Eq, CTryToRust, RedbCodec, RustToC)] -pub struct Version { - pub epoch: u32, - pub raw: String, -} - -impl Default for Version { - fn default() -> Self { - Version { - epoch: 0, - raw: "1.0.0".to_owned(), - } - } -} - -impl Version { - pub fn parse(raw: &str) -> Version { - match raw.split_once(':') { - Some((epoch, rest)) => Version { - epoch: epoch.parse().unwrap_or(0), - raw: rest.to_owned(), - }, - None => Version { - epoch: 0, - raw: raw.to_owned(), - }, - } - } -} - -impl<'de> Deserialize<'de> for Version { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let raw = String::deserialize(deserializer)?; - - Ok(Version::parse(&raw)) - } -} - -impl PartialOrd for Version { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Version { - fn cmp(&self, other: &Self) -> Ordering { - if self.epoch != other.epoch { - return self.epoch.cmp(&other.epoch); - } - - let self_tokens = self.tokenize(); - let other_tokens = other.tokenize(); - - let mut self_iter = self_tokens.iter(); - let mut other_iter = other_tokens.iter(); - - loop { - match (self_iter.next(), other_iter.next()) { - (Some(a), Some(b)) => match a.cmp(b) { - Ordering::Equal => continue, - ordering => return ordering, - }, - (Some(VersionToken::Numeric(_)), None) => return Ordering::Greater, - (Some(VersionToken::Alpha(_)), None) => return Ordering::Less, - (None, Some(VersionToken::Numeric(_))) => return Ordering::Less, - (None, Some(VersionToken::Alpha(_))) => return Ordering::Greater, - (None, None) => return Ordering::Equal, - } - } - } -} - -impl Version { - fn tokenize(&self) -> Vec> { - let bytes = self.raw.as_bytes(); - let mut tokens = Vec::new(); - let mut index = 0; - - while index < bytes.len() { - if !bytes[index].is_ascii_alphanumeric() { - index += 1; - continue; - } - - let start = index; - if bytes[index].is_ascii_digit() { - while index < bytes.len() && bytes[index].is_ascii_digit() { - index += 1; - } - let value = self.raw[start..index].parse().unwrap_or(u64::MAX); - tokens.push(VersionToken::Numeric(value)); - } else { - while index < bytes.len() && bytes[index].is_ascii_alphabetic() { - index += 1; - } - tokens.push(VersionToken::Alpha(&self.raw[start..index])); - } - } - - tokens - } -} - -// ── Package ───────────────────────────────────────────────────────────────── -#[derive(Debug, Clone)] -pub struct PackageTemp { - pub meta: PackageMeta, - pub temp_package_path: String, -} - -#[derive(Debug, Clone, RedbCodec)] -pub struct DeclarativeTrigger { - pub format: String, - pub triggers: Vec, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DecoderTrigger { - PreInstall, - PostInstall, - PreUpgrade, - PostUpgrade, - PreRemove, - PostRemove, -} - -impl DecoderTrigger { - pub const ALL: [DecoderTrigger; 6] = [ - DecoderTrigger::PreInstall, - DecoderTrigger::PostInstall, - DecoderTrigger::PreUpgrade, - DecoderTrigger::PostUpgrade, - DecoderTrigger::PreRemove, - DecoderTrigger::PostRemove, - ]; -} - -#[derive(Debug, Clone, Default, Deserialize, CTryToRust, RedbCodec, RustToC)] -#[serde(default)] -pub struct PackageMeta { - pub name: String, - pub version: Version, - pub arch: String, - pub arch_sub: Option, - pub maintainer: String, - pub description: String, - pub license: Option, - pub url: Option, - pub sha256: [u8; 32], - pub installed_size: u64, -} - -#[derive(Debug, Clone, CTryToRust, RustToC)] -pub struct Dependency { - pub name: String, - pub constraint: u8, - pub version: Version, -} - -// ── PackageEntry ──────────────────────────────────────────────────────────── -#[derive(Debug, Clone)] -pub struct PackageEntry { - pub name: String, - pub arch: String, - pub arch_sub: Option, -} - -// ── FileEntryScope ────────────────────────────────────────────────────────── -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FileEntryScope { - Prefix = 0, - Config = 1, -} - -impl RedbCodable for FileEntryScope { - fn redb_encode(&self, buf: &mut Vec) { - buf.push(*self as u8); - } - - fn redb_decode(data: &[u8], offset: &mut usize) -> FileEntryScope { - let value = data[*offset]; - *offset += 1; - - match value { - 1 => FileEntryScope::Config, - _ => FileEntryScope::Prefix, - } - } -} - -// ── FileEntry ─────────────────────────────────────────────────────────────── -#[derive(Debug, Clone, RedbCodec)] -pub struct FileEntry { - pub path: String, - pub is_user: bool, - pub scope: FileEntryScope, -} - -// ── SearchFileEntry ───────────────────────────────────────────────────────── -#[derive(Debug, Clone, RustToC)] -pub struct SearchFileEntry { - pub path: String, - pub package_name: String, - pub is_user: bool, -} - -// ── PrefixEntry ───────────────────────────────────────────────────────────── -#[derive(Debug, Clone, RustToC)] -pub struct PrefixEntry { - pub prefix_digest: String, - - pub subject: String, - pub message: Option, - - pub timestamp: u64, - - pub working_config: Option, -} +pub struct UninstallPackagesTargets(pub Vec); -// ── ConfigCommitEntry ───────────────────────────────────────────────────────────── -#[derive(Debug, Clone, RustToC)] -pub struct ConfigCommitEntry { - pub config_digest: String, - - pub subject: String, - pub message: Option, -} - -// ── HistoryEntry ──────────────────────────────────────────────────────────── -#[derive(Debug, Clone, RustToC)] -pub struct HistoryEntry { - pub prefix_digest: String, - - pub subject: String, - pub message: Option, - - pub timestamp: u64, - - pub working_config: Option, - pub config_history: Vec, -} - -// ── DiffFileEntryCommon ────────────────────────────────────────────────────── -#[derive(Debug, Clone, RustToC)] -pub struct DiffFileEntryCommon { - pub path: String, - pub kind: FileDiffKind, -} - -// ── DiffPrefixFileEntry ───────────────────────────────────────────────────── -#[derive(Debug, Clone, RustToC)] -pub struct DiffPrefixFileEntry { - pub common: DiffFileEntryCommon, - pub source: DiffFileSource, - pub package_name: String, - pub is_user: bool, -} - -// ── DiffConfigFileEntry ───────────────────────────────────────────────────── -#[derive(Debug, Clone, RustToC)] -pub struct DiffConfigFileEntry { - pub common: DiffFileEntryCommon, - pub package_name: Option, -} - -// ── DiffPackageEntry ──────────────────────────────────────────────────────── -#[derive(Debug, Clone, RustToC)] -pub struct DiffPackageEntry { - pub name: String, - pub kind: PackageDiffKind, - pub version: Version, - - // Only this package's own files. A changed file with no package to - // attach to is not here — it's in `diff::run()`'s separate - // unattached-files return value. - pub files: Vec, -} - -// ── DiffUntrackedFileEntry ────────────────────────────────────────────────── -// A changed /usr file that belongs to no package at all — not package-owned, -// not attached as a user file. By design this shouldn't normally happen -// (every /usr file is meant to come with a package), but if it does, it's -// surfaced here rather than silently dropped. No package_name: there is none. -#[derive(Debug, Clone, RustToC)] -pub struct DiffUntrackedFileEntry { - pub common: DiffFileEntryCommon, - pub source: DiffFileSource, -} - -pub struct Targets(pub Vec); - -impl Targets { +impl UninstallPackagesTargets { pub fn entries(&self) -> &[PackageEntry] { &self.0 } @@ -370,21 +57,21 @@ pub struct DiffPackagesSnapshot { } // ── PartitionMount / PartitionSpec (bootstrap setup) ──────────────────────── -#[derive(Debug, Clone, CTryToRust)] +#[derive(Debug, Clone)] pub struct PartitionMount { pub mount_path: String, pub device_path: String, pub fs_kind: FsKind, } -#[derive(Debug, Clone, CTryToRust)] +#[derive(Debug, Clone)] pub struct PartitionSpec { pub mount_path: String, pub size_mib: u64, pub fs_kind: FsKind, } -#[derive(Debug, Clone, CTryToRust)] +#[derive(Debug, Clone)] pub struct GptLayout { pub esp_size_mib: u64, pub deploy_fs: FsKind, @@ -393,7 +80,7 @@ pub struct GptLayout { pub force_wipe: bool, } -#[derive(Debug, Clone, CTryToRust)] +#[derive(Debug, Clone)] pub struct BtrfsOptions { pub node_size: u32, pub sector_size: u32, diff --git a/lib/types/src/package.rs b/lib/types/src/package.rs new file mode 100644 index 00000000..b5611f80 --- /dev/null +++ b/lib/types/src/package.rs @@ -0,0 +1,171 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::cmp::Ordering; +use std::mem::size_of; + +use serde::{Deserialize, Deserializer}; + +use upac_abi::error::ErrorKind; +use upac_abi::package::{CPackageDependency, CPackageMeta, CVersion}; +use upac_abi::types::{COwned, CSlice}; + +use upac_macro::{CTryToRust, RedbCodec, RustToC}; + +// ── Version ───────────────────────────────────────────────────────────────── +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum VersionToken<'a> { + Alpha(&'a str), + Numeric(u64), +} + +#[derive(Debug, Clone, PartialEq, Eq, CTryToRust, RedbCodec, RustToC)] +pub struct Version { + pub epoch: u32, + pub raw: String, +} + +impl Default for Version { + fn default() -> Self { + Version { + epoch: 0, + raw: "1.0.0".to_owned(), + } + } +} + +impl Version { + pub fn parse(raw: &str) -> Version { + match raw.split_once(':') { + Some((epoch, rest)) => Version { + epoch: epoch.parse().unwrap_or(0), + raw: rest.to_owned(), + }, + None => Version { + epoch: 0, + raw: raw.to_owned(), + }, + } + } +} + +impl<'de> Deserialize<'de> for Version { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let raw = String::deserialize(deserializer)?; + + Ok(Version::parse(&raw)) + } +} + +impl PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Version { + fn cmp(&self, other: &Self) -> Ordering { + if self.epoch != other.epoch { + return self.epoch.cmp(&other.epoch); + } + + let self_tokens = self.tokenize(); + let other_tokens = other.tokenize(); + + let mut self_iter = self_tokens.iter(); + let mut other_iter = other_tokens.iter(); + + loop { + match (self_iter.next(), other_iter.next()) { + (Some(a), Some(b)) => match a.cmp(b) { + Ordering::Equal => continue, + ordering => return ordering, + }, + (Some(VersionToken::Numeric(_)), None) => return Ordering::Greater, + (Some(VersionToken::Alpha(_)), None) => return Ordering::Less, + (None, Some(VersionToken::Numeric(_))) => return Ordering::Less, + (None, Some(VersionToken::Alpha(_))) => return Ordering::Greater, + (None, None) => return Ordering::Equal, + } + } + } +} + +impl Version { + fn tokenize(&self) -> Vec> { + let bytes = self.raw.as_bytes(); + let mut tokens = Vec::new(); + let mut index = 0; + + while index < bytes.len() { + if !bytes[index].is_ascii_alphanumeric() { + index += 1; + continue; + } + + let start = index; + if bytes[index].is_ascii_digit() { + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + } + let value = self.raw[start..index].parse().unwrap_or(u64::MAX); + tokens.push(VersionToken::Numeric(value)); + } else { + while index < bytes.len() && bytes[index].is_ascii_alphabetic() { + index += 1; + } + tokens.push(VersionToken::Alpha(&self.raw[start..index])); + } + } + + tokens + } +} + +// ── Package ───────────────────────────────────────────────────────────────── +#[derive(Debug, Clone)] +pub struct PackageTemp { + pub meta: PackageMeta, + pub temp_package_path: String, +} + +#[derive(Debug, Clone, Default, Deserialize, CTryToRust, RedbCodec, RustToC)] +#[serde(default)] +pub struct PackageMeta { + pub name: String, + pub version: Version, + pub arch: String, + pub arch_sub: Option, + pub maintainer: String, + pub description: String, + pub license: Option, + pub url: Option, + pub sha256: [u8; 32], + pub installed_size: u64, +} + +// ── PackageEntry ──────────────────────────────────────────────────────────── +#[derive(Debug, Clone)] +pub struct PackageEntry { + pub name: String, + pub arch: String, + pub arch_sub: Option, +} + +#[derive(Debug, Clone, CTryToRust, RustToC)] +pub struct PackageDependency { + pub name: String, + pub constraint: u8, + pub version: Version, +} + +#[derive(Debug)] +pub struct DecodedPackageMeta { + pub meta: PackageMeta, + pub dependencies: Vec, +} diff --git a/lib/types/src/states.rs b/lib/types/src/states.rs index ed167e8b..bea4cf3c 100644 --- a/lib/types/src/states.rs +++ b/lib/types/src/states.rs @@ -3,10 +3,12 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::error::{CommandState, ErrorDomain}; +use upac_abi::error::ErrorDomain; use upac_macro::{FromStageIndex, StageKey}; +use crate::error::CommandState; + macro_rules! impl_command_state { ($name:ident, $domain:ident) => { impl CommandState for $name { diff --git a/lib/types/src/tests.rs b/lib/types/src/tests.rs deleted file mode 100644 index 31f09828..00000000 --- a/lib/types/src/tests.rs +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use super::*; - -fn sample_version() -> Version { - Version { - epoch: 1, - raw: "2.5.0-3~rc1".to_owned(), - } -} - -#[test] -fn version_redb_round_trip_preserves_value() { - let original = sample_version(); - - let mut buf = Vec::new(); - original.redb_encode(&mut buf); - - let mut offset = 0; - let restored = Version::redb_decode(&buf, &mut offset); - - assert_eq!(restored, original); - assert_eq!(offset, buf.len()); -} - -#[test] -fn version_ord_equal_versions_compare_equal() { - let a = Version { - epoch: 0, - raw: "1.2.3".to_owned(), - }; - let b = a.clone(); - - assert_eq!(a.cmp(&b), Ordering::Equal); -} - -#[test] -fn version_ord_epoch_dominates_everything_else() { - let low_epoch = Version { - epoch: 0, - raw: "99.99.99".to_owned(), - }; - let high_epoch = Version { - epoch: 1, - raw: "0.0.1".to_owned(), - }; - - assert!(high_epoch > low_epoch); -} - -#[test] -fn version_ord_numeric_segments_compare_numerically() { - let a = Version { - epoch: 0, - raw: "1.9".to_owned(), - }; - let b = Version { - epoch: 0, - raw: "1.10".to_owned(), - }; - - assert!(b > a); -} - -#[test] -fn version_ord_numeric_beats_alpha_at_same_position() { - let release = Version { - epoch: 0, - raw: "1.0".to_owned(), - }; - let pre_release = Version { - epoch: 0, - raw: "1.0a".to_owned(), - }; - - assert!(release > pre_release); -} - -#[test] -fn version_ord_trailing_extra_numeric_is_newer() { - let a = Version { - epoch: 0, - raw: "1.0".to_owned(), - }; - let b = Version { - epoch: 0, - raw: "1.0.1".to_owned(), - }; - - assert!(b > a); -} - -#[test] -fn version_ord_trailing_extra_alpha_is_older() { - let a = Version { - epoch: 0, - raw: "1.0".to_owned(), - }; - let b = Version { - epoch: 0, - raw: "1.0-alpha".to_owned(), - }; - - assert!(b < a); -} - -#[test] -fn version_ord_mixed_format_examples_compare_consistently() { - let semver = Version { - epoch: 0, - raw: "1.23".to_owned(), - }; - let calver_dotted = Version { - epoch: 0, - raw: "26.5.4".to_owned(), - }; - let calver_flat = Version { - epoch: 0, - raw: "20263545".to_owned(), - }; - let alpha_mixed = Version { - epoch: 0, - raw: "1.13pre-1".to_owned(), - }; - let no_suffix = Version { - epoch: 0, - raw: "1.13".to_owned(), - }; - - assert!(calver_dotted > semver); - assert!(calver_flat > calver_dotted); - assert_eq!(alpha_mixed.cmp(&alpha_mixed.clone()), Ordering::Equal); - assert!(no_suffix > alpha_mixed); -} - -#[test] -fn file_entry_redb_round_trip_preserves_value() { - let original = FileEntry { - path: "/usr/bin/up".to_owned(), - is_user: false, - scope: FileEntryScope::Prefix, - }; - - let mut buf = Vec::new(); - original.redb_encode(&mut buf); - - let mut offset = 0; - let restored = FileEntry::redb_decode(&buf, &mut offset); - - assert_eq!(restored.path, original.path); - assert_eq!(restored.is_user, original.is_user); - assert_eq!(restored.scope, original.scope); - assert_eq!(offset, buf.len()); -} diff --git a/lib/types/src/traits.rs b/lib/types/src/traits.rs new file mode 100644 index 00000000..15684c4d --- /dev/null +++ b/lib/types/src/traits.rs @@ -0,0 +1,25 @@ +use upac_abi::hook::{CProgressEvent, HookAck}; + +use crate::error::DecodeError; +use crate::package::DecodedPackageMeta; + +pub trait Booter: Sized { + type Error; + + fn new() -> Result; + fn set_one_shot(&mut self, entry_name: &str) -> Result<(), Self::Error>; + fn confirm_boot(&mut self, entry_name: &str, esp_mount_point: &str) -> Result<(), Self::Error>; + + fn install( + &mut self, esp_mount_point: &str, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, + ) -> Result<(), Self::Error>; +} + +pub trait DecodeMeta { + fn decode(&self, sha256: [u8; 32]) -> Result; +} + +pub trait MessageHook { + fn send(&self, event: &CProgressEvent) -> HookAck; +} diff --git a/lib/types/tests/conversions.rs b/lib/types/tests/conversions.rs index 7718d895..3d56a127 100644 --- a/lib/types/tests/conversions.rs +++ b/lib/types/tests/conversions.rs @@ -4,7 +4,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use upac_abi::package::{CPackageMeta, CVersion}; -use upac_types::{PackageMeta, Version}; +use upac_types::package::{PackageMeta, Version}; fn sample_version() -> Version { Version { From 3abe56d95d2f93102d84a8ad2cdd497f93d46bcf Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 09:17:16 +0400 Subject: [PATCH 048/148] fix: round out CNew/CValidate coverage on package + response structs - CVersion, CPackageMeta, CPackageDependency: add missing CNew (CPackageDependency also gets CFree) - anything nested in a request or response should be constructible the same way everywhere - all 9 response entry structs (CDiffPackageEntry, CDiffFileEntryCommon, CDiffPrefixFileEntry, CDiffConfigFileEntry, CDiffUntrackedFileEntry, CConfigCommitEntry, CSearchFileEntry, CPrefixEntry, CHistoryEntry) and all 12 response wrapper structs: add CNew/CValidate so upac-cli can validate a response before reading it, instead of trusting upac-lib's output blindly (LIB_ABI_VERSION only guards against build skew, not runtime memory corruption) - lib.rs: self::-style imports, CDecodePackageResponse -> CDecodeResponse Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/lib.rs | 13 +++++------ lib/abi/src/package.rs | 6 ++--- lib/abi/src/response.rs | 49 +++++++++++++++++++++-------------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/abi/src/lib.rs b/lib/abi/src/lib.rs index bf44bf80..e78a3a13 100644 --- a/lib/abi/src/lib.rs +++ b/lib/abi/src/lib.rs @@ -5,12 +5,12 @@ use std::ffi::c_void; -use crate::error::ErrorKind; -use crate::hook::{CProgressEvent, HookAck}; -use crate::request::{ +use self::error::ErrorKind; +use self::hook::{CProgressEvent, HookAck}; +use self::request::{ CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, CDecodeRequest, }; -use crate::response::CDecodePackageResponse; +use self::response::CDecodeResponse; pub mod error; pub mod hook; @@ -43,10 +43,9 @@ pub type ConfirmBootFn = pub type InstallFn = unsafe extern "C" fn(request: *const CBootPluginInstallRequest, err_out: *mut ErrorKind) -> i32; -pub type DecodeFn = - unsafe extern "C" fn(request: *const CDecodeRequest, response_out: *mut CDecodePackageResponse) -> i32; +pub type DecodeFn = unsafe extern "C" fn(request: *const CDecodeRequest, response_out: *mut CDecodeResponse) -> i32; -pub type FreeDecodeResponseFn = unsafe extern "C" fn(response: *mut CDecodePackageResponse); +pub type FreeDecodeResponseFn = unsafe extern "C" fn(response: *mut CDecodeResponse); #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/lib/abi/src/package.rs b/lib/abi/src/package.rs index c7985999..f5562bd3 100644 --- a/lib/abi/src/package.rs +++ b/lib/abi/src/package.rs @@ -10,7 +10,7 @@ use crate::memory::free_cslice; use crate::types::{CSlice, check_size}; #[repr(C)] -#[derive(CFree, CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CVersion { pub struct_size: usize, @@ -20,7 +20,7 @@ pub struct CVersion { } #[repr(C)] -#[derive(CFree, CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CPackageMeta { pub struct_size: usize, pub name: CSlice, @@ -50,7 +50,7 @@ pub struct CPackageInfo { } #[repr(C)] -#[derive(CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CPackageDependency { pub struct_size: usize, diff --git a/lib/abi/src/response.rs b/lib/abi/src/response.rs index 4b2ba363..61757160 100644 --- a/lib/abi/src/response.rs +++ b/lib/abi/src/response.rs @@ -13,9 +13,10 @@ use crate::types::{CSlice, CVec, check_size}; use crate::{DiffFileSource, FileDiffKind, PackageDiffKind}; #[repr(C)] -#[derive(CFree)] +#[derive(CFree, CNew, CValidate)] pub struct CDiffPackageEntry { pub struct_size: usize, + pub name: CSlice, pub kind: PackageDiffKind, pub version: CVersion, @@ -23,7 +24,7 @@ pub struct CDiffPackageEntry { } #[repr(C)] -#[derive(CFree, CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CDiffFileEntryCommon { pub struct_size: usize, @@ -32,7 +33,7 @@ pub struct CDiffFileEntryCommon { } #[repr(C)] -#[derive(CFree, CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CDiffPrefixFileEntry { pub struct_size: usize, @@ -43,7 +44,7 @@ pub struct CDiffPrefixFileEntry { } #[repr(C)] -#[derive(CFree, CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CDiffConfigFileEntry { pub struct_size: usize, @@ -53,7 +54,7 @@ pub struct CDiffConfigFileEntry { } #[repr(C)] -#[derive(CFree, CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CDiffUntrackedFileEntry { pub struct_size: usize, @@ -62,7 +63,7 @@ pub struct CDiffUntrackedFileEntry { } #[repr(C)] -#[derive(CFree, CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CConfigCommitEntry { pub struct_size: usize, @@ -73,28 +74,28 @@ pub struct CConfigCommitEntry { } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CListConfigResponse { pub struct_size: usize, pub commits: CVec, } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CListPackagesResponse { pub struct_size: usize, pub metas: CVec, } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CSearchMetaResponse { pub struct_size: usize, pub metas: CVec, } #[repr(C)] -#[derive(CFree, CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CSearchFileEntry { pub struct_size: usize, @@ -104,28 +105,28 @@ pub struct CSearchFileEntry { } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CSearchFilesResponse { pub struct_size: usize, pub files: CVec, } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CSearchInMetaResponse { pub struct_size: usize, pub metas: CVec, } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CSearchInPackageFilesResponse { pub struct_size: usize, pub files: CVec, } #[repr(C)] -#[derive(CFree, CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CPrefixEntry { pub struct_size: usize, @@ -139,14 +140,14 @@ pub struct CPrefixEntry { } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CListPrefixResponse { pub struct_size: usize, pub prefixes: CVec, } #[repr(C)] -#[derive(CFree, CValidate)] +#[derive(CFree, CNew, CValidate)] pub struct CHistoryEntry { pub struct_size: usize, @@ -161,35 +162,35 @@ pub struct CHistoryEntry { } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CListHistoryResponse { pub struct_size: usize, pub history: CVec, } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CDiffPrefixResponse { pub struct_size: usize, pub files: CVec, } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CDiffConfigResponse { pub struct_size: usize, pub files: CVec, } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CDiffPackagesResponse { pub struct_size: usize, pub diff_packages: CVec, } #[repr(C)] -#[derive(CFree, CNew)] +#[derive(CFree, CNew, CValidate)] pub struct CDiffResponse { pub struct_size: usize, pub diff_packages: CVec, @@ -197,8 +198,8 @@ pub struct CDiffResponse { } #[repr(C)] -#[derive(CValidate)] -pub struct CDecodePackageResponse { +#[derive(CValidate, CNew)] +pub struct CDecodeResponse { pub struct_size: usize, pub meta: CPackageMeta, @@ -209,7 +210,7 @@ pub struct CDecodePackageResponse { pub free: FreeDecodeResponseFn, } -impl Drop for CDecodePackageResponse { +impl Drop for CDecodeResponse { fn drop(&mut self) { unsafe { (self.free)(self) }; } From 17df1787b10cf4328b253de7372815d2eb547146 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 09:17:32 +0400 Subject: [PATCH 049/148] fix: add boot.rs and decode request/response mirrors - new lib/types/src/boot.rs: BootPluginSetOneShotRequest, BootPluginConfirmSuccsesBootRequest, BootPluginInstallRequest - CTryToRust+RustToC mirrors of the three CBootPlugin*Request structs, replacing booters' hand-rolled C-struct field parsing - decoder.rs: add DecodeRequest (mirrors CDecodeRequest, incl. the cancel_token raw pointer) and DecodeResponse (mirrors CDecodeResponse: meta + dependencies + declarative_triggers) - DecodedPackageMeta stays separate, it's the narrower per-decoder-format parse result, not the full plugin response Co-Authored-By: Claude Sonnet 5 --- lib/types/src/boot.rs | 34 ++++++++++++++++++++++++++++++++++ lib/types/src/decoder.rs | 25 +++++++++++++++++++++++-- lib/types/src/lib.rs | 3 ++- 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 lib/types/src/boot.rs diff --git a/lib/types/src/boot.rs b/lib/types/src/boot.rs new file mode 100644 index 00000000..d4d74442 --- /dev/null +++ b/lib/types/src/boot.rs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::error::ErrorKind; +use upac_abi::request::{ + CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, +}; +use upac_abi::types::{COwned, CSlice}; + +use upac_macro::{CTryToRust, RustToC}; + +#[derive(Debug, Clone, CTryToRust, RustToC)] +pub struct BootPluginSetOneShotRequest { + pub entry_name: String, +} + +#[derive(Debug, Clone, CTryToRust, RustToC)] +pub struct BootPluginConfirmSuccsesBootRequest { + pub entry_name: String, + pub esp_mount_point: String, +} + +#[derive(Debug, Clone, CTryToRust, RustToC)] +pub struct BootPluginInstallRequest { + pub esp_mount_point: String, + pub esp_partition_number: u32, + pub esp_starting_lba: u64, + pub esp_ending_lba: u64, + pub esp_unique_partition_guid: [u8; 16], + pub to_slot: String, + pub from_slot: String, +} diff --git a/lib/types/src/decoder.rs b/lib/types/src/decoder.rs index 64b74d65..36b71819 100644 --- a/lib/types/src/decoder.rs +++ b/lib/types/src/decoder.rs @@ -5,9 +5,30 @@ use std::io::Read; -use upac_macro::RedbCodec; +use upac_abi::error::ErrorKind; +use upac_abi::hook::CancelToken; +use upac_abi::request::CDecodeRequest; +use upac_abi::response::CDecodeResponse; +use upac_abi::types::{COwned, CSlice}; +use upac_macro::{CTryToRust, RedbCodec, RustToC}; -use crate::error::DecodeError; +use super::error::DecodeError; +use super::package::{PackageDependency, PackageMeta}; + +#[derive(Debug, Clone, CTryToRust, RustToC)] +pub struct DecodeRequest { + pub package_path: String, + pub output_dir: String, + pub checksum: [u8; 32], + pub cancel_token: *mut CancelToken, +} + +#[derive(Debug, Clone, CTryToRust)] +pub struct DecodeResponse { + pub meta: PackageMeta, + pub dependencies: Vec, + pub declarative_triggers: Vec, +} #[derive(Debug, Clone, RedbCodec)] pub struct DeclarativeTrigger { diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 211a5a9d..7e7c4278 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -5,8 +5,9 @@ use upac_abi::FsKind; -use crate::package::{PackageEntry, PackageMeta}; +use self::package::{PackageEntry, PackageMeta}; +pub mod boot; pub mod codec; pub mod decoder; pub mod entry; From e06448cc98d436c1c8fdf3f5b9a836ecdd95f0ae Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 09:17:57 +0400 Subject: [PATCH 050/148] fix: propagate C-ABI struct moves through upac-lib, extract orchestrator::Context - fix imports broken by the abi/types reorg across mutated/*, unmutated/*, plugin/*, orchestrator/*, database/*, deploy/*, scripts/*: CommandState/ DecodeError/Message/MessageHook/ProgressEventBuilder/HookMessageFn now pulled from their new upac_types homes instead of stale upac_abi paths; Dependency -> PackageDependency; CDecodeRequest/CDecodeResponse from their new abi::request/abi::response locations - extract orchestrator::Context (struct, impl, Default) out of orchestrator/mod.rs into a new orchestrator/context.rs; rollback field and type_ids/unwind methods now pub(super) for the parent module's direct access - remove dead deploy/esp.rs and scripts/load.rs Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/boot/mod.rs | 1 + lib/lib/src/composefs/overlay.rs | 7 +- lib/lib/src/composefs/repository.rs | 2 +- lib/lib/src/database/attribution.rs | 5 +- lib/lib/src/database/files.rs | 2 +- lib/lib/src/database/meta.rs | 2 +- lib/lib/src/database/mod.rs | 10 +- lib/lib/src/database/triggers.rs | 2 +- lib/lib/src/deploy/digest.rs | 3 +- lib/lib/src/deploy/esp.rs | 24 ----- lib/lib/src/deploy/mod.rs | 21 +++- lib/lib/src/deploy/retention.rs | 8 +- lib/lib/src/export/mod.rs | 18 +--- lib/lib/src/lock.rs | 4 +- lib/lib/src/mutated/commit/mod.rs | 19 ++-- lib/lib/src/mutated/commit/transaction.rs | 8 +- lib/lib/src/mutated/files/apply.rs | 16 +-- lib/lib/src/mutated/files/checkout.rs | 8 +- lib/lib/src/mutated/files/commit.rs | 8 +- lib/lib/src/mutated/files/mod.rs | 18 ++-- lib/lib/src/mutated/files/open.rs | 12 ++- lib/lib/src/mutated/files/swap.rs | 8 +- lib/lib/src/mutated/gc/cleaning.rs | 8 +- lib/lib/src/mutated/gc/collect.rs | 8 +- lib/lib/src/mutated/gc/mod.rs | 16 ++- lib/lib/src/mutated/gc/pruning.rs | 5 +- lib/lib/src/mutated/installer/checkout.rs | 11 +- lib/lib/src/mutated/installer/commit.rs | 10 +- lib/lib/src/mutated/installer/fetching.rs | 9 +- lib/lib/src/mutated/installer/import.rs | 12 +-- lib/lib/src/mutated/installer/merge.rs | 11 +- lib/lib/src/mutated/installer/mod.rs | 21 ++-- lib/lib/src/mutated/installer/open.rs | 9 +- lib/lib/src/mutated/installer/preparation.rs | 8 +- lib/lib/src/mutated/installer/swap.rs | 9 +- lib/lib/src/mutated/mime/mod.rs | 15 ++- lib/lib/src/mutated/mime/preparing.rs | 9 +- lib/lib/src/mutated/mime/rendering.rs | 18 ++-- lib/lib/src/mutated/mime/writing.rs | 19 ++-- lib/lib/src/mutated/pin/mod.rs | 15 ++- lib/lib/src/mutated/pin/stage.rs | 9 +- lib/lib/src/mutated/rollback/checkout.rs | 12 ++- lib/lib/src/mutated/rollback/merge.rs | 9 +- lib/lib/src/mutated/rollback/mod.rs | 17 +-- lib/lib/src/mutated/rollback/swap.rs | 9 +- lib/lib/src/mutated/uninstaller/checkout.rs | 12 ++- lib/lib/src/mutated/uninstaller/commit.rs | 12 ++- lib/lib/src/mutated/uninstaller/merge.rs | 9 +- lib/lib/src/mutated/uninstaller/mod.rs | 33 +++--- lib/lib/src/mutated/uninstaller/open.rs | 15 +-- .../src/mutated/uninstaller/preparation.rs | 10 +- lib/lib/src/mutated/uninstaller/remove.rs | 15 +-- lib/lib/src/mutated/uninstaller/swap.rs | 9 +- lib/lib/src/mutated/update/checkout.rs | 12 ++- lib/lib/src/mutated/update/commit.rs | 14 +-- lib/lib/src/mutated/update/fetching.rs | 6 +- lib/lib/src/mutated/update/import.rs | 7 +- lib/lib/src/mutated/update/merge.rs | 13 ++- lib/lib/src/mutated/update/mod.rs | 21 ++-- lib/lib/src/mutated/update/open.rs | 11 +- lib/lib/src/mutated/update/preparation.rs | 8 +- lib/lib/src/mutated/update/swap.rs | 9 +- lib/lib/src/orchestrator/context.rs | 100 ++++++++++++++++++ lib/lib/src/orchestrator/cursor.rs | 2 +- lib/lib/src/orchestrator/mod.rs | 94 ++-------------- lib/lib/src/orchestrator/stage.rs | 5 +- lib/lib/src/plugin/boot/mod.rs | 11 +- lib/lib/src/plugin/decoder/mod.rs | 20 ++-- lib/lib/src/plugin/decoder/unpack.rs | 3 +- lib/lib/src/scripts/load.rs | 46 -------- lib/lib/src/scripts/mod.rs | 45 +++++++- lib/lib/src/unmutated/diff/comparing.rs | 15 +-- lib/lib/src/unmutated/diff/mod.rs | 21 ++-- lib/lib/src/unmutated/diff/preparing.rs | 9 +- .../src/unmutated/diff_config/comparing.rs | 11 +- lib/lib/src/unmutated/diff_config/mod.rs | 18 ++-- .../src/unmutated/diff_config/preparing.rs | 9 +- .../src/unmutated/diff_packages/comparing.rs | 13 ++- lib/lib/src/unmutated/diff_packages/mod.rs | 18 ++-- .../src/unmutated/diff_packages/preparing.rs | 12 ++- .../src/unmutated/diff_prefix/comparing.rs | 12 ++- lib/lib/src/unmutated/diff_prefix/mod.rs | 18 ++-- .../src/unmutated/diff_prefix/preparing.rs | 12 ++- lib/lib/src/unmutated/list_config/fetching.rs | 13 ++- lib/lib/src/unmutated/list_config/mod.rs | 18 ++-- .../src/unmutated/list_history/fetching.rs | 12 ++- lib/lib/src/unmutated/list_history/mod.rs | 17 +-- .../src/unmutated/list_packages/fetching.rs | 9 +- lib/lib/src/unmutated/list_packages/mod.rs | 17 +-- lib/lib/src/unmutated/list_prefix/fetching.rs | 12 ++- lib/lib/src/unmutated/list_prefix/mod.rs | 17 +-- lib/lib/src/unmutated/search_files/mod.rs | 17 +-- .../src/unmutated/search_files/searching.rs | 12 ++- lib/lib/src/unmutated/search_in_meta/mod.rs | 17 +-- .../src/unmutated/search_in_meta/searching.rs | 12 ++- .../unmutated/search_in_package_files/mod.rs | 18 ++-- .../search_in_package_files/searching.rs | 13 ++- lib/lib/src/unmutated/search_meta/mod.rs | 17 +-- .../src/unmutated/search_meta/searching.rs | 9 +- lib/lib/tests/orchestrator.rs | 3 +- 100 files changed, 825 insertions(+), 593 deletions(-) delete mode 100644 lib/lib/src/deploy/esp.rs create mode 100644 lib/lib/src/orchestrator/context.rs delete mode 100644 lib/lib/src/scripts/load.rs diff --git a/lib/lib/src/boot/mod.rs b/lib/lib/src/boot/mod.rs index f064caca..9cfe143b 100644 --- a/lib/lib/src/boot/mod.rs +++ b/lib/lib/src/boot/mod.rs @@ -9,6 +9,7 @@ use std::path::Path; use composefs::generic_tree::Stat; use composefs::repository::Repository; use composefs::tree::{Directory, FileSystem, Inode}; + use composefs_boot::bootloader::{BootEntry, get_boot_resources}; use composefs_boot::cmdline::ComposefsCmdline; use composefs_boot::write_boot::write_boot_simple; diff --git a/lib/lib/src/composefs/overlay.rs b/lib/lib/src/composefs/overlay.rs index 8be89349..1d0b4f09 100644 --- a/lib/lib/src/composefs/overlay.rs +++ b/lib/lib/src/composefs/overlay.rs @@ -10,9 +10,10 @@ use std::path::{Path, PathBuf}; use composefs::repository::{ImportContext, Repository}; use composefs::tree::{FileSystem, Inode}; -use crate::composefs::error::RepoError; -use crate::composefs::file::{FileHandle, stat_from_metadata}; -use crate::composefs::repository::ObjectID; +use super::error::RepoError; +use super::file::{FileHandle, stat_from_metadata}; +use super::repository::ObjectID; + use crate::layout::deployment::OVERLAY_OPAQUE_XATTR; pub fn apply_overlay_upper( diff --git a/lib/lib/src/composefs/repository.rs b/lib/lib/src/composefs/repository.rs index f6f466c0..727a8633 100644 --- a/lib/lib/src/composefs/repository.rs +++ b/lib/lib/src/composefs/repository.rs @@ -15,7 +15,7 @@ use composefs::tree::FileSystem; use nix::fcntl::AT_FDCWD; -use crate::composefs::error::RepoError; +use super::error::RepoError; pub type ObjectID = Sha256HashValue; diff --git a/lib/lib/src/database/attribution.rs b/lib/lib/src/database/attribution.rs index 020dbe78..5516d4b0 100644 --- a/lib/lib/src/database/attribution.rs +++ b/lib/lib/src/database/attribution.rs @@ -3,12 +3,13 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception +use upac_types::entry::FileEntry; +use upac_types::package::PackageMeta; + use super::error::DatabaseError; use super::files::FileStore; use super::meta::MetaStore; -use upac_types::{FileEntry, PackageMeta}; - pub struct FileAttribution { pub package_meta: PackageMeta, pub file_entry: FileEntry, diff --git a/lib/lib/src/database/files.rs b/lib/lib/src/database/files.rs index 87d09941..b32e7181 100644 --- a/lib/lib/src/database/files.rs +++ b/lib/lib/src/database/files.rs @@ -9,8 +9,8 @@ use twox_hash::xxhash3_64::Hasher as XxHasher; use uuid::Uuid; -use upac_types::FileEntry; use upac_types::codec::RedbCodable; +use upac_types::entry::FileEntry; use super::error::DatabaseError; use super::{FILES_UUID_HASH_TABLE, FILES_UUID_TABLE, MemoryDatabase, ReadTransactionExt, ReadableSource}; diff --git a/lib/lib/src/database/meta.rs b/lib/lib/src/database/meta.rs index c5a124cb..36abfb06 100644 --- a/lib/lib/src/database/meta.rs +++ b/lib/lib/src/database/meta.rs @@ -9,8 +9,8 @@ use twox_hash::xxhash3_64::Hasher as XxHasher; use uuid::Uuid; -use upac_types::PackageMeta; use upac_types::codec::{RedbCodable, write_len_prefixed, write_opt_str}; +use upac_types::package::PackageMeta; use super::error::DatabaseError; use super::{MemoryDatabase, PACKAGES_HASH_TABLE, PACKAGES_UUID_TABLE, ReadTransactionExt, ReadableSource}; diff --git a/lib/lib/src/database/mod.rs b/lib/lib/src/database/mod.rs index 6957b321..63246ebb 100644 --- a/lib/lib/src/database/mod.rs +++ b/lib/lib/src/database/mod.rs @@ -14,16 +14,16 @@ use redb::{ use uuid::Uuid; -use crate::layout::database::{ - FILES_BY_PATH_TABLE_NAME, FILES_TABLE_NAME, PACKAGES_BY_NAME_TABLE_NAME, PACKAGES_TABLE_NAME, - PACKAGES_TRIGGERS_TABLE_NAME, -}; - use self::error::DatabaseError; use self::files::StoredFileEntry; use self::meta::StoredPackageMeta; use self::triggers::StoredTriggers; +use crate::layout::database::{ + FILES_BY_PATH_TABLE_NAME, FILES_TABLE_NAME, PACKAGES_BY_NAME_TABLE_NAME, PACKAGES_TABLE_NAME, + PACKAGES_TRIGGERS_TABLE_NAME, +}; + pub mod attribution; pub mod error; pub mod files; diff --git a/lib/lib/src/database/triggers.rs b/lib/lib/src/database/triggers.rs index 579b0272..544e59f8 100644 --- a/lib/lib/src/database/triggers.rs +++ b/lib/lib/src/database/triggers.rs @@ -7,8 +7,8 @@ use redb::{ReadableDatabase, TypeName, Value as RedbValue}; use uuid::Uuid; -use upac_types::DeclarativeTrigger; use upac_types::codec::RedbCodable; +use upac_types::decoder::DeclarativeTrigger; use super::error::DatabaseError; use super::{MemoryDatabase, PACKAGES_TRIGGERS_TABLE, ReadTransactionExt, ReadableSource}; diff --git a/lib/lib/src/deploy/digest.rs b/lib/lib/src/deploy/digest.rs index e75816e9..4aa915d4 100644 --- a/lib/lib/src/deploy/digest.rs +++ b/lib/lib/src/deploy/digest.rs @@ -5,7 +5,8 @@ use linux_kernel_cmdline::utf8::CmdlineOwned; -use crate::deploy::error::SysrootError; +use super::SysrootError; + use crate::layout::deployment::PREFIX_DIGEST_CMDLINE_PARAM; pub fn current_prefix_digest() -> Result { diff --git a/lib/lib/src/deploy/esp.rs b/lib/lib/src/deploy/esp.rs deleted file mode 100644 index 5caae6cb..00000000 --- a/lib/lib/src/deploy/esp.rs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::path::PathBuf; - -use rsmount::tables::MountInfo; - -use crate::deploy::error::SysrootError; -use crate::layout::boot::{ESP_MOUNT_FALLBACK, ESP_MOUNT_PRIMARY}; - -pub fn find_esp_mount() -> Result { - let mut mount_table = MountInfo::new()?; - mount_table.import_mountinfo()?; - - for candidate_for_mount in [ESP_MOUNT_PRIMARY, ESP_MOUNT_FALLBACK] { - if mount_table.find_target(candidate_for_mount).is_some() { - return Ok(PathBuf::from(candidate_for_mount)); - } - } - - Err(SysrootError::EspNotFound) -} diff --git a/lib/lib/src/deploy/mod.rs b/lib/lib/src/deploy/mod.rs index 0ce96831..c0b10df2 100644 --- a/lib/lib/src/deploy/mod.rs +++ b/lib/lib/src/deploy/mod.rs @@ -20,20 +20,20 @@ use rsblkid::utils::evaluation::find_canonical_device_name_from_path; use rsmount::tables::MountInfo; -use self::error::SysrootError; - use upac_types::settings::RuntimeSettings; +use self::digest::current_prefix_digest; +use self::error::SysrootError; + use crate::composefs::error::RepoError; use crate::composefs::repository::{self, ObjectID}; use crate::database::record::DeployRecord; -use crate::deploy::digest::current_prefix_digest; use crate::errors::CommonError; +use crate::layout::boot::{ESP_MOUNT_FALLBACK, ESP_MOUNT_PRIMARY}; use crate::layout::deployment::{DEPLOYS_DIR, NEXT_SEQ_PATH, REPO_DIR, ROOT_DIR, SYSROOT_DIR}; pub mod digest; pub mod error; -pub mod esp; pub mod retention; #[cfg(test)] @@ -219,6 +219,19 @@ impl Drop for Deploy { } } +pub fn find_esp_mount() -> Result { + let mut mount_table = MountInfo::new()?; + mount_table.import_mountinfo()?; + + for candidate_for_mount in [ESP_MOUNT_PRIMARY, ESP_MOUNT_FALLBACK] { + if mount_table.find_target(candidate_for_mount).is_some() { + return Ok(PathBuf::from(candidate_for_mount)); + } + } + + Err(SysrootError::EspNotFound) +} + #[cfg(test)] impl Deploy { pub(crate) fn for_testing(deploy_dir: PathBuf) -> Self { diff --git a/lib/lib/src/deploy/retention.rs b/lib/lib/src/deploy/retention.rs index ff4add29..5c02d7f7 100644 --- a/lib/lib/src/deploy/retention.rs +++ b/lib/lib/src/deploy/retention.rs @@ -3,13 +3,15 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use crate::deploy::Deploy; +use upac_types::hook::ProgressEventBuilder; + +use super::Deploy; use crate::errors::CommonError; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; pub struct RetentionStage; diff --git a/lib/lib/src/export/mod.rs b/lib/lib/src/export/mod.rs index 69661933..632f9636 100644 --- a/lib/lib/src/export/mod.rs +++ b/lib/lib/src/export/mod.rs @@ -4,9 +4,10 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use upac_abi::LIB_ABI_VERSION; -use upac_abi::error::{CError, CommandState, ErrorKind}; +use upac_abi::error::{CError, ErrorKind}; use upac_abi::hook::CancelToken; -use upac_abi::response::CUnmutatedResponse; + +use upac_types::error::CommandState; pub mod mutated; pub mod unmutated; @@ -14,7 +15,7 @@ pub mod unmutated; /// # Safety /// Touches no pointers — `unsafe extern "C"` only to match the ABI calling convention. #[unsafe(no_mangle)] -pub unsafe extern "C" fn version_abi() -> u32 { +pub unsafe extern "C" fn lib_abi_version() -> u32 { LIB_ABI_VERSION } @@ -29,17 +30,6 @@ pub unsafe extern "C" fn cancel(token: *mut CancelToken) { unsafe { (*token).cancel() }; } -/// # Safety -/// `response`, if non-null, must point to a valid `CUnmutatedResponse` produced by this library -/// that has not already been freed. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn free_response(response: *mut CUnmutatedResponse) { - if response.is_null() { - return; - } - unsafe { (*response).free() }; -} - pub(crate) unsafe fn write_error(err_out: *mut CError, state: S, error: ErrorKind) { if !err_out.is_null() { unsafe { diff --git a/lib/lib/src/lock.rs b/lib/lib/src/lock.rs index 89b32c03..0c2bc6ee 100644 --- a/lib/lib/src/lock.rs +++ b/lib/lib/src/lock.rs @@ -10,7 +10,7 @@ use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr, bind, socket use upac_abi::error::ErrorKind; -use crate::layout::runtime; +use crate::layout::runtime::LOCK_ADDRESS; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LockError { @@ -51,7 +51,7 @@ pub struct Lock { impl Lock { pub fn acquire() -> Result { let socket = socket(AddressFamily::Unix, SockType::Stream, SockFlag::SOCK_CLOEXEC, None)?; - let address = UnixAddr::new_abstract(runtime::LOCK_ADDRESS.as_bytes())?; + let address = UnixAddr::new_abstract(LOCK_ADDRESS.as_bytes())?; bind(socket.as_raw_fd(), &address)?; diff --git a/lib/lib/src/mutated/commit/mod.rs b/lib/lib/src/mutated/commit/mod.rs index 10eba377..144c29b9 100644 --- a/lib/lib/src/mutated/commit/mod.rs +++ b/lib/lib/src/mutated/commit/mod.rs @@ -5,21 +5,26 @@ use std::os::raw::c_void; +use upac_types::TmpPath; +use upac_types::hook::Message; +use upac_types::states::CommitStateId; +use upac_types::traits::MessageHook; + +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CCommitRequest; -pub use self::error::CommitError; - use self::transaction::TransactionStage; use crate::deploy::retention::RetentionStage; use crate::deploy::{Deploy, DeployMode}; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_mutating}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_mutating}; use crate::scripts::HookStage; use crate::scripts::pipeline::{Operation, PipelineTrigger}; -use upac_types::TmpPath; -use upac_types::states::CommitStateId; + +pub use self::error::CommitError; mod error; mod transaction; @@ -45,7 +50,7 @@ impl<'a> TryFrom<&'a CCommitRequest> for CommitData<'a> { fn try_from(request: &'a CCommitRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(CommitData { tmp_path: (&request.tmp_path).try_into()?, diff --git a/lib/lib/src/mutated/commit/transaction.rs b/lib/lib/src/mutated/commit/transaction.rs index f7c3bbd9..5904e5f0 100644 --- a/lib/lib/src/mutated/commit/transaction.rs +++ b/lib/lib/src/mutated/commit/transaction.rs @@ -6,7 +6,10 @@ use composefs::fsverity::FsVerityHashValue; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; +use upac_types::hook::ProgressEventBuilder; + +use super::{CommitError, CommitMessage, Subject}; use crate::composefs::overlay::apply_overlay_upper; use crate::composefs::repository::commit_tree; @@ -14,9 +17,8 @@ use crate::database::record::DeployRecord; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::deployment::CONFIG_DIR_NAME; -use crate::mutated::commit::{CommitError, CommitMessage, Subject}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; pub struct TransactionStage; diff --git a/lib/lib/src/mutated/files/apply.rs b/lib/lib/src/mutated/files/apply.rs index c8c7d1ff..1e4bc5ab 100644 --- a/lib/lib/src/mutated/files/apply.rs +++ b/lib/lib/src/mutated/files/apply.rs @@ -11,10 +11,16 @@ use composefs::generic_tree::Stat; use composefs::repository::{ImportContext, Repository}; use composefs::tree::FileSystem; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_abi::{DiffFileSource, FileDiffKind}; -use upac_types::{FileEntry, FileEntryScope}; +use upac_types::entry::{FileEntry, FileEntryScope}; +use upac_types::hook::ProgressEventBuilder; + +use super::{ + EtcUpperDir, FilesError, PendingFiles, RequestedFileKind, RequestedFileScope, TargetUuid, TotalFiles, + WorkingDatabase, WorkingTree, +}; use crate::composefs::error::RepoError; use crate::composefs::file::{FileHandle, stat_from_metadata}; @@ -23,12 +29,8 @@ use crate::database::files::FileStoreMut; use crate::deploy::Deploy; use crate::errors::CommonError; use crate::layout::deployment::LIVE_ETC_DIR; -use crate::mutated::files::{ - EtcUpperDir, FilesError, PendingFiles, RequestedFileKind, RequestedFileScope, TargetUuid, TotalFiles, - WorkingDatabase, WorkingTree, -}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct ApplyFileStage; diff --git a/lib/lib/src/mutated/files/checkout.rs b/lib/lib/src/mutated/files/checkout.rs index 88e5a318..9ac0847d 100644 --- a/lib/lib/src/mutated/files/checkout.rs +++ b/lib/lib/src/mutated/files/checkout.rs @@ -3,16 +3,16 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; +use upac_types::hook::ProgressEventBuilder; use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; -use crate::deploy::Deploy; -use crate::deploy::esp::find_esp_mount; +use crate::deploy::{Deploy, find_esp_mount}; use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; use crate::mutated::files::{FilesError, NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; use crate::plugin::boot::resolve_boot_plugin; pub struct CheckoutStage; diff --git a/lib/lib/src/mutated/files/commit.rs b/lib/lib/src/mutated/files/commit.rs index 60a05111..f4897fc2 100644 --- a/lib/lib/src/mutated/files/commit.rs +++ b/lib/lib/src/mutated/files/commit.rs @@ -10,9 +10,12 @@ use composefs::fsverity::FsVerityHashValue; use composefs::generic_tree::Stat; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_types::TmpPath; +use upac_types::hook::ProgressEventBuilder; + +use super::{CommitMessage, FilesError, NewPrefixDigest, Subject, WorkingDatabase, WorkingTree}; use crate::composefs::error::RepoError; use crate::composefs::file::FileHandle; @@ -23,9 +26,8 @@ use crate::database::record::DeployRecord; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::database::{DATABASE_PATH, FILES_SCRATCH_FILENAME}; -use crate::mutated::files::{CommitMessage, FilesError, NewPrefixDigest, Subject, WorkingDatabase, WorkingTree}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct CommitTransactionStage; diff --git a/lib/lib/src/mutated/files/mod.rs b/lib/lib/src/mutated/files/mod.rs index 0e169a25..8a2de9fc 100644 --- a/lib/lib/src/mutated/files/mod.rs +++ b/lib/lib/src/mutated/files/mod.rs @@ -11,13 +11,17 @@ use composefs::tree::FileSystem; use uuid::Uuid; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::package::CPackageInfo; use upac_abi::request::CFilesRequest; use upac_abi::{DiffFileSource, FileDiffKind}; -pub use self::error::FilesError; +use upac_types::TmpPath; +use upac_types::hook::Message; +use upac_types::states::FilesStateId; +use upac_types::traits::MessageHook; use self::apply::ApplyFileStage; use self::checkout::CheckoutStage; @@ -29,12 +33,13 @@ use crate::composefs::repository::ObjectID; use crate::database::MemoryDatabase; use crate::deploy::retention::RetentionStage; use crate::deploy::{Deploy, DeployMode}; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_mutating}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_mutating}; use crate::plugin::boot::BootPlugin; use crate::scripts::HookStage; use crate::scripts::pipeline::{Operation, PipelineTrigger}; -use upac_types::TmpPath; -use upac_types::states::FilesStateId; + +pub use self::error::FilesError; mod apply; mod checkout; @@ -111,7 +116,8 @@ impl<'a> TryFrom<&'a CFilesRequest> for FilesData<'a> { unsafe { request.validate()? }; let file_package = unsafe { request.file_package.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(FilesData { files: Vec::try_from(&request.files)?, diff --git a/lib/lib/src/mutated/files/open.rs b/lib/lib/src/mutated/files/open.rs index 4f26c6f8..163670a4 100644 --- a/lib/lib/src/mutated/files/open.rs +++ b/lib/lib/src/mutated/files/open.rs @@ -7,7 +7,12 @@ use std::collections::VecDeque; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; +use upac_types::hook::ProgressEventBuilder; + +use super::{ + EtcUpperDir, FilesError, PendingFiles, RequestedFilePackage, TargetUuid, TotalFiles, WorkingDatabase, WorkingTree, +}; use crate::composefs::file::FileHandle; use crate::database::meta::MetaStore; @@ -16,11 +21,8 @@ use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::database::DATABASE_PATH; use crate::layout::deployment::CONFIG_DIR_NAME; -use crate::mutated::files::{ - EtcUpperDir, FilesError, PendingFiles, RequestedFilePackage, TargetUuid, TotalFiles, WorkingDatabase, WorkingTree, -}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct OpenTransactionStage; diff --git a/lib/lib/src/mutated/files/swap.rs b/lib/lib/src/mutated/files/swap.rs index 1df78d41..53e8ca99 100644 --- a/lib/lib/src/mutated/files/swap.rs +++ b/lib/lib/src/mutated/files/swap.rs @@ -3,11 +3,13 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; +use upac_types::hook::ProgressEventBuilder; -use crate::mutated::files::{FilesError, ResolvedBootEntry}; +use super::{FilesError, ResolvedBootEntry}; + +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; pub struct SwapStage; diff --git a/lib/lib/src/mutated/gc/cleaning.rs b/lib/lib/src/mutated/gc/cleaning.rs index fd2be858..0c3173f6 100644 --- a/lib/lib/src/mutated/gc/cleaning.rs +++ b/lib/lib/src/mutated/gc/cleaning.rs @@ -3,13 +3,15 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; +use upac_types::hook::ProgressEventBuilder; + +use super::{CollectedRoots, GcError}; use crate::composefs::repository::gc; use crate::deploy::Deploy; -use crate::mutated::gc::{CollectedRoots, GcError}; +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; pub struct CleaningStage; diff --git a/lib/lib/src/mutated/gc/collect.rs b/lib/lib/src/mutated/gc/collect.rs index f407c33a..ae512300 100644 --- a/lib/lib/src/mutated/gc/collect.rs +++ b/lib/lib/src/mutated/gc/collect.rs @@ -3,14 +3,16 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; +use upac_types::hook::ProgressEventBuilder; + +use super::{CollectedRoots, GcError, PendingDeploys, TotalDeploys}; use crate::database::record::DeployRecord; use crate::deploy::Deploy; use crate::errors::CommonError; -use crate::mutated::gc::{CollectedRoots, GcError, PendingDeploys, TotalDeploys}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct CollectRootsStage; diff --git a/lib/lib/src/mutated/gc/mod.rs b/lib/lib/src/mutated/gc/mod.rs index 811caea3..4274da70 100644 --- a/lib/lib/src/mutated/gc/mod.rs +++ b/lib/lib/src/mutated/gc/mod.rs @@ -6,19 +6,25 @@ use std::collections::VecDeque; use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CGcRequest; -pub use self::error::GcError; +use upac_types::hook::Message; +use upac_types::traits::MessageHook; + +use upac_types::states::GcStateId; use self::cleaning::CleaningStage; use self::collect::CollectRootsStage; use self::pruning::PruneStage; use crate::deploy::{Deploy, DeployMode}; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_mutating}; -use upac_types::states::GcStateId; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_mutating}; + +pub use self::error::GcError; mod cleaning; mod collect; @@ -42,7 +48,7 @@ impl<'a> TryFrom<&'a CGcRequest> for GcData<'a> { fn try_from(request: &'a CGcRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(GcData { hook_message: request.base.on_hook, diff --git a/lib/lib/src/mutated/gc/pruning.rs b/lib/lib/src/mutated/gc/pruning.rs index 50497216..6ab08a85 100644 --- a/lib/lib/src/mutated/gc/pruning.rs +++ b/lib/lib/src/mutated/gc/pruning.rs @@ -5,12 +5,13 @@ use std::collections::VecDeque; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; +use upac_types::hook::ProgressEventBuilder; use crate::deploy::Deploy; use crate::mutated::gc::{CollectedRoots, GcError, PendingDeploys, TotalDeploys}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; pub struct PruneStage; diff --git a/lib/lib/src/mutated/installer/checkout.rs b/lib/lib/src/mutated/installer/checkout.rs index b0d379f1..f8bf3103 100644 --- a/lib/lib/src/mutated/installer/checkout.rs +++ b/lib/lib/src/mutated/installer/checkout.rs @@ -3,16 +3,19 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{InstallError, NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry}; use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; use crate::deploy::Deploy; -use crate::deploy::esp::find_esp_mount; +use crate::deploy::find_esp_mount; use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; -use crate::mutated::installer::{InstallError, NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; use crate::plugin::boot::resolve_boot_plugin; pub struct CheckoutStage; diff --git a/lib/lib/src/mutated/installer/commit.rs b/lib/lib/src/mutated/installer/commit.rs index 3711861b..511d7cf8 100644 --- a/lib/lib/src/mutated/installer/commit.rs +++ b/lib/lib/src/mutated/installer/commit.rs @@ -10,9 +10,12 @@ use composefs::fsverity::FsVerityHashValue; use composefs::generic_tree::Stat; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_types::TmpPath; +use upac_types::hook::ProgressEventBuilder; + +use super::{ImportedConfigDefaults, ImportedDatabase, ImportedTree, InstallError, NewConfigDefaults, NewPrefixDigest}; use crate::composefs::error::RepoError; use crate::composefs::file::FileHandle; @@ -20,11 +23,8 @@ use crate::composefs::repository::commit_tree; use crate::database::InMemory; use crate::deploy::Deploy; use crate::layout::database::{DATABASE_PATH, INSTALLER_SCRATCH_FILENAME}; -use crate::mutated::installer::{ - ImportedConfigDefaults, ImportedDatabase, ImportedTree, InstallError, NewConfigDefaults, NewPrefixDigest, -}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct CommitTransactionStage; diff --git a/lib/lib/src/mutated/installer/fetching.rs b/lib/lib/src/mutated/installer/fetching.rs index 98603bef..95d0ed27 100644 --- a/lib/lib/src/mutated/installer/fetching.rs +++ b/lib/lib/src/mutated/installer/fetching.rs @@ -3,10 +3,13 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use crate::mutated::installer::InstallError; -use crate::orchestrator::Context; +use upac_types::hook::ProgressEventBuilder; + +use super::InstallError; + +use crate::orchestrator::context::Context; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; pub struct FetchingStage; diff --git a/lib/lib/src/mutated/installer/import.rs b/lib/lib/src/mutated/installer/import.rs index a50a3dec..0358455b 100644 --- a/lib/lib/src/mutated/installer/import.rs +++ b/lib/lib/src/mutated/installer/import.rs @@ -7,9 +7,12 @@ use std::path::Path; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use upac_types::{FileEntry, FileEntryScope}; +use upac_types::entry::{FileEntry, FileEntryScope}; +use upac_types::hook::ProgressEventBuilder; + +use super::{ImportedConfigDefaults, ImportedDatabase, ImportedTree, InstallError, PendingPackages, TotalPackages}; use crate::composefs::file::import_if_dir; use crate::database::files::FileStoreMut; @@ -17,11 +20,8 @@ use crate::database::meta::MetaStoreMut; use crate::database::triggers::TriggerStoreMut; use crate::deploy::Deploy; use crate::errors::CommonError; -use crate::mutated::installer::{ - ImportedConfigDefaults, ImportedDatabase, ImportedTree, InstallError, PendingPackages, TotalPackages, -}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct ImportPackageStage; diff --git a/lib/lib/src/mutated/installer/merge.rs b/lib/lib/src/mutated/installer/merge.rs index 0976a69e..5dab1ca5 100644 --- a/lib/lib/src/mutated/installer/merge.rs +++ b/lib/lib/src/mutated/installer/merge.rs @@ -8,7 +8,11 @@ use std::fs::create_dir_all; use composefs::fsverity::FsVerityHashValue; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{AllowConflictFiles, CommitMessage, InstallError, NewConfigDefaults, NewPrefixDigest, Subject}; use crate::composefs::overlay::{apply_overlay_upper, apply_tree_overlay}; use crate::composefs::repository::commit_tree; @@ -18,11 +22,8 @@ use crate::database::record::DeployRecord; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::deployment::CONFIG_DIR_NAME; -use crate::mutated::installer::{ - AllowConflictFiles, CommitMessage, InstallError, NewConfigDefaults, NewPrefixDigest, Subject, -}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct MergeStage; diff --git a/lib/lib/src/mutated/installer/mod.rs b/lib/lib/src/mutated/installer/mod.rs index ca5ec8c3..d34d381d 100644 --- a/lib/lib/src/mutated/installer/mod.rs +++ b/lib/lib/src/mutated/installer/mod.rs @@ -8,13 +8,17 @@ use std::os::raw::c_void; use composefs::tree::FileSystem; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CInstallRequest; -use upac_types::{DeclarativeTrigger, PackageTemp}; - -pub use self::error::InstallError; +use upac_types::TmpPath; +use upac_types::decoder::DeclarativeTrigger; +use upac_types::hook::Message; +use upac_types::package::PackageTemp; +use upac_types::states::InstallStateId; +use upac_types::traits::MessageHook; use self::checkout::CheckoutStage; use self::commit::CommitTransactionStage; @@ -30,13 +34,14 @@ use crate::database::MemoryDatabase; use crate::deploy::retention::RetentionStage; use crate::deploy::{Deploy, DeployMode}; use crate::errors::CommonError; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_mutating}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_mutating}; use crate::plugin::boot::BootPlugin; use crate::plugin::decoder::unpack::PackageUnpacker; use crate::scripts::HookStage; use crate::scripts::pipeline::{Operation, PipelineTrigger}; -use upac_types::TmpPath; -use upac_types::states::InstallStateId; + +pub use self::error::InstallError; mod checkout; mod commit; @@ -89,7 +94,7 @@ impl<'a> TryFrom<&'a CInstallRequest> for InstallData<'a> { fn try_from(request: &'a CInstallRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(InstallData { packages: Vec::try_from(&request.packages)?, diff --git a/lib/lib/src/mutated/installer/open.rs b/lib/lib/src/mutated/installer/open.rs index a3d090d1..e6baef6b 100644 --- a/lib/lib/src/mutated/installer/open.rs +++ b/lib/lib/src/mutated/installer/open.rs @@ -7,16 +7,19 @@ use composefs::generic_tree::Stat; use composefs::repository::ImportContext; use composefs::tree::FileSystem; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{ImportedConfigDefaults, ImportedDatabase, ImportedTree, InstallError}; use crate::composefs::file::FileHandle; use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::database::DATABASE_PATH; -use crate::mutated::installer::{ImportedConfigDefaults, ImportedDatabase, ImportedTree, InstallError}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; pub struct OpenTransactionStage; diff --git a/lib/lib/src/mutated/installer/preparation.rs b/lib/lib/src/mutated/installer/preparation.rs index 6a0b4dc3..8ee89cf5 100644 --- a/lib/lib/src/mutated/installer/preparation.rs +++ b/lib/lib/src/mutated/installer/preparation.rs @@ -7,14 +7,16 @@ use std::fs::remove_dir_all; use std::path::PathBuf; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_types::TmpPath; +use upac_types::hook::ProgressEventBuilder; + +use super::{InstallError, PendingPackagePaths, PendingPackages, TotalPackages, UnpackerState}; use crate::errors::CommonError; -use crate::mutated::installer::{InstallError, PendingPackagePaths, PendingPackages, TotalPackages, UnpackerState}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct PreparationStage; diff --git a/lib/lib/src/mutated/installer/swap.rs b/lib/lib/src/mutated/installer/swap.rs index d7d53e9d..8610fe56 100644 --- a/lib/lib/src/mutated/installer/swap.rs +++ b/lib/lib/src/mutated/installer/swap.rs @@ -3,11 +3,14 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use crate::mutated::installer::{InstallError, ResolvedBootEntry}; +use upac_types::hook::ProgressEventBuilder; + +use super::{InstallError, ResolvedBootEntry}; + +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; pub struct SwapStage; diff --git a/lib/lib/src/mutated/mime/mod.rs b/lib/lib/src/mutated/mime/mod.rs index c7bbf632..9b63e0c7 100644 --- a/lib/lib/src/mutated/mime/mod.rs +++ b/lib/lib/src/mutated/mime/mod.rs @@ -6,18 +6,23 @@ use std::collections::VecDeque; use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CMimeSyncRequest; -pub use self::error::MimeError; +use upac_types::hook::Message; +use upac_types::states::MimeStateId; +use upac_types::traits::MessageHook; use self::preparing::PreparingStage; use self::rendering::RenderingStage; use self::writing::WritingStage; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_mutating}; -use upac_types::states::MimeStateId; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_mutating}; + +pub use self::error::MimeError; mod error; mod preparing; @@ -42,7 +47,7 @@ impl<'a> TryFrom<&'a CMimeSyncRequest> for MimeData<'a> { fn try_from(request: &'a CMimeSyncRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(MimeData { hook_message: request.base.on_hook, diff --git a/lib/lib/src/mutated/mime/preparing.rs b/lib/lib/src/mutated/mime/preparing.rs index 7b31fa68..9cd5277b 100644 --- a/lib/lib/src/mutated/mime/preparing.rs +++ b/lib/lib/src/mutated/mime/preparing.rs @@ -5,12 +5,15 @@ use std::fs; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{DesktopContent, MimeError}; use crate::errors::CommonError; use crate::layout::{decoders, mime}; -use crate::mutated::mime::{DesktopContent, MimeError}; -use crate::orchestrator::Context; +use crate::orchestrator::context::Context; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; use crate::plugin::decoder::manifest::load_decoder_manifests; diff --git a/lib/lib/src/mutated/mime/rendering.rs b/lib/lib/src/mutated/mime/rendering.rs index d42e4645..120b2a90 100644 --- a/lib/lib/src/mutated/mime/rendering.rs +++ b/lib/lib/src/mutated/mime/rendering.rs @@ -9,12 +9,15 @@ use std::io::Result as IoResult; use quick_xml::Writer as XmlWriter; use quick_xml::events::{BytesDecl, BytesText, Event}; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use crate::layout::mime; -use crate::mutated::mime::{DesktopContent, MimeError, PendingWrites, TotalWrites}; +use upac_types::hook::ProgressEventBuilder; + +use super::{DesktopContent, MimeError, PendingWrites, TotalWrites}; + +use crate::layout::mime::{DESKTOP_FILE_PATH, MIME_XML_PATH, SHARED_MIME_INFO_XMLNS}; +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; use crate::plugin::decoder::manifest::DecoderManifest; pub struct RenderingStage; @@ -30,10 +33,7 @@ impl Stage for RenderingStage { let mime_type_line = Self::render_mime_type_line(&manifests); let desktop_content = Self::rewrite_desktop_mime_type(&desktop_content.0, &mime_type_line)?; - let pending = VecDeque::from([ - (mime::MIME_XML_PATH, mime_xml), - (mime::DESKTOP_FILE_PATH, desktop_content), - ]); + let pending = VecDeque::from([(MIME_XML_PATH, mime_xml), (DESKTOP_FILE_PATH, desktop_content)]); context.put(PendingWrites(pending)); context.put(TotalWrites(2)); @@ -50,7 +50,7 @@ impl RenderingStage { writer .create_element("mime-info") - .with_attribute(("xmlns", mime::SHARED_MIME_INFO_XMLNS)) + .with_attribute(("xmlns", SHARED_MIME_INFO_XMLNS)) .write_inner_content(|writer| { for manifest in manifests.values() { Self::write_mime_type_element(writer, manifest)?; diff --git a/lib/lib/src/mutated/mime/writing.rs b/lib/lib/src/mutated/mime/writing.rs index 9ce90c1c..4783aa57 100644 --- a/lib/lib/src/mutated/mime/writing.rs +++ b/lib/lib/src/mutated/mime/writing.rs @@ -6,14 +6,17 @@ use std::path::Path; use std::process::Command; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{MimeError, PendingWrites, TotalWrites}; use crate::errors::CommonError; use crate::fs::WrittenFile; -use crate::layout::mime; -use crate::mutated::mime::{MimeError, PendingWrites, TotalWrites}; +use crate::layout::mime::{APPLICATIONS_DIR, MIME_DB_DIR, UPDATE_DESKTOP_DATABASE_BIN, UPDATE_MIME_DATABASE_BIN}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct WritingStage; @@ -34,12 +37,8 @@ impl Stage for WritingStage { progress = progress.subject(path.to_owned()).progress(processed, total.0); let result = if pending.0.is_empty() { - let _ = Command::new(mime::UPDATE_MIME_DATABASE_BIN) - .arg(mime::MIME_DB_DIR) - .status(); - let _ = Command::new(mime::UPDATE_DESKTOP_DATABASE_BIN) - .arg(mime::APPLICATIONS_DIR) - .status(); + let _ = Command::new(UPDATE_MIME_DATABASE_BIN).arg(MIME_DB_DIR).status(); + let _ = Command::new(UPDATE_DESKTOP_DATABASE_BIN).arg(APPLICATIONS_DIR).status(); StageResult::Advance } else { diff --git a/lib/lib/src/mutated/pin/mod.rs b/lib/lib/src/mutated/pin/mod.rs index 77675f8c..23cde075 100644 --- a/lib/lib/src/mutated/pin/mod.rs +++ b/lib/lib/src/mutated/pin/mod.rs @@ -5,17 +5,22 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CPinRequest; +use upac_types::hook::Message; +use upac_types::traits::MessageHook; -pub use self::error::PinError; +use upac_types::states::PinStateId; use self::stage::SetPinnedStage; use crate::deploy::{Deploy, DeployMode}; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_mutating}; -use upac_types::states::PinStateId; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_mutating}; + +pub use self::error::PinError; mod error; mod stage; @@ -39,7 +44,7 @@ impl<'a> TryFrom<&'a CPinRequest> for PinData<'a> { fn try_from(request: &'a CPinRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(PinData { prefix_digest: (&request.prefix_digest).try_into()?, diff --git a/lib/lib/src/mutated/pin/stage.rs b/lib/lib/src/mutated/pin/stage.rs index 059aeeb9..693d1b02 100644 --- a/lib/lib/src/mutated/pin/stage.rs +++ b/lib/lib/src/mutated/pin/stage.rs @@ -5,13 +5,16 @@ use std::mem::replace; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{PinError, RequestedPinned, RequestedPrefixDigest}; use crate::database::record::DeployRecord; use crate::deploy::Deploy; -use crate::mutated::pin::{PinError, RequestedPinned, RequestedPrefixDigest}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; pub struct SetPinnedStage; diff --git a/lib/lib/src/mutated/rollback/checkout.rs b/lib/lib/src/mutated/rollback/checkout.rs index bcaef910..124ee3b4 100644 --- a/lib/lib/src/mutated/rollback/checkout.rs +++ b/lib/lib/src/mutated/rollback/checkout.rs @@ -3,16 +3,18 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{RequestedBootPlugin, ResolvedBootEntry, RollbackError, TargetPrefixDigest}; use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; -use crate::deploy::Deploy; -use crate::deploy::esp::find_esp_mount; +use crate::deploy::{Deploy, find_esp_mount}; use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; -use crate::mutated::rollback::{RequestedBootPlugin, ResolvedBootEntry, RollbackError, TargetPrefixDigest}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; use crate::plugin::boot::resolve_boot_plugin; pub struct CheckoutStage; diff --git a/lib/lib/src/mutated/rollback/merge.rs b/lib/lib/src/mutated/rollback/merge.rs index ce0d41fe..238dd003 100644 --- a/lib/lib/src/mutated/rollback/merge.rs +++ b/lib/lib/src/mutated/rollback/merge.rs @@ -3,13 +3,16 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{RequestedConfigDigest, RollbackError, TargetPrefixDigest}; use crate::database::record::DeployRecord; use crate::deploy::Deploy; -use crate::mutated::rollback::{RequestedConfigDigest, RollbackError, TargetPrefixDigest}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; pub struct MergeStage; diff --git a/lib/lib/src/mutated/rollback/mod.rs b/lib/lib/src/mutated/rollback/mod.rs index dc911dbb..ecae6db2 100644 --- a/lib/lib/src/mutated/rollback/mod.rs +++ b/lib/lib/src/mutated/rollback/mod.rs @@ -5,11 +5,15 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CRollbackRequest; -pub use self::error::RollbackError; +use upac_types::TmpPath; +use upac_types::hook::Message; +use upac_types::states::RollbackStateId; +use upac_types::traits::MessageHook; use self::checkout::CheckoutStage; use self::merge::MergeStage; @@ -17,12 +21,13 @@ use self::swap::SwapStage; use crate::deploy::retention::RetentionStage; use crate::deploy::{Deploy, DeployMode}; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_mutating}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_mutating}; use crate::plugin::boot::BootPlugin; use crate::scripts::HookStage; use crate::scripts::pipeline::{Operation, PipelineTrigger}; -use upac_types::TmpPath; -use upac_types::states::RollbackStateId; + +pub use self::error::RollbackError; mod checkout; mod error; @@ -55,7 +60,7 @@ impl<'a> TryFrom<&'a CRollbackRequest> for RollbackData<'a> { fn try_from(request: &'a CRollbackRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(RollbackData { config_digest: (&request.config_digest).try_into()?, diff --git a/lib/lib/src/mutated/rollback/swap.rs b/lib/lib/src/mutated/rollback/swap.rs index e55104d8..4e712408 100644 --- a/lib/lib/src/mutated/rollback/swap.rs +++ b/lib/lib/src/mutated/rollback/swap.rs @@ -3,11 +3,14 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use crate::mutated::rollback::{ResolvedBootEntry, RollbackError}; +use upac_types::hook::ProgressEventBuilder; + +use super::{ResolvedBootEntry, RollbackError}; + +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; pub struct SwapStage; diff --git a/lib/lib/src/mutated/uninstaller/checkout.rs b/lib/lib/src/mutated/uninstaller/checkout.rs index 05fe546f..a46291ac 100644 --- a/lib/lib/src/mutated/uninstaller/checkout.rs +++ b/lib/lib/src/mutated/uninstaller/checkout.rs @@ -3,16 +3,18 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry, UninstallError}; use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; -use crate::deploy::Deploy; -use crate::deploy::esp::find_esp_mount; +use crate::deploy::{Deploy, find_esp_mount}; use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; -use crate::mutated::uninstaller::{NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry, UninstallError}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; use crate::plugin::boot::resolve_boot_plugin; pub struct CheckoutStage; diff --git a/lib/lib/src/mutated/uninstaller/commit.rs b/lib/lib/src/mutated/uninstaller/commit.rs index 905767b4..42cef5f2 100644 --- a/lib/lib/src/mutated/uninstaller/commit.rs +++ b/lib/lib/src/mutated/uninstaller/commit.rs @@ -10,9 +10,14 @@ use composefs::fsverity::FsVerityHashValue; use composefs::generic_tree::Stat; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_types::TmpPath; +use upac_types::hook::ProgressEventBuilder; + +use super::{ + NewPrefixDigest, RemovedConfigPaths, UninstallError, WorkingDatabase, WorkingRemovedConfigPaths, WorkingTree, +}; use crate::composefs::error::RepoError; use crate::composefs::file::FileHandle; @@ -20,11 +25,8 @@ use crate::composefs::repository::commit_tree; use crate::database::InMemory; use crate::deploy::Deploy; use crate::layout::database::{DATABASE_PATH, UNINSTALL_SCRATCH_FILENAME}; -use crate::mutated::uninstaller::{ - NewPrefixDigest, RemovedConfigPaths, UninstallError, WorkingDatabase, WorkingRemovedConfigPaths, WorkingTree, -}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct CommitTransactionStage; diff --git a/lib/lib/src/mutated/uninstaller/merge.rs b/lib/lib/src/mutated/uninstaller/merge.rs index 4a14086c..022bd544 100644 --- a/lib/lib/src/mutated/uninstaller/merge.rs +++ b/lib/lib/src/mutated/uninstaller/merge.rs @@ -8,7 +8,11 @@ use std::fs::create_dir_all; use composefs::fsverity::FsVerityHashValue; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{CommitMessage, NewPrefixDigest, RemovedConfigPaths, Subject, UninstallError}; use crate::composefs::file::FileHandle; use crate::composefs::overlay::apply_overlay_upper; @@ -19,9 +23,8 @@ use crate::database::record::DeployRecord; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::deployment::CONFIG_DIR_NAME; -use crate::mutated::uninstaller::{CommitMessage, NewPrefixDigest, RemovedConfigPaths, Subject, UninstallError}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct MergeStage; diff --git a/lib/lib/src/mutated/uninstaller/mod.rs b/lib/lib/src/mutated/uninstaller/mod.rs index 5ac2546a..4455340e 100644 --- a/lib/lib/src/mutated/uninstaller/mod.rs +++ b/lib/lib/src/mutated/uninstaller/mod.rs @@ -10,24 +10,17 @@ use composefs::tree::FileSystem; use uuid::Uuid; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::package::CPackageInfo; use upac_abi::request::CUninstallRequest; -use crate::composefs::repository::ObjectID; -use crate::database::MemoryDatabase; -use crate::deploy::retention::RetentionStage; -use crate::deploy::{Deploy, DeployMode}; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_mutating}; -use crate::plugin::boot::BootPlugin; -use crate::scripts::HookStage; -use crate::scripts::pipeline::{Operation, PipelineTrigger}; - +use upac_types::hook::Message; +use upac_types::package::PackageEntry; use upac_types::states::UninstallStateId; -use upac_types::{PackageEntry, Targets, TmpPath}; - -pub use self::error::UninstallError; +use upac_types::traits::MessageHook; +use upac_types::{TmpPath, UninstallPackagesTargets}; use self::checkout::CheckoutStage; use self::commit::CommitTransactionStage; @@ -37,6 +30,18 @@ use self::preparation::PreparationStage; use self::remove::RemovePackageStage; use self::swap::SwapStage; +use crate::composefs::repository::ObjectID; +use crate::database::MemoryDatabase; +use crate::deploy::retention::RetentionStage; +use crate::deploy::{Deploy, DeployMode}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_mutating}; +use crate::plugin::boot::BootPlugin; +use crate::scripts::HookStage; +use crate::scripts::pipeline::{Operation, PipelineTrigger}; + +pub use self::error::UninstallError; + mod checkout; mod commit; mod error; @@ -106,7 +111,7 @@ impl<'a> TryFrom<&'a CUninstallRequest> for UninstallData<'a> { fn try_from(request: &'a CUninstallRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(UninstallData { packages: Vec::try_from(&request.packages)?, diff --git a/lib/lib/src/mutated/uninstaller/open.rs b/lib/lib/src/mutated/uninstaller/open.rs index e069c445..5a759055 100644 --- a/lib/lib/src/mutated/uninstaller/open.rs +++ b/lib/lib/src/mutated/uninstaller/open.rs @@ -5,19 +5,22 @@ use std::collections::VecDeque; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{ + PackageUuidsToRemove, PendingUuids, TotalPackages, UninstallError, WorkingDatabase, WorkingRemovedConfigPaths, + WorkingTree, +}; use crate::composefs::file::FileHandle; use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::database::DATABASE_PATH; -use crate::mutated::uninstaller::{ - PackageUuidsToRemove, PendingUuids, TotalPackages, UninstallError, WorkingDatabase, WorkingRemovedConfigPaths, - WorkingTree, -}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct OpenTransactionStage; diff --git a/lib/lib/src/mutated/uninstaller/preparation.rs b/lib/lib/src/mutated/uninstaller/preparation.rs index d3432c76..06cc3d83 100644 --- a/lib/lib/src/mutated/uninstaller/preparation.rs +++ b/lib/lib/src/mutated/uninstaller/preparation.rs @@ -3,9 +3,12 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use upac_types::{DeclarativeTrigger, Targets}; +use upac_types::decoder::DeclarativeTrigger; +use upac_types::hook::ProgressEventBuilder; + +use super::{PackageUuidsToRemove, UninstallError}; use crate::composefs::file::FileHandle; use crate::database::meta::MetaStore; @@ -14,9 +17,8 @@ use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::database::DATABASE_PATH; -use crate::mutated::uninstaller::{PackageUuidsToRemove, UninstallError}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; pub struct PreparationStage; diff --git a/lib/lib/src/mutated/uninstaller/remove.rs b/lib/lib/src/mutated/uninstaller/remove.rs index c7ce000e..fe8593f5 100644 --- a/lib/lib/src/mutated/uninstaller/remove.rs +++ b/lib/lib/src/mutated/uninstaller/remove.rs @@ -3,20 +3,23 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use upac_types::FileEntryScope; +use upac_types::hook::ProgressEventBuilder; + +use upac_types::entry::FileEntryScope; + +use super::{ + PendingUuids, Purge, TotalPackages, UninstallError, WorkingDatabase, WorkingRemovedConfigPaths, WorkingTree, +}; use crate::composefs::file::FileHandle; use crate::database::files::{FileStore, FileStoreMut}; use crate::database::meta::{MetaStore, MetaStoreMut}; use crate::database::triggers::TriggerStoreMut; use crate::errors::CommonError; -use crate::mutated::uninstaller::{ - PendingUuids, Purge, TotalPackages, UninstallError, WorkingDatabase, WorkingRemovedConfigPaths, WorkingTree, -}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct RemovePackageStage; diff --git a/lib/lib/src/mutated/uninstaller/swap.rs b/lib/lib/src/mutated/uninstaller/swap.rs index e48ee0ae..cc5b4214 100644 --- a/lib/lib/src/mutated/uninstaller/swap.rs +++ b/lib/lib/src/mutated/uninstaller/swap.rs @@ -3,11 +3,14 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use crate::mutated::uninstaller::{ResolvedBootEntry, UninstallError}; +use upac_types::hook::ProgressEventBuilder; + +use super::{ResolvedBootEntry, UninstallError}; + +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; pub struct SwapStage; diff --git a/lib/lib/src/mutated/update/checkout.rs b/lib/lib/src/mutated/update/checkout.rs index ffee7e4f..2ae11d56 100644 --- a/lib/lib/src/mutated/update/checkout.rs +++ b/lib/lib/src/mutated/update/checkout.rs @@ -3,16 +3,18 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry, UpdateError}; use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; -use crate::deploy::Deploy; -use crate::deploy::esp::find_esp_mount; +use crate::deploy::{Deploy, find_esp_mount}; use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; -use crate::mutated::update::{NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry, UpdateError}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; use crate::plugin::boot::resolve_boot_plugin; pub struct CheckoutStage; diff --git a/lib/lib/src/mutated/update/commit.rs b/lib/lib/src/mutated/update/commit.rs index 60d1a4f7..6393c444 100644 --- a/lib/lib/src/mutated/update/commit.rs +++ b/lib/lib/src/mutated/update/commit.rs @@ -10,9 +10,15 @@ use composefs::fsverity::FsVerityHashValue; use composefs::generic_tree::Stat; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_types::TmpPath; +use upac_types::hook::ProgressEventBuilder; + +use super::{ + ImportedConfigDefaults, ImportedDatabase, ImportedRemovedConfigPaths, ImportedTree, NewConfigDefaults, + NewPrefixDigest, RemovedConfigPaths, UpdateError, +}; use crate::composefs::error::RepoError; use crate::composefs::file::FileHandle; @@ -20,12 +26,8 @@ use crate::composefs::repository::commit_tree; use crate::database::InMemory; use crate::deploy::Deploy; use crate::layout::database::{DATABASE_PATH, UPDATE_SCRATCH_FILENAME}; -use crate::mutated::update::{ - ImportedConfigDefaults, ImportedDatabase, ImportedRemovedConfigPaths, ImportedTree, NewConfigDefaults, - NewPrefixDigest, RemovedConfigPaths, UpdateError, -}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct CommitTransactionStage; diff --git a/lib/lib/src/mutated/update/fetching.rs b/lib/lib/src/mutated/update/fetching.rs index 77b98cd4..a7653ad2 100644 --- a/lib/lib/src/mutated/update/fetching.rs +++ b/lib/lib/src/mutated/update/fetching.rs @@ -3,10 +3,12 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; use crate::mutated::update::UpdateError; -use crate::orchestrator::Context; +use crate::orchestrator::context::Context; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; pub struct FetchingStage; diff --git a/lib/lib/src/mutated/update/import.rs b/lib/lib/src/mutated/update/import.rs index 510f14b6..ec1639b7 100644 --- a/lib/lib/src/mutated/update/import.rs +++ b/lib/lib/src/mutated/update/import.rs @@ -7,9 +7,10 @@ use std::path::Path; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use upac_types::{FileEntry, FileEntryScope}; +use upac_types::entry::{FileEntry, FileEntryScope}; +use upac_types::hook::ProgressEventBuilder; use crate::composefs::file::{FileHandle, import_if_dir}; use crate::database::files::{FileStore, FileStoreMut}; @@ -21,8 +22,8 @@ use crate::mutated::update::{ AllowDowngrade, ImportedConfigDefaults, ImportedDatabase, ImportedRemovedConfigPaths, ImportedTree, PendingPackages, TotalPackages, UpdateError, }; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct ImportPackageStage; diff --git a/lib/lib/src/mutated/update/merge.rs b/lib/lib/src/mutated/update/merge.rs index 3a2791b1..2c912d09 100644 --- a/lib/lib/src/mutated/update/merge.rs +++ b/lib/lib/src/mutated/update/merge.rs @@ -8,7 +8,13 @@ use std::fs::create_dir_all; use composefs::fsverity::FsVerityHashValue; use composefs::repository::ImportContext; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{ + AllowConflictFiles, CommitMessage, NewConfigDefaults, NewPrefixDigest, RemovedConfigPaths, Subject, UpdateError, +}; use crate::composefs::file::FileHandle; use crate::composefs::overlay::{apply_overlay_upper, apply_tree_overlay}; @@ -19,11 +25,8 @@ use crate::database::record::DeployRecord; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::deployment::CONFIG_DIR_NAME; -use crate::mutated::update::{ - AllowConflictFiles, CommitMessage, NewConfigDefaults, NewPrefixDigest, RemovedConfigPaths, Subject, UpdateError, -}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct MergeStage; diff --git a/lib/lib/src/mutated/update/mod.rs b/lib/lib/src/mutated/update/mod.rs index 4acb0b3a..881b65a4 100644 --- a/lib/lib/src/mutated/update/mod.rs +++ b/lib/lib/src/mutated/update/mod.rs @@ -8,13 +8,17 @@ use std::os::raw::c_void; use composefs::tree::FileSystem; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CUpdateRequest; -use upac_types::{DeclarativeTrigger, PackageTemp}; - -pub use self::error::UpdateError; +use upac_types::TmpPath; +use upac_types::decoder::DeclarativeTrigger; +use upac_types::hook::Message; +use upac_types::package::PackageTemp; +use upac_types::states::UpdateStateId; +use upac_types::traits::MessageHook; use self::checkout::CheckoutStage; use self::commit::CommitTransactionStage; @@ -30,13 +34,14 @@ use crate::database::MemoryDatabase; use crate::deploy::retention::RetentionStage; use crate::deploy::{Deploy, DeployMode}; use crate::errors::CommonError; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_mutating}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_mutating}; use crate::plugin::boot::BootPlugin; use crate::plugin::decoder::unpack::PackageUnpacker; use crate::scripts::HookStage; use crate::scripts::pipeline::{Operation, PipelineTrigger}; -use upac_types::TmpPath; -use upac_types::states::UpdateStateId; + +pub use self::error::UpdateError; mod checkout; mod commit; @@ -93,7 +98,7 @@ impl<'a> TryFrom<&'a CUpdateRequest> for UpdateData<'a> { fn try_from(request: &'a CUpdateRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(UpdateData { packages: Vec::try_from(&request.packages)?, diff --git a/lib/lib/src/mutated/update/open.rs b/lib/lib/src/mutated/update/open.rs index d4a82b45..ba9f4bce 100644 --- a/lib/lib/src/mutated/update/open.rs +++ b/lib/lib/src/mutated/update/open.rs @@ -7,18 +7,19 @@ use composefs::generic_tree::Stat; use composefs::repository::ImportContext; use composefs::tree::FileSystem; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{ImportedConfigDefaults, ImportedDatabase, ImportedRemovedConfigPaths, ImportedTree, UpdateError}; use crate::composefs::file::FileHandle; use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::database::DATABASE_PATH; -use crate::mutated::update::{ - ImportedConfigDefaults, ImportedDatabase, ImportedRemovedConfigPaths, ImportedTree, UpdateError, -}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; pub struct OpenTransactionStage; diff --git a/lib/lib/src/mutated/update/preparation.rs b/lib/lib/src/mutated/update/preparation.rs index 47385be8..7cea0522 100644 --- a/lib/lib/src/mutated/update/preparation.rs +++ b/lib/lib/src/mutated/update/preparation.rs @@ -7,14 +7,16 @@ use std::fs::remove_dir_all; use std::path::PathBuf; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_types::TmpPath; +use upac_types::hook::ProgressEventBuilder; + +use super::{PendingPackagePaths, PendingPackages, TotalPackages, UnpackerState, UpdateError}; use crate::errors::CommonError; -use crate::mutated::update::{PendingPackagePaths, PendingPackages, TotalPackages, UnpackerState, UpdateError}; +use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get, ctx_take}; pub struct PreparationStage; diff --git a/lib/lib/src/mutated/update/swap.rs b/lib/lib/src/mutated/update/swap.rs index 9d179e71..e2cd04ca 100644 --- a/lib/lib/src/mutated/update/swap.rs +++ b/lib/lib/src/mutated/update/swap.rs @@ -3,11 +3,14 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use crate::mutated::update::{ResolvedBootEntry, UpdateError}; +use upac_types::hook::ProgressEventBuilder; + +use super::{ResolvedBootEntry, UpdateError}; + +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; pub struct SwapStage; diff --git a/lib/lib/src/orchestrator/context.rs b/lib/lib/src/orchestrator/context.rs new file mode 100644 index 00000000..5655a580 --- /dev/null +++ b/lib/lib/src/orchestrator/context.rs @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::any::{Any, TypeId}; +use std::collections::{HashMap, HashSet}; +use std::io::Error as IoError; +use std::sync::Arc; + +use tokio::runtime::Runtime; + +use upac_abi::hook::HookAck; +use upac_types::hook::ProgressEventBuilder; +use upac_types::traits::MessageHook; + +use crate::orchestrator::stage::RollbackGuard; + +macro_rules! ctx_get { + ($context:expr, $ty:ty) => { + $context + .get::<$ty>() + .ok_or($crate::errors::CommonError::MissingResult)? + }; +} +pub(crate) use ctx_get; + +macro_rules! ctx_take { + ($context:expr, $ty:ty) => { + $context + .take::<$ty>() + .ok_or($crate::errors::CommonError::MissingResult)? + }; +} +pub(crate) use ctx_take; + +pub struct Context { + slots: HashMap>, + pub(super) rollback: Vec>, +} + +impl Context { + pub fn new() -> Self { + Self { + slots: HashMap::new(), + rollback: Vec::new(), + } + } + + pub fn put(&mut self, value: T) { + self.slots.insert(TypeId::of::(), Box::new(value)); + } + + pub fn get(&self) -> Option<&T> { + self.slots + .get(&TypeId::of::()) + .and_then(|slot| slot.downcast_ref::()) + } + + pub fn take(&mut self) -> Option { + self.slots + .remove(&TypeId::of::()) + .and_then(|slot| slot.downcast::().ok()) + .map(|boxed| *boxed) + } + + pub fn runtime(&mut self) -> Result, IoError> { + if let Some(runtime) = self.get::>() { + return Ok(Arc::clone(runtime)); + } + + let runtime = Arc::new(Runtime::new()?); + self.put(Arc::clone(&runtime)); + + Ok(runtime) + } + + pub fn send_progress(&self, progress: &ProgressEventBuilder) { + if let Some(hook) = self.get::>() { + let event = progress.build(); + while hook.send(&event) == HookAck::Retry {} + } + } + + pub(super) fn type_ids(&self) -> HashSet { + self.slots.keys().copied().collect() + } + + pub(super) fn unwind(&mut self) { + while let Some(mut guard) = self.rollback.pop() { + let _ = guard.rollback(); + } + } +} + +impl Default for Context { + fn default() -> Self { + Self::new() + } +} diff --git a/lib/lib/src/orchestrator/cursor.rs b/lib/lib/src/orchestrator/cursor.rs index 97146021..2afcc76e 100644 --- a/lib/lib/src/orchestrator/cursor.rs +++ b/lib/lib/src/orchestrator/cursor.rs @@ -8,7 +8,7 @@ use std::any::TypeId; use upac_abi::hook::CancelToken; use crate::errors::CommonError; -use crate::orchestrator::Context; +use crate::orchestrator::context::Context; use crate::orchestrator::stage::{Stage, StageResult}; pub struct Cursor { diff --git a/lib/lib/src/orchestrator/mod.rs b/lib/lib/src/orchestrator/mod.rs index eb5da1b0..e96096be 100644 --- a/lib/lib/src/orchestrator/mod.rs +++ b/lib/lib/src/orchestrator/mod.rs @@ -3,24 +3,25 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::any::{Any, TypeId}; -use std::collections::{HashMap, HashSet}; -use std::io::Error as IoError; +use std::any::TypeId; use std::sync::Arc; use tokio::runtime::Runtime; use tokio::task::JoinSet; -use upac_abi::hook::{CancelToken, HookAck, MessageHook, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; +use upac_types::hook::ProgressEventBuilder; use crate::errors::CommonError; use crate::lock::Lock; +use crate::orchestrator::context::Context; use crate::orchestrator::cursor::Cursor; use crate::orchestrator::error::OrchestratorError; -use crate::orchestrator::stage::{ConcurrentStage, RollbackGuard, Stage, StageResult}; +use crate::orchestrator::stage::{ConcurrentStage, Stage, StageResult}; mod cursor; +pub mod context; pub mod error; pub mod stage; @@ -69,91 +70,8 @@ macro_rules! run_unmutated { } pub(crate) use run_unmutated; -macro_rules! ctx_get { - ($context:expr, $ty:ty) => { - $context - .get::<$ty>() - .ok_or($crate::errors::CommonError::MissingResult)? - }; -} -pub(crate) use ctx_get; - -macro_rules! ctx_take { - ($context:expr, $ty:ty) => { - $context - .take::<$ty>() - .ok_or($crate::errors::CommonError::MissingResult)? - }; -} -pub(crate) use ctx_take; - pub type StagePipelineError = TypeId; -pub struct Context { - slots: HashMap>, - rollback: Vec>, -} - -impl Context { - pub fn new() -> Self { - Self { - slots: HashMap::new(), - rollback: Vec::new(), - } - } - - pub fn put(&mut self, value: T) { - self.slots.insert(TypeId::of::(), Box::new(value)); - } - - pub fn get(&self) -> Option<&T> { - self.slots - .get(&TypeId::of::()) - .and_then(|slot| slot.downcast_ref::()) - } - - pub fn take(&mut self) -> Option { - self.slots - .remove(&TypeId::of::()) - .and_then(|slot| slot.downcast::().ok()) - .map(|boxed| *boxed) - } - - pub fn runtime(&mut self) -> Result, IoError> { - if let Some(runtime) = self.get::>() { - return Ok(Arc::clone(runtime)); - } - - let runtime = Arc::new(Runtime::new()?); - self.put(Arc::clone(&runtime)); - - Ok(runtime) - } - - pub fn send_progress(&self, progress: &ProgressEventBuilder) { - if let Some(hook) = self.get::>() { - let event = progress.build(); - while hook.send(&event) == HookAck::Retry {} - } - } - - fn type_ids(&self) -> HashSet { - self.slots.keys().copied().collect() - } - - fn unwind(&mut self) { - while let Some(mut guard) = self.rollback.pop() { - let _ = guard.rollback(); - } - } -} - -impl Default for Context { - fn default() -> Self { - Self::new() - } -} - pub trait Orchestrator: Sized { fn run_exclusive(self, context: &mut Context, cancel: &CancelToken) -> Result<(), OrchestratorError>; diff --git a/lib/lib/src/orchestrator/stage.rs b/lib/lib/src/orchestrator/stage.rs index 9c7fdcfb..134306a1 100644 --- a/lib/lib/src/orchestrator/stage.rs +++ b/lib/lib/src/orchestrator/stage.rs @@ -6,9 +6,10 @@ use std::any::{Any, TypeId}; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; +use upac_types::hook::ProgressEventBuilder; -use crate::orchestrator::Context; +use crate::orchestrator::context::Context; #[derive(Clone, Copy)] pub enum StageResult { diff --git a/lib/lib/src/plugin/boot/mod.rs b/lib/lib/src/plugin/boot/mod.rs index 50fd5a76..46c3b627 100644 --- a/lib/lib/src/plugin/boot/mod.rs +++ b/lib/lib/src/plugin/boot/mod.rs @@ -6,8 +6,8 @@ use std::mem::MaybeUninit; use upac_abi::boot::{ - CBootPluginRequest, CBootSlotsRequest, ConfirmBootFn, EspLoaderSourceFn, InstallFn, ProbeFn, RegisterBootSlotsFn, - SetOneShotFn, + CBootPluginRequest, CBootSlotsRequest, CConfirmBootRequest, ConfirmBootFn, EspLoaderSourceFn, InstallFn, ProbeFn, + RegisterBootSlotsFn, SetOneShotFn, }; use upac_abi::error::ErrorKind; use upac_abi::types::{CBorrowed, CSlice}; @@ -155,8 +155,11 @@ impl BootPlugin { Ok(()) } - pub fn confirm_boot(&self, entry_name: &str) -> Result<(), BootPluginError> { - let request = CBootPluginRequest::new(CSlice::from_borrowed(entry_name.as_bytes())); + pub fn confirm_boot(&self, entry_name: &str, esp_mount_point: &str) -> Result<(), BootPluginError> { + let request = CConfirmBootRequest::new( + CSlice::from_borrowed(entry_name.as_bytes()), + CSlice::from_borrowed(esp_mount_point.as_bytes()), + ); let mut error = MaybeUninit::::uninit(); let code = unsafe { (self.confirm_boot)(&request, error.as_mut_ptr()) }; diff --git a/lib/lib/src/plugin/decoder/mod.rs b/lib/lib/src/plugin/decoder/mod.rs index e77620c0..7a2c7995 100644 --- a/lib/lib/src/plugin/decoder/mod.rs +++ b/lib/lib/src/plugin/decoder/mod.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_types::{Dependency, PackageMeta}; +use upac_types::package::{PackageDependency, PackageMeta}; #[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] use std::mem::MaybeUninit; @@ -17,10 +17,16 @@ use libloading::Library; use upac_abi::DECODER_ABI_VERSION; #[cfg(feature = "dynamic-plugins")] -use upac_abi::decoder::AbiVersionFn; +use upac_abi::DecodePluginAbiVersionFn; #[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] -use upac_abi::decoder::{CDecodeRequest, CDecodeResponse, DecodeFn}; +use upac_abi::DecodeFn; + +#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] +use upac_abi::request::CDecodeRequest; + +#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] +use upac_abi::response::CDecodePackageResponse; #[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] use upac_abi::hook::CancelToken; @@ -55,7 +61,7 @@ pub mod unpack; /// types elsewhere in the crate keep compiling. pub struct DecodedPackage { pub meta: PackageMeta, - pub dependencies: Vec, + pub dependencies: Vec, pub declarative_triggers: Vec, } @@ -93,7 +99,7 @@ impl Decoder { pub fn load(library_name: &str) -> Result { let library = unsafe { Library::new(library_name) }.map_err(|_| DecoderError::Load)?; - let abi_version: AbiVersionFn = unsafe { load_symbol(&library, "abi_version")? }; + let abi_version: DecodePluginAbiVersionFn = unsafe { load_symbol(&library, "abi_version")? }; let decode: DecodeFn = unsafe { load_symbol(&library, "decode")? }; let got = unsafe { abi_version() }; @@ -123,7 +129,7 @@ impl Decoder { cancel as *const CancelToken as *mut CancelToken, ); - let mut response = MaybeUninit::::uninit(); + let mut response = MaybeUninit::::uninit(); let code = unsafe { (self.decode)(&request, response.as_mut_ptr()) }; if code != 0 { @@ -138,7 +144,7 @@ impl Decoder { let dependencies = unsafe { response.dependencies.as_slice() } .iter() - .map(Dependency::try_from) + .map(PackageDependency::try_from) .collect::, _>>()?; let declarative_triggers = unsafe { response.declarative_triggers.as_slice() } diff --git a/lib/lib/src/plugin/decoder/unpack.rs b/lib/lib/src/plugin/decoder/unpack.rs index d3394c52..c3b6ae1a 100644 --- a/lib/lib/src/plugin/decoder/unpack.rs +++ b/lib/lib/src/plugin/decoder/unpack.rs @@ -5,7 +5,8 @@ use upac_abi::hook::CancelToken; -use upac_types::{DeclarativeTrigger, PackageTemp}; +use upac_types::decoder::DeclarativeTrigger; +use upac_types::package::PackageTemp; use crate::plugin::decoder::error::DecoderError; diff --git a/lib/lib/src/scripts/load.rs b/lib/lib/src/scripts/load.rs deleted file mode 100644 index 98943b3b..00000000 --- a/lib/lib/src/scripts/load.rs +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs; -use std::str::from_utf8; - -use upac_pki::signature::{HookSignature, RootCertificate}; - -use crate::scripts::error::HookError; -use crate::scripts::file::HookFile; - -pub fn load_hooks( - hooks_dir: &str, root_cert_path: &str, hook_extension: &str, signature_extension: &str, -) -> Result, HookError> { - let root_bytes = fs::read(root_cert_path)?; - let root_certificate = RootCertificate::from_bytes(&root_bytes)?; - - let mut hooks = Vec::new(); - - for entry in fs::read_dir(hooks_dir)? { - let path = entry?.path(); - - if path.extension().and_then(|extension| extension.to_str()) != Some(hook_extension) { - continue; - } - - let mut signature_path = path.clone().into_os_string(); - signature_path.push("."); - signature_path.push(signature_extension); - - let hook_bytes = fs::read(&path)?; - let signature_bytes = fs::read(&signature_path)?; - - let signature = HookSignature::from_bytes(&signature_bytes)?; - signature.verify(&hook_bytes, &root_certificate)?; - - let hook_text = from_utf8(&hook_bytes)?; - let hook_file = HookFile::parse(hook_text)?; - - hooks.push(hook_file); - } - - Ok(hooks) -} diff --git a/lib/lib/src/scripts/mod.rs b/lib/lib/src/scripts/mod.rs index 13468a37..1a97a6ab 100644 --- a/lib/lib/src/scripts/mod.rs +++ b/lib/lib/src/scripts/mod.rs @@ -5,23 +5,24 @@ use std::collections::{HashMap, HashSet}; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; +use upac_types::hook::ProgressEventBuilder; -use upac_types::DeclarativeTrigger; +use upac_types::decoder::DeclarativeTrigger; use crate::errors::CommonError; use crate::layout::hooks::{HOOK_EXTENSION, HOOKS_DIR, ROOT_CERT_PATH, SIGNATURE_EXTENSION}; +use crate::orchestrator::context::Context; use crate::orchestrator::stage::{ConcurrentStage, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, Orchestrator, ParallelOrchestrator}; +use crate::orchestrator::{Orchestrator, ParallelOrchestrator}; use crate::plugin::decoder::triggers::build_trigger_table; use crate::scripts::error::HookError; -use crate::scripts::load::load_hooks; +use crate::scripts::file::HookFile; use crate::scripts::pipeline::{PipelineTrigger, Timing}; use crate::scripts::primitive::Primitive; pub mod error; pub mod file; -pub mod load; pub mod pipeline; pub mod primitive; @@ -83,3 +84,37 @@ impl + Send + 'static> Stage for HookStage { Ok((progress, StageResult::Advance, Box::new(Vec::::new()))) } } + +pub fn load_hooks( + hooks_dir: &str, root_cert_path: &str, hook_extension: &str, signature_extension: &str, +) -> Result, HookError> { + let root_bytes = fs::read(root_cert_path)?; + let root_certificate = RootCertificate::from_bytes(&root_bytes)?; + + let mut hooks = Vec::new(); + + for entry in fs::read_dir(hooks_dir)? { + let path = entry?.path(); + + if path.extension().and_then(|extension| extension.to_str()) != Some(hook_extension) { + continue; + } + + let mut signature_path = path.clone().into_os_string(); + signature_path.push("."); + signature_path.push(signature_extension); + + let hook_bytes = fs::read(&path)?; + let signature_bytes = fs::read(&signature_path)?; + + let signature = HookSignature::from_bytes(&signature_bytes)?; + signature.verify(&hook_bytes, &root_certificate)?; + + let hook_text = from_utf8(&hook_bytes)?; + let hook_file = HookFile::parse(hook_text)?; + + hooks.push(hook_file); + } + + Ok(hooks) +} diff --git a/lib/lib/src/unmutated/diff/comparing.rs b/lib/lib/src/unmutated/diff/comparing.rs index aaa8fdf5..ff03cddd 100644 --- a/lib/lib/src/unmutated/diff/comparing.rs +++ b/lib/lib/src/unmutated/diff/comparing.rs @@ -5,17 +5,18 @@ use std::collections::HashMap; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_abi::{FileDiffKind, PackageDiffKind}; +use upac_types::entry::{DiffFileEntryCommon, DiffPackageEntry, DiffPrefixFileEntry, DiffUntrackedFileEntry}; +use upac_types::hook::ProgressEventBuilder; +use upac_types::package::{PackageMeta, Version}; + +use super::{DiffError, DiffSnapshot}; + use crate::database::attribution::FileAttribute; +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; -use crate::unmutated::diff::{DiffError, DiffSnapshot}; - -use upac_types::{ - DiffFileEntryCommon, DiffPackageEntry, DiffPrefixFileEntry, DiffUntrackedFileEntry, PackageMeta, Version, -}; type PackageIdentity = (String, String, Option); diff --git a/lib/lib/src/unmutated/diff/mod.rs b/lib/lib/src/unmutated/diff/mod.rs index 611a7d16..b5db152b 100644 --- a/lib/lib/src/unmutated/diff/mod.rs +++ b/lib/lib/src/unmutated/diff/mod.rs @@ -5,22 +5,27 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CDiffRequest; use upac_abi::{DiffFileSource, FileDiffKind}; -pub use self::error::DiffError; +use upac_types::entry::{DiffPackageEntry, DiffUntrackedFileEntry}; +use upac_types::hook::Message; +use upac_types::package::PackageMeta; +use upac_types::states::DiffStateId; +use upac_types::traits::MessageHook; +use upac_types::{RequestedConfigDigestRange, RequestedPrefixDigestRange}; use self::comparing::ComparingStage; use self::preparing::PreparingStage; use crate::database::MemoryDatabase; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use upac_types::states::DiffStateId; -use upac_types::{ - DiffPackageEntry, DiffUntrackedFileEntry, PackageMeta, RequestedConfigDigestRange, RequestedPrefixDigestRange, -}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; + +pub use self::error::DiffError; mod comparing; mod error; @@ -52,7 +57,7 @@ impl<'a> TryFrom<&'a CDiffRequest> for DiffData<'a> { fn try_from(request: &'a CDiffRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(DiffData { from_prefix_digest: (&request.from_prefix_digest).try_into()?, diff --git a/lib/lib/src/unmutated/diff/preparing.rs b/lib/lib/src/unmutated/diff/preparing.rs index faef783f..40ad92d8 100644 --- a/lib/lib/src/unmutated/diff/preparing.rs +++ b/lib/lib/src/unmutated/diff/preparing.rs @@ -4,7 +4,11 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use upac_abi::DiffFileSource; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{DiffError, DiffSnapshot}; use crate::composefs::diff::TreeDiff; use crate::composefs::file::FileHandle; @@ -14,9 +18,8 @@ use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::digest::current_prefix_digest; use crate::deploy::{Deploy, DeployMode}; use crate::layout::database::DATABASE_PATH; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; -use crate::unmutated::diff::{DiffError, DiffSnapshot}; use upac_types::{RequestedConfigDigestRange, RequestedPrefixDigestRange}; diff --git a/lib/lib/src/unmutated/diff_config/comparing.rs b/lib/lib/src/unmutated/diff_config/comparing.rs index b6ccca0d..87839cf7 100644 --- a/lib/lib/src/unmutated/diff_config/comparing.rs +++ b/lib/lib/src/unmutated/diff_config/comparing.rs @@ -4,14 +4,17 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use upac_abi::FileDiffKind; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{DiffConfigError, DiffConfigSnapshot}; use crate::database::attribution::FileAttribute; +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; -use crate::unmutated::diff_config::{DiffConfigError, DiffConfigSnapshot}; -use upac_types::{DiffConfigFileEntry, DiffFileEntryCommon}; +use upac_types::entry::{DiffConfigFileEntry, DiffFileEntryCommon}; pub struct ComparingStage; diff --git a/lib/lib/src/unmutated/diff_config/mod.rs b/lib/lib/src/unmutated/diff_config/mod.rs index f956a7f3..0d0abd71 100644 --- a/lib/lib/src/unmutated/diff_config/mod.rs +++ b/lib/lib/src/unmutated/diff_config/mod.rs @@ -6,19 +6,25 @@ use std::os::raw::c_void; use upac_abi::FileDiffKind; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CDiffConfigRequest; -pub use self::error::DiffConfigError; +use upac_types::RequestedConfigDigestRange; +use upac_types::entry::DiffConfigFileEntry; +use upac_types::hook::Message; +use upac_types::states::DiffConfigStateId; +use upac_types::traits::MessageHook; use self::comparing::ComparingStage; use self::preparing::PreparingStage; use crate::database::MemoryDatabase; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use upac_types::states::DiffConfigStateId; -use upac_types::{DiffConfigFileEntry, RequestedConfigDigestRange}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; + +pub use self::error::DiffConfigError; mod comparing; mod error; @@ -46,7 +52,7 @@ impl<'a> TryFrom<&'a CDiffConfigRequest> for DiffConfigData<'a> { fn try_from(request: &'a CDiffConfigRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(DiffConfigData { from_config_digest: (&request.from_config_digest).try_into()?, diff --git a/lib/lib/src/unmutated/diff_config/preparing.rs b/lib/lib/src/unmutated/diff_config/preparing.rs index 04f20f14..79a4aa11 100644 --- a/lib/lib/src/unmutated/diff_config/preparing.rs +++ b/lib/lib/src/unmutated/diff_config/preparing.rs @@ -3,7 +3,11 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{DiffConfigError, DiffConfigSnapshot}; use crate::composefs::diff::TreeDiff; use crate::composefs::file::FileHandle; @@ -11,9 +15,8 @@ use crate::database::record::DeployRecord; use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::{Deploy, DeployMode}; use crate::layout::database::DATABASE_PATH; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; -use crate::unmutated::diff_config::{DiffConfigError, DiffConfigSnapshot}; use upac_types::RequestedConfigDigestRange; diff --git a/lib/lib/src/unmutated/diff_packages/comparing.rs b/lib/lib/src/unmutated/diff_packages/comparing.rs index aecf7544..a8ec8960 100644 --- a/lib/lib/src/unmutated/diff_packages/comparing.rs +++ b/lib/lib/src/unmutated/diff_packages/comparing.rs @@ -6,13 +6,16 @@ use std::collections::HashMap; use upac_abi::PackageDiffKind; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; -use crate::unmutated::diff_packages::DiffPackagesError; +use upac_types::DiffPackagesSnapshot; +use upac_types::entry::DiffPackageEntry; +use upac_types::hook::ProgressEventBuilder; + +use super::DiffPackagesError; -use upac_types::{DiffPackageEntry, DiffPackagesSnapshot}; +use crate::orchestrator::context::{Context, ctx_take}; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; pub struct ComparingStage; diff --git a/lib/lib/src/unmutated/diff_packages/mod.rs b/lib/lib/src/unmutated/diff_packages/mod.rs index fc80c1f7..83693a06 100644 --- a/lib/lib/src/unmutated/diff_packages/mod.rs +++ b/lib/lib/src/unmutated/diff_packages/mod.rs @@ -5,18 +5,24 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CDiffPackagesRequest; -pub use self::error::DiffPackagesError; +use upac_types::RequestedPrefixDigestRange; +use upac_types::entry::DiffPackageEntry; +use upac_types::hook::Message; +use upac_types::states::DiffPackagesStateId; +use upac_types::traits::MessageHook; use self::comparing::ComparingStage; use self::preparing::PreparingStage; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use upac_types::states::DiffPackagesStateId; -use upac_types::{DiffPackageEntry, RequestedPrefixDigestRange}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; + +pub use self::error::DiffPackagesError; mod comparing; mod error; @@ -38,7 +44,7 @@ impl<'a> TryFrom<&'a CDiffPackagesRequest> for DiffPackagesData<'a> { fn try_from(request: &'a CDiffPackagesRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(DiffPackagesData { from_prefix_digest: (&request.from_prefix_digest).try_into()?, diff --git a/lib/lib/src/unmutated/diff_packages/preparing.rs b/lib/lib/src/unmutated/diff_packages/preparing.rs index 04ffb02d..1b085f09 100644 --- a/lib/lib/src/unmutated/diff_packages/preparing.rs +++ b/lib/lib/src/unmutated/diff_packages/preparing.rs @@ -3,7 +3,12 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; +use upac_types::{DiffPackagesSnapshot, RequestedPrefixDigestRange}; + +use super::DiffPackagesError; use crate::composefs::file::FileHandle; use crate::database::meta::MetaStore; @@ -11,11 +16,8 @@ use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::digest::current_prefix_digest; use crate::deploy::{Deploy, DeployMode}; use crate::layout::database::DATABASE_PATH; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; -use crate::unmutated::diff_packages::DiffPackagesError; - -use upac_types::{DiffPackagesSnapshot, RequestedPrefixDigestRange}; pub struct PreparingStage; diff --git a/lib/lib/src/unmutated/diff_prefix/comparing.rs b/lib/lib/src/unmutated/diff_prefix/comparing.rs index 06db41c3..184ebee8 100644 --- a/lib/lib/src/unmutated/diff_prefix/comparing.rs +++ b/lib/lib/src/unmutated/diff_prefix/comparing.rs @@ -3,15 +3,17 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_abi::{DiffFileSource, FileDiffKind}; +use upac_types::entry::{DiffFileEntryCommon, DiffPrefixFileEntry}; +use upac_types::hook::ProgressEventBuilder; + +use super::{DiffPrefixError, DiffPrefixSnapshot}; + use crate::database::attribution::FileAttribute; +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_take}; -use crate::unmutated::diff_prefix::{DiffPrefixError, DiffPrefixSnapshot}; - -use upac_types::{DiffFileEntryCommon, DiffPrefixFileEntry}; pub struct ComparingStage; diff --git a/lib/lib/src/unmutated/diff_prefix/mod.rs b/lib/lib/src/unmutated/diff_prefix/mod.rs index 9c97b14a..64d94b12 100644 --- a/lib/lib/src/unmutated/diff_prefix/mod.rs +++ b/lib/lib/src/unmutated/diff_prefix/mod.rs @@ -6,19 +6,25 @@ use std::os::raw::c_void; use upac_abi::FileDiffKind; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CDiffPrefixRequest; -pub use self::error::DiffPrefixError; +use upac_types::RequestedPrefixDigestRange; +use upac_types::entry::DiffPrefixFileEntry; +use upac_types::hook::Message; +use upac_types::states::DiffPrefixStateId; +use upac_types::traits::MessageHook; use self::comparing::ComparingStage; use self::preparing::PreparingStage; use crate::database::MemoryDatabase; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use upac_types::states::DiffPrefixStateId; -use upac_types::{DiffPrefixFileEntry, RequestedPrefixDigestRange}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; + +pub use self::error::DiffPrefixError; mod comparing; mod error; @@ -46,7 +52,7 @@ impl<'a> TryFrom<&'a CDiffPrefixRequest> for DiffPrefixData<'a> { fn try_from(request: &'a CDiffPrefixRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(DiffPrefixData { from_prefix_digest: (&request.from_prefix_digest).try_into()?, diff --git a/lib/lib/src/unmutated/diff_prefix/preparing.rs b/lib/lib/src/unmutated/diff_prefix/preparing.rs index d307554b..e385cddb 100644 --- a/lib/lib/src/unmutated/diff_prefix/preparing.rs +++ b/lib/lib/src/unmutated/diff_prefix/preparing.rs @@ -3,7 +3,12 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::RequestedPrefixDigestRange; +use upac_types::hook::ProgressEventBuilder; + +use super::{DiffPrefixError, DiffPrefixSnapshot}; use crate::composefs::diff::TreeDiff; use crate::composefs::file::FileHandle; @@ -12,11 +17,8 @@ use crate::database::MemoryDatabase; use crate::deploy::digest::current_prefix_digest; use crate::deploy::{Deploy, DeployMode}; use crate::layout::database::DATABASE_PATH; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; -use crate::unmutated::diff_prefix::{DiffPrefixError, DiffPrefixSnapshot}; - -use upac_types::RequestedPrefixDigestRange; pub struct PreparingStage; diff --git a/lib/lib/src/unmutated/list_config/fetching.rs b/lib/lib/src/unmutated/list_config/fetching.rs index daee05d8..156f1375 100644 --- a/lib/lib/src/unmutated/list_config/fetching.rs +++ b/lib/lib/src/unmutated/list_config/fetching.rs @@ -3,16 +3,19 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::RequestedPrefixDigest; +use upac_types::entry::ConfigCommitEntry; +use upac_types::hook::ProgressEventBuilder; + +use super::ListConfigError; use crate::database::record::DeployRecord; use crate::deploy::digest::current_prefix_digest; use crate::deploy::{Deploy, DeployMode}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; -use crate::unmutated::list_config::ListConfigError; - -use upac_types::{ConfigCommitEntry, RequestedPrefixDigest}; pub struct FetchingStage; diff --git a/lib/lib/src/unmutated/list_config/mod.rs b/lib/lib/src/unmutated/list_config/mod.rs index 84a5dd78..7a2b536b 100644 --- a/lib/lib/src/unmutated/list_config/mod.rs +++ b/lib/lib/src/unmutated/list_config/mod.rs @@ -5,17 +5,23 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CListConfigRequest; -pub use self::error::ListConfigError; +use upac_types::RequestedPrefixDigest; +use upac_types::entry::ConfigCommitEntry; +use upac_types::hook::Message; +use upac_types::states::ListConfigStateId; +use upac_types::traits::MessageHook; use self::fetching::FetchingStage; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use upac_types::states::ListConfigStateId; -use upac_types::{ConfigCommitEntry, RequestedPrefixDigest}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; + +pub use self::error::ListConfigError; mod error; mod fetching; @@ -35,7 +41,7 @@ impl<'a> TryFrom<&'a CListConfigRequest> for ListConfigData<'a> { fn try_from(request: &'a CListConfigRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(ListConfigData { prefix_digest: (&request.prefix_digest).try_into()?, diff --git a/lib/lib/src/unmutated/list_history/fetching.rs b/lib/lib/src/unmutated/list_history/fetching.rs index e206bf33..1fe8391a 100644 --- a/lib/lib/src/unmutated/list_history/fetching.rs +++ b/lib/lib/src/unmutated/list_history/fetching.rs @@ -3,15 +3,17 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::entry::{ConfigCommitEntry, HistoryEntry}; +use upac_types::hook::ProgressEventBuilder; + +use super::ListHistoryError; use crate::database::record::DeployRecord; use crate::deploy::{Deploy, DeployMode}; -use crate::orchestrator::Context; +use crate::orchestrator::context::Context; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::unmutated::list_history::ListHistoryError; - -use upac_types::{ConfigCommitEntry, HistoryEntry}; pub struct FetchingStage; diff --git a/lib/lib/src/unmutated/list_history/mod.rs b/lib/lib/src/unmutated/list_history/mod.rs index f9bac4b7..d9b2b0ac 100644 --- a/lib/lib/src/unmutated/list_history/mod.rs +++ b/lib/lib/src/unmutated/list_history/mod.rs @@ -5,17 +5,22 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CListHistoryRequest; -pub use self::error::ListHistoryError; +use upac_types::entry::HistoryEntry; +use upac_types::hook::Message; +use upac_types::states::ListHistoryStateId; +use upac_types::traits::MessageHook; use self::fetching::FetchingStage; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use upac_types::HistoryEntry; -use upac_types::states::ListHistoryStateId; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; + +pub use self::error::ListHistoryError; mod error; mod fetching; @@ -33,7 +38,7 @@ impl<'a> TryFrom<&'a CListHistoryRequest> for ListHistoryData<'a> { fn try_from(request: &'a CListHistoryRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(ListHistoryData { hook_message: request.base.on_hook, diff --git a/lib/lib/src/unmutated/list_packages/fetching.rs b/lib/lib/src/unmutated/list_packages/fetching.rs index bae1dc0e..41b371a2 100644 --- a/lib/lib/src/unmutated/list_packages/fetching.rs +++ b/lib/lib/src/unmutated/list_packages/fetching.rs @@ -3,7 +3,11 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::ListPackagesError; use crate::composefs::file::FileHandle; use crate::database::meta::MetaStore; @@ -11,9 +15,8 @@ use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::digest::current_prefix_digest; use crate::deploy::{Deploy, DeployMode}; use crate::layout::database::DATABASE_PATH; -use crate::orchestrator::Context; +use crate::orchestrator::context::Context; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::unmutated::list_packages::ListPackagesError; pub struct FetchingStage; diff --git a/lib/lib/src/unmutated/list_packages/mod.rs b/lib/lib/src/unmutated/list_packages/mod.rs index bd33bbcd..63408660 100644 --- a/lib/lib/src/unmutated/list_packages/mod.rs +++ b/lib/lib/src/unmutated/list_packages/mod.rs @@ -5,17 +5,22 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CListPackagesRequest; -pub use self::error::ListPackagesError; +use upac_types::hook::Message; +use upac_types::package::PackageMeta; +use upac_types::states::ListPackagesStateId; +use upac_types::traits::MessageHook; use self::fetching::FetchingStage; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use upac_types::PackageMeta; -use upac_types::states::ListPackagesStateId; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; + +pub use self::error::ListPackagesError; mod error; mod fetching; @@ -33,7 +38,7 @@ impl<'a> TryFrom<&'a CListPackagesRequest> for ListPackagesData<'a> { fn try_from(request: &'a CListPackagesRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(ListPackagesData { hook_message: request.base.on_hook, diff --git a/lib/lib/src/unmutated/list_prefix/fetching.rs b/lib/lib/src/unmutated/list_prefix/fetching.rs index 9cefb2ee..a18a943e 100644 --- a/lib/lib/src/unmutated/list_prefix/fetching.rs +++ b/lib/lib/src/unmutated/list_prefix/fetching.rs @@ -3,15 +3,17 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::entry::PrefixEntry; +use upac_types::hook::ProgressEventBuilder; + +use super::ListPrefixError; use crate::database::record::DeployRecord; use crate::deploy::{Deploy, DeployMode}; -use crate::orchestrator::Context; +use crate::orchestrator::context::Context; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::unmutated::list_prefix::ListPrefixError; - -use upac_types::PrefixEntry; pub struct FetchingStage; diff --git a/lib/lib/src/unmutated/list_prefix/mod.rs b/lib/lib/src/unmutated/list_prefix/mod.rs index a7844407..07f41022 100644 --- a/lib/lib/src/unmutated/list_prefix/mod.rs +++ b/lib/lib/src/unmutated/list_prefix/mod.rs @@ -5,17 +5,22 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CListPrefixRequest; -pub use self::error::ListPrefixError; +use upac_types::entry::PrefixEntry; +use upac_types::hook::Message; +use upac_types::states::ListPrefixStateId; +use upac_types::traits::MessageHook; use self::fetching::FetchingStage; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use upac_types::PrefixEntry; -use upac_types::states::ListPrefixStateId; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; + +pub use self::error::ListPrefixError; mod error; mod fetching; @@ -33,7 +38,7 @@ impl<'a> TryFrom<&'a CListPrefixRequest> for ListPrefixData<'a> { fn try_from(request: &'a CListPrefixRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(ListPrefixData { hook_message: request.base.on_hook, diff --git a/lib/lib/src/unmutated/search_files/mod.rs b/lib/lib/src/unmutated/search_files/mod.rs index 9fbd871f..bb6e3015 100644 --- a/lib/lib/src/unmutated/search_files/mod.rs +++ b/lib/lib/src/unmutated/search_files/mod.rs @@ -5,18 +5,23 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CSearchFilesRequest; -pub use self::error::SearchFilesError; +use upac_types::entry::SearchFileEntry; +use upac_types::hook::Message; +use upac_types::states::SearchFilesStateId; +use upac_types::traits::MessageHook; use self::searching::SearchingStage; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; use crate::search::Search; -use upac_types::SearchFileEntry; -use upac_types::states::SearchFilesStateId; + +pub use self::error::SearchFilesError; mod error; mod searching; @@ -37,7 +42,7 @@ impl<'a> TryFrom<&'a CSearchFilesRequest> for SearchFilesData<'a> { fn try_from(request: &'a CSearchFilesRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(SearchFilesData { search: (&request.search).try_into()?, diff --git a/lib/lib/src/unmutated/search_files/searching.rs b/lib/lib/src/unmutated/search_files/searching.rs index 94a24ca9..5ea5ff4b 100644 --- a/lib/lib/src/unmutated/search_files/searching.rs +++ b/lib/lib/src/unmutated/search_files/searching.rs @@ -3,7 +3,12 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::entry::SearchFileEntry; +use upac_types::hook::ProgressEventBuilder; + +use super::SearchFilesError; use crate::composefs::file::FileHandle; use crate::database::files::FileStore; @@ -12,12 +17,9 @@ use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::digest::current_prefix_digest; use crate::deploy::{Deploy, DeployMode}; use crate::layout::database::DATABASE_PATH; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; use crate::search::Search; -use crate::unmutated::search_files::SearchFilesError; - -use upac_types::SearchFileEntry; pub struct SearchingStage; diff --git a/lib/lib/src/unmutated/search_in_meta/mod.rs b/lib/lib/src/unmutated/search_in_meta/mod.rs index acb5bfc9..85685e4b 100644 --- a/lib/lib/src/unmutated/search_in_meta/mod.rs +++ b/lib/lib/src/unmutated/search_in_meta/mod.rs @@ -5,18 +5,23 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CSearchInMetaRequest; -pub use self::error::SearchInMetaError; +use upac_types::hook::Message; +use upac_types::package::{PackageEntry, PackageMeta}; +use upac_types::states::SearchInMetaStateId; +use upac_types::traits::MessageHook; use self::searching::SearchingStage; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; use crate::search::Search; -use upac_types::states::SearchInMetaStateId; -use upac_types::{PackageEntry, PackageMeta}; + +pub use self::error::SearchInMetaError; mod error; mod searching; @@ -40,7 +45,7 @@ impl<'a> TryFrom<&'a CSearchInMetaRequest> for SearchInMetaData<'a> { fn try_from(request: &'a CSearchInMetaRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(SearchInMetaData { name: (&request.package.name).try_into()?, diff --git a/lib/lib/src/unmutated/search_in_meta/searching.rs b/lib/lib/src/unmutated/search_in_meta/searching.rs index 03ece769..7d384981 100644 --- a/lib/lib/src/unmutated/search_in_meta/searching.rs +++ b/lib/lib/src/unmutated/search_in_meta/searching.rs @@ -3,7 +3,12 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; +use upac_types::package::PackageEntry; + +use super::SearchInMetaError; use crate::composefs::file::FileHandle; use crate::database::error::DatabaseError; @@ -12,12 +17,9 @@ use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::digest::current_prefix_digest; use crate::deploy::{Deploy, DeployMode}; use crate::layout::database::DATABASE_PATH; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; use crate::search::Search; -use crate::unmutated::search_in_meta::SearchInMetaError; - -use upac_types::PackageEntry; pub struct SearchingStage; diff --git a/lib/lib/src/unmutated/search_in_package_files/mod.rs b/lib/lib/src/unmutated/search_in_package_files/mod.rs index 4869b7b4..a7d816c4 100644 --- a/lib/lib/src/unmutated/search_in_package_files/mod.rs +++ b/lib/lib/src/unmutated/search_in_package_files/mod.rs @@ -5,18 +5,24 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CSearchInPackageFilesRequest; -pub use self::error::SearchInPackageFilesError; +use upac_types::entry::SearchFileEntry; +use upac_types::hook::Message; +use upac_types::package::PackageEntry; +use upac_types::states::SearchInPackageFilesStateId; +use upac_types::traits::MessageHook; use self::searching::SearchingStage; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; use crate::search::Search; -use upac_types::states::SearchInPackageFilesStateId; -use upac_types::{PackageEntry, SearchFileEntry}; + +pub use self::error::SearchInPackageFilesError; mod error; mod searching; @@ -40,7 +46,7 @@ impl<'a> TryFrom<&'a CSearchInPackageFilesRequest> for SearchInPackageFilesData< fn try_from(request: &'a CSearchInPackageFilesRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(SearchInPackageFilesData { name: (&request.package.name).try_into()?, diff --git a/lib/lib/src/unmutated/search_in_package_files/searching.rs b/lib/lib/src/unmutated/search_in_package_files/searching.rs index 971c716b..a165c4fc 100644 --- a/lib/lib/src/unmutated/search_in_package_files/searching.rs +++ b/lib/lib/src/unmutated/search_in_package_files/searching.rs @@ -3,7 +3,13 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::entry::SearchFileEntry; +use upac_types::hook::ProgressEventBuilder; +use upac_types::package::PackageEntry; + +use super::SearchInPackageFilesError; use crate::composefs::file::FileHandle; use crate::database::error::DatabaseError; @@ -13,12 +19,9 @@ use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::digest::current_prefix_digest; use crate::deploy::{Deploy, DeployMode}; use crate::layout::database::DATABASE_PATH; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; use crate::search::Search; -use crate::unmutated::search_in_package_files::SearchInPackageFilesError; - -use upac_types::{PackageEntry, SearchFileEntry}; pub struct SearchingStage; diff --git a/lib/lib/src/unmutated/search_meta/mod.rs b/lib/lib/src/unmutated/search_meta/mod.rs index 1c14122c..1395bbde 100644 --- a/lib/lib/src/unmutated/search_meta/mod.rs +++ b/lib/lib/src/unmutated/search_meta/mod.rs @@ -5,18 +5,23 @@ use std::os::raw::c_void; +use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; +use upac_abi::hook::CancelToken; use upac_abi::request::CSearchMetaRequest; -pub use self::error::SearchMetaError; +use upac_types::hook::Message; +use upac_types::package::PackageMeta; +use upac_types::states::SearchMetaStateId; +use upac_types::traits::MessageHook; use self::searching::SearchingStage; -use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; +use crate::orchestrator::context::Context; +use crate::orchestrator::{Orchestrator, SequentialOrchestrator, run_unmutated}; use crate::search::Search; -use upac_types::PackageMeta; -use upac_types::states::SearchMetaStateId; + +pub use self::error::SearchMetaError; mod error; mod searching; @@ -37,7 +42,7 @@ impl<'a> TryFrom<&'a CSearchMetaRequest> for SearchMetaData<'a> { fn try_from(request: &'a CSearchMetaRequest) -> Result { unsafe { request.validate()? }; - let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + let cancel_token = unsafe { &*request.base.cancel_token }; Ok(SearchMetaData { search: (&request.search).try_into()?, diff --git a/lib/lib/src/unmutated/search_meta/searching.rs b/lib/lib/src/unmutated/search_meta/searching.rs index 8952ff4e..b63442c5 100644 --- a/lib/lib/src/unmutated/search_meta/searching.rs +++ b/lib/lib/src/unmutated/search_meta/searching.rs @@ -3,7 +3,11 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::SearchMetaError; use crate::composefs::file::FileHandle; use crate::database::meta::MetaStore; @@ -11,10 +15,9 @@ use crate::database::{InMemory, MemoryDatabase}; use crate::deploy::digest::current_prefix_digest; use crate::deploy::{Deploy, DeployMode}; use crate::layout::database::DATABASE_PATH; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::orchestrator::{Context, ctx_get}; use crate::search::Search; -use crate::unmutated::search_meta::SearchMetaError; pub struct SearchingStage; diff --git a/lib/lib/tests/orchestrator.rs b/lib/lib/tests/orchestrator.rs index ac0b4555..3b2db695 100644 --- a/lib/lib/tests/orchestrator.rs +++ b/lib/lib/tests/orchestrator.rs @@ -9,9 +9,10 @@ use std::sync::{Arc, Mutex}; use upac::errors::CommonError; use upac::lock::LockError; +use upac::orchestrator::context::Context; use upac::orchestrator::error::OrchestratorError; use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use upac::orchestrator::{Context, Orchestrator, SequentialOrchestrator}; +use upac::orchestrator::{Orchestrator, SequentialOrchestrator}; use upac_abi::error::ErrorKind; use upac_abi::hook::{CancelToken, ProgressEventBuilder}; From 871dfea47f4026ea13a8a9cb0bd589924b279d2d Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 09:18:07 +0400 Subject: [PATCH 051/148] fix: teach CTryToRust/RustToC raw pointers and Vec, CValidate null-checks CancelToken - c_try_to_rust: Type::Ptr fields now null-check and pass through (needed for CDecodeRequest.cancel_token: *mut CancelToken); vec_from_c gets a String-element special case (was falling into the generic composite path and trying to build a nonexistent CString conversion) - rust_to_c: matching Type::Ptr passthrough (no null-check, outbound direction trusts its own pointer) and Vec special case - c_validate: field_ptr_validate now recognizes *mut CancelToken specifically and null-checks it directly (no nested .validate() call, CancelToken doesn't derive it) - previously only VALIDATABLE_COMPOSITES pointees got checked, so every CRequestBase-based request had to hand-roll its own cancel_token null-check; now centralized in the derive Co-Authored-By: Claude Sonnet 5 --- lib/macro/src/c_try_to_rust/mod.rs | 28 +++++++++++++++++++++++++++- lib/macro/src/c_validate/mod.rs | 12 +++++++++++- lib/macro/src/rust_to_c/mod.rs | 8 +++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/lib/macro/src/c_try_to_rust/mod.rs b/lib/macro/src/c_try_to_rust/mod.rs index 8858e250..07ae3a27 100644 --- a/lib/macro/src/c_try_to_rust/mod.rs +++ b/lib/macro/src/c_try_to_rust/mod.rs @@ -40,7 +40,18 @@ fn vec_from_c(ident: &Ident, segment: &PathSegment) -> TokenStream2 { return quote! { compile_error!("CTryToRust: unsupported Vec element type") }; }; - if PRIMITIVES.contains(&inner_name.as_str()) { + if inner_name == "String" { + quote! { + { + unsafe { value.#ident.validate()? }; + unsafe { value.#ident.as_slice() } + .iter() + .map(<&str>::try_from) + .map(|element| element.map(str::to_owned)) + .collect::, ErrorKind>>()? + } + } + } else if PRIMITIVES.contains(&inner_name.as_str()) { quote! { { unsafe { value.#ident.validate()? }; @@ -71,11 +82,26 @@ fn field_path_from_c(ident: &Ident, segment: &PathSegment) -> TokenStream2 { } } +fn ptr_from_c(ident: &Ident) -> TokenStream2 { + quote! { + { + if value.#ident.is_null() { + return Err(ErrorKind::InvalidEntry); + } + value.#ident + } + } +} + fn field_from_c_fallible(ident: &Ident, ty: &Type) -> TokenStream2 { if let Type::Array(_) = ty { return quote! { value.#ident }; } + if let Type::Ptr(_) = ty { + return ptr_from_c(ident); + } + let Type::Path(type_path) = ty else { return quote! { compile_error!("CTryToRust: unsupported field type") }; }; diff --git a/lib/macro/src/c_validate/mod.rs b/lib/macro/src/c_validate/mod.rs index 0076080e..691f9947 100644 --- a/lib/macro/src/c_validate/mod.rs +++ b/lib/macro/src/c_validate/mod.rs @@ -89,7 +89,17 @@ fn field_ptr_validate(ident: &Ident, ptr: &TypePtr) -> TokenStream2 { return quote! {}; }; - if VALIDATABLE_COMPOSITES.contains(&seg.ident.to_string().as_str()) { + let name = seg.ident.to_string(); + + if name == "CancelToken" { + return quote! { + if self.#ident.is_null() { + return Err(ErrorKind::InvalidEntry); + } + }; + } + + if VALIDATABLE_COMPOSITES.contains(&name.as_str()) { quote! { unsafe { if self.#ident.is_null() { diff --git a/lib/macro/src/rust_to_c/mod.rs b/lib/macro/src/rust_to_c/mod.rs index cc13f0de..bc4646fe 100644 --- a/lib/macro/src/rust_to_c/mod.rs +++ b/lib/macro/src/rust_to_c/mod.rs @@ -35,7 +35,9 @@ fn vec_to_c(ident: &Ident, segment: &PathSegment) -> TokenStream2 { return quote! { compile_error!("RustToC: unsupported Vec element type") }; }; - if PRIMITIVES.contains(&inner_name.as_str()) { + if inner_name == "String" { + quote! { CVec::from_owned(value.#ident.into_iter().map(|element| CSlice::from_owned(element.into_bytes())).collect()) } + } else if PRIMITIVES.contains(&inner_name.as_str()) { quote! { CVec::from_owned(value.#ident) } } else { let c_inner = format_ident!("C{inner_name}"); @@ -58,6 +60,10 @@ fn field_to_c(ident: &Ident, ty: &Type) -> TokenStream2 { return quote! { value.#ident }; } + if let Type::Ptr(_) = ty { + return quote! { value.#ident }; + } + let Type::Path(type_path) = ty else { return quote! { compile_error!("RustToC: unsupported field type") }; }; From 5e847174de341d6eefb0be5d18d45b2b011bfb57 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 09:20:13 +0400 Subject: [PATCH 052/148] fix: added issues and fixed formatting Co-Authored-By: Claude Sonnet 5 --- lib/pki/src/error.rs | 1 + lib/pki/src/generate.rs | 5 ++++- lib/pki/src/signature.rs | 7 +++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/pki/src/error.rs b/lib/pki/src/error.rs index 9c70a175..a652beaa 100644 --- a/lib/pki/src/error.rs +++ b/lib/pki/src/error.rs @@ -7,6 +7,7 @@ use std::array::TryFromSliceError; use std::fmt::{Display, Formatter, Result}; use der::Error as DerError; + use rcgen::Error as RcgenError; #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/lib/pki/src/generate.rs b/lib/pki/src/generate.rs index 42862ca4..dc4ef9dc 100644 --- a/lib/pki/src/generate.rs +++ b/lib/pki/src/generate.rs @@ -5,14 +5,17 @@ use der::pem::LineEnding; use der::{Decode, DecodePem, Encode, EncodePem}; + use rcgen::{ BasicConstraints, CertificateParams, DistinguishedName, DnType, IsCa, Issuer, KeyPair, KeyUsagePurpose, PKCS_ED25519, }; + use rustls_pki_types::CertificateDer; + use x509_cert::Certificate; -use crate::error::PkiError; +use super::error::PkiError; pub struct SerializedIdentity { pub key_der: Vec, diff --git a/lib/pki/src/signature.rs b/lib/pki/src/signature.rs index 0de299a3..21ac2496 100644 --- a/lib/pki/src/signature.rs +++ b/lib/pki/src/signature.rs @@ -5,12 +5,15 @@ use der::pem::LineEnding; use der::{Decode, DecodePem, Encode, EncodePem}; + use ed25519_dalek::{Signature, Verifier, VerifyingKey}; + use rcgen::SigningKey; + use x509_cert::Certificate; -use crate::error::PkiError; -use crate::generate::SigningIdentity; +use super::error::PkiError; +use super::generate::SigningIdentity; const SIGNATURE_LEN: usize = 64; const LENGTH_PREFIX_LEN: usize = 4; From 117741808bc911a30042973af3ad1b64e8ba3559 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 14:05:11 +0400 Subject: [PATCH 053/148] fix: build out types::request/response mirrors, drop dead boot/decoder duplicates Co-Authored-By: Claude Sonnet 5 --- lib/types/src/boot.rs | 34 ------ lib/types/src/decoder.rs | 23 +--- lib/types/src/lib.rs | 3 +- lib/types/src/package.rs | 9 +- lib/types/src/request.rs | 224 ++++++++++++++++++++++++++++++++++++++ lib/types/src/response.rs | 92 ++++++++++++++++ 6 files changed, 327 insertions(+), 58 deletions(-) delete mode 100644 lib/types/src/boot.rs create mode 100644 lib/types/src/request.rs create mode 100644 lib/types/src/response.rs diff --git a/lib/types/src/boot.rs b/lib/types/src/boot.rs deleted file mode 100644 index d4d74442..00000000 --- a/lib/types/src/boot.rs +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use upac_abi::error::ErrorKind; -use upac_abi::request::{ - CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, -}; -use upac_abi::types::{COwned, CSlice}; - -use upac_macro::{CTryToRust, RustToC}; - -#[derive(Debug, Clone, CTryToRust, RustToC)] -pub struct BootPluginSetOneShotRequest { - pub entry_name: String, -} - -#[derive(Debug, Clone, CTryToRust, RustToC)] -pub struct BootPluginConfirmSuccsesBootRequest { - pub entry_name: String, - pub esp_mount_point: String, -} - -#[derive(Debug, Clone, CTryToRust, RustToC)] -pub struct BootPluginInstallRequest { - pub esp_mount_point: String, - pub esp_partition_number: u32, - pub esp_starting_lba: u64, - pub esp_ending_lba: u64, - pub esp_unique_partition_guid: [u8; 16], - pub to_slot: String, - pub from_slot: String, -} diff --git a/lib/types/src/decoder.rs b/lib/types/src/decoder.rs index 36b71819..97af9502 100644 --- a/lib/types/src/decoder.rs +++ b/lib/types/src/decoder.rs @@ -5,30 +5,9 @@ use std::io::Read; -use upac_abi::error::ErrorKind; -use upac_abi::hook::CancelToken; -use upac_abi::request::CDecodeRequest; -use upac_abi::response::CDecodeResponse; -use upac_abi::types::{COwned, CSlice}; -use upac_macro::{CTryToRust, RedbCodec, RustToC}; +use upac_macro::RedbCodec; use super::error::DecodeError; -use super::package::{PackageDependency, PackageMeta}; - -#[derive(Debug, Clone, CTryToRust, RustToC)] -pub struct DecodeRequest { - pub package_path: String, - pub output_dir: String, - pub checksum: [u8; 32], - pub cancel_token: *mut CancelToken, -} - -#[derive(Debug, Clone, CTryToRust)] -pub struct DecodeResponse { - pub meta: PackageMeta, - pub dependencies: Vec, - pub declarative_triggers: Vec, -} #[derive(Debug, Clone, RedbCodec)] pub struct DeclarativeTrigger { diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 7e7c4278..beefaf38 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -7,13 +7,14 @@ use upac_abi::FsKind; use self::package::{PackageEntry, PackageMeta}; -pub mod boot; pub mod codec; pub mod decoder; pub mod entry; pub mod error; pub mod hook; pub mod package; +pub mod request; +pub mod response; pub mod settings; pub mod states; pub mod traits; diff --git a/lib/types/src/package.rs b/lib/types/src/package.rs index b5611f80..eb8ad1b8 100644 --- a/lib/types/src/package.rs +++ b/lib/types/src/package.rs @@ -9,7 +9,7 @@ use std::mem::size_of; use serde::{Deserialize, Deserializer}; use upac_abi::error::ErrorKind; -use upac_abi::package::{CPackageDependency, CPackageMeta, CVersion}; +use upac_abi::package::{CPackageDependency, CPackageInfo, CPackageMeta, CVersion}; use upac_abi::types::{COwned, CSlice}; use upac_macro::{CTryToRust, RedbCodec, RustToC}; @@ -157,6 +157,13 @@ pub struct PackageEntry { pub arch_sub: Option, } +#[derive(Debug, Clone, CTryToRust, RustToC)] +pub struct PackageInfo { + pub name: String, + pub arch: String, + pub arch_sub: Option, +} + #[derive(Debug, Clone, CTryToRust, RustToC)] pub struct PackageDependency { pub name: String, diff --git a/lib/types/src/request.rs b/lib/types/src/request.rs new file mode 100644 index 00000000..d3729c5d --- /dev/null +++ b/lib/types/src/request.rs @@ -0,0 +1,224 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::mem::size_of; +use std::os::raw::c_void; + +use upac_abi::HookMessageFn; +use upac_abi::hook::CancelToken; +use upac_abi::package::CPackageInfo; +use upac_abi::request::{ + CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, CCommitRequest, + CDecodeRequest, CDiffConfigRequest, CDiffPackagesRequest, CDiffPrefixRequest, CDiffRequest, CFilesRequest, + CGcRequest, CInstallRequest, CListConfigRequest, CListHistoryRequest, CListPackagesRequest, CListPrefixRequest, + CMimeSyncRequest, CPinRequest, CRequestBase, CRollbackRequest, CSearchFilesRequest, CSearchInMetaRequest, + CSearchInPackageFilesRequest, CSearchMetaRequest, CUninstallRequest, CUpdateRequest, +}; +use upac_abi::types::{COwned, CSlice, CVec}; +use upac_abi::{DiffFileSource, FileDiffKind}; + +use upac_macro::RustToC; + +use super::package::PackageInfo; + +#[derive(Debug, Clone, RustToC)] +pub struct RequestBase { + pub on_hook: Option, + pub hook_ctx: *mut c_void, + pub cancel_token: *mut CancelToken, +} + +#[derive(Debug, Clone, RustToC)] +pub struct InstallRequest { + pub base: RequestBase, + pub tmp_path: String, + pub subject: String, + pub message: Option, + pub packages: Vec, + pub boot_plugin: Option, + pub allow_conflict_files: bool, +} + +#[derive(Debug, Clone, RustToC)] +pub struct UpdateRequest { + pub base: RequestBase, + pub tmp_path: String, + pub subject: String, + pub message: Option, + pub packages: Vec, + pub boot_plugin: Option, + pub allow_downgrade: bool, + pub allow_conflict_files: bool, +} + +#[derive(Debug, Clone, RustToC)] +pub struct UninstallRequest { + pub base: RequestBase, + pub tmp_path: String, + pub subject: String, + pub message: Option, + pub packages: Vec, + pub boot_plugin: Option, + pub purge: bool, +} + +#[derive(Debug, Clone, RustToC)] +pub struct RollbackRequest { + pub base: RequestBase, + pub tmp_path: String, + pub config_digest: String, + pub boot_plugin: Option, +} + +#[derive(Debug, Clone, RustToC)] +pub struct CommitRequest { + pub base: RequestBase, + pub tmp_path: String, + pub subject: String, + pub message: Option, +} + +#[derive(Debug, Clone, RustToC)] +pub struct FilesRequest { + pub base: RequestBase, + pub tmp_path: String, + pub subject: String, + pub message: Option, + pub files: Vec, + pub file_kind: FileDiffKind, + pub scope: DiffFileSource, + pub file_package: *const CPackageInfo, + pub boot_plugin: Option, +} + +#[derive(Debug, Clone, RustToC)] +pub struct GcRequest { + pub base: RequestBase, +} + +#[derive(Debug, Clone, RustToC)] +pub struct MimeSyncRequest { + pub base: RequestBase, +} + +#[derive(Debug, Clone, RustToC)] +pub struct PinRequest { + pub base: RequestBase, + pub prefix_digest: String, + pub pinned: bool, +} + +#[derive(Debug, Clone, RustToC)] +pub struct ListPackagesRequest { + pub base: RequestBase, +} + +#[derive(Debug, Clone, RustToC)] +pub struct ListConfigRequest { + pub base: RequestBase, + pub prefix_digest: Option, +} + +#[derive(Debug, Clone, RustToC)] +pub struct ListPrefixRequest { + pub base: RequestBase, +} + +#[derive(Debug, Clone, RustToC)] +pub struct ListHistoryRequest { + pub base: RequestBase, +} + +#[derive(Debug, Clone, RustToC)] +pub struct DiffPrefixRequest { + pub base: RequestBase, + pub from_prefix_digest: Option, + pub to_prefix_digest: Option, +} + +#[derive(Debug, Clone, RustToC)] +pub struct DiffConfigRequest { + pub base: RequestBase, + pub from_config_digest: Option, + pub to_config_digest: Option, +} + +#[derive(Debug, Clone, RustToC)] +pub struct DiffPackagesRequest { + pub base: RequestBase, + pub from_prefix_digest: Option, + pub to_prefix_digest: Option, +} + +#[derive(Debug, Clone, RustToC)] +pub struct DiffRequest { + pub base: RequestBase, + pub from_prefix_digest: Option, + pub to_prefix_digest: Option, + pub from_config_digest: Option, + pub to_config_digest: Option, +} + +#[derive(Debug, Clone, RustToC)] +pub struct SearchMetaRequest { + pub base: RequestBase, + pub search: String, + pub is_regex: bool, +} + +#[derive(Debug, Clone, RustToC)] +pub struct SearchFilesRequest { + pub base: RequestBase, + pub search: String, + pub is_regex: bool, +} + +#[derive(Debug, Clone, RustToC)] +pub struct SearchInMetaRequest { + pub base: RequestBase, + pub package: PackageInfo, + pub search: String, + pub is_regex: bool, +} + +#[derive(Debug, Clone, RustToC)] +pub struct SearchInPackageFilesRequest { + pub base: RequestBase, + pub package: PackageInfo, + pub search: String, + pub is_regex: bool, +} + +#[derive(Debug, Clone, RustToC)] +pub struct DecodeRequest { + pub package_path: String, + pub output_dir: String, + pub checksum: [u8; 32], + pub cancel_token: *mut CancelToken, +} + +#[derive(Debug, Clone, RustToC)] +pub struct BootPluginSetOneShotRequest { + pub entry_name: String, +} + +#[derive(Debug, Clone, RustToC)] +pub struct BootPluginConfirmSuccsesBootRequest { + pub entry_name: String, + + pub esp_mount_point: String, +} + +#[derive(Debug, Clone, RustToC)] +pub struct BootPluginInstallRequest { + pub esp_mount_point: String, + pub esp_partition_number: u32, + pub esp_starting_lba: u64, + pub esp_ending_lba: u64, + pub esp_unique_partition_guid: [u8; 16], + + pub to_slot: String, + pub from_slot: String, +} diff --git a/lib/types/src/response.rs b/lib/types/src/response.rs new file mode 100644 index 00000000..cf0cc927 --- /dev/null +++ b/lib/types/src/response.rs @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::mem::size_of; + +use upac_abi::error::ErrorKind; +use upac_abi::package::CPackageMeta; +use upac_abi::response::{ + CConfigCommitEntry, CDecodeResponse, CDiffConfigFileEntry, CDiffConfigResponse, CDiffPackageEntry, + CDiffPackagesResponse, CDiffPrefixFileEntry, CDiffPrefixResponse, CDiffResponse, CDiffUntrackedFileEntry, + CHistoryEntry, CListConfigResponse, CListHistoryResponse, CListPackagesResponse, CListPrefixResponse, CPrefixEntry, + CSearchFileEntry, CSearchFilesResponse, CSearchInMetaResponse, CSearchInPackageFilesResponse, CSearchMetaResponse, +}; +use upac_abi::types::{COwned, CVec}; + +use upac_macro::{CTryToRust, RustToC}; + +use super::entry::{ + ConfigCommitEntry, DiffConfigFileEntry, DiffPackageEntry, DiffPrefixFileEntry, DiffUntrackedFileEntry, + HistoryEntry, PrefixEntry, SearchFileEntry, +}; +use super::package::{PackageDependency, PackageMeta}; + +#[derive(Debug, Clone, RustToC)] +pub struct ListConfigResponse { + pub commits: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct ListPackagesResponse { + pub metas: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct SearchMetaResponse { + pub metas: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct SearchFilesResponse { + pub files: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct SearchInMetaResponse { + pub metas: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct SearchInPackageFilesResponse { + pub files: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct ListPrefixResponse { + pub prefixes: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct ListHistoryResponse { + pub history: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct DiffPrefixResponse { + pub files: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct DiffConfigResponse { + pub files: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct DiffPackagesResponse { + pub diff_packages: Vec, +} + +#[derive(Debug, Clone, RustToC)] +pub struct DiffResponse { + pub diff_packages: Vec, + pub unattached_files: Vec, +} + +#[derive(Debug, Clone, CTryToRust)] +pub struct DecodeResponse { + pub meta: PackageMeta, + pub dependencies: Vec, + pub declarative_triggers: Vec, +} From 779f160f6c7822f8d89555f39809e19a8fae4135 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 14:05:23 +0400 Subject: [PATCH 054/148] fix: return Response structs from diff/list/search unmutated commands Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/export/mutated/commit.rs | 6 +++-- lib/lib/src/export/mutated/files.rs | 4 ++-- lib/lib/src/export/mutated/gc.rs | 6 +++-- lib/lib/src/export/mutated/installer.rs | 6 +++-- lib/lib/src/export/mutated/mime.rs | 6 +++-- lib/lib/src/export/mutated/pin.rs | 6 +++-- lib/lib/src/export/mutated/rollback.rs | 6 +++-- lib/lib/src/export/mutated/uninstaller.rs | 6 +++-- lib/lib/src/export/mutated/update.rs | 6 +++-- lib/lib/src/export/unmutated/diff.rs | 23 ++++++------------- lib/lib/src/export/unmutated/diff_config.rs | 19 ++++++--------- lib/lib/src/export/unmutated/diff_packages.rs | 19 ++++++--------- lib/lib/src/export/unmutated/diff_prefix.rs | 15 +++++------- lib/lib/src/export/unmutated/list_config.rs | 19 ++++++--------- lib/lib/src/export/unmutated/list_history.rs | 19 ++++++--------- lib/lib/src/export/unmutated/list_packages.rs | 18 +++++---------- lib/lib/src/export/unmutated/list_prefix.rs | 19 ++++++--------- lib/lib/src/export/unmutated/search_files.rs | 19 ++++++--------- .../src/export/unmutated/search_in_meta.rs | 18 +++++---------- .../unmutated/search_in_package_files.rs | 19 ++++++--------- lib/lib/src/export/unmutated/search_meta.rs | 17 +++++--------- lib/lib/src/unmutated/diff/mod.rs | 14 ++++++++--- lib/lib/src/unmutated/diff_config/mod.rs | 9 +++++--- lib/lib/src/unmutated/diff_packages/mod.rs | 9 +++++--- lib/lib/src/unmutated/diff_prefix/mod.rs | 9 +++++--- lib/lib/src/unmutated/list_config/mod.rs | 9 +++++--- lib/lib/src/unmutated/list_history/mod.rs | 9 +++++--- lib/lib/src/unmutated/list_packages/mod.rs | 9 +++++--- lib/lib/src/unmutated/list_prefix/mod.rs | 9 +++++--- lib/lib/src/unmutated/search_files/mod.rs | 9 +++++--- lib/lib/src/unmutated/search_in_meta/mod.rs | 9 +++++--- .../unmutated/search_in_package_files/mod.rs | 9 +++++--- lib/lib/src/unmutated/search_meta/mod.rs | 9 +++++--- 33 files changed, 191 insertions(+), 198 deletions(-) diff --git a/lib/lib/src/export/mutated/commit.rs b/lib/lib/src/export/mutated/commit.rs index cd590eae..e49fe1ca 100644 --- a/lib/lib/src/export/mutated/commit.rs +++ b/lib/lib/src/export/mutated/commit.rs @@ -11,7 +11,7 @@ use upac_abi::request::CCommitRequest; use upac_types::states::CommitStateId; use crate::export::{try_convert_abi, write_error}; -use crate::mutated::commit::CommitData; +use crate::mutated::commit::{CommitData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -20,14 +20,16 @@ use crate::mutated::commit::CommitData; pub unsafe extern "C" fn commit(request_c: CCommitRequest, err_out: *mut CError) -> i32 { let commit_data = try_convert_abi!(CommitData::try_from(&request_c), err_out, CommitStateId); - let result = catch_unwind(AssertUnwindSafe(|| crate::mutated::commit::run(commit_data))); + let result = catch_unwind(AssertUnwindSafe(|| run(commit_data))); match result { Ok(Ok(())) => 0, + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, CommitStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/mutated/files.rs b/lib/lib/src/export/mutated/files.rs index 9593e1ea..0c076217 100644 --- a/lib/lib/src/export/mutated/files.rs +++ b/lib/lib/src/export/mutated/files.rs @@ -11,7 +11,7 @@ use upac_abi::request::CFilesRequest; use upac_types::states::FilesStateId; use crate::export::{try_convert_abi, write_error}; -use crate::mutated::files::FilesData; +use crate::mutated::files::{FilesData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -20,7 +20,7 @@ use crate::mutated::files::FilesData; pub unsafe extern "C" fn files(request_c: CFilesRequest, err_out: *mut CError) -> i32 { let files_data = try_convert_abi!(FilesData::try_from(&request_c), err_out, FilesStateId); - let result = catch_unwind(AssertUnwindSafe(|| crate::mutated::files::run(files_data))); + let result = catch_unwind(AssertUnwindSafe(|| run(files_data))); match result { Ok(Ok(())) => 0, diff --git a/lib/lib/src/export/mutated/gc.rs b/lib/lib/src/export/mutated/gc.rs index 25713a94..5843e4c6 100644 --- a/lib/lib/src/export/mutated/gc.rs +++ b/lib/lib/src/export/mutated/gc.rs @@ -11,7 +11,7 @@ use upac_abi::request::CGcRequest; use upac_types::states::GcStateId; use crate::export::{try_convert_abi, write_error}; -use crate::mutated::gc::GcData; +use crate::mutated::gc::{GcData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -20,14 +20,16 @@ use crate::mutated::gc::GcData; pub unsafe extern "C" fn gc(request_c: CGcRequest, err_out: *mut CError) -> i32 { let gc_data = try_convert_abi!(GcData::try_from(&request_c), err_out, GcStateId); - let result = catch_unwind(AssertUnwindSafe(|| crate::mutated::gc::run(gc_data))); + let result = catch_unwind(AssertUnwindSafe(|| run(gc_data))); match result { Ok(Ok(())) => 0, + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, GcStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/mutated/installer.rs b/lib/lib/src/export/mutated/installer.rs index 6844f40b..25fc7824 100644 --- a/lib/lib/src/export/mutated/installer.rs +++ b/lib/lib/src/export/mutated/installer.rs @@ -11,7 +11,7 @@ use upac_abi::request::CInstallRequest; use upac_types::states::InstallStateId; use crate::export::{try_convert_abi, write_error}; -use crate::mutated::installer::InstallData; +use crate::mutated::installer::{InstallData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -20,14 +20,16 @@ use crate::mutated::installer::InstallData; pub unsafe extern "C" fn install(request_c: CInstallRequest, err_out: *mut CError) -> i32 { let install_data = try_convert_abi!(InstallData::try_from(&request_c), err_out, InstallStateId); - let result = catch_unwind(AssertUnwindSafe(|| crate::mutated::installer::run(install_data))); + let result = catch_unwind(AssertUnwindSafe(|| run(install_data))); match result { Ok(Ok(())) => 0, + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, InstallStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/mutated/mime.rs b/lib/lib/src/export/mutated/mime.rs index 9a26bcc6..5db3fa6c 100644 --- a/lib/lib/src/export/mutated/mime.rs +++ b/lib/lib/src/export/mutated/mime.rs @@ -11,7 +11,7 @@ use upac_abi::request::CMimeSyncRequest; use upac_types::states::MimeStateId; use crate::export::{try_convert_abi, write_error}; -use crate::mutated::mime::MimeData; +use crate::mutated::mime::{MimeData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -20,14 +20,16 @@ use crate::mutated::mime::MimeData; pub unsafe extern "C" fn mime(request_c: CMimeSyncRequest, err_out: *mut CError) -> i32 { let mime_data = try_convert_abi!(MimeData::try_from(&request_c), err_out, MimeStateId); - let result = catch_unwind(AssertUnwindSafe(|| crate::mutated::mime::run(mime_data))); + let result = catch_unwind(AssertUnwindSafe(|| run(mime_data))); match result { Ok(Ok(())) => 0, + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, MimeStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/mutated/pin.rs b/lib/lib/src/export/mutated/pin.rs index 9e07965e..dd96ed26 100644 --- a/lib/lib/src/export/mutated/pin.rs +++ b/lib/lib/src/export/mutated/pin.rs @@ -11,7 +11,7 @@ use upac_abi::request::CPinRequest; use upac_types::states::PinStateId; use crate::export::{try_convert_abi, write_error}; -use crate::mutated::pin::PinData; +use crate::mutated::pin::{PinData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -20,14 +20,16 @@ use crate::mutated::pin::PinData; pub unsafe extern "C" fn pin_deploy(request_c: CPinRequest, err_out: *mut CError) -> i32 { let pin_data = try_convert_abi!(PinData::try_from(&request_c), err_out, PinStateId); - let result = catch_unwind(AssertUnwindSafe(|| crate::mutated::pin::run(pin_data))); + let result = catch_unwind(AssertUnwindSafe(|| run(pin_data))); match result { Ok(Ok(())) => 0, + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, PinStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/mutated/rollback.rs b/lib/lib/src/export/mutated/rollback.rs index d5570724..614c43ac 100644 --- a/lib/lib/src/export/mutated/rollback.rs +++ b/lib/lib/src/export/mutated/rollback.rs @@ -11,7 +11,7 @@ use upac_abi::request::CRollbackRequest; use upac_types::states::RollbackStateId; use crate::export::{try_convert_abi, write_error}; -use crate::mutated::rollback::RollbackData; +use crate::mutated::rollback::{RollbackData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -20,14 +20,16 @@ use crate::mutated::rollback::RollbackData; pub unsafe extern "C" fn rollback(request_c: CRollbackRequest, err_out: *mut CError) -> i32 { let rollback_data = try_convert_abi!(RollbackData::try_from(&request_c), err_out, RollbackStateId); - let result = catch_unwind(AssertUnwindSafe(|| crate::mutated::rollback::run(rollback_data))); + let result = catch_unwind(AssertUnwindSafe(|| run(rollback_data))); match result { Ok(Ok(())) => 0, + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, RollbackStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/mutated/uninstaller.rs b/lib/lib/src/export/mutated/uninstaller.rs index ce2b5a14..f7f6627c 100644 --- a/lib/lib/src/export/mutated/uninstaller.rs +++ b/lib/lib/src/export/mutated/uninstaller.rs @@ -11,7 +11,7 @@ use upac_abi::request::CUninstallRequest; use upac_types::states::UninstallStateId; use crate::export::{try_convert_abi, write_error}; -use crate::mutated::uninstaller::UninstallData; +use crate::mutated::uninstaller::{UninstallData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -20,14 +20,16 @@ use crate::mutated::uninstaller::UninstallData; pub unsafe extern "C" fn uninstall(request_c: CUninstallRequest, err_out: *mut CError) -> i32 { let uninstall_data = try_convert_abi!(UninstallData::try_from(&request_c), err_out, UninstallStateId); - let result = catch_unwind(AssertUnwindSafe(|| crate::mutated::uninstaller::run(uninstall_data))); + let result = catch_unwind(AssertUnwindSafe(|| run(uninstall_data))); match result { Ok(Ok(())) => 0, + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, UninstallStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/mutated/update.rs b/lib/lib/src/export/mutated/update.rs index 3a767191..23d90aae 100644 --- a/lib/lib/src/export/mutated/update.rs +++ b/lib/lib/src/export/mutated/update.rs @@ -11,7 +11,7 @@ use upac_abi::request::CUpdateRequest; use upac_types::states::UpdateStateId; use crate::export::{try_convert_abi, write_error}; -use crate::mutated::update::UpdateData; +use crate::mutated::update::{UpdateData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -20,14 +20,16 @@ use crate::mutated::update::UpdateData; pub unsafe extern "C" fn update(request_c: CUpdateRequest, err_out: *mut CError) -> i32 { let update_data = try_convert_abi!(UpdateData::try_from(&request_c), err_out, UpdateStateId); - let result = catch_unwind(AssertUnwindSafe(|| crate::mutated::update::run(update_data))); + let result = catch_unwind(AssertUnwindSafe(|| run(update_data))); match result { Ok(Ok(())) => 0, + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, UpdateStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/diff.rs b/lib/lib/src/export/unmutated/diff.rs index e51f0b34..db6554e0 100644 --- a/lib/lib/src/export/unmutated/diff.rs +++ b/lib/lib/src/export/unmutated/diff.rs @@ -7,13 +7,12 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffRequest; -use upac_abi::response::{CDiffPackageEntry, CDiffResponse, CDiffUntrackedFileEntry}; -use upac_abi::types::{COwned, CVec}; +use upac_abi::response::CDiffResponse; use upac_types::states::DiffStateId; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::diff::DiffData; +use crate::unmutated::diff::{DiffData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -23,29 +22,21 @@ use crate::unmutated::diff::DiffData; pub unsafe extern "C" fn diff(request_c: CDiffRequest, response_out: *mut CDiffResponse, err_out: *mut CError) -> i32 { let diff_data = try_convert_abi!(DiffData::try_from(&request_c), err_out, DiffStateId); - let result = catch_unwind(AssertUnwindSafe(|| crate::unmutated::diff::run(diff_data))); + let result = catch_unwind(AssertUnwindSafe(|| run(diff_data))); match result { - Ok(Ok((diff_packages, unattached_files))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CDiffResponse::new( - CVec::from_owned(diff_packages.into_iter().map(CDiffPackageEntry::from).collect()), - CVec::from_owned( - unattached_files - .into_iter() - .map(CDiffUntrackedFileEntry::from) - .collect(), - ), - ); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, DiffStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/diff_config.rs b/lib/lib/src/export/unmutated/diff_config.rs index 5d33e01d..f6cdaaf8 100644 --- a/lib/lib/src/export/unmutated/diff_config.rs +++ b/lib/lib/src/export/unmutated/diff_config.rs @@ -7,13 +7,12 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffConfigRequest; -use upac_abi::response::{CDiffConfigFileEntry, CDiffConfigResponse}; -use upac_abi::types::{COwned, CVec}; +use upac_abi::response::CDiffConfigResponse; use upac_types::states::DiffConfigStateId; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::diff_config::DiffConfigData; +use crate::unmutated::diff_config::{DiffConfigData, run}; /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the @@ -25,25 +24,21 @@ pub unsafe extern "C" fn diff_config( ) -> i32 { let diff_config_data = try_convert_abi!(DiffConfigData::try_from(&request_c), err_out, DiffConfigStateId); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::diff_config::run(diff_config_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(diff_config_data))); match result { - Ok(Ok((files,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CDiffConfigResponse::new(CVec::from_owned( - files.into_iter().map(CDiffConfigFileEntry::from).collect(), - )); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, DiffConfigStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/diff_packages.rs b/lib/lib/src/export/unmutated/diff_packages.rs index 3544a928..f9a8c920 100644 --- a/lib/lib/src/export/unmutated/diff_packages.rs +++ b/lib/lib/src/export/unmutated/diff_packages.rs @@ -7,11 +7,10 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffPackagesRequest; -use upac_abi::response::{CDiffPackageEntry, CDiffPackagesResponse}; -use upac_abi::types::{COwned, CVec}; +use upac_abi::response::CDiffPackagesResponse; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::diff_packages::DiffPackagesData; +use crate::unmutated::diff_packages::{DiffPackagesData, run}; use upac_types::states::DiffPackagesStateId; @@ -25,25 +24,21 @@ pub unsafe extern "C" fn diff_packages( ) -> i32 { let diff_packages_data = try_convert_abi!(DiffPackagesData::try_from(&request_c), err_out, DiffPackagesStateId); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::diff_packages::run(diff_packages_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(diff_packages_data))); match result { - Ok(Ok((diff_packages,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CDiffPackagesResponse::new(CVec::from_owned( - diff_packages.into_iter().map(CDiffPackageEntry::from).collect(), - )); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, DiffPackagesStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/diff_prefix.rs b/lib/lib/src/export/unmutated/diff_prefix.rs index 904d7863..ca4003db 100644 --- a/lib/lib/src/export/unmutated/diff_prefix.rs +++ b/lib/lib/src/export/unmutated/diff_prefix.rs @@ -7,11 +7,10 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffPrefixRequest; -use upac_abi::response::{CDiffPrefixFileEntry, CDiffPrefixResponse}; -use upac_abi::types::{COwned, CVec}; +use upac_abi::response::CDiffPrefixResponse; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::diff_prefix::DiffPrefixData; +use crate::unmutated::diff_prefix::{DiffPrefixData, run}; use upac_types::states::DiffPrefixStateId; @@ -30,20 +29,18 @@ pub unsafe extern "C" fn diff_prefix( })); match result { - Ok(Ok((files,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CDiffPrefixResponse::new(CVec::from_owned( - files.into_iter().map(CDiffPrefixFileEntry::from).collect(), - )); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, DiffPrefixStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/list_config.rs b/lib/lib/src/export/unmutated/list_config.rs index c0cd50f9..056c2f9a 100644 --- a/lib/lib/src/export/unmutated/list_config.rs +++ b/lib/lib/src/export/unmutated/list_config.rs @@ -7,11 +7,10 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CListConfigRequest; -use upac_abi::response::{CConfigCommitEntry, CListConfigResponse}; -use upac_abi::types::{COwned, CVec}; +use upac_abi::response::CListConfigResponse; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::list_config::ListConfigData; +use crate::unmutated::list_config::{ListConfigData, run}; use upac_types::states::ListConfigStateId; @@ -25,25 +24,21 @@ pub unsafe extern "C" fn list_config( ) -> i32 { let list_config_data = try_convert_abi!(ListConfigData::try_from(&request_c), err_out, ListConfigStateId); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::list_config::run(list_config_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(list_config_data))); match result { - Ok(Ok((commits,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CListConfigResponse::new(CVec::from_owned( - commits.into_iter().map(CConfigCommitEntry::from).collect(), - )); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, ListConfigStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/list_history.rs b/lib/lib/src/export/unmutated/list_history.rs index ea8a0a63..ed3f0fdb 100644 --- a/lib/lib/src/export/unmutated/list_history.rs +++ b/lib/lib/src/export/unmutated/list_history.rs @@ -7,11 +7,10 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CListHistoryRequest; -use upac_abi::response::{CHistoryEntry, CListHistoryResponse}; -use upac_abi::types::{COwned, CVec}; +use upac_abi::response::CListHistoryResponse; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::list_history::ListHistoryData; +use crate::unmutated::list_history::{ListHistoryData, run}; use upac_types::states::ListHistoryStateId; @@ -25,25 +24,21 @@ pub unsafe extern "C" fn list_history( ) -> i32 { let list_history_data = try_convert_abi!(ListHistoryData::try_from(&request_c), err_out, ListHistoryStateId); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::list_history::run(list_history_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(list_history_data))); match result { - Ok(Ok((history,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CListHistoryResponse::new(CVec::from_owned( - history.into_iter().map(CHistoryEntry::from).collect(), - )); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, ListHistoryStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/list_packages.rs b/lib/lib/src/export/unmutated/list_packages.rs index 442aa115..dbf0dfc5 100644 --- a/lib/lib/src/export/unmutated/list_packages.rs +++ b/lib/lib/src/export/unmutated/list_packages.rs @@ -6,13 +6,11 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; -use upac_abi::package::CPackageMeta; use upac_abi::request::CListPackagesRequest; use upac_abi::response::CListPackagesResponse; -use upac_abi::types::{COwned, CVec}; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::list_packages::ListPackagesData; +use crate::unmutated::list_packages::{ListPackagesData, run}; use upac_types::states::ListPackagesStateId; @@ -26,25 +24,21 @@ pub unsafe extern "C" fn list_packages( ) -> i32 { let list_packages_data = try_convert_abi!(ListPackagesData::try_from(&request_c), err_out, ListPackagesStateId); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::list_packages::run(list_packages_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(list_packages_data))); match result { - Ok(Ok((metas,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CListPackagesResponse::new(CVec::from_owned( - metas.into_iter().map(CPackageMeta::from).collect(), - )); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, ListPackagesStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/list_prefix.rs b/lib/lib/src/export/unmutated/list_prefix.rs index 5a2adbc0..2e519f2a 100644 --- a/lib/lib/src/export/unmutated/list_prefix.rs +++ b/lib/lib/src/export/unmutated/list_prefix.rs @@ -7,11 +7,10 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CListPrefixRequest; -use upac_abi::response::{CListPrefixResponse, CPrefixEntry}; -use upac_abi::types::{COwned, CVec}; +use upac_abi::response::CListPrefixResponse; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::list_prefix::ListPrefixData; +use crate::unmutated::list_prefix::{ListPrefixData, run}; use upac_types::states::ListPrefixStateId; @@ -25,25 +24,21 @@ pub unsafe extern "C" fn list_prefix( ) -> i32 { let list_prefix_data = try_convert_abi!(ListPrefixData::try_from(&request_c), err_out, ListPrefixStateId); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::list_prefix::run(list_prefix_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(list_prefix_data))); match result { - Ok(Ok((prefixes,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CListPrefixResponse::new(CVec::from_owned( - prefixes.into_iter().map(CPrefixEntry::from).collect(), - )); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, ListPrefixStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/search_files.rs b/lib/lib/src/export/unmutated/search_files.rs index 6e4ff0e6..7c29dd83 100644 --- a/lib/lib/src/export/unmutated/search_files.rs +++ b/lib/lib/src/export/unmutated/search_files.rs @@ -7,11 +7,10 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CSearchFilesRequest; -use upac_abi::response::{CSearchFileEntry, CSearchFilesResponse}; -use upac_abi::types::{COwned, CVec}; +use upac_abi::response::CSearchFilesResponse; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::search_files::SearchFilesData; +use crate::unmutated::search_files::{SearchFilesData, run}; use upac_types::states::SearchFilesStateId; @@ -25,25 +24,21 @@ pub unsafe extern "C" fn search_files( ) -> i32 { let search_files_data = try_convert_abi!(SearchFilesData::try_from(&request_c), err_out, SearchFilesStateId); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::search_files::run(search_files_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(search_files_data))); match result { - Ok(Ok((files,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CSearchFilesResponse::new(CVec::from_owned( - files.into_iter().map(CSearchFileEntry::from).collect(), - )); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, SearchFilesStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/search_in_meta.rs b/lib/lib/src/export/unmutated/search_in_meta.rs index 20ee402a..a0396b77 100644 --- a/lib/lib/src/export/unmutated/search_in_meta.rs +++ b/lib/lib/src/export/unmutated/search_in_meta.rs @@ -6,13 +6,11 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; -use upac_abi::package::CPackageMeta; use upac_abi::request::CSearchInMetaRequest; use upac_abi::response::CSearchInMetaResponse; -use upac_abi::types::{COwned, CVec}; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::search_in_meta::SearchInMetaData; +use crate::unmutated::search_in_meta::{SearchInMetaData, run}; use upac_types::states::SearchInMetaStateId; @@ -26,25 +24,21 @@ pub unsafe extern "C" fn search_in_meta( ) -> i32 { let search_in_meta_data = try_convert_abi!(SearchInMetaData::try_from(&request_c), err_out, SearchInMetaStateId); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::search_in_meta::run(search_in_meta_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(search_in_meta_data))); match result { - Ok(Ok((metas,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CSearchInMetaResponse::new(CVec::from_owned( - metas.into_iter().map(CPackageMeta::from).collect(), - )); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, SearchInMetaStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/search_in_package_files.rs b/lib/lib/src/export/unmutated/search_in_package_files.rs index 1c2e1aa3..0dedaa3c 100644 --- a/lib/lib/src/export/unmutated/search_in_package_files.rs +++ b/lib/lib/src/export/unmutated/search_in_package_files.rs @@ -7,11 +7,10 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CSearchInPackageFilesRequest; -use upac_abi::response::{CSearchFileEntry, CSearchInPackageFilesResponse}; -use upac_abi::types::{COwned, CVec}; +use upac_abi::response::CSearchInPackageFilesResponse; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::search_in_package_files::SearchInPackageFilesData; +use crate::unmutated::search_in_package_files::{SearchInPackageFilesData, run}; use upac_types::states::SearchInPackageFilesStateId; @@ -29,25 +28,21 @@ pub unsafe extern "C" fn search_in_package_files( SearchInPackageFilesStateId ); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::search_in_package_files::run(search_in_package_files_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(search_in_package_files_data))); match result { - Ok(Ok((files,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = CSearchInPackageFilesResponse::new(CVec::from_owned( - files.into_iter().map(CSearchFileEntry::from).collect(), - )); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, SearchInPackageFilesStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/export/unmutated/search_meta.rs b/lib/lib/src/export/unmutated/search_meta.rs index e8b211c6..8c140513 100644 --- a/lib/lib/src/export/unmutated/search_meta.rs +++ b/lib/lib/src/export/unmutated/search_meta.rs @@ -6,13 +6,11 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; -use upac_abi::package::CPackageMeta; use upac_abi::request::CSearchMetaRequest; use upac_abi::response::CSearchMetaResponse; -use upac_abi::types::{COwned, CVec}; use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::search_meta::SearchMetaData; +use crate::unmutated::search_meta::{SearchMetaData, run}; use upac_types::states::SearchMetaStateId; @@ -26,24 +24,21 @@ pub unsafe extern "C" fn search_meta( ) -> i32 { let search_meta_data = try_convert_abi!(SearchMetaData::try_from(&request_c), err_out, SearchMetaStateId); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::search_meta::run(search_meta_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(search_meta_data))); match result { - Ok(Ok((metas,))) => { + Ok(Ok(response)) => { if !response_out.is_null() { - unsafe { - *response_out = - CSearchMetaResponse::new(CVec::from_owned(metas.into_iter().map(CPackageMeta::from).collect())); - } + unsafe { *response_out = response.into() }; } 0 } + Ok(Err((state, error))) => { unsafe { write_error(err_out, state, ErrorKind::from(error)) }; -1 } + Err(_) => { unsafe { write_error(err_out, SearchMetaStateId::Setup, ErrorKind::Unexpected) }; -1 diff --git a/lib/lib/src/unmutated/diff/mod.rs b/lib/lib/src/unmutated/diff/mod.rs index b5db152b..e3d801d2 100644 --- a/lib/lib/src/unmutated/diff/mod.rs +++ b/lib/lib/src/unmutated/diff/mod.rs @@ -14,6 +14,7 @@ use upac_abi::{DiffFileSource, FileDiffKind}; use upac_types::entry::{DiffPackageEntry, DiffUntrackedFileEntry}; use upac_types::hook::Message; use upac_types::package::PackageMeta; +use upac_types::response::DiffResponse; use upac_types::states::DiffStateId; use upac_types::traits::MessageHook; use upac_types::{RequestedConfigDigestRange, RequestedPrefixDigestRange}; @@ -73,21 +74,23 @@ impl<'a> TryFrom<&'a CDiffRequest> for DiffData<'a> { } } -pub fn run(data: DiffData) -> Result<(Vec, Vec), (DiffStateId, DiffError)> { +pub fn run(data: DiffData) -> Result { let mut context = Context::new(); context.put(RequestedPrefixDigestRange { from: data.from_prefix_digest.map(str::to_owned), to: data.to_prefix_digest.map(str::to_owned), }); + context.put(RequestedConfigDigestRange { from: data.from_config_digest.map(str::to_owned), to: data.to_config_digest.map(str::to_owned), }); + context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); let orchestrator = SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]); - run_unmutated!( + let (diff_packages, unattached_files) = run_unmutated!( orchestrator, context, data.cancel_token, @@ -95,5 +98,10 @@ pub fn run(data: DiffData) -> Result<(Vec, Vec, Vec - ) + )?; + + Ok(DiffResponse { + diff_packages, + unattached_files, + }) } diff --git a/lib/lib/src/unmutated/diff_config/mod.rs b/lib/lib/src/unmutated/diff_config/mod.rs index 0d0abd71..89f68af6 100644 --- a/lib/lib/src/unmutated/diff_config/mod.rs +++ b/lib/lib/src/unmutated/diff_config/mod.rs @@ -14,6 +14,7 @@ use upac_abi::request::CDiffConfigRequest; use upac_types::RequestedConfigDigestRange; use upac_types::entry::DiffConfigFileEntry; use upac_types::hook::Message; +use upac_types::response::DiffConfigResponse; use upac_types::states::DiffConfigStateId; use upac_types::traits::MessageHook; @@ -66,7 +67,7 @@ impl<'a> TryFrom<&'a CDiffConfigRequest> for DiffConfigData<'a> { } } -pub fn run(data: DiffConfigData) -> Result<(Vec,), (DiffConfigStateId, DiffConfigError)> { +pub fn run(data: DiffConfigData) -> Result { let mut context = Context::new(); context.put(RequestedConfigDigestRange { from: data.from_config_digest.map(str::to_owned), @@ -76,12 +77,14 @@ pub fn run(data: DiffConfigData) -> Result<(Vec,), (DiffCon let orchestrator = SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]); - run_unmutated!( + let (files,) = run_unmutated!( orchestrator, context, data.cancel_token, DiffConfigStateId, DiffConfigError, Vec - ) + )?; + + Ok(DiffConfigResponse { files }) } diff --git a/lib/lib/src/unmutated/diff_packages/mod.rs b/lib/lib/src/unmutated/diff_packages/mod.rs index 83693a06..e302bec9 100644 --- a/lib/lib/src/unmutated/diff_packages/mod.rs +++ b/lib/lib/src/unmutated/diff_packages/mod.rs @@ -13,6 +13,7 @@ use upac_abi::request::CDiffPackagesRequest; use upac_types::RequestedPrefixDigestRange; use upac_types::entry::DiffPackageEntry; use upac_types::hook::Message; +use upac_types::response::DiffPackagesResponse; use upac_types::states::DiffPackagesStateId; use upac_types::traits::MessageHook; @@ -58,7 +59,7 @@ impl<'a> TryFrom<&'a CDiffPackagesRequest> for DiffPackagesData<'a> { } } -pub fn run(data: DiffPackagesData) -> Result<(Vec,), (DiffPackagesStateId, DiffPackagesError)> { +pub fn run(data: DiffPackagesData) -> Result { let mut context = Context::new(); context.put(RequestedPrefixDigestRange { from: data.from_prefix_digest.map(str::to_owned), @@ -68,12 +69,14 @@ pub fn run(data: DiffPackagesData) -> Result<(Vec,), (DiffPack let orchestrator = SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]); - run_unmutated!( + let (diff_packages,) = run_unmutated!( orchestrator, context, data.cancel_token, DiffPackagesStateId, DiffPackagesError, Vec - ) + )?; + + Ok(DiffPackagesResponse { diff_packages }) } diff --git a/lib/lib/src/unmutated/diff_prefix/mod.rs b/lib/lib/src/unmutated/diff_prefix/mod.rs index 64d94b12..8dd5a44c 100644 --- a/lib/lib/src/unmutated/diff_prefix/mod.rs +++ b/lib/lib/src/unmutated/diff_prefix/mod.rs @@ -14,6 +14,7 @@ use upac_abi::request::CDiffPrefixRequest; use upac_types::RequestedPrefixDigestRange; use upac_types::entry::DiffPrefixFileEntry; use upac_types::hook::Message; +use upac_types::response::DiffPrefixResponse; use upac_types::states::DiffPrefixStateId; use upac_types::traits::MessageHook; @@ -66,7 +67,7 @@ impl<'a> TryFrom<&'a CDiffPrefixRequest> for DiffPrefixData<'a> { } } -pub fn run(data: DiffPrefixData) -> Result<(Vec,), (DiffPrefixStateId, DiffPrefixError)> { +pub fn run(data: DiffPrefixData) -> Result { let mut context = Context::new(); context.put(RequestedPrefixDigestRange { from: data.from_prefix_digest.map(str::to_owned), @@ -76,12 +77,14 @@ pub fn run(data: DiffPrefixData) -> Result<(Vec,), (DiffPre let orchestrator = SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]); - run_unmutated!( + let (files,) = run_unmutated!( orchestrator, context, data.cancel_token, DiffPrefixStateId, DiffPrefixError, Vec - ) + )?; + + Ok(DiffPrefixResponse { files }) } diff --git a/lib/lib/src/unmutated/list_config/mod.rs b/lib/lib/src/unmutated/list_config/mod.rs index 7a2b536b..754736ab 100644 --- a/lib/lib/src/unmutated/list_config/mod.rs +++ b/lib/lib/src/unmutated/list_config/mod.rs @@ -13,6 +13,7 @@ use upac_abi::request::CListConfigRequest; use upac_types::RequestedPrefixDigest; use upac_types::entry::ConfigCommitEntry; use upac_types::hook::Message; +use upac_types::response::ListConfigResponse; use upac_types::states::ListConfigStateId; use upac_types::traits::MessageHook; @@ -54,19 +55,21 @@ impl<'a> TryFrom<&'a CListConfigRequest> for ListConfigData<'a> { } } -pub fn run(data: ListConfigData) -> Result<(Vec,), (ListConfigStateId, ListConfigError)> { +pub fn run(data: ListConfigData) -> Result { let mut context = Context::new(); context.put(RequestedPrefixDigest(data.prefix_digest.map(str::to_owned))); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); let orchestrator = SequentialOrchestrator::new(vec![Box::new(FetchingStage)]); - run_unmutated!( + let (commits,) = run_unmutated!( orchestrator, context, data.cancel_token, ListConfigStateId, ListConfigError, Vec - ) + )?; + + Ok(ListConfigResponse { commits }) } diff --git a/lib/lib/src/unmutated/list_history/mod.rs b/lib/lib/src/unmutated/list_history/mod.rs index d9b2b0ac..264f320f 100644 --- a/lib/lib/src/unmutated/list_history/mod.rs +++ b/lib/lib/src/unmutated/list_history/mod.rs @@ -12,6 +12,7 @@ use upac_abi::request::CListHistoryRequest; use upac_types::entry::HistoryEntry; use upac_types::hook::Message; +use upac_types::response::ListHistoryResponse; use upac_types::states::ListHistoryStateId; use upac_types::traits::MessageHook; @@ -49,18 +50,20 @@ impl<'a> TryFrom<&'a CListHistoryRequest> for ListHistoryData<'a> { } } -pub fn run(data: ListHistoryData) -> Result<(Vec,), (ListHistoryStateId, ListHistoryError)> { +pub fn run(data: ListHistoryData) -> Result { let mut context = Context::new(); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); let orchestrator = SequentialOrchestrator::new(vec![Box::new(FetchingStage)]); - run_unmutated!( + let (history,) = run_unmutated!( orchestrator, context, data.cancel_token, ListHistoryStateId, ListHistoryError, Vec - ) + )?; + + Ok(ListHistoryResponse { history }) } diff --git a/lib/lib/src/unmutated/list_packages/mod.rs b/lib/lib/src/unmutated/list_packages/mod.rs index 63408660..2201ed22 100644 --- a/lib/lib/src/unmutated/list_packages/mod.rs +++ b/lib/lib/src/unmutated/list_packages/mod.rs @@ -12,6 +12,7 @@ use upac_abi::request::CListPackagesRequest; use upac_types::hook::Message; use upac_types::package::PackageMeta; +use upac_types::response::ListPackagesResponse; use upac_types::states::ListPackagesStateId; use upac_types::traits::MessageHook; @@ -49,18 +50,20 @@ impl<'a> TryFrom<&'a CListPackagesRequest> for ListPackagesData<'a> { } } -pub fn run(data: ListPackagesData) -> Result<(Vec,), (ListPackagesStateId, ListPackagesError)> { +pub fn run(data: ListPackagesData) -> Result { let mut context = Context::new(); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); let orchestrator = SequentialOrchestrator::new(vec![Box::new(FetchingStage)]); - run_unmutated!( + let (metas,) = run_unmutated!( orchestrator, context, data.cancel_token, ListPackagesStateId, ListPackagesError, Vec - ) + )?; + + Ok(ListPackagesResponse { metas }) } diff --git a/lib/lib/src/unmutated/list_prefix/mod.rs b/lib/lib/src/unmutated/list_prefix/mod.rs index 07f41022..aae03e52 100644 --- a/lib/lib/src/unmutated/list_prefix/mod.rs +++ b/lib/lib/src/unmutated/list_prefix/mod.rs @@ -12,6 +12,7 @@ use upac_abi::request::CListPrefixRequest; use upac_types::entry::PrefixEntry; use upac_types::hook::Message; +use upac_types::response::ListPrefixResponse; use upac_types::states::ListPrefixStateId; use upac_types::traits::MessageHook; @@ -49,18 +50,20 @@ impl<'a> TryFrom<&'a CListPrefixRequest> for ListPrefixData<'a> { } } -pub fn run(data: ListPrefixData) -> Result<(Vec,), (ListPrefixStateId, ListPrefixError)> { +pub fn run(data: ListPrefixData) -> Result { let mut context = Context::new(); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); let orchestrator = SequentialOrchestrator::new(vec![Box::new(FetchingStage)]); - run_unmutated!( + let (prefixes,) = run_unmutated!( orchestrator, context, data.cancel_token, ListPrefixStateId, ListPrefixError, Vec - ) + )?; + + Ok(ListPrefixResponse { prefixes }) } diff --git a/lib/lib/src/unmutated/search_files/mod.rs b/lib/lib/src/unmutated/search_files/mod.rs index bb6e3015..2791043d 100644 --- a/lib/lib/src/unmutated/search_files/mod.rs +++ b/lib/lib/src/unmutated/search_files/mod.rs @@ -12,6 +12,7 @@ use upac_abi::request::CSearchFilesRequest; use upac_types::entry::SearchFileEntry; use upac_types::hook::Message; +use upac_types::response::SearchFilesResponse; use upac_types::states::SearchFilesStateId; use upac_types::traits::MessageHook; @@ -56,7 +57,7 @@ impl<'a> TryFrom<&'a CSearchFilesRequest> for SearchFilesData<'a> { } } -pub fn run(data: SearchFilesData) -> Result<(Vec,), (SearchFilesStateId, SearchFilesError)> { +pub fn run(data: SearchFilesData) -> Result { let search = Search::new(data.search, data.is_regex) .map_err(|error| (SearchFilesStateId::Setup, SearchFilesError::from(error)))?; @@ -66,12 +67,14 @@ pub fn run(data: SearchFilesData) -> Result<(Vec,), (SearchFile let orchestrator = SequentialOrchestrator::new(vec![Box::new(SearchingStage)]); - run_unmutated!( + let (files,) = run_unmutated!( orchestrator, context, data.cancel_token, SearchFilesStateId, SearchFilesError, Vec - ) + )?; + + Ok(SearchFilesResponse { files }) } diff --git a/lib/lib/src/unmutated/search_in_meta/mod.rs b/lib/lib/src/unmutated/search_in_meta/mod.rs index 85685e4b..cb622dfc 100644 --- a/lib/lib/src/unmutated/search_in_meta/mod.rs +++ b/lib/lib/src/unmutated/search_in_meta/mod.rs @@ -12,6 +12,7 @@ use upac_abi::request::CSearchInMetaRequest; use upac_types::hook::Message; use upac_types::package::{PackageEntry, PackageMeta}; +use upac_types::response::SearchInMetaResponse; use upac_types::states::SearchInMetaStateId; use upac_types::traits::MessageHook; @@ -62,7 +63,7 @@ impl<'a> TryFrom<&'a CSearchInMetaRequest> for SearchInMetaData<'a> { } } -pub fn run(data: SearchInMetaData) -> Result<(Vec,), (SearchInMetaStateId, SearchInMetaError)> { +pub fn run(data: SearchInMetaData) -> Result { let search = Search::new(data.search, data.is_regex) .map_err(|error| (SearchInMetaStateId::Setup, SearchInMetaError::from(error)))?; @@ -77,12 +78,14 @@ pub fn run(data: SearchInMetaData) -> Result<(Vec,), (SearchInMetaS let orchestrator = SequentialOrchestrator::new(vec![Box::new(SearchingStage)]); - run_unmutated!( + let (metas,) = run_unmutated!( orchestrator, context, data.cancel_token, SearchInMetaStateId, SearchInMetaError, Vec - ) + )?; + + Ok(SearchInMetaResponse { metas }) } diff --git a/lib/lib/src/unmutated/search_in_package_files/mod.rs b/lib/lib/src/unmutated/search_in_package_files/mod.rs index a7d816c4..aedecf1c 100644 --- a/lib/lib/src/unmutated/search_in_package_files/mod.rs +++ b/lib/lib/src/unmutated/search_in_package_files/mod.rs @@ -13,6 +13,7 @@ use upac_abi::request::CSearchInPackageFilesRequest; use upac_types::entry::SearchFileEntry; use upac_types::hook::Message; use upac_types::package::PackageEntry; +use upac_types::response::SearchInPackageFilesResponse; use upac_types::states::SearchInPackageFilesStateId; use upac_types::traits::MessageHook; @@ -65,7 +66,7 @@ impl<'a> TryFrom<&'a CSearchInPackageFilesRequest> for SearchInPackageFilesData< pub fn run( data: SearchInPackageFilesData, -) -> Result<(Vec,), (SearchInPackageFilesStateId, SearchInPackageFilesError)> { +) -> Result { let search = Search::new(data.search, data.is_regex).map_err(|error| { ( SearchInPackageFilesStateId::Setup, @@ -84,12 +85,14 @@ pub fn run( let orchestrator = SequentialOrchestrator::new(vec![Box::new(SearchingStage)]); - run_unmutated!( + let (files,) = run_unmutated!( orchestrator, context, data.cancel_token, SearchInPackageFilesStateId, SearchInPackageFilesError, Vec - ) + )?; + + Ok(SearchInPackageFilesResponse { files }) } diff --git a/lib/lib/src/unmutated/search_meta/mod.rs b/lib/lib/src/unmutated/search_meta/mod.rs index 1395bbde..d32c7478 100644 --- a/lib/lib/src/unmutated/search_meta/mod.rs +++ b/lib/lib/src/unmutated/search_meta/mod.rs @@ -12,6 +12,7 @@ use upac_abi::request::CSearchMetaRequest; use upac_types::hook::Message; use upac_types::package::PackageMeta; +use upac_types::response::SearchMetaResponse; use upac_types::states::SearchMetaStateId; use upac_types::traits::MessageHook; @@ -56,7 +57,7 @@ impl<'a> TryFrom<&'a CSearchMetaRequest> for SearchMetaData<'a> { } } -pub fn run(data: SearchMetaData) -> Result<(Vec,), (SearchMetaStateId, SearchMetaError)> { +pub fn run(data: SearchMetaData) -> Result { let search = Search::new(data.search, data.is_regex) .map_err(|error| (SearchMetaStateId::Setup, SearchMetaError::from(error)))?; @@ -66,12 +67,14 @@ pub fn run(data: SearchMetaData) -> Result<(Vec,), (SearchMetaState let orchestrator = SequentialOrchestrator::new(vec![Box::new(SearchingStage)]); - run_unmutated!( + let (metas,) = run_unmutated!( orchestrator, context, data.cancel_token, SearchMetaStateId, SearchMetaError, Vec - ) + )?; + + Ok(SearchMetaResponse { metas }) } From 7ee09e39068276852d9e185ea084c09a0568fd04 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 14:05:54 +0400 Subject: [PATCH 055/148] fix: teach CTryToRust to pass through Option Co-Authored-By: Claude Sonnet 5 --- lib/macro/src/c_try_to_rust/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/macro/src/c_try_to_rust/mod.rs b/lib/macro/src/c_try_to_rust/mod.rs index 07ae3a27..155a5586 100644 --- a/lib/macro/src/c_try_to_rust/mod.rs +++ b/lib/macro/src/c_try_to_rust/mod.rs @@ -30,6 +30,8 @@ fn option_from_c(ident: &Ident, segment: &PathSegment) -> TokenStream2 { if inner_name == "String" { quote! { Option::<&str>::try_from(&value.#ident)?.map(str::to_owned) } + } else if inner_name == "HookMessageFn" { + quote! { value.#ident } } else { quote! { compile_error!("CTryToRust: unsupported Option inner type") } } From 540cc85c32eb1d52555881f87f917562ae50c1f4 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 14:19:11 +0400 Subject: [PATCH 056/148] fix: added a validation trait for type-agnostic validation Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/types.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/abi/src/types.rs b/lib/abi/src/types.rs index 124b61d5..778b2a31 100644 --- a/lib/abi/src/types.rs +++ b/lib/abi/src/types.rs @@ -40,6 +40,13 @@ pub trait COwned { unsafe fn into_owned(self) -> Self::Owned; } +pub trait CValidatable { + /// # Safety + /// Same contract as the inherent `validate()` this forwards to — the receiver must be a + /// freshly-received C-ABI struct that hasn't yet been trusted for reads. + unsafe fn validate(&self) -> Result<(), ErrorKind>; +} + #[repr(C)] #[derive(Clone, Copy)] pub struct CSlice { From 53ca2da57cd6586662eab1fde8b857b52095ffa2 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 14:19:31 +0400 Subject: [PATCH 057/148] fix: added use of validation trait Co-Authored-By: Claude Sonnet 5 --- lib/macro/src/c_validate/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/macro/src/c_validate/mod.rs b/lib/macro/src/c_validate/mod.rs index 691f9947..3bce2a22 100644 --- a/lib/macro/src/c_validate/mod.rs +++ b/lib/macro/src/c_validate/mod.rs @@ -9,7 +9,9 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; + use quote::quote; + use syn::{Data, DeriveInput, Error, Field, Fields, Ident, PathSegment, Type, TypePtr, parse_macro_input}; use crate::common::{VALIDATABLE_COMPOSITES, generic_arg, segment_name}; @@ -139,6 +141,12 @@ fn validate_impl(name: &Ident, validations: &[TokenStream2]) -> TokenStream2 { Ok(()) } } + + impl crate::types::CValidatable for #name { + unsafe fn validate(&self) -> Result<(), ErrorKind> { + unsafe { #name::validate(self) } + } + } } } From 375456054638729ed4f6c659c14adea3f64d3886 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 18:41:58 +0400 Subject: [PATCH 058/148] fix: migrate upac-cli commands onto types::request mirrors, validate responses via CValidatable Co-Authored-By: Claude Sonnet 5 --- user/upac-cli/src/commands/commit/diff.rs | 25 +++-- user/upac-cli/src/commands/commit/history.rs | 16 ++- user/upac-cli/src/commands/commit/list.rs | 17 ++- user/upac-cli/src/commands/commit/mod.rs | 4 - user/upac-cli/src/commands/commit/new.rs | 27 +++-- user/upac-cli/src/commands/commit/pin.rs | 19 +++- user/upac-cli/src/commands/commit/prefixes.rs | 16 ++- user/upac-cli/src/commands/commit/unpin.rs | 19 +++- user/upac-cli/src/commands/diff.rs | 31 ++--- user/upac-cli/src/commands/display.rs | 2 +- user/upac-cli/src/commands/file/add.rs | 60 +++++----- user/upac-cli/src/commands/file/diff.rs | 25 +++-- user/upac-cli/src/commands/file/mod.rs | 4 - user/upac-cli/src/commands/file/remove.rs | 60 +++++----- user/upac-cli/src/commands/file/search.rs | 43 +++++-- user/upac-cli/src/commands/gc.rs | 16 ++- user/upac-cli/src/commands/mime/mod.rs | 4 - user/upac-cli/src/commands/mime/sync.rs | 16 ++- user/upac-cli/src/commands/package/diff.rs | 25 +++-- user/upac-cli/src/commands/package/install.rs | 43 ++++--- user/upac-cli/src/commands/package/list.rs | 16 ++- user/upac-cli/src/commands/package/mod.rs | 4 - user/upac-cli/src/commands/package/remove.rs | 81 +++++++------ user/upac-cli/src/commands/package/search.rs | 42 +++++-- user/upac-cli/src/commands/package/update.rs | 45 ++++---- user/upac-cli/src/commands/rollback.rs | 28 +++-- user/upac-cli/src/libcore.rs | 18 +-- user/upac-cli/src/main.rs | 14 +-- user/upac-cli/src/types/abi.rs | 54 +++------ user/upac-cli/src/types/errors.rs | 48 +++++--- user/upac-cli/src/types/mod.rs | 2 +- user/upac-cli/src/types/progress.rs | 3 +- user/upac-cli/tests/inline/abi.rs | 106 +++--------------- user/upac-cli/tests/inline/progress.rs | 9 +- 34 files changed, 494 insertions(+), 448 deletions(-) diff --git a/user/upac-cli/src/commands/commit/diff.rs b/user/upac-cli/src/commands/commit/diff.rs index 2f8403d2..20998cf2 100644 --- a/user/upac-cli/src/commands/commit/diff.rs +++ b/user/upac-cli/src/commands/commit/diff.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; +use std::ptr::null_mut; use anyhow::Result; @@ -14,8 +14,11 @@ use colored::Colorize; use upac_abi::FileDiffKind; use upac_abi::request::CDiffConfigRequest; +use upac_types::request::{DiffConfigRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{invoke_with_response, optional_slice, request_base}; +use crate::types::abi::invoke_with_response; #[derive(ClapArgs)] pub struct Args { @@ -24,14 +27,16 @@ pub struct Args { } pub fn run(args: Args, ctx: CommandContext) -> Result<()> { - let from_config = args.from.as_deref().map(CString::new).transpose()?; - let to_config = args.to.as_deref().map(CString::new).transpose()?; - - let request = CDiffConfigRequest::new( - request_base(), - optional_slice(from_config.as_ref()), - optional_slice(to_config.as_ref()), - ); + let request: CDiffConfigRequest = DiffConfigRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + from_config_digest: args.from, + to_config_digest: args.to, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.diff_config)(request, out, error) })?; diff --git a/user/upac-cli/src/commands/commit/history.rs b/user/upac-cli/src/commands/commit/history.rs index fd763e72..2575e5fc 100644 --- a/user/upac-cli/src/commands/commit/history.rs +++ b/user/upac-cli/src/commands/commit/history.rs @@ -3,6 +3,8 @@ // // SPDX-License-Identifier: GPL-3.0-only +use std::ptr::null_mut; + use anyhow::Result; use chrono::{Local, TimeZone}; @@ -13,14 +15,24 @@ use colored::Colorize; use upac_abi::request::CListHistoryRequest; +use upac_types::request::{ListHistoryRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{invoke_with_response, request_base}; +use crate::types::abi::invoke_with_response; #[derive(ClapArgs)] pub struct Args {} pub fn run(_args: Args, ctx: CommandContext) -> Result<()> { - let request = CListHistoryRequest::new(request_base()); + let request: CListHistoryRequest = ListHistoryRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.list_history)(request, out, error) })?; diff --git a/user/upac-cli/src/commands/commit/list.rs b/user/upac-cli/src/commands/commit/list.rs index 1e346122..233bc0db 100644 --- a/user/upac-cli/src/commands/commit/list.rs +++ b/user/upac-cli/src/commands/commit/list.rs @@ -3,6 +3,8 @@ // // SPDX-License-Identifier: GPL-3.0-only +use std::ptr::null_mut; + use anyhow::Result; use clap::Args as ClapArgs; @@ -11,14 +13,25 @@ use colored::Colorize; use upac_abi::request::CListConfigRequest; +use upac_types::request::{ListConfigRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{empty_slice, invoke_with_response, request_base}; +use crate::types::abi::invoke_with_response; #[derive(ClapArgs)] pub struct Args {} pub fn run(_args: Args, ctx: CommandContext) -> Result<()> { - let request = CListConfigRequest::new(request_base(), empty_slice()); + let request: CListConfigRequest = ListConfigRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + prefix_digest: None, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.list_config)(request, out, error) })?; diff --git a/user/upac-cli/src/commands/commit/mod.rs b/user/upac-cli/src/commands/commit/mod.rs index 84e67d28..7ab04fbf 100644 --- a/user/upac-cli/src/commands/commit/mod.rs +++ b/user/upac-cli/src/commands/commit/mod.rs @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: GPL-3.0-only -// ── Imports ───────────────────────────────────────────────────────────────── use anyhow::Result; use clap::{Args, Subcommand}; @@ -18,14 +17,12 @@ pub mod pin; pub mod prefixes; pub mod unpin; -// ── Args ───────────────────────────────────────────────────────────────────── #[derive(Args)] pub struct CommitArgs { #[command(subcommand)] pub command: CommitCommand, } -// ── Subcommands ─────────────────────────────────────────────────────────────── #[derive(Subcommand)] pub enum CommitCommand { Diff(diff::Args), @@ -37,7 +34,6 @@ pub enum CommitCommand { Unpin(unpin::Args), } -// ── Dispatch ────────────────────────────────────────────────────────────────── pub fn run(args: CommitArgs, context: CommandContext) -> Result<()> { match args.command { CommitCommand::Diff(args) => diff::run(args, context), diff --git a/user/upac-cli/src/commands/commit/new.rs b/user/upac-cli/src/commands/commit/new.rs index 2d7ccfd3..d8230fee 100644 --- a/user/upac-cli/src/commands/commit/new.rs +++ b/user/upac-cli/src/commands/commit/new.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; +use std::ptr::null_mut; use anyhow::Result; @@ -11,8 +11,11 @@ use clap::Args as ClapArgs; use upac_abi::request::CCommitRequest; +use upac_types::request::{CommitRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{empty_slice, invoke, request_base, slice_from_cstr}; +use crate::types::abi::invoke; #[derive(ClapArgs)] pub struct Args { @@ -21,14 +24,18 @@ pub struct Args { pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; - let subject = CString::new(args.message)?; - - let request = CCommitRequest::new( - request_base(), - slice_from_cstr(&ctx.tmp_path), - slice_from_cstr(&subject), - empty_slice(), - ); + + let request: CCommitRequest = CommitRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + tmp_path: ctx.tmp_path.to_string_lossy().into_owned(), + subject: args.message, + message: None, + } + .into(); invoke(|error| unsafe { (symbols.commit)(request, error) }) } diff --git a/user/upac-cli/src/commands/commit/pin.rs b/user/upac-cli/src/commands/commit/pin.rs index 76e32365..573eec8e 100644 --- a/user/upac-cli/src/commands/commit/pin.rs +++ b/user/upac-cli/src/commands/commit/pin.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; +use std::ptr::null_mut; use anyhow::Result; @@ -11,8 +11,11 @@ use clap::Args as ClapArgs; use upac_abi::request::CPinRequest; +use upac_types::request::{PinRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{invoke, request_base, slice_from_cstr}; +use crate::types::abi::invoke; #[derive(ClapArgs)] pub struct Args { @@ -21,9 +24,17 @@ pub struct Args { pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; - let prefix_digest = CString::new(args.digest)?; - let request = CPinRequest::new(request_base(), slice_from_cstr(&prefix_digest), true); + let request: CPinRequest = PinRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + prefix_digest: args.digest, + pinned: true, + } + .into(); invoke(|error| unsafe { (symbols.pin_deploy)(request, error) }) } diff --git a/user/upac-cli/src/commands/commit/prefixes.rs b/user/upac-cli/src/commands/commit/prefixes.rs index 5f6614a6..f4c06a99 100644 --- a/user/upac-cli/src/commands/commit/prefixes.rs +++ b/user/upac-cli/src/commands/commit/prefixes.rs @@ -3,6 +3,8 @@ // // SPDX-License-Identifier: GPL-3.0-only +use std::ptr::null_mut; + use anyhow::Result; use chrono::{Local, TimeZone}; @@ -13,14 +15,24 @@ use colored::Colorize; use upac_abi::request::CListPrefixRequest; +use upac_types::request::{ListPrefixRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{invoke_with_response, request_base}; +use crate::types::abi::invoke_with_response; #[derive(ClapArgs)] pub struct Args {} pub fn run(_args: Args, ctx: CommandContext) -> Result<()> { - let request = CListPrefixRequest::new(request_base()); + let request: CListPrefixRequest = ListPrefixRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.list_prefix)(request, out, error) })?; diff --git a/user/upac-cli/src/commands/commit/unpin.rs b/user/upac-cli/src/commands/commit/unpin.rs index d86621c5..d76b3a7d 100644 --- a/user/upac-cli/src/commands/commit/unpin.rs +++ b/user/upac-cli/src/commands/commit/unpin.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; +use std::ptr::null_mut; use anyhow::Result; @@ -11,8 +11,11 @@ use clap::Args as ClapArgs; use upac_abi::request::CPinRequest; +use upac_types::request::{PinRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{invoke, request_base, slice_from_cstr}; +use crate::types::abi::invoke; #[derive(ClapArgs)] pub struct Args { @@ -21,9 +24,17 @@ pub struct Args { pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; - let prefix_digest = CString::new(args.digest)?; - let request = CPinRequest::new(request_base(), slice_from_cstr(&prefix_digest), false); + let request: CPinRequest = PinRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + prefix_digest: args.digest, + pinned: false, + } + .into(); invoke(|error| unsafe { (symbols.pin_deploy)(request, error) }) } diff --git a/user/upac-cli/src/commands/diff.rs b/user/upac-cli/src/commands/diff.rs index 1f8f2116..b63c6e1e 100644 --- a/user/upac-cli/src/commands/diff.rs +++ b/user/upac-cli/src/commands/diff.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; +use std::ptr::null_mut; use anyhow::Result; @@ -14,9 +14,12 @@ use colored::Colorize; use upac_abi::request::CDiffRequest; use upac_abi::{DiffFileSource, FileDiffKind, PackageDiffKind}; +use upac_types::request::{DiffRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::commands::display::VersionDisplay; use crate::types::CommandContext; -use crate::types::abi::{invoke_with_response, optional_slice, request_base}; +use crate::types::abi::invoke_with_response; #[derive(ClapArgs)] pub struct Args { @@ -31,18 +34,18 @@ pub struct Args { } pub fn run(args: Args, ctx: CommandContext) -> Result<()> { - let from_prefix = args.from_prefix.as_deref().map(CString::new).transpose()?; - let to_prefix = args.to_prefix.as_deref().map(CString::new).transpose()?; - let from_config = args.from_config.as_deref().map(CString::new).transpose()?; - let to_config = args.to_config.as_deref().map(CString::new).transpose()?; - - let request = CDiffRequest::new( - request_base(), - optional_slice(from_prefix.as_ref()), - optional_slice(to_prefix.as_ref()), - optional_slice(from_config.as_ref()), - optional_slice(to_config.as_ref()), - ); + let request: CDiffRequest = DiffRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + from_prefix_digest: args.from_prefix, + to_prefix_digest: args.to_prefix, + from_config_digest: args.from_config, + to_config_digest: args.to_config, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.diff)(request, out, error) })?; diff --git a/user/upac-cli/src/commands/display.rs b/user/upac-cli/src/commands/display.rs index 0e61509b..fe4db66a 100644 --- a/user/upac-cli/src/commands/display.rs +++ b/user/upac-cli/src/commands/display.rs @@ -10,7 +10,7 @@ use colored::Colorize; use strum::AsRefStr; use upac_abi::package::{CPackageMeta, CVersion}; -use upac_types::Version; +use upac_types::package::Version; use crate::locale::LOADER; diff --git a/user/upac-cli/src/commands/file/add.rs b/user/upac-cli/src/commands/file/add.rs index b769f40b..5c65d833 100644 --- a/user/upac-cli/src/commands/file/add.rs +++ b/user/upac-cli/src/commands/file/add.rs @@ -3,19 +3,21 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; - use anyhow::Result; use clap::Args as ClapArgs; use upac_abi::FileDiffKind; use upac_abi::error::ErrorDomain; -use upac_abi::request::{CFilesRequest, CRequestBase}; +use upac_abi::package::CPackageInfo; +use upac_abi::request::CFilesRequest; + +use upac_types::package::PackageInfo; +use upac_types::request::{FilesRequest, RequestBase}; use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{FileScope, borrowed_vec, invoke, optional_slice, package_info, slice_from_cstr}; +use crate::types::abi::{FileScope, invoke}; use crate::types::progress::{ProgressState, on_progress}; #[derive(ClapArgs)] @@ -39,37 +41,31 @@ pub struct Args { pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; - let package_name = CString::new(args.package)?; - let package_arch = CString::new(args.arch)?; - let package_arch_sub = args.arch_sub.map(CString::new).transpose()?; - let subject = CString::new("file add")?; - let message = args.message.map(CString::new).transpose()?; - let boot_plugin = args.boot.map(CString::new).transpose()?; - let scope = args.scope.into(); - - let file_cstrings = args - .files - .iter() - .map(|file_path| CString::new(file_path.as_str())) - .collect::, _>>()?; - let file_slices: Vec<_> = file_cstrings.iter().map(slice_from_cstr).collect(); - - let package = package_info(&package_name, &package_arch, package_arch_sub.as_ref()); + let package: CPackageInfo = PackageInfo { + name: args.package, + arch: args.arch, + arch_sub: args.arch_sub, + } + .into(); let mut progress = ProgressState::new(ErrorDomain::Files); - let base = CRequestBase::new(Some(on_progress), progress.ctx_ptr(), cancel_token_ptr()); - let request = CFilesRequest::new( - base, - slice_from_cstr(&ctx.tmp_path), - slice_from_cstr(&subject), - optional_slice(message.as_ref()), - borrowed_vec(&file_slices), - FileDiffKind::Added, - scope, - &package, - optional_slice(boot_plugin.as_ref()), - ); + let request: CFilesRequest = FilesRequest { + base: RequestBase { + on_hook: Some(on_progress), + hook_ctx: progress.ctx_ptr(), + cancel_token: cancel_token_ptr(), + }, + tmp_path: ctx.tmp_path.to_string_lossy().into_owned(), + subject: "file add".to_owned(), + message: args.message, + files: args.files, + file_kind: FileDiffKind::Added, + scope: args.scope.into(), + file_package: &package, + boot_plugin: args.boot, + } + .into(); let result = invoke(|error| unsafe { (symbols.files)(request, error) }); progress.finish(); diff --git a/user/upac-cli/src/commands/file/diff.rs b/user/upac-cli/src/commands/file/diff.rs index ebf73eec..9bb53d54 100644 --- a/user/upac-cli/src/commands/file/diff.rs +++ b/user/upac-cli/src/commands/file/diff.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; +use std::ptr::null_mut; use anyhow::Result; @@ -14,8 +14,11 @@ use colored::Colorize; use upac_abi::FileDiffKind; use upac_abi::request::CDiffPrefixRequest; +use upac_types::request::{DiffPrefixRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{invoke_with_response, optional_slice, request_base}; +use crate::types::abi::invoke_with_response; #[derive(ClapArgs)] pub struct Args { @@ -24,14 +27,16 @@ pub struct Args { } pub fn run(args: Args, ctx: CommandContext) -> Result<()> { - let from_prefix = args.from.as_deref().map(CString::new).transpose()?; - let to_prefix = args.to.as_deref().map(CString::new).transpose()?; - - let request = CDiffPrefixRequest::new( - request_base(), - optional_slice(from_prefix.as_ref()), - optional_slice(to_prefix.as_ref()), - ); + let request: CDiffPrefixRequest = DiffPrefixRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + from_prefix_digest: args.from, + to_prefix_digest: args.to, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.diff_prefix)(request, out, error) })?; diff --git a/user/upac-cli/src/commands/file/mod.rs b/user/upac-cli/src/commands/file/mod.rs index 027dbe5a..f2ecd687 100644 --- a/user/upac-cli/src/commands/file/mod.rs +++ b/user/upac-cli/src/commands/file/mod.rs @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: GPL-3.0-only -// ── Imports ───────────────────────────────────────────────────────────────── use anyhow::Result; use clap::{Args, Subcommand}; @@ -15,14 +14,12 @@ pub mod diff; pub mod remove; pub mod search; -// ── Args ───────────────────────────────────────────────────────────────────── #[derive(Args)] pub struct FileArgs { #[command(subcommand)] pub command: FileCommand, } -// ── Subcommands ─────────────────────────────────────────────────────────────── #[derive(Subcommand)] pub enum FileCommand { Add(add::Args), @@ -31,7 +28,6 @@ pub enum FileCommand { Search(search::Args), } -// ── Dispatch ────────────────────────────────────────────────────────────────── pub fn run(args: FileArgs, context: CommandContext) -> Result<()> { match args.command { FileCommand::Add(args) => add::run(args, context), diff --git a/user/upac-cli/src/commands/file/remove.rs b/user/upac-cli/src/commands/file/remove.rs index 058d0878..5ad8d18d 100644 --- a/user/upac-cli/src/commands/file/remove.rs +++ b/user/upac-cli/src/commands/file/remove.rs @@ -3,19 +3,21 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; - use anyhow::Result; use clap::Args as ClapArgs; use upac_abi::FileDiffKind; use upac_abi::error::ErrorDomain; -use upac_abi::request::{CFilesRequest, CRequestBase}; +use upac_abi::package::CPackageInfo; +use upac_abi::request::CFilesRequest; + +use upac_types::package::PackageInfo; +use upac_types::request::{FilesRequest, RequestBase}; use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{FileScope, borrowed_vec, invoke, optional_slice, package_info, slice_from_cstr}; +use crate::types::abi::{FileScope, invoke}; use crate::types::progress::{ProgressState, on_progress}; #[derive(ClapArgs)] @@ -39,37 +41,31 @@ pub struct Args { pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; - let package_name = CString::new(args.package)?; - let package_arch = CString::new(args.arch)?; - let package_arch_sub = args.arch_sub.map(CString::new).transpose()?; - let subject = CString::new("file remove")?; - let message = args.message.map(CString::new).transpose()?; - let boot_plugin = args.boot.map(CString::new).transpose()?; - let scope = args.scope.into(); - - let file_cstrings = args - .files - .iter() - .map(|file_path| CString::new(file_path.as_str())) - .collect::, _>>()?; - let file_slices: Vec<_> = file_cstrings.iter().map(slice_from_cstr).collect(); - - let package = package_info(&package_name, &package_arch, package_arch_sub.as_ref()); + let package: CPackageInfo = PackageInfo { + name: args.package, + arch: args.arch, + arch_sub: args.arch_sub, + } + .into(); let mut progress = ProgressState::new(ErrorDomain::Files); - let base = CRequestBase::new(Some(on_progress), progress.ctx_ptr(), cancel_token_ptr()); - let request = CFilesRequest::new( - base, - slice_from_cstr(&ctx.tmp_path), - slice_from_cstr(&subject), - optional_slice(message.as_ref()), - borrowed_vec(&file_slices), - FileDiffKind::Removed, - scope, - &package, - optional_slice(boot_plugin.as_ref()), - ); + let request: CFilesRequest = FilesRequest { + base: RequestBase { + on_hook: Some(on_progress), + hook_ctx: progress.ctx_ptr(), + cancel_token: cancel_token_ptr(), + }, + tmp_path: ctx.tmp_path.to_string_lossy().into_owned(), + subject: "file remove".to_owned(), + message: args.message, + files: args.files, + file_kind: FileDiffKind::Removed, + scope: args.scope.into(), + file_package: &package, + boot_plugin: args.boot, + } + .into(); let result = invoke(|error| unsafe { (symbols.files)(request, error) }); progress.finish(); diff --git a/user/upac-cli/src/commands/file/search.rs b/user/upac-cli/src/commands/file/search.rs index f6de5696..d2920abc 100644 --- a/user/upac-cli/src/commands/file/search.rs +++ b/user/upac-cli/src/commands/file/search.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; +use std::ptr::null_mut; use anyhow::Result; @@ -16,9 +16,13 @@ use i18n_embed_fl::fl; use upac_abi::request::{CSearchFilesRequest, CSearchInPackageFilesRequest}; use upac_abi::response::CSearchFileEntry; +use upac_types::package::PackageInfo; +use upac_types::request::{RequestBase, SearchFilesRequest, SearchInPackageFilesRequest}; + +use crate::cancel_token_ptr; use crate::locale::LOADER; use crate::types::CommandContext; -use crate::types::abi::{invoke_with_response, package_info, request_base, slice_from_cstr}; +use crate::types::abi::invoke_with_response; #[derive(ClapArgs)] pub struct Args { @@ -34,21 +38,27 @@ pub struct Args { } pub fn run(args: Args, ctx: CommandContext) -> Result<()> { - let query = CString::new(args.query.as_str())?; - match args.package.as_deref() { Some(package) => { let Some(arch) = args.package_arch.as_deref() else { anyhow::bail!(fl!(LOADER, "err-invalid-entry")); }; - let package_name = CString::new(package)?; - let package_arch = CString::new(arch)?; - let package_arch_sub = args.package_arch_sub.as_deref().map(CString::new).transpose()?; - let package = package_info(&package_name, &package_arch, package_arch_sub.as_ref()); - - let request = - CSearchInPackageFilesRequest::new(request_base(), package, slice_from_cstr(&query), args.regex); + let request: CSearchInPackageFilesRequest = SearchInPackageFilesRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + package: PackageInfo { + name: package.to_owned(), + arch: arch.to_owned(), + arch_sub: args.package_arch_sub.clone(), + }, + search: args.query.clone(), + is_regex: args.regex, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.search_in_package_files)(request, out, error) })?; @@ -58,7 +68,16 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { unsafe { response.free() }; } None => { - let request = CSearchFilesRequest::new(request_base(), slice_from_cstr(&query), args.regex); + let request: CSearchFilesRequest = SearchFilesRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + search: args.query.clone(), + is_regex: args.regex, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.search_files)(request, out, error) })?; diff --git a/user/upac-cli/src/commands/gc.rs b/user/upac-cli/src/commands/gc.rs index 842fed52..5b7d60f1 100644 --- a/user/upac-cli/src/commands/gc.rs +++ b/user/upac-cli/src/commands/gc.rs @@ -3,14 +3,19 @@ // // SPDX-License-Identifier: GPL-3.0-only +use std::ptr::null_mut; + use anyhow::Result; use clap::Args as ClapArgs; use upac_abi::request::CGcRequest; +use upac_types::request::{GcRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{invoke, request_base}; +use crate::types::abi::invoke; #[derive(ClapArgs)] pub struct Args {} @@ -18,7 +23,14 @@ pub struct Args {} pub fn run(_args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; - let request = CGcRequest::new(request_base()); + let request: CGcRequest = GcRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + } + .into(); invoke(|error| unsafe { (symbols.gc)(request, error) }) } diff --git a/user/upac-cli/src/commands/mime/mod.rs b/user/upac-cli/src/commands/mime/mod.rs index 823e9320..aab1f687 100644 --- a/user/upac-cli/src/commands/mime/mod.rs +++ b/user/upac-cli/src/commands/mime/mod.rs @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: GPL-3.0-only -// ── Imports ───────────────────────────────────────────────────────────────── use anyhow::Result; use clap::{Args, Subcommand}; @@ -12,20 +11,17 @@ use crate::types::CommandContext; pub mod sync; -// ── Args ───────────────────────────────────────────────────────────────────── #[derive(Args)] pub struct MimeArgs { #[command(subcommand)] pub command: MimeCommand, } -// ── Subcommands ─────────────────────────────────────────────────────────────── #[derive(Subcommand)] pub enum MimeCommand { Sync(sync::Args), } -// ── Dispatch ────────────────────────────────────────────────────────────────── pub fn run(args: MimeArgs, context: CommandContext) -> Result<()> { match args.command { MimeCommand::Sync(args) => sync::run(args, context), diff --git a/user/upac-cli/src/commands/mime/sync.rs b/user/upac-cli/src/commands/mime/sync.rs index cdb9912b..80b46633 100644 --- a/user/upac-cli/src/commands/mime/sync.rs +++ b/user/upac-cli/src/commands/mime/sync.rs @@ -3,14 +3,19 @@ // // SPDX-License-Identifier: GPL-3.0-only +use std::ptr::null_mut; + use anyhow::Result; use clap::Args as ClapArgs; use upac_abi::request::CMimeSyncRequest; +use upac_types::request::{MimeSyncRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{invoke, request_base}; +use crate::types::abi::invoke; #[derive(ClapArgs)] pub struct Args {} @@ -18,7 +23,14 @@ pub struct Args {} pub fn run(_args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; - let request = CMimeSyncRequest::new(request_base()); + let request: CMimeSyncRequest = MimeSyncRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + } + .into(); invoke(|error| unsafe { (symbols.mime)(request, error) }) } diff --git a/user/upac-cli/src/commands/package/diff.rs b/user/upac-cli/src/commands/package/diff.rs index 371ae360..af558016 100644 --- a/user/upac-cli/src/commands/package/diff.rs +++ b/user/upac-cli/src/commands/package/diff.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; +use std::ptr::null_mut; use anyhow::Result; @@ -14,9 +14,12 @@ use colored::Colorize; use upac_abi::PackageDiffKind; use upac_abi::request::CDiffPackagesRequest; +use upac_types::request::{DiffPackagesRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::commands::display::VersionDisplay; use crate::types::CommandContext; -use crate::types::abi::{invoke_with_response, optional_slice, request_base}; +use crate::types::abi::invoke_with_response; #[derive(ClapArgs)] pub struct Args { @@ -25,14 +28,16 @@ pub struct Args { } pub fn run(args: Args, ctx: CommandContext) -> Result<()> { - let from_prefix = args.from.as_deref().map(CString::new).transpose()?; - let to_prefix = args.to.as_deref().map(CString::new).transpose()?; - - let request = CDiffPackagesRequest::new( - request_base(), - optional_slice(from_prefix.as_ref()), - optional_slice(to_prefix.as_ref()), - ); + let request: CDiffPackagesRequest = DiffPackagesRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + from_prefix_digest: args.from, + to_prefix_digest: args.to, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.diff_packages)(request, out, error) })?; diff --git a/user/upac-cli/src/commands/package/install.rs b/user/upac-cli/src/commands/package/install.rs index e813b91b..2a577196 100644 --- a/user/upac-cli/src/commands/package/install.rs +++ b/user/upac-cli/src/commands/package/install.rs @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; use std::fs::canonicalize; use anyhow::Result; @@ -13,18 +12,18 @@ use clap::Args as ClapArgs; use i18n_embed_fl::fl; use upac_abi::error::ErrorDomain; -use upac_abi::request::{CInstallRequest, CRequestBase}; +use upac_abi::request::CInstallRequest; + +use upac_types::request::{InstallRequest, RequestBase}; use crate::cancel_token_ptr; use crate::locale::LOADER; use crate::types::CommandContext; -use crate::types::abi::{borrowed_vec, invoke, optional_slice, slice_from_cstr}; +use crate::types::abi::invoke; use crate::types::progress::{ProgressState, on_progress}; #[derive(ClapArgs)] pub struct Args { - // Required flag, not a positional: keeps the positional slot free for a future - // name-based network install (e.g. `up pkg install foo`), separate from this local-file path. #[arg(short, long = "file", required = true, num_args = 1..)] pub files: Vec, #[arg(short, long)] @@ -38,31 +37,29 @@ pub struct Args { pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; - let subject = CString::new("install")?; - let message = args.message.map(CString::new).transpose()?; - let boot_plugin = args.boot.map(CString::new).transpose()?; - - let mut paths = Vec::with_capacity(args.files.len()); + let mut packages = Vec::with_capacity(args.files.len()); for file_path in &args.files { let absolute = canonicalize(file_path).map_err(|_| anyhow::anyhow!("{}: {file_path}", fl!(LOADER, "err-not-found")))?; - paths.push(CString::new(absolute.to_string_lossy().as_ref())?); + packages.push(absolute.to_string_lossy().into_owned()); } - let path_slices: Vec<_> = paths.iter().map(slice_from_cstr).collect(); - let mut progress = ProgressState::new(ErrorDomain::Install); - let base = CRequestBase::new(Some(on_progress), progress.ctx_ptr(), cancel_token_ptr()); - let request = CInstallRequest::new( - base, - slice_from_cstr(&ctx.tmp_path), - slice_from_cstr(&subject), - optional_slice(message.as_ref()), - borrowed_vec(&path_slices), - optional_slice(boot_plugin.as_ref()), - !args.no_conflict_files, - ); + let request: CInstallRequest = InstallRequest { + base: RequestBase { + on_hook: Some(on_progress), + hook_ctx: progress.ctx_ptr(), + cancel_token: cancel_token_ptr(), + }, + tmp_path: ctx.tmp_path.to_string_lossy().into_owned(), + subject: "install".to_owned(), + message: args.message, + packages, + boot_plugin: args.boot, + allow_conflict_files: !args.no_conflict_files, + } + .into(); let result = invoke(|error| unsafe { (symbols.install)(request, error) }); progress.finish(); diff --git a/user/upac-cli/src/commands/package/list.rs b/user/upac-cli/src/commands/package/list.rs index 0516797d..8660030c 100644 --- a/user/upac-cli/src/commands/package/list.rs +++ b/user/upac-cli/src/commands/package/list.rs @@ -3,15 +3,20 @@ // // SPDX-License-Identifier: GPL-3.0-only +use std::ptr::null_mut; + use anyhow::Result; use clap::Args as ClapArgs; use upac_abi::request::CListPackagesRequest; +use upac_types::request::{ListPackagesRequest, RequestBase}; + +use crate::cancel_token_ptr; use crate::commands::display::{PackageField, PackageFormatter}; use crate::types::CommandContext; -use crate::types::abi::{invoke_with_response, request_base}; +use crate::types::abi::invoke_with_response; #[cfg(test)] #[path = "../../../tests/inline/list.rs"] @@ -42,7 +47,14 @@ pub struct Args { } pub fn run(args: Args, ctx: CommandContext) -> Result<()> { - let request = CListPackagesRequest::new(request_base()); + let request: CListPackagesRequest = ListPackagesRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.list_packages)(request, out, error) })?; diff --git a/user/upac-cli/src/commands/package/mod.rs b/user/upac-cli/src/commands/package/mod.rs index 3a28345c..4f421bc5 100644 --- a/user/upac-cli/src/commands/package/mod.rs +++ b/user/upac-cli/src/commands/package/mod.rs @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: GPL-3.0-only -// ── Imports ───────────────────────────────────────────────────────────────── use anyhow::Result; use clap::{Args, Subcommand}; @@ -17,14 +16,12 @@ pub mod remove; pub mod search; pub mod update; -// ── Args ───────────────────────────────────────────────────────────────────── #[derive(Args)] pub struct PkgArgs { #[command(subcommand)] pub command: PkgCommand, } -// ── Subcommands ─────────────────────────────────────────────────────────────── #[derive(Subcommand)] pub enum PkgCommand { Install(install::Args), @@ -36,7 +33,6 @@ pub enum PkgCommand { Search(search::Args), } -// ── Dispatch ────────────────────────────────────────────────────────────────── pub fn run(args: PkgArgs, context: CommandContext) -> Result<()> { match args.command { PkgCommand::Install(args) => install::run(args, context), diff --git a/user/upac-cli/src/commands/package/remove.rs b/user/upac-cli/src/commands/package/remove.rs index eb3f5918..3fc60253 100644 --- a/user/upac-cli/src/commands/package/remove.rs +++ b/user/upac-cli/src/commands/package/remove.rs @@ -3,8 +3,8 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; use std::io::{self, Write}; +use std::ptr::null_mut; use anyhow::Result; @@ -15,15 +15,16 @@ use colored::Colorize; use i18n_embed_fl::fl; use upac_abi::error::ErrorDomain; -use upac_abi::request::{CListPackagesRequest, CRequestBase, CUninstallRequest}; +use upac_abi::request::{CListPackagesRequest, CUninstallRequest}; use upac_abi::types::CSlice; +use upac_types::package::PackageInfo; +use upac_types::request::{ListPackagesRequest, RequestBase, UninstallRequest}; + use crate::cancel_token_ptr; use crate::locale::LOADER; use crate::types::CommandContext; -use crate::types::abi::{ - borrowed_vec, invoke, invoke_with_response, optional_slice, package_info, request_base, slice_from_cstr, -}; +use crate::types::abi::{invoke, invoke_with_response}; use crate::types::progress::{ProgressState, on_progress}; #[cfg(test)] @@ -31,7 +32,6 @@ use crate::types::progress::{ProgressState, on_progress}; mod tests; type InstalledEntry = (String, String, Option); -type ResolvedEntry = (CString, CString, Option); #[derive(ClapArgs)] pub struct Args { @@ -89,12 +89,19 @@ struct RemoveMachine { ctx: CommandContext, installed: Vec, - resolved: Vec, + resolved: Vec, } impl RemoveMachine { fn state_listing(&mut self) -> Result { - let request = CListPackagesRequest::new(request_base()); + let request: CListPackagesRequest = ListPackagesRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (self.ctx.lib.ro.list_packages)(request, out, error) })?; @@ -124,14 +131,12 @@ impl RemoveMachine { .args .names .iter() - .map(|name| { - Ok(( - CString::new(name.as_str())?, - CString::new(arch)?, - self.args.arch_sub.as_deref().map(CString::new).transpose()?, - )) + .map(|name| PackageInfo { + name: name.clone(), + arch: arch.to_owned(), + arch_sub: self.args.arch_sub.clone(), }) - .collect::>()?; + .collect(); Ok(State::Removing) } @@ -142,11 +147,11 @@ impl RemoveMachine { .iter() .map(|name| { let (arch, arch_sub) = find_installed(&self.installed, name)?; - Ok(( - CString::new(name.as_str())?, - CString::new(arch)?, - arch_sub.as_deref().map(CString::new).transpose()?, - )) + Ok(PackageInfo { + name: name.clone(), + arch, + arch_sub, + }) }) .collect::>()?; Ok(State::Removing) @@ -155,28 +160,22 @@ impl RemoveMachine { fn state_removing(&mut self) -> Result { let symbols = self.ctx.lib.require_write()?; - let subject = CString::new("remove")?; - let message = self.args.message.as_deref().map(CString::new).transpose()?; - let boot_plugin = self.args.boot.as_deref().map(CString::new).transpose()?; - - let packages: Vec<_> = self - .resolved - .iter() - .map(|(name, arch, arch_sub)| package_info(name, arch, arch_sub.as_ref())) - .collect(); - let mut progress = ProgressState::new(ErrorDomain::Uninstall); - let base = CRequestBase::new(Some(on_progress), progress.ctx_ptr(), cancel_token_ptr()); - - let request = CUninstallRequest::new( - base, - slice_from_cstr(&self.ctx.tmp_path), - slice_from_cstr(&subject), - optional_slice(message.as_ref()), - borrowed_vec(&packages), - optional_slice(boot_plugin.as_ref()), - self.args.purge, - ); + + let request: CUninstallRequest = UninstallRequest { + base: RequestBase { + on_hook: Some(on_progress), + hook_ctx: progress.ctx_ptr(), + cancel_token: cancel_token_ptr(), + }, + tmp_path: self.ctx.tmp_path.to_string_lossy().into_owned(), + subject: "remove".to_owned(), + message: self.args.message.clone(), + packages: std::mem::take(&mut self.resolved), + boot_plugin: self.args.boot.clone(), + purge: self.args.purge, + } + .into(); let result = invoke(|error| unsafe { (symbols.uninstall)(request, error) }); progress.finish(); diff --git a/user/upac-cli/src/commands/package/search.rs b/user/upac-cli/src/commands/package/search.rs index 68b54ffd..b0eec814 100644 --- a/user/upac-cli/src/commands/package/search.rs +++ b/user/upac-cli/src/commands/package/search.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; +use std::ptr::null_mut; use anyhow::Result; @@ -13,10 +13,14 @@ use i18n_embed_fl::fl; use upac_abi::request::{CSearchInMetaRequest, CSearchMetaRequest}; +use upac_types::package::PackageInfo; +use upac_types::request::{RequestBase, SearchInMetaRequest, SearchMetaRequest}; + +use crate::cancel_token_ptr; use crate::commands::display::{PackageField, PackageFormatter}; use crate::locale::LOADER; use crate::types::CommandContext; -use crate::types::abi::{invoke_with_response, package_info, request_base, slice_from_cstr}; +use crate::types::abi::invoke_with_response; #[cfg(test)] #[path = "../../../tests/inline/search.rs"] @@ -56,7 +60,6 @@ pub struct Args { } pub fn run(args: Args, ctx: CommandContext) -> Result<()> { - let query = CString::new(args.query.as_str())?; let extra_fields = build_extra_fields(&args); match args.package.as_deref() { @@ -65,12 +68,21 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { anyhow::bail!(fl!(LOADER, "err-invalid-entry")); }; - let package_name = CString::new(package)?; - let package_arch = CString::new(arch)?; - let package_arch_sub = args.package_arch_sub.as_deref().map(CString::new).transpose()?; - let package = package_info(&package_name, &package_arch, package_arch_sub.as_ref()); - - let request = CSearchInMetaRequest::new(request_base(), package, slice_from_cstr(&query), args.regex); + let request: CSearchInMetaRequest = SearchInMetaRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + package: PackageInfo { + name: package.to_owned(), + arch: arch.to_owned(), + arch_sub: args.package_arch_sub.clone(), + }, + search: args.query.clone(), + is_regex: args.regex, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.search_in_meta)(request, out, error) })?; @@ -83,8 +95,18 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { unsafe { response.free() }; } + None => { - let request = CSearchMetaRequest::new(request_base(), slice_from_cstr(&query), args.regex); + let request: CSearchMetaRequest = SearchMetaRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + search: args.query.clone(), + is_regex: args.regex, + } + .into(); let response = invoke_with_response(|out, error| unsafe { (ctx.lib.ro.search_meta)(request, out, error) })?; PackageFormatter { diff --git a/user/upac-cli/src/commands/package/update.rs b/user/upac-cli/src/commands/package/update.rs index f2d8ae26..92e7a093 100644 --- a/user/upac-cli/src/commands/package/update.rs +++ b/user/upac-cli/src/commands/package/update.rs @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; use std::fs::canonicalize; use anyhow::Result; @@ -13,18 +12,18 @@ use clap::Args as ClapArgs; use i18n_embed_fl::fl; use upac_abi::error::ErrorDomain; -use upac_abi::request::{CRequestBase, CUpdateRequest}; +use upac_abi::request::CUpdateRequest; + +use upac_types::request::{RequestBase, UpdateRequest}; use crate::cancel_token_ptr; use crate::locale::LOADER; use crate::types::CommandContext; -use crate::types::abi::{borrowed_vec, invoke, optional_slice, slice_from_cstr}; +use crate::types::abi::invoke; use crate::types::progress::{ProgressState, on_progress}; #[derive(ClapArgs)] pub struct Args { - // Required flag, not a positional: keeps the positional slot free for a future - // name-based network update (e.g. `up pkg update foo`), separate from this local-file path. #[arg(short, long = "file", required = true, num_args = 1..)] pub files: Vec, #[arg(short, long)] @@ -40,32 +39,30 @@ pub struct Args { pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; - let subject = CString::new("update")?; - let message = args.message.map(CString::new).transpose()?; - let boot_plugin = args.boot.map(CString::new).transpose()?; - - let mut paths = Vec::with_capacity(args.files.len()); + let mut packages = Vec::with_capacity(args.files.len()); for file_path in &args.files { let absolute = canonicalize(file_path).map_err(|_| anyhow::anyhow!("{}: {file_path}", fl!(LOADER, "err-not-found")))?; - paths.push(CString::new(absolute.to_string_lossy().as_ref())?); + packages.push(absolute.to_string_lossy().into_owned()); } - let path_slices: Vec<_> = paths.iter().map(slice_from_cstr).collect(); - let mut progress = ProgressState::new(ErrorDomain::Update); - let base = CRequestBase::new(Some(on_progress), progress.ctx_ptr(), cancel_token_ptr()); - let request = CUpdateRequest::new( - base, - slice_from_cstr(&ctx.tmp_path), - slice_from_cstr(&subject), - optional_slice(message.as_ref()), - borrowed_vec(&path_slices), - optional_slice(boot_plugin.as_ref()), - args.allow_downgrade, - !args.no_conflict_files, - ); + let request: CUpdateRequest = UpdateRequest { + base: RequestBase { + on_hook: Some(on_progress), + hook_ctx: progress.ctx_ptr(), + cancel_token: cancel_token_ptr(), + }, + tmp_path: ctx.tmp_path.to_string_lossy().into_owned(), + subject: "update".to_owned(), + message: args.message, + packages, + boot_plugin: args.boot, + allow_downgrade: args.allow_downgrade, + allow_conflict_files: !args.no_conflict_files, + } + .into(); let result = invoke(|error| unsafe { (symbols.update)(request, error) }); progress.finish(); diff --git a/user/upac-cli/src/commands/rollback.rs b/user/upac-cli/src/commands/rollback.rs index c0f80e70..b78a82be 100644 --- a/user/upac-cli/src/commands/rollback.rs +++ b/user/upac-cli/src/commands/rollback.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; +use std::ptr::null_mut; use anyhow::Result; @@ -11,8 +11,11 @@ use clap::Args as ClapArgs; use upac_abi::request::CRollbackRequest; +use upac_types::request::{RequestBase, RollbackRequest}; + +use crate::cancel_token_ptr; use crate::types::CommandContext; -use crate::types::abi::{invoke, optional_slice, request_base, slice_from_cstr}; +use crate::types::abi::invoke; #[derive(ClapArgs)] pub struct Args { @@ -23,15 +26,18 @@ pub struct Args { pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; - let config_digest = CString::new(args.commit)?; - let boot_plugin = args.boot.map(CString::new).transpose()?; - - let request = CRollbackRequest::new( - request_base(), - slice_from_cstr(&ctx.tmp_path), - slice_from_cstr(&config_digest), - optional_slice(boot_plugin.as_ref()), - ); + + let request: CRollbackRequest = RollbackRequest { + base: RequestBase { + on_hook: None, + hook_ctx: null_mut(), + cancel_token: cancel_token_ptr(), + }, + tmp_path: ctx.tmp_path.to_string_lossy().into_owned(), + config_digest: args.commit, + boot_plugin: args.boot, + } + .into(); invoke(|error| unsafe { (symbols.rollback)(request, error) }) } diff --git a/user/upac-cli/src/libcore.rs b/user/upac-cli/src/libcore.rs index bc11b76c..a0fa14dc 100644 --- a/user/upac-cli/src/libcore.rs +++ b/user/upac-cli/src/libcore.rs @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: GPL-3.0-only -// ── Imports ───────────────────────────────────────────────────────────────── use anyhow::Result; use i18n_embed_fl::fl; @@ -25,16 +24,17 @@ use upac_abi::response::{ CSearchInPackageFilesResponse, CSearchMetaResponse, }; +#[cfg(feature = "dynamic-plugins")] +use libloading::Library; + +use super::types::errors::{AbiMismatch, LibError}; + use crate::locale::LOADER; -use crate::types::errors::{AbiMismatch, LibError}; #[cfg(test)] #[path = "../tests/inline/libcore.rs"] mod tests; -#[cfg(feature = "dynamic-plugins")] -use libloading::Library; - #[cfg(feature = "static-link")] use upac::export::mutated::{ commit::commit, files::files, gc::gc, installer::install, mime::mime, pin::pin_deploy, rollback::rollback, @@ -50,7 +50,6 @@ use upac::export::unmutated::{ #[cfg(feature = "static-link")] use upac::export::{cancel, version_abi}; -// ── Static-link symbol construction ──────────────────────────────────────── #[cfg(feature = "static-link")] impl RoSymbols { fn from_static() -> Self { @@ -112,7 +111,6 @@ impl Lib { } } -// ── Dynamic-link symbol loading ──────────────────────────────────────────── #[cfg(feature = "dynamic-plugins")] pub trait LoadLibrarySymbols: Sized { fn load(lib: &Library) -> Result; @@ -195,7 +193,6 @@ impl Lib { } } -// ── Read-only symbols ───────────────────────────────────────────────────────── pub struct RoSymbols { pub list_packages: unsafe extern "C" fn(CListPackagesRequest, *mut CListPackagesResponse, *mut CError) -> i32, pub search_meta: unsafe extern "C" fn(CSearchMetaRequest, *mut CSearchMetaResponse, *mut CError) -> i32, @@ -212,7 +209,6 @@ pub struct RoSymbols { unsafe extern "C" fn(CSearchInPackageFilesRequest, *mut CSearchInPackageFilesResponse, *mut CError) -> i32, } -// ── Mutating symbols ────────────────────────────────────────────────────────── pub struct RwSymbols { pub install: unsafe extern "C" fn(CInstallRequest, *mut CError) -> i32, pub update: unsafe extern "C" fn(CUpdateRequest, *mut CError) -> i32, @@ -225,7 +221,6 @@ pub struct RwSymbols { pub pin_deploy: unsafe extern "C" fn(CPinRequest, *mut CError) -> i32, } -// ── Wrapper around either libupac.so or the statically linked upac-lib ────── pub struct Lib { pub ro: RoSymbols, pub rw: RwSymbols, @@ -237,9 +232,6 @@ pub struct Lib { } impl Lib { - /// Gates access to the mutating symbol table behind an effective-root check — call sites for - /// install/update/uninstall/commit/rollback/files/gc/mime go through here instead of reading - /// `self.rw` directly, so the check can't be forgotten at a new call site. pub fn require_write(&self) -> Result<&RwSymbols> { if !Uid::effective().is_root() { anyhow::bail!(fl!(LOADER, "err-requires-root")); diff --git a/user/upac-cli/src/main.rs b/user/upac-cli/src/main.rs index 67254c61..ec4f1e38 100644 --- a/user/upac-cli/src/main.rs +++ b/user/upac-cli/src/main.rs @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: GPL-3.0-only -// ── Imports ───────────────────────────────────────────────────────────────── use std::process::ExitCode; use std::ptr::addr_of_mut; use std::sync::Arc; @@ -18,11 +17,11 @@ use i18n_embed_fl::fl; use upac_abi::hook::CancelToken; -use crate::commands::commit::CommitArgs; -use crate::commands::file::FileArgs; -use crate::commands::package::PkgArgs; -use crate::libcore::Lib; -use crate::types::CommandContext; +use self::commands::commit::CommitArgs; +use self::commands::file::FileArgs; +use self::commands::package::PkgArgs; +use self::libcore::Lib; +use self::types::CommandContext; mod libcore; mod layout { @@ -48,7 +47,6 @@ pub(crate) fn cancel_token_ptr() -> *mut CancelToken { addr_of_mut!(CANCEL_TOKEN) } -// ── CLI arguments ───────────────────────────────────────────────────────────── #[derive(Parser)] #[command(author, version, about)] enum Command { @@ -61,7 +59,6 @@ enum Command { Rollback(commands::rollback::Args), } -// ── Entry points ─────────────────────────────────────────────────────────────── fn main() -> ExitCode { locale::init(); @@ -78,6 +75,7 @@ fn run() -> Result<()> { let lib = Arc::new(Lib::load()?); let lib_cancel = Arc::clone(&lib); + ctrlc::set_handler(move || { unsafe { (lib_cancel.cancel)(cancel_token_ptr()) }; })?; diff --git a/user/upac-cli/src/types/abi.rs b/user/upac-cli/src/types/abi.rs index b64a30da..b13bebae 100644 --- a/user/upac-cli/src/types/abi.rs +++ b/user/upac-cli/src/types/abi.rs @@ -3,20 +3,15 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; use std::mem::MaybeUninit; -use std::ptr::{null, null_mut}; use anyhow::Result; use upac_abi::DiffFileSource; use upac_abi::error::CError; -use upac_abi::package::CPackageInfo; -use upac_abi::request::CRequestBase; -use upac_abi::types::{CBorrowed, CSlice, CVec}; +use upac_abi::types::CValidatable; -use crate::cancel_token_ptr; -use crate::types::errors::LibError; +use crate::types::errors::{InvalidResponse, LibError}; #[cfg(test)] #[path = "../../tests/inline/abi.rs"] @@ -37,47 +32,28 @@ impl From for DiffFileSource { } } -pub fn request_base() -> CRequestBase { - CRequestBase::new(None, null_mut(), cancel_token_ptr()) -} - -pub fn slice_from_cstr(value: &CString) -> CSlice { - CSlice { - ptr: value.as_ptr().cast(), - len: value.as_bytes().len(), - } -} - -pub fn empty_slice() -> CSlice { - CSlice { ptr: null(), len: 0 } -} - -pub fn optional_slice(value: Option<&CString>) -> CSlice { - match value { - Some(value) => slice_from_cstr(value), - None => empty_slice(), - } -} - -pub fn package_info(name: &CString, arch: &CString, arch_sub: Option<&CString>) -> CPackageInfo { - CPackageInfo::new(slice_from_cstr(name), slice_from_cstr(arch), optional_slice(arch_sub)) -} - -pub fn borrowed_vec(items: &[T]) -> CVec { - CVec::from_borrowed(items) -} - pub fn invoke(call: impl FnOnce(*mut CError) -> i32) -> Result<()> { let mut error = MaybeUninit::uninit(); + let code = call(error.as_mut_ptr()); + unsafe { LibError::check(code, error.as_ptr())? }; + Ok(()) } -pub fn invoke_with_response(call: impl FnOnce(*mut R, *mut CError) -> i32) -> Result { +pub fn invoke_with_response(call: impl FnOnce(*mut R, *mut CError) -> i32) -> Result { let mut response = MaybeUninit::zeroed(); + let mut error = MaybeUninit::uninit(); + let code = call(response.as_mut_ptr(), error.as_mut_ptr()); + unsafe { LibError::check(code, error.as_ptr())? }; - Ok(unsafe { response.assume_init() }) + + let response = unsafe { response.assume_init() }; + + unsafe { response.validate() }.map_err(|error| InvalidResponse { error })?; + + Ok(response) } diff --git a/user/upac-cli/src/types/errors.rs b/user/upac-cli/src/types/errors.rs index 3ff422dd..3bab1099 100644 --- a/user/upac-cli/src/types/errors.rs +++ b/user/upac-cli/src/types/errors.rs @@ -43,6 +43,19 @@ impl Display for AbiMismatch { impl Error for AbiMismatch {} +#[derive(Debug)] +pub struct InvalidResponse { + pub error: ErrorKind, +} + +impl Display for InvalidResponse { + fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult { + write!(formatter, "{}", error_kind_message(self.error)) + } +} + +impl Error for InvalidResponse {} + pub(crate) struct StageName { domain: ErrorDomain, state: u32, @@ -92,6 +105,24 @@ impl Display for StageName { } } +fn error_kind_message(kind: ErrorKind) -> String { + match kind { + ErrorKind::Unexpected => fl!(LOADER, "err-unexpected"), + ErrorKind::OutOfMemory => fl!(LOADER, "err-oom"), + ErrorKind::NotFound => fl!(LOADER, "err-not-found"), + ErrorKind::AlreadyExists => fl!(LOADER, "err-already-exists"), + ErrorKind::PermissionDenied => fl!(LOADER, "err-permission-denied"), + ErrorKind::InvalidPath => fl!(LOADER, "err-invalid-path"), + ErrorKind::NoSpaceLeft => fl!(LOADER, "err-no-space"), + ErrorKind::Cancelled => fl!(LOADER, "err-cancelled"), + ErrorKind::ReadFailed => fl!(LOADER, "err-read"), + ErrorKind::WriteFailed => fl!(LOADER, "err-write"), + ErrorKind::NotInitialized => fl!(LOADER, "err-not-initialized"), + ErrorKind::AbiMismatch => fl!(LOADER, "err-abi-mismatch"), + ErrorKind::InvalidEntry => fl!(LOADER, "err-invalid-entry"), + } +} + #[derive(Debug)] pub struct LibError { pub error: CError, @@ -99,25 +130,10 @@ pub struct LibError { impl Display for LibError { fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult { - let message = match self.error.error { - ErrorKind::Unexpected => fl!(LOADER, "err-unexpected"), - ErrorKind::OutOfMemory => fl!(LOADER, "err-oom"), - ErrorKind::NotFound => fl!(LOADER, "err-not-found"), - ErrorKind::AlreadyExists => fl!(LOADER, "err-already-exists"), - ErrorKind::PermissionDenied => fl!(LOADER, "err-permission-denied"), - ErrorKind::InvalidPath => fl!(LOADER, "err-invalid-path"), - ErrorKind::NoSpaceLeft => fl!(LOADER, "err-no-space"), - ErrorKind::Cancelled => fl!(LOADER, "err-cancelled"), - ErrorKind::ReadFailed => fl!(LOADER, "err-read"), - ErrorKind::WriteFailed => fl!(LOADER, "err-write"), - ErrorKind::NotInitialized => fl!(LOADER, "err-not-initialized"), - ErrorKind::AbiMismatch => fl!(LOADER, "err-abi-mismatch"), - ErrorKind::InvalidEntry => fl!(LOADER, "err-invalid-entry"), - }; write!( formatter, "{} ({:?}: {})", - message, + error_kind_message(self.error.error), self.error.domain, StageName::from(&self.error) ) diff --git a/user/upac-cli/src/types/mod.rs b/user/upac-cli/src/types/mod.rs index 87730c79..3c43f1f5 100644 --- a/user/upac-cli/src/types/mod.rs +++ b/user/upac-cli/src/types/mod.rs @@ -9,7 +9,7 @@ use std::env::temp_dir; use std::ffi::CString; use std::sync::Arc; -use crate::libcore::Lib; +use super::libcore::Lib; pub mod abi; pub mod errors; diff --git a/user/upac-cli/src/types/progress.rs b/user/upac-cli/src/types/progress.rs index 07563e9d..1fc53775 100644 --- a/user/upac-cli/src/types/progress.rs +++ b/user/upac-cli/src/types/progress.rs @@ -35,8 +35,9 @@ pub unsafe extern "C" fn on_progress(event: *const CProgressEvent, ctx: *mut c_v pub struct ProgressState { pub(crate) bar: ProgressBar, - domain: ErrorDomain, pub(crate) is_bar: bool, + + domain: ErrorDomain, settings: ProgressSettings, } diff --git a/user/upac-cli/tests/inline/abi.rs b/user/upac-cli/tests/inline/abi.rs index b6410467..6774d4fc 100644 --- a/user/upac-cli/tests/inline/abi.rs +++ b/user/upac-cli/tests/inline/abi.rs @@ -3,92 +3,13 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::ffi::CString; - use upac_abi::error::{CError, ErrorDomain, ErrorKind}; -use upac_abi::types::CSlice; +use upac_abi::package::CPackageMeta; +use upac_abi::response::CListPackagesResponse; +use upac_abi::types::{COwned, CVec}; use crate::locale; -use crate::types::abi::{ - borrowed_vec, empty_slice, invoke, invoke_with_response, optional_slice, package_info, request_base, - slice_from_cstr, -}; - -fn as_str(slice: &CSlice) -> &str { - <&str>::try_from(slice).unwrap() -} - -#[test] -fn slice_from_cstr_preserves_the_bytes() { - let value = CString::new("hello").unwrap(); - - assert_eq!(as_str(&slice_from_cstr(&value)), "hello"); -} - -#[test] -fn empty_slice_is_null_and_zero_length() { - let slice = empty_slice(); - - assert!(slice.ptr.is_null()); - assert_eq!(slice.len, 0); -} - -#[test] -fn optional_slice_some_preserves_the_bytes() { - let value = CString::new("hello").unwrap(); - - assert_eq!(as_str(&optional_slice(Some(&value))), "hello"); -} - -#[test] -fn optional_slice_none_is_empty() { - let slice = optional_slice(None); - - assert!(slice.ptr.is_null()); - assert_eq!(slice.len, 0); -} - -#[test] -fn package_info_builds_the_expected_fields() { - let name = CString::new("upac").unwrap(); - let arch = CString::new("x86_64").unwrap(); - let arch_sub = CString::new("v3").unwrap(); - - let info = package_info(&name, &arch, Some(&arch_sub)); - - assert_eq!(as_str(&info.name), "upac"); - assert_eq!(as_str(&info.arch), "x86_64"); - assert_eq!(as_str(&info.arch_sub), "v3"); -} - -#[test] -fn package_info_without_arch_sub_leaves_it_empty() { - let name = CString::new("upac").unwrap(); - let arch = CString::new("x86_64").unwrap(); - - let info = package_info(&name, &arch, None); - - assert!(info.arch_sub.ptr.is_null()); -} - -#[test] -fn borrowed_vec_wraps_the_slice_without_copying() { - let items = [1u32, 2, 3]; - - let vec = borrowed_vec(&items); - - assert_eq!(vec.len, 3); - assert_eq!(vec.ptr, items.as_ptr() as *mut u32); -} - -#[test] -fn request_base_has_no_hook_and_a_non_null_cancel_token() { - let base = request_base(); - - assert!(base.on_hook.is_none()); - assert!(base.hook_ctx.is_null()); - assert!(!base.cancel_token.is_null()); -} +use crate::types::abi::{invoke, invoke_with_response}; #[test] fn invoke_returns_ok_on_a_zero_code() { @@ -112,20 +33,20 @@ fn invoke_propagates_the_localized_error_on_a_nonzero_code() { } #[test] -fn invoke_with_response_returns_the_response_on_a_zero_code() { - let result = invoke_with_response(|response: *mut u32, _error| { - unsafe { *response = 42 }; +fn invoke_with_response_returns_the_validated_response_on_a_zero_code() { + let result = invoke_with_response(|response: *mut CListPackagesResponse, _error| { + unsafe { *response = CListPackagesResponse::new(CVec::from_owned(Vec::::new())) }; 0 }); - assert_eq!(result.unwrap(), 42); + assert!(result.is_ok()); } #[test] fn invoke_with_response_propagates_the_localized_error_on_a_nonzero_code() { locale::init_for_test(); - let result = invoke_with_response(|_response: *mut u32, error| unsafe { + let result = invoke_with_response(|_response: *mut CListPackagesResponse, error| unsafe { *error = CError { domain: ErrorDomain::Install, state: 0, @@ -134,5 +55,12 @@ fn invoke_with_response_propagates_the_localized_error_on_a_nonzero_code() { 1 }); - assert_eq!(result.unwrap_err().to_string(), "File not found (Install: Pre-hooks)"); + assert_eq!(result.err().unwrap().to_string(), "File not found (Install: Pre-hooks)"); +} + +#[test] +fn invoke_with_response_rejects_an_unvalidated_response() { + let result = invoke_with_response(|_response: *mut CListPackagesResponse, _error| 0); + + assert!(result.is_err()); } diff --git a/user/upac-cli/tests/inline/progress.rs b/user/upac-cli/tests/inline/progress.rs index 2a6d7f4b..840bae73 100644 --- a/user/upac-cli/tests/inline/progress.rs +++ b/user/upac-cli/tests/inline/progress.rs @@ -8,10 +8,9 @@ use std::mem::size_of; use upac_abi::error::ErrorDomain; use upac_abi::hook::CProgressEvent; -use upac_abi::types::CSlice; +use upac_abi::types::{CBorrowed, CSlice}; use crate::locale; -use crate::types::abi::{empty_slice, slice_from_cstr}; use crate::types::progress::ProgressState; fn event(stage: u32, current: u64, total: u64, subject: CSlice) -> CProgressEvent { @@ -30,7 +29,7 @@ fn apply_with_zero_total_stays_on_spinner() { locale::init_for_test(); let mut state = ProgressState::new(ErrorDomain::Install); - state.apply(&event(0, 0, 0, empty_slice())); + state.apply(&event(0, 0, 0, CSlice::from_slice(None))); assert!(!state.is_bar); assert_eq!(state.bar.message(), "Pre-hooks"); @@ -41,7 +40,7 @@ fn apply_with_nonzero_total_switches_to_bar_and_sets_position() { locale::init_for_test(); let mut state = ProgressState::new(ErrorDomain::Install); - state.apply(&event(0, 3, 10, empty_slice())); + state.apply(&event(0, 3, 10, CSlice::from_slice(None))); assert!(state.is_bar); assert_eq!(state.bar.length(), Some(10)); @@ -54,7 +53,7 @@ fn apply_includes_subject_in_message_when_present() { let mut state = ProgressState::new(ErrorDomain::Install); let subject = CString::new("foo.txt").unwrap(); - state.apply(&event(0, 0, 0, slice_from_cstr(&subject))); + state.apply(&event(0, 0, 0, CSlice::from_borrowed(subject.as_bytes()))); assert_eq!(state.bar.message(), "Pre-hooks: foo.txt"); } From c8eb0d6d18cfa6952fb8ddff38b3031a7666156f Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 18:50:09 +0400 Subject: [PATCH 059/148] fix: removed unnecessary options, fixed design-related imports Co-Authored-By: Claude Sonnet 5 --- lib/pki/src/error.rs | 13 ------------- lib/pki/src/generate.rs | 9 +++++++++ lib/pki/src/signature.rs | 3 +++ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/pki/src/error.rs b/lib/pki/src/error.rs index a652beaa..d05a5fed 100644 --- a/lib/pki/src/error.rs +++ b/lib/pki/src/error.rs @@ -4,7 +4,6 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use std::array::TryFromSliceError; -use std::fmt::{Display, Formatter, Result}; use der::Error as DerError; @@ -17,18 +16,6 @@ pub enum PkiError { Generation, } -impl Display for PkiError { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - match self { - PkiError::Malformed => write!(f, "malformed PKI data"), - PkiError::InvalidSignature => write!(f, "invalid signature"), - PkiError::Generation => write!(f, "certificate generation failed"), - } - } -} - -impl std::error::Error for PkiError {} - impl From for PkiError { fn from(_: DerError) -> Self { PkiError::Malformed diff --git a/lib/pki/src/generate.rs b/lib/pki/src/generate.rs index dc4ef9dc..18671651 100644 --- a/lib/pki/src/generate.rs +++ b/lib/pki/src/generate.rs @@ -49,8 +49,11 @@ impl Identity for RootIdentity { fn from_bytes(serialized: &SerializedIdentity) -> Result { let key_pair = KeyPair::try_from(serialized.key_der.as_slice())?; + let certificate_der = CertificateDer::from(serialized.certificate_der.as_slice()); + let issuer = Issuer::from_ca_cert_der(&certificate_der, key_pair)?; + let certificate = Certificate::from_der(&serialized.certificate_der)?; Ok(RootIdentity { issuer, certificate }) @@ -65,7 +68,9 @@ impl Identity for RootIdentity { fn from_pem(pem: &PemIdentity) -> Result { let key_pair = KeyPair::from_pem(&pem.key_pem)?; + let issuer = Issuer::from_ca_cert_pem(&pem.certificate_pem, key_pair)?; + let certificate = Certificate::from_pem(pem.certificate_pem.as_bytes())?; Ok(RootIdentity { issuer, certificate }) @@ -87,6 +92,7 @@ impl Identity for SigningIdentity { fn from_bytes(serialized: &SerializedIdentity) -> Result { let key_pair = KeyPair::try_from(serialized.key_der.as_slice())?; + let certificate = Certificate::from_der(&serialized.certificate_der)?; Ok(SigningIdentity { key_pair, certificate }) @@ -101,6 +107,7 @@ impl Identity for SigningIdentity { fn from_pem(pem: &PemIdentity) -> Result { let key_pair = KeyPair::from_pem(&pem.key_pem)?; + let certificate = Certificate::from_pem(pem.certificate_pem.as_bytes())?; Ok(SigningIdentity { key_pair, certificate }) @@ -117,6 +124,7 @@ pub fn generate_root(common_name: &str) -> Result { params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign]; let key_pair = KeyPair::generate_for(&PKCS_ED25519)?; + let certificate_der = params.self_signed(&key_pair)?; let certificate = Certificate::from_der(certificate_der.der())?; @@ -135,6 +143,7 @@ pub fn generate_signing_cert(common_name: &str, root: &RootIdentity) -> Result Result { let signature_bytes = signing.key_pair.sign(hook_bytes)?; + let signature = Signature::try_from(signature_bytes.as_slice()).map_err(|_| PkiError::Malformed)?; Ok(HookSignature { @@ -129,6 +130,7 @@ impl HookSignature { let verifying_key = Self::extract_verifying_key(issuer_certificate)?; let tbs_der = certificate.tbs_certificate().to_der()?; + let signature = Signature::try_from(certificate.signature().raw_bytes()).map_err(|_| PkiError::Malformed)?; verifying_key @@ -144,6 +146,7 @@ impl HookSignature { .subject_public_key_info() .subject_public_key .raw_bytes(); + let key_bytes: [u8; 32] = key_bytes.try_into()?; VerifyingKey::from_bytes(&key_bytes).map_err(|_| PkiError::Malformed) From b2fc943559fc32f81b4ea73f5691a7a6a95635b1 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 18:50:18 +0400 Subject: [PATCH 060/148] fix: removed unnecessary options, fixed design-related imports Co-Authored-By: Claude Sonnet 5 --- user/sign-cli/src/commands/generate_cert.rs | 3 +++ user/sign-cli/src/commands/generate_root.rs | 2 ++ user/sign-cli/src/commands/sign_hook.rs | 4 ++++ user/sign-cli/src/commands/verify_hook.rs | 3 +++ user/sign-cli/src/errors.rs | 3 ++- user/sign-cli/src/main.rs | 2 -- 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/user/sign-cli/src/commands/generate_cert.rs b/user/sign-cli/src/commands/generate_cert.rs index 0df15478..490a4d4c 100644 --- a/user/sign-cli/src/commands/generate_cert.rs +++ b/user/sign-cli/src/commands/generate_cert.rs @@ -42,13 +42,16 @@ pub fn run(args: Args) -> Result<()> { certificate_pem: read_to_string(&args.root_cert) .with_context(|| format!("{}: {}", fl!(LOADER, "err-read"), args.root_cert.display()))?, }; + let root = RootIdentity::from_pem(&root_pem).map_err(LocalizedPkiError)?; let signing = generate_signing_cert(&args.common_name, &root).map_err(LocalizedPkiError)?; + let pem = signing.to_pem().map_err(LocalizedPkiError)?; write(&args.key_out, &pem.key_pem) .with_context(|| format!("{}: {}", fl!(LOADER, "err-write"), args.key_out.display()))?; + write(&args.cert_out, &pem.certificate_pem) .with_context(|| format!("{}: {}", fl!(LOADER, "err-write"), args.cert_out.display()))?; diff --git a/user/sign-cli/src/commands/generate_root.rs b/user/sign-cli/src/commands/generate_root.rs index c6b4b106..e0bd589d 100644 --- a/user/sign-cli/src/commands/generate_root.rs +++ b/user/sign-cli/src/commands/generate_root.rs @@ -33,10 +33,12 @@ pub struct Args { pub fn run(args: Args) -> Result<()> { let root = generate_root(&args.common_name).map_err(LocalizedPkiError)?; + let pem = root.to_pem().map_err(LocalizedPkiError)?; write(&args.key_out, &pem.key_pem) .with_context(|| format!("{}: {}", fl!(LOADER, "err-write"), args.key_out.display()))?; + write(&args.cert_out, &pem.certificate_pem) .with_context(|| format!("{}: {}", fl!(LOADER, "err-write"), args.cert_out.display()))?; diff --git a/user/sign-cli/src/commands/sign_hook.rs b/user/sign-cli/src/commands/sign_hook.rs index a0da1cc5..6db1920d 100644 --- a/user/sign-cli/src/commands/sign_hook.rs +++ b/user/sign-cli/src/commands/sign_hook.rs @@ -38,14 +38,18 @@ pub fn run(args: Args) -> Result<()> { let signing_pem = PemIdentity { key_pem: read_to_string(&args.key) .with_context(|| format!("{}: {}", fl!(LOADER, "err-read"), args.key.display()))?, + certificate_pem: read_to_string(&args.cert) .with_context(|| format!("{}: {}", fl!(LOADER, "err-read"), args.cert.display()))?, }; + let signing = SigningIdentity::from_pem(&signing_pem).map_err(LocalizedPkiError)?; let hook_bytes = read(&args.hook).with_context(|| format!("{}: {}", fl!(LOADER, "err-read"), args.hook.display()))?; + let signature = HookSignature::sign(&hook_bytes, &signing).map_err(LocalizedPkiError)?; + let signature_bytes = signature.to_bytes().map_err(LocalizedPkiError)?; write(&args.signature, signature_bytes) diff --git a/user/sign-cli/src/commands/verify_hook.rs b/user/sign-cli/src/commands/verify_hook.rs index 454d75ab..e8321777 100644 --- a/user/sign-cli/src/commands/verify_hook.rs +++ b/user/sign-cli/src/commands/verify_hook.rs @@ -34,12 +34,15 @@ pub struct Args { pub fn run(args: Args) -> Result<()> { let hook_bytes = read(&args.hook).with_context(|| format!("{}: {}", fl!(LOADER, "err-read"), args.hook.display()))?; + let signature_bytes = read(&args.signature).with_context(|| format!("{}: {}", fl!(LOADER, "err-read"), args.signature.display()))?; + let root_cert_pem = read_to_string(&args.root_cert) .with_context(|| format!("{}: {}", fl!(LOADER, "err-read"), args.root_cert.display()))?; let signature = HookSignature::from_bytes(&signature_bytes).map_err(LocalizedPkiError)?; + let root_certificate = RootCertificate::from_pem(&root_cert_pem).map_err(LocalizedPkiError)?; signature diff --git a/user/sign-cli/src/errors.rs b/user/sign-cli/src/errors.rs index 195d9724..f141dc2c 100644 --- a/user/sign-cli/src/errors.rs +++ b/user/sign-cli/src/errors.rs @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-only +use std::error::Error; use std::fmt::{Display, Formatter}; use i18n_embed_fl::fl; @@ -29,4 +30,4 @@ impl Display for LocalizedPkiError { } } -impl std::error::Error for LocalizedPkiError {} +impl Error for LocalizedPkiError {} diff --git a/user/sign-cli/src/main.rs b/user/sign-cli/src/main.rs index bb322b28..a31dc866 100644 --- a/user/sign-cli/src/main.rs +++ b/user/sign-cli/src/main.rs @@ -27,7 +27,6 @@ mod layout { } mod locale; -// ── CLI arguments ───────────────────────────────────────────────────────────── #[derive(Parser)] #[command(name = "up-si", author, version, about)] enum Command { @@ -37,7 +36,6 @@ enum Command { VerifyHook(commands::verify_hook::Args), } -// ── Entry points ─────────────────────────────────────────────────────────────── fn main() -> ExitCode { locale::init(); From 0262e69609842018f94b78f42b804f7ae84d7aad Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 8 Sep 2026 23:25:34 +0400 Subject: [PATCH 061/148] fix: rebuild boot-plugin loading around BootPlugins/BootPlugin, fix dynamic-plugins/builtin-booters feature unification in setup Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/plugin/boot/dynamic_link.rs | 46 +++--- lib/lib/src/plugin/boot/error.rs | 2 - lib/lib/src/plugin/boot/manifest.rs | 50 ++++--- lib/lib/src/plugin/boot/mod.rs | 186 ++++++------------------ lib/lib/src/plugin/boot/static_link.rs | 91 ++++-------- 5 files changed, 125 insertions(+), 250 deletions(-) diff --git a/lib/lib/src/plugin/boot/dynamic_link.rs b/lib/lib/src/plugin/boot/dynamic_link.rs index 081428ee..6763c964 100644 --- a/lib/lib/src/plugin/boot/dynamic_link.rs +++ b/lib/lib/src/plugin/boot/dynamic_link.rs @@ -5,13 +5,17 @@ use libloading::Library; -use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{ - AbiVersionFn, ConfirmBootFn, EspLoaderSourceFn, InstallFn, ProbeFn, RegisterBootSlotsFn, SetOneShotFn, -}; +use upac_abi::{BOOT_ABI_VERSION, BootPluginAbiVersionFn, ConfirmBootFn, InstallFn, SetOneShotFn}; use super::BootPlugin; use super::error::BootPluginError; +use super::manifest::BootPluginManifests; + +macro_rules! load_symbol { + ($library:expr, $name:literal) => { + unsafe { load_symbol(&$library, $name)? } + }; +} unsafe fn load_symbol(library: &Library, name: &str) -> Result { unsafe { library.get::(name.as_bytes()) } @@ -20,33 +24,39 @@ unsafe fn load_symbol(library: &Library, name: &str) -> Result Result { + pub(super) fn load_plugin(library_name: &str) -> Result { let library = unsafe { Library::new(library_name) }.map_err(|_| BootPluginError::Load)?; - let abi_version: AbiVersionFn = unsafe { load_symbol(&library, "abi_version")? }; - let probe: ProbeFn = unsafe { load_symbol(&library, "probe")? }; - let set_one_shot: SetOneShotFn = unsafe { load_symbol(&library, "set_one_shot")? }; - let confirm_boot: ConfirmBootFn = unsafe { load_symbol(&library, "confirm_boot")? }; - let esp_loader_source: EspLoaderSourceFn = unsafe { load_symbol(&library, "esp_loader_source")? }; - let register_boot_slots: RegisterBootSlotsFn = unsafe { load_symbol(&library, "register_boot_slots")? }; - let install: InstallFn = unsafe { load_symbol(&library, "install")? }; + let booter_abi_version: BootPluginAbiVersionFn = load_symbol!(library, "abi_version"); + let set_one_shot: SetOneShotFn = load_symbol!(library, "set_one_shot"); + let confirm_boot: ConfirmBootFn = load_symbol!(library, "confirm_boot"); + let install: InstallFn = load_symbol!(library, "install"); - let got = unsafe { abi_version() }; - if got != BOOT_ABI_VERSION { + let got_booter_abi_version = unsafe { booter_abi_version() }; + if got_booter_abi_version != BOOT_ABI_VERSION { return Err(BootPluginError::AbiMismatch { - got, + got: got_booter_abi_version, expected: BOOT_ABI_VERSION, }); } Ok(BootPlugin { - probe, set_one_shot, confirm_boot, - esp_loader_source, - register_boot_slots, + install, _library: Some(library), }) } } + +pub(super) fn load_boot_plugin_dynamic( + manifests: &BootPluginManifests, name: &str, +) -> Result { + let manifest = manifests + .0 + .get(name) + .ok_or_else(|| BootPluginError::UnknownName(name.to_owned()))?; + + BootPlugin::load_plugin(&manifest.library) +} diff --git a/lib/lib/src/plugin/boot/error.rs b/lib/lib/src/plugin/boot/error.rs index ca7434fa..7cc8f7ca 100644 --- a/lib/lib/src/plugin/boot/error.rs +++ b/lib/lib/src/plugin/boot/error.rs @@ -21,7 +21,6 @@ pub enum BootPluginError { DuplicateName(String), UnknownName(String), NoClaimant, - AmbiguousClaim, } impl From for BootPluginError { @@ -48,7 +47,6 @@ impl From for ErrorKind { BootPluginError::DuplicateName(_) => ErrorKind::InvalidEntry, BootPluginError::UnknownName(_) => ErrorKind::NotFound, BootPluginError::NoClaimant => ErrorKind::NotFound, - BootPluginError::AmbiguousClaim => ErrorKind::InvalidEntry, } } } diff --git a/lib/lib/src/plugin/boot/manifest.rs b/lib/lib/src/plugin/boot/manifest.rs index fc801b7f..ce088a7f 100644 --- a/lib/lib/src/plugin/boot/manifest.rs +++ b/lib/lib/src/plugin/boot/manifest.rs @@ -4,12 +4,14 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use std::collections::HashMap; -use std::fs; +use std::fs::{read_dir, read_to_string}; use std::io::ErrorKind; use serde::Deserialize; -use crate::plugin::boot::error::BootPluginError; +use super::error::BootPluginError; + +use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; #[derive(Debug, Clone, Deserialize)] pub struct BootPluginManifest { @@ -17,33 +19,35 @@ pub struct BootPluginManifest { pub library: String, } -pub fn load_boot_plugin_manifests( - boot_plugins_dir: &str, manifest_extension: &str, -) -> Result, BootPluginError> { - let mut manifests = HashMap::new(); +pub struct BootPluginManifests(pub HashMap); - let dir = match fs::read_dir(boot_plugins_dir) { - Ok(dir) => dir, - Err(error) if error.kind() == ErrorKind::NotFound => return Ok(manifests), - Err(error) => return Err(error.into()), - }; +impl BootPluginManifests { + pub fn new() -> Result { + let mut manifests = HashMap::new(); - for entry in dir { - let path = entry?.path(); + let dir = match read_dir(BOOT_PLUGINS_DIR) { + Ok(dir) => dir, + Err(error) if error.kind() == ErrorKind::NotFound => return Ok(BootPluginManifests(manifests)), + Err(error) => return Err(error.into()), + }; - if path.extension().and_then(|extension| extension.to_str()) != Some(manifest_extension) { - continue; - } + for entry in dir { + let path = entry?.path(); + + if path.extension().and_then(|extension| extension.to_str()) != Some(MANIFEST_EXTENSION) { + continue; + } - let raw = fs::read_to_string(&path)?; - let manifest: BootPluginManifest = toml::from_str(&raw)?; + let raw = read_to_string(&path)?; + let manifest: BootPluginManifest = toml::from_str(&raw)?; - if manifests.contains_key(&manifest.name) { - return Err(BootPluginError::DuplicateName(manifest.name)); + if manifests.contains_key(&manifest.name) { + return Err(BootPluginError::DuplicateName(manifest.name)); + } + + manifests.insert(manifest.name.clone(), manifest); } - manifests.insert(manifest.name.clone(), manifest); + Ok(BootPluginManifests(manifests)) } - - Ok(manifests) } diff --git a/lib/lib/src/plugin/boot/mod.rs b/lib/lib/src/plugin/boot/mod.rs index 46c3b627..ce49c46a 100644 --- a/lib/lib/src/plugin/boot/mod.rs +++ b/lib/lib/src/plugin/boot/mod.rs @@ -5,20 +5,24 @@ use std::mem::MaybeUninit; -use upac_abi::boot::{ - CBootPluginRequest, CBootSlotsRequest, CConfirmBootRequest, ConfirmBootFn, EspLoaderSourceFn, InstallFn, ProbeFn, - RegisterBootSlotsFn, SetOneShotFn, -}; use upac_abi::error::ErrorKind; -use upac_abi::types::{CBorrowed, CSlice}; +use upac_abi::request::{ + CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, +}; +use upac_abi::{ConfirmBootFn, InstallFn, SetOneShotFn}; + +use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginInstallRequest, BootPluginSetOneShotRequest}; -use crate::plugin::boot::error::BootPluginError; +use self::error::BootPluginError; + +#[cfg(all(feature = "dynamic-plugins", feature = "builtin-booters"))] +compile_error!("dynamic-plugins and builtin-booters are mutually exclusive"); #[cfg(feature = "dynamic-plugins")] use libloading::Library; #[cfg(feature = "dynamic-plugins")] -use crate::plugin::boot::manifest::load_boot_plugin_manifests; +use self::manifest::BootPluginManifests; pub mod error; @@ -31,107 +35,37 @@ mod dynamic_link; #[cfg(feature = "builtin-booters")] mod static_link; -/// Resolves a boot plugin by loading shared objects described by on-disk manifests. -/// -/// Built with `dynamic-plugins`: plugins are discovered at runtime from -/// `boot_plugins_dir`. Any plugin compiled in via `builtin-*` is still reachable -/// through [`static_link::static_plugins`], but on-disk manifests take part in the -/// same search. -#[cfg(feature = "dynamic-plugins")] -pub fn resolve_boot_plugin( - boot_plugins_dir: &str, manifest_extension: &str, requested: Option<&str>, -) -> Result { - let manifests = load_boot_plugin_manifests(boot_plugins_dir, manifest_extension)?; - - match requested { - Some(name) => { - if let Some(manifest) = manifests.get(name) { - return BootPlugin::load(&manifest.library); - } - - #[cfg(feature = "builtin-booters")] - if let Some((_, plugin)) = static_link::static_plugins() - .into_iter() - .find(|(plugin_name, _)| *plugin_name == name) - { - return Ok(plugin); - } - - Err(BootPluginError::UnknownName(name.to_owned())) - } - None => { - let mut claimants = Vec::new(); - for manifest in manifests.values() { - let plugin = BootPlugin::load(&manifest.library)?; - if plugin.probes() { - claimants.push(plugin); - } - } - - #[cfg(feature = "builtin-booters")] - for (_, plugin) in static_link::static_plugins() { - if plugin.probes() { - claimants.push(plugin); - } - } - - let mut claimants = claimants.into_iter(); - match (claimants.next(), claimants.next()) { - (Some(plugin), None) => Ok(plugin), - (None, _) => Err(BootPluginError::NoClaimant), - (Some(_), Some(_)) => Err(BootPluginError::AmbiguousClaim), - } - } - } +pub struct BootPlugins { + #[cfg(feature = "dynamic-plugins")] + manifests: BootPluginManifests, } -/// Resolves a boot plugin from the set compiled into this build. -/// -/// Built without `dynamic-plugins`: this binary contains no code path that loads -/// executable objects from disk. `boot_plugins_dir` and `manifest_extension` are -/// accepted to keep the signature stable across build configurations, and ignored. -/// -/// With no `builtin-*` feature enabled the candidate set is empty and every call -/// returns [`BootPluginError::NoClaimant`]. -#[cfg(not(feature = "dynamic-plugins"))] -pub fn resolve_boot_plugin( - _boot_plugins_dir: &str, _manifest_extension: &str, requested: Option<&str>, -) -> Result { - #[cfg(not(feature = "builtin-booters"))] - { - let _ = requested; - Err(BootPluginError::NoClaimant) +impl BootPlugins { + pub fn new() -> Result { + Ok(BootPlugins { + #[cfg(feature = "dynamic-plugins")] + manifests: BootPluginManifests::new()?, + }) } - #[cfg(feature = "builtin-booters")] - { - let plugins = static_link::static_plugins(); - - match requested { - Some(name) => plugins - .into_iter() - .find(|(plugin_name, _)| *plugin_name == name) - .map(|(_, plugin)| plugin) - .ok_or_else(|| BootPluginError::UnknownName(name.to_owned())), - None => { - let mut claimants = plugins.into_iter().filter(|(_, plugin)| plugin.probes()); - - match (claimants.next(), claimants.next()) { - (Some((_, plugin)), None) => Ok(plugin), - (None, _) => Err(BootPluginError::NoClaimant), - (Some(_), Some(_)) => Err(BootPluginError::AmbiguousClaim), - } - } + pub fn load(&self, name: &str) -> Result { + #[cfg(feature = "dynamic-plugins")] + return dynamic_link::load_boot_plugin_dynamic(&self.manifests, name); + + #[cfg(feature = "builtin-booters")] + return static_link::load_boot_plugin_static(name); + + #[cfg(not(any(feature = "dynamic-plugins", feature = "builtin-booters")))] + { + let _ = name; + Err(BootPluginError::NoClaimant) } } } pub struct BootPlugin { - probe: ProbeFn, set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, - esp_loader_source: EspLoaderSourceFn, - register_boot_slots: RegisterBootSlotsFn, install: InstallFn, #[cfg(feature = "dynamic-plugins")] @@ -139,71 +73,39 @@ pub struct BootPlugin { } impl BootPlugin { - pub fn probes(&self) -> bool { - unsafe { (self.probe)() == 1 } - } + pub fn set_one_shot(&self, request: BootPluginSetOneShotRequest) -> Result<(), BootPluginError> { + let request: CBootPluginSetOneShotRequest = request.into(); - pub fn set_one_shot(&self, entry_name: &str) -> Result<(), BootPluginError> { - let request = CBootPluginRequest::new(CSlice::from_borrowed(entry_name.as_bytes())); let mut error = MaybeUninit::::uninit(); - let code = unsafe { (self.set_one_shot)(&request, error.as_mut_ptr()) }; - if code != 0 { + let response_code = unsafe { (self.set_one_shot)(&request, error.as_mut_ptr()) }; + if response_code != 0 { return Err(BootPluginError::Reported(unsafe { error.assume_init() })); } Ok(()) } - pub fn confirm_boot(&self, entry_name: &str, esp_mount_point: &str) -> Result<(), BootPluginError> { - let request = CConfirmBootRequest::new( - CSlice::from_borrowed(entry_name.as_bytes()), - CSlice::from_borrowed(esp_mount_point.as_bytes()), - ); - let mut error = MaybeUninit::::uninit(); - - let code = unsafe { (self.confirm_boot)(&request, error.as_mut_ptr()) }; - if code != 0 { - return Err(BootPluginError::Reported(unsafe { error.assume_init() })); - } + pub fn confirm_boot(&self, request: BootPluginConfirmSuccsesBootRequest) -> Result<(), BootPluginError> { + let request: CBootPluginConfirmSuccsesBootRequest = request.into(); - Ok(()) - } - - pub fn esp_loader_source(&self) -> Option { - let slice = unsafe { (self.esp_loader_source)() }; - - Option::<&str>::try_from(&slice).ok().flatten().map(str::to_owned) - } - - pub fn register_boot_slots( - &self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, - esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, - ) -> Result<(), BootPluginError> { - let request = CBootSlotsRequest::new( - esp_partition_number, - esp_starting_lba, - esp_ending_lba, - esp_unique_partition_guid, - CSlice::from_borrowed(to_slot.as_bytes()), - CSlice::from_borrowed(from_slot.as_bytes()), - ); let mut error = MaybeUninit::::uninit(); - let code = unsafe { (self.register_boot_slots)(&request, error.as_mut_ptr()) }; - if code != 0 { + let response_code = unsafe { (self.confirm_boot)(&request, error.as_mut_ptr()) }; + if response_code != 0 { return Err(BootPluginError::Reported(unsafe { error.assume_init() })); } Ok(()) } - pub fn install(&self, esp_mount_point: &str) -> Result<(), BootPluginError> { - let request = CBootPluginRequest::new(CSlice::from_borrowed(esp_mount_point.as_bytes())); + pub fn install(&self, request: BootPluginInstallRequest) -> Result<(), BootPluginError> { + let request: CBootPluginInstallRequest = request.into(); + let mut error = MaybeUninit::::uninit(); - let code = unsafe { (self.install)(&request, error.as_mut_ptr()) }; - if code != 0 { + let response_code = unsafe { (self.install)(&request, error.as_mut_ptr()) }; + if response_code != 0 { return Err(BootPluginError::Reported(unsafe { error.assume_init() })); } diff --git a/lib/lib/src/plugin/boot/static_link.rs b/lib/lib/src/plugin/boot/static_link.rs index 433ec20c..7dea4df6 100644 --- a/lib/lib/src/plugin/boot/static_link.rs +++ b/lib/lib/src/plugin/boot/static_link.rs @@ -3,46 +3,33 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::boot::{ConfirmBootFn, EspLoaderSourceFn, InstallFn, ProbeFn, RegisterBootSlotsFn, SetOneShotFn}; +use upac_abi::{ConfirmBootFn, InstallFn, SetOneShotFn}; use super::BootPlugin; +use super::error::BootPluginError; #[cfg(feature = "builtin-grub")] -use upac_boot_grub::{ - confirm_boot as grub_confirm_boot, esp_loader_source as grub_esp_loader_source, install as grub_install, - probe as grub_probe, register_boot_slots as grub_register_boot_slots, set_one_shot as grub_set_one_shot, -}; +use upac_boot_grub::{confirm_boot as grub_confirm_boot, install as grub_install, set_one_shot as grub_set_one_shot}; #[cfg(feature = "builtin-systemd-boot")] use upac_boot_systemd_boot::{ - confirm_boot as systemd_boot_confirm_boot, esp_loader_source as systemd_boot_esp_loader_source, - install as systemd_boot_install, probe as systemd_boot_probe, - register_boot_slots as systemd_boot_register_boot_slots, set_one_shot as systemd_boot_set_one_shot, + confirm_boot as systemd_boot_confirm_boot, install as systemd_boot_install, + set_one_shot as systemd_boot_set_one_shot, }; #[cfg(feature = "builtin-uki")] -use upac_boot_uki::{ - confirm_boot as uki_confirm_boot, esp_loader_source as uki_esp_loader_source, install as uki_install, - probe as uki_probe, register_boot_slots as uki_register_boot_slots, set_one_shot as uki_set_one_shot, -}; +use upac_boot_uki::{confirm_boot as uki_confirm_boot, install as uki_install, set_one_shot as uki_set_one_shot}; #[cfg(feature = "builtin-refind")] use upac_boot_refind::{ - confirm_boot as refind_confirm_boot, esp_loader_source as refind_esp_loader_source, install as refind_install, - probe as refind_probe, register_boot_slots as refind_register_boot_slots, set_one_shot as refind_set_one_shot, + confirm_boot as refind_confirm_boot, install as refind_install, set_one_shot as refind_set_one_shot, }; impl BootPlugin { - fn from_static( - probe: ProbeFn, set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, esp_loader_source: EspLoaderSourceFn, - register_boot_slots: RegisterBootSlotsFn, install: InstallFn, - ) -> Self { + fn load_plugin_from_static(set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, install: InstallFn) -> Self { BootPlugin { - probe, set_one_shot, confirm_boot, - esp_loader_source, - register_boot_slots, install, #[cfg(feature = "dynamic-plugins")] @@ -51,68 +38,42 @@ impl BootPlugin { } } -/// The boot plugins linked into this build, in probe order. -/// -/// No ABI version check is performed here: these are compiled from the same source -/// tree by the same compiler, so `BOOT_ABI_VERSION` matches by construction. -#[allow( - clippy::vec_init_then_push, - reason = "each push is independently cfg-gated, vec![] can't express that" -)] -pub(super) fn static_plugins() -> Vec<(&'static str, BootPlugin)> { - let mut plugins = Vec::new(); - +pub(super) fn load_boot_plugin_static(name: &str) -> Result { #[cfg(feature = "builtin-uki")] - plugins.push(( - "uki", - BootPlugin::from_static( - uki_probe, + if name == "uki" { + return Ok(BootPlugin::load_plugin_from_static( uki_set_one_shot, uki_confirm_boot, - uki_esp_loader_source, - uki_register_boot_slots, uki_install, - ), - )); + )); + } #[cfg(feature = "builtin-systemd-boot")] - plugins.push(( - "systemd-boot", - BootPlugin::from_static( - systemd_boot_probe, + if name == "systemd-boot" { + return Ok(BootPlugin::load_plugin_from_static( systemd_boot_set_one_shot, systemd_boot_confirm_boot, - systemd_boot_esp_loader_source, - systemd_boot_register_boot_slots, systemd_boot_install, - ), - )); + )); + } #[cfg(feature = "builtin-grub")] - plugins.push(( - "grub", - BootPlugin::from_static( - grub_probe, + if name == "grub" { + return Ok(BootPlugin::load_plugin_from_static( grub_set_one_shot, grub_confirm_boot, - grub_esp_loader_source, - grub_register_boot_slots, grub_install, - ), - )); + )); + } #[cfg(feature = "builtin-refind")] - plugins.push(( - "refind", - BootPlugin::from_static( - refind_probe, + if name == "refind" { + return Ok(BootPlugin::load_plugin_from_static( refind_set_one_shot, refind_confirm_boot, - refind_esp_loader_source, - refind_register_boot_slots, refind_install, - ), - )); + )); + } - plugins + Err(BootPluginError::UnknownName(name.to_owned())) } From 84ae14b10d534e7802aeaa65066a67a4b88c2fd4 Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 9 Sep 2026 23:16:49 +0400 Subject: [PATCH 062/148] fix: require boot_plugin on install/update/uninstall/rollback/files requests Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/request.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/abi/src/request.rs b/lib/abi/src/request.rs index aaa726f1..e25f57de 100644 --- a/lib/abi/src/request.rs +++ b/lib/abi/src/request.rs @@ -31,6 +31,8 @@ pub struct CInstallRequest { pub struct_size: usize, pub base: CRequestBase, + pub boot_plugin: CSlice, + pub tmp_path: CSlice, pub subject: CSlice, @@ -38,8 +40,7 @@ pub struct CInstallRequest { pub message: CSlice, pub packages: CVec, - #[optional] - pub boot_plugin: CSlice, + pub allow_conflict_files: bool, } @@ -49,6 +50,8 @@ pub struct CUpdateRequest { pub struct_size: usize, pub base: CRequestBase, + pub boot_plugin: CSlice, + pub tmp_path: CSlice, pub subject: CSlice, @@ -56,8 +59,7 @@ pub struct CUpdateRequest { pub message: CSlice, pub packages: CVec, - #[optional] - pub boot_plugin: CSlice, + pub allow_downgrade: bool, pub allow_conflict_files: bool, } @@ -68,13 +70,14 @@ pub struct CUninstallRequest { pub struct_size: usize, pub base: CRequestBase, + pub boot_plugin: CSlice, + pub tmp_path: CSlice, pub subject: CSlice, #[optional] pub message: CSlice, pub packages: CVec, - #[optional] - pub boot_plugin: CSlice, + pub purge: bool, } @@ -84,10 +87,10 @@ pub struct CRollbackRequest { pub struct_size: usize, pub base: CRequestBase, + pub boot_plugin: CSlice, + pub tmp_path: CSlice, pub config_digest: CSlice, - #[optional] - pub boot_plugin: CSlice, } #[repr(C)] @@ -108,16 +111,17 @@ pub struct CFilesRequest { pub struct_size: usize, pub base: CRequestBase, + pub boot_plugin: CSlice, + pub tmp_path: CSlice, pub subject: CSlice, #[optional] pub message: CSlice, pub files: CVec, pub file_kind: FileDiffKind, - pub scope: DiffFileSource, pub file_package: *const CPackageInfo, - #[optional] - pub boot_plugin: CSlice, + + pub scope: DiffFileSource, } #[repr(C)] From 422c520d9ae35e02d643b336a6ac2f02092f84cd Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 9 Sep 2026 23:33:36 +0400 Subject: [PATCH 063/148] fix: require boot_plugin in types::request mirrors, matching abi Co-Authored-By: Claude Sonnet 5 --- lib/types/src/request.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/types/src/request.rs b/lib/types/src/request.rs index d3729c5d..6fedfe30 100644 --- a/lib/types/src/request.rs +++ b/lib/types/src/request.rs @@ -33,11 +33,15 @@ pub struct RequestBase { #[derive(Debug, Clone, RustToC)] pub struct InstallRequest { pub base: RequestBase, + pub tmp_path: String, + pub subject: String, pub message: Option, pub packages: Vec, - pub boot_plugin: Option, + + pub boot_plugin: String, + pub allow_conflict_files: bool, } @@ -45,10 +49,14 @@ pub struct InstallRequest { pub struct UpdateRequest { pub base: RequestBase, pub tmp_path: String, + pub subject: String, pub message: Option, + pub packages: Vec, - pub boot_plugin: Option, + + pub boot_plugin: String, + pub allow_downgrade: bool, pub allow_conflict_files: bool, } @@ -56,11 +64,16 @@ pub struct UpdateRequest { #[derive(Debug, Clone, RustToC)] pub struct UninstallRequest { pub base: RequestBase, + pub tmp_path: String, + pub subject: String, pub message: Option, + pub packages: Vec, - pub boot_plugin: Option, + + pub boot_plugin: String, + pub purge: bool, } @@ -69,7 +82,7 @@ pub struct RollbackRequest { pub base: RequestBase, pub tmp_path: String, pub config_digest: String, - pub boot_plugin: Option, + pub boot_plugin: String, } #[derive(Debug, Clone, RustToC)] @@ -83,14 +96,19 @@ pub struct CommitRequest { #[derive(Debug, Clone, RustToC)] pub struct FilesRequest { pub base: RequestBase, + pub tmp_path: String, + pub subject: String, pub message: Option, + pub files: Vec, pub file_kind: FileDiffKind, - pub scope: DiffFileSource, pub file_package: *const CPackageInfo, - pub boot_plugin: Option, + + pub boot_plugin: String, + + pub scope: DiffFileSource, } #[derive(Debug, Clone, RustToC)] From 3168497d53acd9e1158f4a592652ddb3825a5484 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 00:02:31 +0400 Subject: [PATCH 064/148] fix: require boot_plugin end-to-end in lib mutating commands, switch to BootPlugins::load Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/mutated/files/apply.rs | 4 ++-- lib/lib/src/mutated/files/checkout.rs | 9 ++------- lib/lib/src/mutated/files/mod.rs | 16 ++++++++++------ lib/lib/src/mutated/files/open.rs | 5 +++-- lib/lib/src/mutated/files/swap.rs | 6 +++++- lib/lib/src/mutated/installer/checkout.rs | 5 ++--- lib/lib/src/mutated/installer/mod.rs | 15 +++++++++++---- lib/lib/src/mutated/installer/swap.rs | 5 ++++- lib/lib/src/mutated/rollback/checkout.rs | 7 +++---- lib/lib/src/mutated/rollback/mod.rs | 10 +++++++--- lib/lib/src/mutated/rollback/swap.rs | 5 ++++- lib/lib/src/mutated/uninstaller/checkout.rs | 7 +++---- lib/lib/src/mutated/uninstaller/mod.rs | 14 ++++++++++---- lib/lib/src/mutated/uninstaller/swap.rs | 5 ++++- lib/lib/src/mutated/update/checkout.rs | 7 +++---- lib/lib/src/mutated/update/mod.rs | 12 +++++++++--- lib/lib/src/mutated/update/swap.rs | 5 ++++- 17 files changed, 86 insertions(+), 51 deletions(-) diff --git a/lib/lib/src/mutated/files/apply.rs b/lib/lib/src/mutated/files/apply.rs index 1e4bc5ab..d48b00cf 100644 --- a/lib/lib/src/mutated/files/apply.rs +++ b/lib/lib/src/mutated/files/apply.rs @@ -18,7 +18,7 @@ use upac_types::entry::{FileEntry, FileEntryScope}; use upac_types::hook::ProgressEventBuilder; use super::{ - EtcUpperDir, FilesError, PendingFiles, RequestedFileKind, RequestedFileScope, TargetUuid, TotalFiles, + ConfigUpperDir, FilesError, PendingFiles, RequestedFileKind, RequestedFileScope, TargetUuid, TotalFiles, WorkingDatabase, WorkingTree, }; @@ -43,7 +43,7 @@ impl Stage for ApplyFileStage { let mut woking_database = ctx_take!(context, WorkingDatabase); let mut import_ctx = ctx_take!(context, ImportContext); - let config_upper_dir = ctx_get!(context, EtcUpperDir); + let config_upper_dir = ctx_get!(context, ConfigUpperDir); let uuid = ctx_get!(context, TargetUuid); let file_kind = ctx_get!(context, RequestedFileKind); let scope = ctx_get!(context, RequestedFileScope); diff --git a/lib/lib/src/mutated/files/checkout.rs b/lib/lib/src/mutated/files/checkout.rs index 9ac0847d..294b4888 100644 --- a/lib/lib/src/mutated/files/checkout.rs +++ b/lib/lib/src/mutated/files/checkout.rs @@ -9,11 +9,10 @@ use upac_types::hook::ProgressEventBuilder; use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; use crate::deploy::{Deploy, find_esp_mount}; -use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; use crate::mutated::files::{FilesError, NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry}; use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::plugin::boot::resolve_boot_plugin; +use crate::plugin::boot::BootPlugins; pub struct CheckoutStage; @@ -32,11 +31,7 @@ impl Stage for CheckoutStage { let esp_mount = find_esp_mount()?; let entry_name = write_boot_entry(&repository, &deploy_tree, digest, &esp_mount, &new_prefix.0)?; - let plugin = resolve_boot_plugin( - BOOT_PLUGINS_DIR, - MANIFEST_EXTENSION, - requested_boot_plugins.0.as_deref(), - )?; + let plugin = BootPlugins::new()?.load(&requested_boot_plugins.0)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/files/mod.rs b/lib/lib/src/mutated/files/mod.rs index 8a2de9fc..0cdab519 100644 --- a/lib/lib/src/mutated/files/mod.rs +++ b/lib/lib/src/mutated/files/mod.rs @@ -58,7 +58,7 @@ pub(crate) struct RequestedFilePackage { pub(crate) struct NewPrefixDigest(pub String); pub(crate) struct Subject(pub String); pub(crate) struct CommitMessage(pub Option); -pub(crate) struct RequestedBootPlugin(pub Option); +pub(crate) struct RequestedBootPlugin(pub String); pub(crate) struct ResolvedBootEntry { pub plugin: BootPlugin, pub entry_name: String, @@ -69,7 +69,7 @@ pub(crate) struct TotalFiles(pub u64); pub(crate) struct WorkingTree(pub FileSystem); pub(crate) struct WorkingDatabase(pub MemoryDatabase); pub(crate) struct TargetUuid(pub Uuid); -pub(crate) struct EtcUpperDir(pub PathBuf); +pub(crate) struct ConfigUpperDir(pub PathBuf); pub struct FilesPackage<'a> { pub name: &'a str, @@ -92,11 +92,13 @@ impl<'a> TryFrom<&'a CPackageInfo> for FilesPackage<'a> { } pub struct FilesData<'a> { + pub scope: DiffFileSource, + pub files: Vec<&'a str>, pub file_kind: FileDiffKind, - pub scope: DiffFileSource, pub file_package: FilesPackage<'a>, - pub boot_plugin: Option<&'a str>, + + pub boot_plugin: &'a str, pub tmp_path: &'a str, @@ -120,10 +122,12 @@ impl<'a> TryFrom<&'a CFilesRequest> for FilesData<'a> { let cancel_token = unsafe { &*request.base.cancel_token }; Ok(FilesData { + scope: request.scope, + files: Vec::try_from(&request.files)?, file_kind: request.file_kind, - scope: request.scope, file_package: FilesPackage::try_from(file_package)?, + boot_plugin: (&request.boot_plugin).try_into()?, tmp_path: (&request.tmp_path).try_into()?, @@ -160,7 +164,7 @@ pub fn run(data: FilesData) -> Result<(), (FilesStateId, FilesError)> { context.put(TmpPath(data.tmp_path.to_owned())); context.put(Subject(data.subject.to_owned())); context.put(CommitMessage(data.message.map(str::to_owned))); - context.put(RequestedBootPlugin(data.boot_plugin.map(str::to_owned))); + context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); let orchestrator = assemble(); diff --git a/lib/lib/src/mutated/files/open.rs b/lib/lib/src/mutated/files/open.rs index 163670a4..abfc57f9 100644 --- a/lib/lib/src/mutated/files/open.rs +++ b/lib/lib/src/mutated/files/open.rs @@ -11,7 +11,8 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; use super::{ - EtcUpperDir, FilesError, PendingFiles, RequestedFilePackage, TargetUuid, TotalFiles, WorkingDatabase, WorkingTree, + ConfigUpperDir, FilesError, PendingFiles, RequestedFilePackage, TargetUuid, TotalFiles, WorkingDatabase, + WorkingTree, }; use crate::composefs::file::FileHandle; @@ -55,7 +56,7 @@ impl Stage for OpenTransactionStage { context.put(WorkingTree(tree)); context.put(WorkingDatabase(database)); context.put(ImportContext::default()); - context.put(EtcUpperDir(config_upper_dir)); + context.put(ConfigUpperDir(config_upper_dir)); context.put(TargetUuid(uuid)); context.put(PendingFiles(pending)); context.put(TotalFiles(total)); diff --git a/lib/lib/src/mutated/files/swap.rs b/lib/lib/src/mutated/files/swap.rs index 53e8ca99..fa25e6e2 100644 --- a/lib/lib/src/mutated/files/swap.rs +++ b/lib/lib/src/mutated/files/swap.rs @@ -4,7 +4,9 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use upac_abi::hook::CancelToken; + use upac_types::hook::ProgressEventBuilder; +use upac_types::request::BootPluginSetOneShotRequest; use super::{FilesError, ResolvedBootEntry}; @@ -19,7 +21,9 @@ impl Stage for SwapStage { ) -> Result<(ProgressEventBuilder, StageResult, Box), FilesError> { let resolved = ctx_take!(context, ResolvedBootEntry); - resolved.plugin.set_one_shot(&resolved.entry_name)?; + resolved.plugin.set_one_shot(BootPluginSetOneShotRequest { + entry_name: resolved.entry_name, + })?; Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/installer/checkout.rs b/lib/lib/src/mutated/installer/checkout.rs index f8bf3103..674353e8 100644 --- a/lib/lib/src/mutated/installer/checkout.rs +++ b/lib/lib/src/mutated/installer/checkout.rs @@ -13,10 +13,9 @@ use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; use crate::deploy::Deploy; use crate::deploy::find_esp_mount; -use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::plugin::boot::resolve_boot_plugin; +use crate::plugin::boot::BootPlugins; pub struct CheckoutStage; @@ -35,7 +34,7 @@ impl Stage for CheckoutStage { let esp_mount = find_esp_mount()?; let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_prefix.0)?; - let plugin = resolve_boot_plugin(BOOT_PLUGINS_DIR, MANIFEST_EXTENSION, requested.0.as_deref())?; + let plugin = BootPlugins::new()?.load(&requested_boot_plugins.0)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/installer/mod.rs b/lib/lib/src/mutated/installer/mod.rs index d34d381d..06085a99 100644 --- a/lib/lib/src/mutated/installer/mod.rs +++ b/lib/lib/src/mutated/installer/mod.rs @@ -57,8 +57,9 @@ pub(crate) struct NewPrefixDigest(pub String); pub(crate) struct NewConfigDefaults(pub FileSystem); pub(crate) struct Subject(pub String); pub(crate) struct CommitMessage(pub Option); -pub(crate) struct RequestedBootPlugin(pub Option); pub(crate) struct AllowConflictFiles(pub bool); + +pub(crate) struct RequestedBootPlugin(pub String); pub(crate) struct ResolvedBootEntry { pub plugin: BootPlugin, pub entry_name: String, @@ -66,17 +67,21 @@ pub(crate) struct ResolvedBootEntry { pub(crate) struct PendingPackagePaths(pub VecDeque); pub(crate) struct UnpackerState(pub PackageUnpacker); + pub(crate) struct PendingPackages(pub VecDeque<(PackageTemp, DeclarativeTrigger)>); pub(crate) struct TotalPackages(pub u64); + pub(crate) struct ImportedTree(pub FileSystem); pub(crate) struct ImportedConfigDefaults(pub FileSystem); pub(crate) struct ImportedDatabase(pub MemoryDatabase); pub struct InstallData<'a> { pub packages: Vec<&'a str>, - pub boot_plugin: Option<&'a str>, + pub allow_conflict_files: bool, + pub boot_plugin: &'a str, + pub tmp_path: &'a str, pub subject: &'a str, @@ -98,9 +103,11 @@ impl<'a> TryFrom<&'a CInstallRequest> for InstallData<'a> { Ok(InstallData { packages: Vec::try_from(&request.packages)?, - boot_plugin: (&request.boot_plugin).try_into()?, + allow_conflict_files: request.allow_conflict_files, + boot_plugin: (&request.boot_plugin).try_into()?, + tmp_path: (&request.tmp_path).try_into()?, subject: (&request.subject).try_into()?, @@ -133,7 +140,7 @@ pub fn run(data: InstallData) -> Result<(), (InstallStateId, InstallError)> { context.put(TmpPath(data.tmp_path.to_owned())); context.put(Subject(data.subject.to_owned())); context.put(CommitMessage(data.message.map(str::to_owned))); - context.put(RequestedBootPlugin(data.boot_plugin.map(str::to_owned))); + context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); context.put(AllowConflictFiles(data.allow_conflict_files)); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); diff --git a/lib/lib/src/mutated/installer/swap.rs b/lib/lib/src/mutated/installer/swap.rs index 8610fe56..e4f03ffa 100644 --- a/lib/lib/src/mutated/installer/swap.rs +++ b/lib/lib/src/mutated/installer/swap.rs @@ -6,6 +6,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; +use upac_types::request::BootPluginSetOneShotRequest; use super::{InstallError, ResolvedBootEntry}; @@ -20,7 +21,9 @@ impl Stage for SwapStage { ) -> Result<(ProgressEventBuilder, StageResult, Box), InstallError> { let resolved = ctx_take!(context, ResolvedBootEntry); - resolved.plugin.set_one_shot(&resolved.entry_name)?; + resolved.plugin.set_one_shot(BootPluginSetOneShotRequest { + entry_name: resolved.entry_name, + })?; Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/rollback/checkout.rs b/lib/lib/src/mutated/rollback/checkout.rs index 124ee3b4..9f943e2f 100644 --- a/lib/lib/src/mutated/rollback/checkout.rs +++ b/lib/lib/src/mutated/rollback/checkout.rs @@ -12,10 +12,9 @@ use super::{RequestedBootPlugin, ResolvedBootEntry, RollbackError, TargetPrefixD use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; use crate::deploy::{Deploy, find_esp_mount}; -use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::plugin::boot::resolve_boot_plugin; +use crate::plugin::boot::BootPlugins; pub struct CheckoutStage; @@ -25,7 +24,7 @@ impl Stage for CheckoutStage { ) -> Result<(ProgressEventBuilder, StageResult, Box), RollbackError> { let target = ctx_get!(context, TargetPrefixDigest); let deploy = ctx_get!(context, Deploy); - let requested = ctx_get!(context, RequestedBootPlugin); + let requested_boot_plugins = ctx_get!(context, RequestedBootPlugin); let repository = deploy.open_repository()?; let tree = deploy.open_tree(&target.0)?; @@ -34,7 +33,7 @@ impl Stage for CheckoutStage { let esp_mount = find_esp_mount()?; let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &target.0)?; - let plugin = resolve_boot_plugin(BOOT_PLUGINS_DIR, MANIFEST_EXTENSION, requested.0.as_deref())?; + let plugin = BootPlugins::new()?.load(&requested_boot_plugins.0)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/rollback/mod.rs b/lib/lib/src/mutated/rollback/mod.rs index ecae6db2..fb8f7c55 100644 --- a/lib/lib/src/mutated/rollback/mod.rs +++ b/lib/lib/src/mutated/rollback/mod.rs @@ -35,8 +35,10 @@ mod merge; mod swap; pub(crate) struct RequestedConfigDigest(pub String); -pub(crate) struct RequestedBootPlugin(pub Option); pub(crate) struct TargetPrefixDigest(pub String); + +pub(crate) struct RequestedBootPlugin(pub String); + pub(crate) struct ResolvedBootEntry { pub plugin: BootPlugin, pub entry_name: String, @@ -44,7 +46,8 @@ pub(crate) struct ResolvedBootEntry { pub struct RollbackData<'a> { pub config_digest: &'a str, - pub boot_plugin: Option<&'a str>, + + pub boot_plugin: &'a str, pub tmp_path: &'a str, @@ -64,6 +67,7 @@ impl<'a> TryFrom<&'a CRollbackRequest> for RollbackData<'a> { Ok(RollbackData { config_digest: (&request.config_digest).try_into()?, + boot_plugin: (&request.boot_plugin).try_into()?, tmp_path: (&request.tmp_path).try_into()?, @@ -83,7 +87,7 @@ pub fn run(data: RollbackData) -> Result<(), (RollbackStateId, RollbackError)> { let mut context = Context::new(); context.put(deploy); context.put(RequestedConfigDigest(data.config_digest.to_owned())); - context.put(RequestedBootPlugin(data.boot_plugin.map(str::to_owned))); + context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); context.put(TmpPath(data.tmp_path.to_owned())); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); diff --git a/lib/lib/src/mutated/rollback/swap.rs b/lib/lib/src/mutated/rollback/swap.rs index 4e712408..0c084691 100644 --- a/lib/lib/src/mutated/rollback/swap.rs +++ b/lib/lib/src/mutated/rollback/swap.rs @@ -6,6 +6,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; +use upac_types::request::BootPluginSetOneShotRequest; use super::{ResolvedBootEntry, RollbackError}; @@ -20,7 +21,9 @@ impl Stage for SwapStage { ) -> Result<(ProgressEventBuilder, StageResult, Box), RollbackError> { let resolved = ctx_take!(context, ResolvedBootEntry); - resolved.plugin.set_one_shot(&resolved.entry_name)?; + resolved.plugin.set_one_shot(BootPluginSetOneShotRequest { + entry_name: resolved.entry_name, + })?; Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/uninstaller/checkout.rs b/lib/lib/src/mutated/uninstaller/checkout.rs index a46291ac..327bafd9 100644 --- a/lib/lib/src/mutated/uninstaller/checkout.rs +++ b/lib/lib/src/mutated/uninstaller/checkout.rs @@ -12,10 +12,9 @@ use super::{NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry, UninstallEr use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; use crate::deploy::{Deploy, find_esp_mount}; -use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::plugin::boot::resolve_boot_plugin; +use crate::plugin::boot::BootPlugins; pub struct CheckoutStage; @@ -25,7 +24,7 @@ impl Stage for CheckoutStage { ) -> Result<(ProgressEventBuilder, StageResult, Box), UninstallError> { let new_prefix = ctx_get!(context, NewPrefixDigest); let deploy = ctx_get!(context, Deploy); - let requested = ctx_get!(context, RequestedBootPlugin); + let requested_boot_plugins = ctx_get!(context, RequestedBootPlugin); let repository = deploy.open_repository()?; let tree = deploy.open_tree(&new_prefix.0)?; @@ -34,7 +33,7 @@ impl Stage for CheckoutStage { let esp_mount = find_esp_mount()?; let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_prefix.0)?; - let plugin = resolve_boot_plugin(BOOT_PLUGINS_DIR, MANIFEST_EXTENSION, requested.0.as_deref())?; + let plugin = BootPlugins::new()?.load(&requested_boot_plugins.0)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/uninstaller/mod.rs b/lib/lib/src/mutated/uninstaller/mod.rs index 4455340e..1dd9ecfe 100644 --- a/lib/lib/src/mutated/uninstaller/mod.rs +++ b/lib/lib/src/mutated/uninstaller/mod.rs @@ -56,8 +56,10 @@ pub(crate) struct NewPrefixDigest(pub String); pub(crate) struct RemovedConfigPaths(pub Vec); pub(crate) struct Subject(pub String); pub(crate) struct CommitMessage(pub Option); -pub(crate) struct RequestedBootPlugin(pub Option); + pub(crate) struct Purge(pub bool); + +pub(crate) struct RequestedBootPlugin(pub String); pub(crate) struct ResolvedBootEntry { pub plugin: BootPlugin, pub entry_name: String, @@ -91,9 +93,11 @@ impl<'a> TryFrom<&'a CPackageInfo> for UninstallPackage<'a> { pub struct UninstallData<'a> { pub packages: Vec>, - pub boot_plugin: Option<&'a str>, + pub purge: bool, + pub boot_plugin: &'a str, + pub tmp_path: &'a str, pub subject: &'a str, @@ -115,9 +119,11 @@ impl<'a> TryFrom<&'a CUninstallRequest> for UninstallData<'a> { Ok(UninstallData { packages: Vec::try_from(&request.packages)?, - boot_plugin: (&request.boot_plugin).try_into()?, + purge: request.purge, + boot_plugin: (&request.boot_plugin).try_into()?, + tmp_path: (&request.tmp_path).try_into()?, subject: (&request.subject).try_into()?, @@ -152,7 +158,7 @@ pub fn run(data: UninstallData) -> Result<(), (UninstallStateId, UninstallError) context.put(TmpPath(data.tmp_path.to_owned())); context.put(Subject(data.subject.to_owned())); context.put(CommitMessage(data.message.map(str::to_owned))); - context.put(RequestedBootPlugin(data.boot_plugin.map(str::to_owned))); + context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); context.put(Purge(data.purge)); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); diff --git a/lib/lib/src/mutated/uninstaller/swap.rs b/lib/lib/src/mutated/uninstaller/swap.rs index cc5b4214..e4f6f896 100644 --- a/lib/lib/src/mutated/uninstaller/swap.rs +++ b/lib/lib/src/mutated/uninstaller/swap.rs @@ -6,6 +6,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; +use upac_types::request::BootPluginSetOneShotRequest; use super::{ResolvedBootEntry, UninstallError}; @@ -20,7 +21,9 @@ impl Stage for SwapStage { ) -> Result<(ProgressEventBuilder, StageResult, Box), UninstallError> { let resolved = ctx_take!(context, ResolvedBootEntry); - resolved.plugin.set_one_shot(&resolved.entry_name)?; + resolved.plugin.set_one_shot(BootPluginSetOneShotRequest { + entry_name: resolved.entry_name, + })?; Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/update/checkout.rs b/lib/lib/src/mutated/update/checkout.rs index 2ae11d56..edeb9202 100644 --- a/lib/lib/src/mutated/update/checkout.rs +++ b/lib/lib/src/mutated/update/checkout.rs @@ -12,10 +12,9 @@ use super::{NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry, UpdateError use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; use crate::deploy::{Deploy, find_esp_mount}; -use crate::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::plugin::boot::resolve_boot_plugin; +use crate::plugin::boot::BootPlugins; pub struct CheckoutStage; @@ -25,7 +24,7 @@ impl Stage for CheckoutStage { ) -> Result<(ProgressEventBuilder, StageResult, Box), UpdateError> { let new_prefix = ctx_get!(context, NewPrefixDigest); let deploy = ctx_get!(context, Deploy); - let requested = ctx_get!(context, RequestedBootPlugin); + let requested_boot_plugins = ctx_get!(context, RequestedBootPlugin); let repository = deploy.open_repository()?; let tree = deploy.open_tree(&new_prefix.0)?; @@ -34,7 +33,7 @@ impl Stage for CheckoutStage { let esp_mount = find_esp_mount()?; let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_prefix.0)?; - let plugin = resolve_boot_plugin(BOOT_PLUGINS_DIR, MANIFEST_EXTENSION, requested.0.as_deref())?; + let plugin = BootPlugins::new()?.load(&requested_boot_plugins.0)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/update/mod.rs b/lib/lib/src/mutated/update/mod.rs index 881b65a4..5d46476c 100644 --- a/lib/lib/src/mutated/update/mod.rs +++ b/lib/lib/src/mutated/update/mod.rs @@ -58,11 +58,13 @@ pub(crate) struct NewConfigDefaults(pub FileSystem); pub(crate) struct RemovedConfigPaths(pub Vec); pub(crate) struct Subject(pub String); pub(crate) struct CommitMessage(pub Option); -pub(crate) struct RequestedBootPlugin(pub Option); + +pub(crate) struct RequestedBootPlugin(pub String); pub(crate) struct ResolvedBootEntry { pub plugin: BootPlugin, pub entry_name: String, } + pub(crate) struct AllowDowngrade(pub bool); pub(crate) struct AllowConflictFiles(pub bool); @@ -77,7 +79,9 @@ pub(crate) struct ImportedRemovedConfigPaths(pub Vec); pub struct UpdateData<'a> { pub packages: Vec<&'a str>, - pub boot_plugin: Option<&'a str>, + + pub boot_plugin: &'a str, + pub allow_downgrade: bool, pub allow_conflict_files: bool, @@ -102,7 +106,9 @@ impl<'a> TryFrom<&'a CUpdateRequest> for UpdateData<'a> { Ok(UpdateData { packages: Vec::try_from(&request.packages)?, + boot_plugin: (&request.boot_plugin).try_into()?, + allow_downgrade: request.allow_downgrade, allow_conflict_files: request.allow_conflict_files, @@ -138,7 +144,7 @@ pub fn run(data: UpdateData) -> Result<(), (UpdateStateId, UpdateError)> { context.put(TmpPath(data.tmp_path.to_owned())); context.put(Subject(data.subject.to_owned())); context.put(CommitMessage(data.message.map(str::to_owned))); - context.put(RequestedBootPlugin(data.boot_plugin.map(str::to_owned))); + context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); context.put(AllowDowngrade(data.allow_downgrade)); context.put(AllowConflictFiles(data.allow_conflict_files)); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); diff --git a/lib/lib/src/mutated/update/swap.rs b/lib/lib/src/mutated/update/swap.rs index e2cd04ca..396d4a65 100644 --- a/lib/lib/src/mutated/update/swap.rs +++ b/lib/lib/src/mutated/update/swap.rs @@ -6,6 +6,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; +use upac_types::request::BootPluginSetOneShotRequest; use super::{ResolvedBootEntry, UpdateError}; @@ -20,7 +21,9 @@ impl Stage for SwapStage { ) -> Result<(ProgressEventBuilder, StageResult, Box), UpdateError> { let resolved = ctx_take!(context, ResolvedBootEntry); - resolved.plugin.set_one_shot(&resolved.entry_name)?; + resolved.plugin.set_one_shot(BootPluginSetOneShotRequest { + entry_name: resolved.entry_name, + })?; Ok((progress, StageResult::Advance, Box::new(NoRollback))) } From 62655f36bde291418c2fdcb5e1a46ad19b52d1c4 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 00:16:38 +0400 Subject: [PATCH 065/148] fix: require boot_plugin in install/update/remove/rollback/file commands, fall back to /etc/upac.d/upac.toml Co-Authored-By: Claude Sonnet 5 --- user/upac-cli/Cargo.toml | 2 +- user/upac-cli/i18n/en/upac-cli.ftl | 1 + user/upac-cli/i18n/ru/upac-cli.ftl | 1 + user/upac-cli/src/commands/file/add.rs | 11 ++++++++++- user/upac-cli/src/commands/file/remove.rs | 11 ++++++++++- user/upac-cli/src/commands/package/install.rs | 8 +++++++- user/upac-cli/src/commands/package/remove.rs | 10 +++++++++- user/upac-cli/src/commands/package/update.rs | 8 +++++++- user/upac-cli/src/commands/rollback.rs | 11 ++++++++++- 9 files changed, 56 insertions(+), 7 deletions(-) diff --git a/user/upac-cli/Cargo.toml b/user/upac-cli/Cargo.toml index ad2ab2ad..1b7b62a9 100644 --- a/user/upac-cli/Cargo.toml +++ b/user/upac-cli/Cargo.toml @@ -28,7 +28,7 @@ name = "up" path = "src/main.rs" [dependencies] -upac-lib = { workspace = true, optional = true } +upac-lib = { workspace = true, optional = true, default-features = false } upac-abi = { workspace = true } upac-types = { workspace = true } diff --git a/user/upac-cli/i18n/en/upac-cli.ftl b/user/upac-cli/i18n/en/upac-cli.ftl index 1828014d..110b5851 100644 --- a/user/upac-cli/i18n/en/upac-cli.ftl +++ b/user/upac-cli/i18n/en/upac-cli.ftl @@ -24,6 +24,7 @@ err-invalid-entry = Invalid entry err-pkg-not-found = Package not found err-already-exists = Already exists err-not-initialized = Not initialized +err-boot-plugin-required = No boot plugin specified (use --boot or set [boot] plugin in /etc/upac.d/upac.toml) stage-pre-hooks = Pre-hooks stage-post-hooks = Post-hooks diff --git a/user/upac-cli/i18n/ru/upac-cli.ftl b/user/upac-cli/i18n/ru/upac-cli.ftl index f86983af..94b7515c 100644 --- a/user/upac-cli/i18n/ru/upac-cli.ftl +++ b/user/upac-cli/i18n/ru/upac-cli.ftl @@ -24,6 +24,7 @@ err-invalid-entry = Некорректная запись err-pkg-not-found = Пакет не найден err-already-exists = Уже существует err-not-initialized = Не инициализировано +err-boot-plugin-required = Не указан boot-плагин (используйте --boot или задайте [boot] plugin в /etc/upac.d/upac.toml) stage-pre-hooks = Пре-хуки stage-post-hooks = Пост-хуки diff --git a/user/upac-cli/src/commands/file/add.rs b/user/upac-cli/src/commands/file/add.rs index 5c65d833..45e02616 100644 --- a/user/upac-cli/src/commands/file/add.rs +++ b/user/upac-cli/src/commands/file/add.rs @@ -7,6 +7,8 @@ use anyhow::Result; use clap::Args as ClapArgs; +use i18n_embed_fl::fl; + use upac_abi::FileDiffKind; use upac_abi::error::ErrorDomain; use upac_abi::package::CPackageInfo; @@ -14,8 +16,10 @@ use upac_abi::request::CFilesRequest; use upac_types::package::PackageInfo; use upac_types::request::{FilesRequest, RequestBase}; +use upac_types::settings::RuntimeSettings; use crate::cancel_token_ptr; +use crate::locale::LOADER; use crate::types::CommandContext; use crate::types::abi::{FileScope, invoke}; use crate::types::progress::{ProgressState, on_progress}; @@ -50,6 +54,11 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let mut progress = ProgressState::new(ErrorDomain::Files); + let boot_plugin = args + .boot + .or_else(|| RuntimeSettings::load().boot.plugin) + .ok_or_else(|| anyhow::anyhow!(fl!(LOADER, "err-boot-plugin-required")))?; + let request: CFilesRequest = FilesRequest { base: RequestBase { on_hook: Some(on_progress), @@ -63,7 +72,7 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { file_kind: FileDiffKind::Added, scope: args.scope.into(), file_package: &package, - boot_plugin: args.boot, + boot_plugin: boot_plugin, } .into(); diff --git a/user/upac-cli/src/commands/file/remove.rs b/user/upac-cli/src/commands/file/remove.rs index 5ad8d18d..ec8d4ee5 100644 --- a/user/upac-cli/src/commands/file/remove.rs +++ b/user/upac-cli/src/commands/file/remove.rs @@ -7,6 +7,8 @@ use anyhow::Result; use clap::Args as ClapArgs; +use i18n_embed_fl::fl; + use upac_abi::FileDiffKind; use upac_abi::error::ErrorDomain; use upac_abi::package::CPackageInfo; @@ -14,8 +16,10 @@ use upac_abi::request::CFilesRequest; use upac_types::package::PackageInfo; use upac_types::request::{FilesRequest, RequestBase}; +use upac_types::settings::RuntimeSettings; use crate::cancel_token_ptr; +use crate::locale::LOADER; use crate::types::CommandContext; use crate::types::abi::{FileScope, invoke}; use crate::types::progress::{ProgressState, on_progress}; @@ -50,6 +54,11 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let mut progress = ProgressState::new(ErrorDomain::Files); + let boot_plugin = args + .boot + .or_else(|| RuntimeSettings::load().boot.plugin) + .ok_or_else(|| anyhow::anyhow!(fl!(LOADER, "err-boot-plugin-required")))?; + let request: CFilesRequest = FilesRequest { base: RequestBase { on_hook: Some(on_progress), @@ -63,7 +72,7 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { file_kind: FileDiffKind::Removed, scope: args.scope.into(), file_package: &package, - boot_plugin: args.boot, + boot_plugin: boot_plugin, } .into(); diff --git a/user/upac-cli/src/commands/package/install.rs b/user/upac-cli/src/commands/package/install.rs index 2a577196..e5c7b0a2 100644 --- a/user/upac-cli/src/commands/package/install.rs +++ b/user/upac-cli/src/commands/package/install.rs @@ -15,6 +15,7 @@ use upac_abi::error::ErrorDomain; use upac_abi::request::CInstallRequest; use upac_types::request::{InstallRequest, RequestBase}; +use upac_types::settings::RuntimeSettings; use crate::cancel_token_ptr; use crate::locale::LOADER; @@ -46,6 +47,11 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let mut progress = ProgressState::new(ErrorDomain::Install); + let boot_plugin = args + .boot + .or_else(|| RuntimeSettings::load().boot.plugin) + .ok_or_else(|| anyhow::anyhow!(fl!(LOADER, "err-boot-plugin-required")))?; + let request: CInstallRequest = InstallRequest { base: RequestBase { on_hook: Some(on_progress), @@ -56,7 +62,7 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { subject: "install".to_owned(), message: args.message, packages, - boot_plugin: args.boot, + boot_plugin: boot_plugin, allow_conflict_files: !args.no_conflict_files, } .into(); diff --git a/user/upac-cli/src/commands/package/remove.rs b/user/upac-cli/src/commands/package/remove.rs index 3fc60253..053807b8 100644 --- a/user/upac-cli/src/commands/package/remove.rs +++ b/user/upac-cli/src/commands/package/remove.rs @@ -20,6 +20,7 @@ use upac_abi::types::CSlice; use upac_types::package::PackageInfo; use upac_types::request::{ListPackagesRequest, RequestBase, UninstallRequest}; +use upac_types::settings::RuntimeSettings; use crate::cancel_token_ptr; use crate::locale::LOADER; @@ -162,6 +163,13 @@ impl RemoveMachine { let mut progress = ProgressState::new(ErrorDomain::Uninstall); + let boot_plugin = self + .args + .boot + .clone() + .or_else(|| RuntimeSettings::load().boot.plugin) + .ok_or_else(|| anyhow::anyhow!(fl!(LOADER, "err-boot-plugin-required")))?; + let request: CUninstallRequest = UninstallRequest { base: RequestBase { on_hook: Some(on_progress), @@ -172,7 +180,7 @@ impl RemoveMachine { subject: "remove".to_owned(), message: self.args.message.clone(), packages: std::mem::take(&mut self.resolved), - boot_plugin: self.args.boot.clone(), + boot_plugin, purge: self.args.purge, } .into(); diff --git a/user/upac-cli/src/commands/package/update.rs b/user/upac-cli/src/commands/package/update.rs index 92e7a093..f499d0c7 100644 --- a/user/upac-cli/src/commands/package/update.rs +++ b/user/upac-cli/src/commands/package/update.rs @@ -15,6 +15,7 @@ use upac_abi::error::ErrorDomain; use upac_abi::request::CUpdateRequest; use upac_types::request::{RequestBase, UpdateRequest}; +use upac_types::settings::RuntimeSettings; use crate::cancel_token_ptr; use crate::locale::LOADER; @@ -48,6 +49,11 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let mut progress = ProgressState::new(ErrorDomain::Update); + let boot_plugin = args + .boot + .or_else(|| RuntimeSettings::load().boot.plugin) + .ok_or_else(|| anyhow::anyhow!(fl!(LOADER, "err-boot-plugin-required")))?; + let request: CUpdateRequest = UpdateRequest { base: RequestBase { on_hook: Some(on_progress), @@ -58,7 +64,7 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { subject: "update".to_owned(), message: args.message, packages, - boot_plugin: args.boot, + boot_plugin: boot_plugin, allow_downgrade: args.allow_downgrade, allow_conflict_files: !args.no_conflict_files, } diff --git a/user/upac-cli/src/commands/rollback.rs b/user/upac-cli/src/commands/rollback.rs index b78a82be..5f74899c 100644 --- a/user/upac-cli/src/commands/rollback.rs +++ b/user/upac-cli/src/commands/rollback.rs @@ -9,11 +9,15 @@ use anyhow::Result; use clap::Args as ClapArgs; +use i18n_embed_fl::fl; + use upac_abi::request::CRollbackRequest; use upac_types::request::{RequestBase, RollbackRequest}; +use upac_types::settings::RuntimeSettings; use crate::cancel_token_ptr; +use crate::locale::LOADER; use crate::types::CommandContext; use crate::types::abi::invoke; @@ -27,6 +31,11 @@ pub struct Args { pub fn run(args: Args, ctx: CommandContext) -> Result<()> { let symbols = ctx.lib.require_write()?; + let boot_plugin = args + .boot + .or_else(|| RuntimeSettings::load().boot.plugin) + .ok_or_else(|| anyhow::anyhow!(fl!(LOADER, "err-boot-plugin-required")))?; + let request: CRollbackRequest = RollbackRequest { base: RequestBase { on_hook: None, @@ -35,7 +44,7 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { }, tmp_path: ctx.tmp_path.to_string_lossy().into_owned(), config_digest: args.commit, - boot_plugin: args.boot, + boot_plugin: boot_plugin, } .into(); From ed2d8e5214dc19b636e73013bb6bd8ffa583682f Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 00:17:09 +0400 Subject: [PATCH 066/148] fix: add boot.plugin to RuntimeSettings for a configurable default boot plugin Co-Authored-By: Claude Sonnet 5 --- lib/types/src/settings.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/types/src/settings.rs b/lib/types/src/settings.rs index 978833db..73e8fbdd 100644 --- a/lib/types/src/settings.rs +++ b/lib/types/src/settings.rs @@ -39,11 +39,18 @@ impl Default for ProgressSettings { } } +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(default)] +pub struct BootSettings { + pub plugin: Option, +} + #[derive(Debug, Clone, Default, Deserialize)] #[serde(default)] pub struct RuntimeSettings { pub gc: GcSettings, pub progress: ProgressSettings, + pub boot: BootSettings, } impl RuntimeSettings { From 588e6ad75c0127ee6781ac033acacd733dba2b0e Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 01:14:17 +0400 Subject: [PATCH 067/148] fix: add CTryToRust to BootPlugin{SetOneShot,ConfirmSuccsesBoot,Install}Request for plugin-side parsing Co-Authored-By: Claude Sonnet 5 --- lib/types/src/request.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/types/src/request.rs b/lib/types/src/request.rs index 6fedfe30..e863fa11 100644 --- a/lib/types/src/request.rs +++ b/lib/types/src/request.rs @@ -7,6 +7,7 @@ use std::mem::size_of; use std::os::raw::c_void; use upac_abi::HookMessageFn; +use upac_abi::error::ErrorKind; use upac_abi::hook::CancelToken; use upac_abi::package::CPackageInfo; use upac_abi::request::{ @@ -19,7 +20,7 @@ use upac_abi::request::{ use upac_abi::types::{COwned, CSlice, CVec}; use upac_abi::{DiffFileSource, FileDiffKind}; -use upac_macro::RustToC; +use upac_macro::{CTryToRust, RustToC}; use super::package::PackageInfo; @@ -217,19 +218,19 @@ pub struct DecodeRequest { pub cancel_token: *mut CancelToken, } -#[derive(Debug, Clone, RustToC)] +#[derive(Debug, Clone, RustToC, CTryToRust)] pub struct BootPluginSetOneShotRequest { pub entry_name: String, } -#[derive(Debug, Clone, RustToC)] +#[derive(Debug, Clone, RustToC, CTryToRust)] pub struct BootPluginConfirmSuccsesBootRequest { pub entry_name: String, pub esp_mount_point: String, } -#[derive(Debug, Clone, RustToC)] +#[derive(Debug, Clone, RustToC, CTryToRust)] pub struct BootPluginInstallRequest { pub esp_mount_point: String, pub esp_partition_number: u32, From ab7577739c858a67fd7ee911de27640ba695e858 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 01:23:12 +0400 Subject: [PATCH 068/148] fix: rebuild upac-grub around current upac_abi/upac_types::traits::Booter shapes Co-Authored-By: Claude Sonnet 5 --- booters/booter.toml | 21 +++++- booters/grub/Cargo.toml | 3 +- booters/grub/build.rs | 29 ++++----- booters/grub/src/backend.rs | 22 +++---- booters/grub/src/lib.rs | 123 ++++++++++++++++++------------------ 5 files changed, 105 insertions(+), 93 deletions(-) diff --git a/booters/booter.toml b/booters/booter.toml index 88c055f0..30d5be4d 100644 --- a/booters/booter.toml +++ b/booters/booter.toml @@ -34,9 +34,22 @@ loader_entry_default_var = "LoaderEntryDefault" # `write_t2_simple` always writes UKI images under (`EFI/Linux/`, hardcoded upstream, not # configurable) — used to build the file-path device-path segment when genesis registers a # UEFI Boot#### entry for a UKI slot, so the entry's path always matches where the image -# actually lands. +# actually lands. efi_linux_real_path is the same directory spelled as a real, mounted- +# filesystem-relative path (forward slashes, joined via `Path::join`) — used by +# `Uki::confirm_boot` to find `to.efi`/`from.efi` on the real, mounted ESP; the two can't share +# a definition (see `lib/setup/lib.toml`'s own `genesis.efi_linux_dir` for the same split, +# there for the same reason: different crate, different path flavor needed). +# +# to_slot/from_slot must stay in sync with `upac-lib`'s own `lib.toml` +# (`boot.upac_uki_to_slot`/`upac_uki_from_slot`) — two separate crates, can't share a single +# Rust constant, so each carries its own literal copy. `Uki::confirm_boot` only performs the +# to/from file swap when the confirmed `entry_name` is `to_slot` specifically (confirming a +# fallback boot into `from_slot` means `to_slot` may be broken — nothing should be promoted). [uki] efi_linux_dir = "\\EFI\\Linux\\" +efi_linux_real_path = "EFI/Linux" +to_slot = "upac-to" +from_slot = "upac-from" # source is the fixed, package-convention path (source-tree-relative) where systemd's own # packaging always installs its EFI binary — used by genesis to copy the loader onto a brand-new @@ -61,15 +74,17 @@ source = "usr/lib/systemd/boot/efi/systemd-bootx64.efi" # — genesis relies on the same firmware fallback path uki/systemd-boot/refind already use via # esp_loader_source, not a Boot#### entry, so a fresh disk boots without any NVRAM setup). [grub] -grubenv_primary = "/boot/grub/grubenv" -grubenv_fallback = "/boot/grub2/grubenv" reboot_bin_primary = "grub-reboot" reboot_bin_fallback = "grub2-reboot" + set_default_bin_primary = "grub-set-default" set_default_bin_fallback = "grub2-set-default" + install_bin_primary = "grub-install" install_bin_fallback = "grub2-install" + install_target = "x86_64-efi" + install_bootloader_id = "upac" # rEFInd has no separate one-shot/persistent pair of variables like systemd-boot's diff --git a/booters/grub/Cargo.toml b/booters/grub/Cargo.toml index 9cf8f60d..365c7f82 100644 --- a/booters/grub/Cargo.toml +++ b/booters/grub/Cargo.toml @@ -28,7 +28,8 @@ name = "upac_boot_grub" crate-type = ["cdylib", "rlib"] [dependencies] -upac-abi = { workspace = true } +upac-abi = { workspace = true } +upac-types = { workspace = true } [build-dependencies] toml = { workspace = true } diff --git a/booters/grub/build.rs b/booters/grub/build.rs index 23be7f05..1e56aec4 100644 --- a/booters/grub/build.rs +++ b/booters/grub/build.rs @@ -19,26 +19,25 @@ fn main() -> Result<(), Box> { let raw = read_to_string(&source)?; let config: Value = from_str(&raw)?; - let mut generated = String::new(); - - let sections = config.as_table().ok_or("booter.toml: root must be a table")?; - for (section, entries) in sections { - generated.push_str(&format!("pub mod {section} {{\n")); + let section = "grub"; + let entries = config + .get(section) + .and_then(Value::as_table) + .ok_or_else(|| format!("booter.toml: [{section}] must be a table"))?; - let entries = entries - .as_table() - .ok_or_else(|| format!("booter.toml: [{section}] must be a table"))?; - for (key, value) in entries { - let value = value - .as_str() - .ok_or_else(|| format!("booter.toml: {section}.{key} must be a string"))?; + let mut generated = String::new(); + generated.push_str(&format!("pub mod {section} {{\n")); - generated.push_str(&format!(" pub const {}: &str = {value:?};\n", key.to_uppercase())); - } + for (key, value) in entries { + let value = value + .as_str() + .ok_or_else(|| format!("booter.toml: {section}.{key} must be a string"))?; - generated.push_str("}\n"); + generated.push_str(&format!(" pub const {}: &str = {value:?};\n", key.to_uppercase())); } + generated.push_str("}\n"); + let out = Path::new(&var("OUT_DIR")?).join("layout.rs"); write(out, generated)?; diff --git a/booters/grub/src/backend.rs b/booters/grub/src/backend.rs index 507622af..8eb5621e 100644 --- a/booters/grub/src/backend.rs +++ b/booters/grub/src/backend.rs @@ -8,12 +8,12 @@ use std::io::ErrorKind as IoErrorKind; use std::path::Path; use std::process::Command; -use upac_abi::boot::Booter; +use upac_types::traits::Booter; use crate::error::GrubError; use crate::grub::{ - GRUBENV_FALLBACK, GRUBENV_PRIMARY, INSTALL_BIN_FALLBACK, INSTALL_BIN_PRIMARY, INSTALL_BOOTLOADER_ID, - INSTALL_TARGET, REBOOT_BIN_FALLBACK, REBOOT_BIN_PRIMARY, SET_DEFAULT_BIN_FALLBACK, SET_DEFAULT_BIN_PRIMARY, + INSTALL_BIN_FALLBACK, INSTALL_BIN_PRIMARY, INSTALL_BOOTLOADER_ID, INSTALL_TARGET, REBOOT_BIN_FALLBACK, + REBOOT_BIN_PRIMARY, SET_DEFAULT_BIN_FALLBACK, SET_DEFAULT_BIN_PRIMARY, }; const GRUB_CFG_CONTENTS: &str = "insmod blscfg\nblscfg\n"; @@ -27,20 +27,18 @@ impl Booter for Grub { Ok(Grub) } - fn probes() -> bool { - Path::new(GRUBENV_PRIMARY).exists() || Path::new(GRUBENV_FALLBACK).exists() - } - fn set_one_shot(&mut self, entry_name: &str) -> Result<(), GrubError> { self.run_first_available([REBOOT_BIN_PRIMARY, REBOOT_BIN_FALLBACK], &[entry_name]) } - fn confirm_boot(&mut self, entry_name: &str) -> Result<(), GrubError> { + fn confirm_boot(&mut self, entry_name: &str, esp_mount_point: &str) -> Result<(), GrubError> { + let _ = esp_mount_point; + self.run_first_available([SET_DEFAULT_BIN_PRIMARY, SET_DEFAULT_BIN_FALLBACK], &[entry_name]) } - fn register_boot_slots( - &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + fn install( + &mut self, esp_mount_point: &str, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, ) -> Result<(), GrubError> { let _ = ( @@ -52,10 +50,6 @@ impl Booter for Grub { from_slot, ); - Ok(()) - } - - fn install(&mut self, esp_mount_point: &str) -> Result<(), GrubError> { self.run_first_available( [INSTALL_BIN_PRIMARY, INSTALL_BIN_FALLBACK], &[ diff --git a/booters/grub/src/lib.rs b/booters/grub/src/lib.rs index a685aa41..e7ea7335 100644 --- a/booters/grub/src/lib.rs +++ b/booters/grub/src/lib.rs @@ -3,125 +3,128 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::str::from_utf8; - use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{Booter, CBootPluginRequest, CBootSlotsRequest}; use upac_abi::error::ErrorKind; -use upac_abi::types::{CBorrowed, CSlice}; +use upac_abi::request::{ + CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, +}; + +use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginInstallRequest, BootPluginSetOneShotRequest}; +use upac_types::traits::Booter; -use crate::backend::Grub; -use crate::error::GrubError; +use self::backend::Grub; +use self::error::GrubError; mod backend; mod error; include!(concat!(env!("OUT_DIR"), "/layout.rs")); -/// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::AbiVersionFn`. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn abi_version() -> u32 { - BOOT_ABI_VERSION -} - -/// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::ProbeFn`. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn probe() -> i32 { - i32::from(Grub::probes()) +macro_rules! write_error { + ($err_out:expr, $error:expr) => { + if !$err_out.is_null() { + unsafe { *$err_out = $error.into() }; + } + }; } /// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::EspLoaderSourceFn`. +/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::BootPluginAbiVersionFn`. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn esp_loader_source() -> CSlice { - CSlice::from_slice(Grub::esp_loader_source().map(str::as_bytes)) +pub unsafe extern "C" fn abi_version() -> u32 { + BOOT_ABI_VERSION } /// # Safety -/// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the +/// `request`, if non-null, must point to a valid, initialized `CBootPluginSetOneShotRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginSetOneShotRequest, err_out: *mut ErrorKind) -> i32 { if request.is_null() { - write_error(err_out, GrubError::InvalidRequest); + write_error!(err_out, GrubError::InvalidRequest); + return -1; } - let result = string_from_request(unsafe { &*request }) - .and_then(|entry_name| Grub::new().and_then(|mut grub| grub.set_one_shot(&entry_name))); + let result = BootPluginSetOneShotRequest::try_from(unsafe { &*request }) + .map_err(|_| GrubError::InvalidRequest) + .and_then(|request| Grub::new().and_then(|mut grub| grub.set_one_shot(&request.entry_name))); match result { Ok(()) => 0, + Err(error) => { - write_error(err_out, error); + write_error!(err_out, error); + -1 } } } /// # Safety -/// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the +/// `request`, if non-null, must point to a valid, initialized `CBootPluginConfirmSuccsesBootRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn confirm_boot( + request: *const CBootPluginConfirmSuccsesBootRequest, err_out: *mut ErrorKind, +) -> i32 { if request.is_null() { - write_error(err_out, GrubError::InvalidRequest); + write_error!(err_out, GrubError::InvalidRequest); + return -1; } - let result = string_from_request(unsafe { &*request }) - .and_then(|entry_name| Grub::new().and_then(|mut grub| grub.confirm_boot(&entry_name))); + let result = BootPluginConfirmSuccsesBootRequest::try_from(unsafe { &*request }) + .map_err(|_| GrubError::InvalidRequest) + .and_then(|request| { + Grub::new().and_then(|mut grub| grub.confirm_boot(&request.entry_name, &request.esp_mount_point)) + }); match result { Ok(()) => 0, + Err(error) => { - write_error(err_out, error); + write_error!(err_out, error); + -1 } } } /// # Safety -/// Touches no pointers — grub has no Boot#### entries to register, always succeeds. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, _err_out: *mut ErrorKind) -> i32 { - 0 -} - -/// # Safety -/// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the +/// `request`, if non-null, must point to a valid, initialized `CBootPluginInstallRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn install(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn install(request: *const CBootPluginInstallRequest, err_out: *mut ErrorKind) -> i32 { if request.is_null() { - write_error(err_out, GrubError::InvalidRequest); + write_error!(err_out, GrubError::InvalidRequest); + return -1; } - let result = string_from_request(unsafe { &*request }) - .and_then(|esp_mount_point| Grub::new().and_then(|mut grub| grub.install(&esp_mount_point))); + let result = BootPluginInstallRequest::try_from(unsafe { &*request }) + .map_err(|_| GrubError::InvalidRequest) + .and_then(|request| { + Grub::new().and_then(|mut grub| { + grub.install( + &request.esp_mount_point, + request.esp_partition_number, + request.esp_starting_lba, + request.esp_ending_lba, + request.esp_unique_partition_guid, + &request.to_slot, + &request.from_slot, + ) + }) + }); match result { Ok(()) => 0, + Err(error) => { - write_error(err_out, error); + write_error!(err_out, error); + -1 } } } - -fn string_from_request(request: &CBootPluginRequest) -> Result { - let bytes = unsafe { request.value.as_borrowed() }; - - from_utf8(bytes) - .map(str::to_owned) - .map_err(|_| GrubError::InvalidRequest) -} - -fn write_error(err_out: *mut ErrorKind, error: GrubError) { - if !err_out.is_null() { - unsafe { *err_out = error.into() }; - } -} From b71b75d96f7578ecc6539f4bad0ca24752ececcd Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 01:41:24 +0400 Subject: [PATCH 069/148] fix: rebuild upac-refind around current upac_abi/upac_types::traits::Booter shapes Co-Authored-By: Claude Sonnet 5 --- booters/refind/Cargo.toml | 1 + booters/refind/build.rs | 38 ++++++++------ booters/refind/src/backend.rs | 57 +++++++------------- booters/refind/src/lib.rs | 97 +++++++++++++++-------------------- 4 files changed, 84 insertions(+), 109 deletions(-) diff --git a/booters/refind/Cargo.toml b/booters/refind/Cargo.toml index 77204ac9..571cc6b9 100644 --- a/booters/refind/Cargo.toml +++ b/booters/refind/Cargo.toml @@ -29,6 +29,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] upac-abi = { workspace = true } +upac-types = { workspace = true } nix = { workspace = true, features = ["ioctl"] } diff --git a/booters/refind/build.rs b/booters/refind/build.rs index 23be7f05..e0d9eec8 100644 --- a/booters/refind/build.rs +++ b/booters/refind/build.rs @@ -20,27 +20,33 @@ fn main() -> Result<(), Box> { let config: Value = from_str(&raw)?; let mut generated = String::new(); + generated.push_str(&generate_section(&config, "boot")?); + generated.push_str(&generate_section(&config, "refind")?); - let sections = config.as_table().ok_or("booter.toml: root must be a table")?; - for (section, entries) in sections { - generated.push_str(&format!("pub mod {section} {{\n")); + let out = Path::new(&var("OUT_DIR")?).join("layout.rs"); + write(out, generated)?; + + Ok(()) +} - let entries = entries - .as_table() - .ok_or_else(|| format!("booter.toml: [{section}] must be a table"))?; - for (key, value) in entries { - let value = value - .as_str() - .ok_or_else(|| format!("booter.toml: {section}.{key} must be a string"))?; +fn generate_section(config: &Value, section: &str) -> Result> { + let entries = config + .get(section) + .and_then(Value::as_table) + .ok_or_else(|| format!("booter.toml: [{section}] must be a table"))?; - generated.push_str(&format!(" pub const {}: &str = {value:?};\n", key.to_uppercase())); - } + let mut generated = String::new(); + generated.push_str(&format!("pub mod {section} {{\n")); + + for (key, value) in entries { + let value = value + .as_str() + .ok_or_else(|| format!("booter.toml: {section}.{key} must be a string"))?; - generated.push_str("}\n"); + generated.push_str(&format!(" pub const {}: &str = {value:?};\n", key.to_uppercase())); } - let out = Path::new(&var("OUT_DIR")?).join("layout.rs"); - write(out, generated)?; + generated.push_str("}\n"); - Ok(()) + Ok(generated) } diff --git a/booters/refind/src/backend.rs b/booters/refind/src/backend.rs index 8fdb78dc..d1abd87e 100644 --- a/booters/refind/src/backend.rs +++ b/booters/refind/src/backend.rs @@ -16,17 +16,25 @@ use uuid::Uuid; use nix::{ioctl_read, ioctl_write_ptr}; -use upac_abi::boot::Booter; +use upac_types::traits::Booter; -use crate::boot::EFIVARFS_PATH; -use crate::error::RefindError; -use crate::refind::{PREVIOUS_BOOT_GUID, PREVIOUS_BOOT_VAR, SOURCE}; +use super::boot::EFIVARFS_PATH; +use super::error::RefindError; +use super::refind::{PREVIOUS_BOOT_GUID, PREVIOUS_BOOT_VAR}; const FS_IMMUTABLE_FL: c_long = 0x0000_0010; ioctl_read!(fs_ioc_getflags, b'f', 1, c_long); ioctl_write_ptr!(fs_ioc_setflags, b'f', 2, c_long); +macro_rules! encode_utf16_null { + ($value:expr) => {{ + let mut bytes: Vec = $value.encode_utf16().flat_map(u16::to_le_bytes).collect(); + bytes.extend_from_slice(&[0x00, 0x00]); + bytes + }}; +} + pub struct Refind { manager: Box, } @@ -40,36 +48,22 @@ impl Booter for Refind { }) } - fn probes() -> bool { - let Ok(manager) = catch_unwind(AssertUnwindSafe(efivar::system)) else { - return false; - }; - let Ok(guid) = Uuid::from_str(PREVIOUS_BOOT_GUID) else { - return false; - }; - - manager - .exists(&Variable::new_with_vendor(PREVIOUS_BOOT_VAR, guid)) - .unwrap_or(false) - } - fn set_one_shot(&mut self, entry_name: &str) -> Result<(), RefindError> { self.write_previous_boot(entry_name) } - fn confirm_boot(&mut self, entry_name: &str) -> Result<(), RefindError> { - self.write_previous_boot(entry_name) - } + fn confirm_boot(&mut self, entry_name: &str, esp_mount_point: &str) -> Result<(), RefindError> { + let _ = esp_mount_point; - fn esp_loader_source() -> Option<&'static str> { - Some(SOURCE) + self.write_previous_boot(entry_name) } - fn register_boot_slots( - &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + fn install( + &mut self, esp_mount_point: &str, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, ) -> Result<(), RefindError> { let _ = ( + esp_mount_point, esp_partition_number, esp_starting_lba, esp_ending_lba, @@ -80,12 +74,6 @@ impl Booter for Refind { Ok(()) } - - fn install(&mut self, esp_mount_point: &str) -> Result<(), RefindError> { - let _ = esp_mount_point; - - Ok(()) - } } impl Refind { @@ -96,7 +84,7 @@ impl Refind { Self::clear_immutable(&variable); self.manager - .write(&variable, VariableFlags::default(), &encode_utf16_null(entry_name))?; + .write(&variable, VariableFlags::default(), &encode_utf16_null!(entry_name))?; Ok(()) } @@ -121,10 +109,3 @@ impl Refind { } } } - -fn encode_utf16_null(value: &str) -> Vec { - let mut bytes: Vec = value.encode_utf16().flat_map(u16::to_le_bytes).collect(); - bytes.extend_from_slice(&[0x00, 0x00]); - - bytes -} diff --git a/booters/refind/src/lib.rs b/booters/refind/src/lib.rs index 616630f3..7bd4f6a7 100644 --- a/booters/refind/src/lib.rs +++ b/booters/refind/src/lib.rs @@ -3,111 +3,98 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::str::from_utf8; - use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{Booter, CBootPluginRequest, CBootSlotsRequest}; use upac_abi::error::ErrorKind; -use upac_abi::types::{CBorrowed, CSlice}; +use upac_abi::request::{ + CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, +}; + +use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginSetOneShotRequest}; +use upac_types::traits::Booter; -use crate::backend::Refind; -use crate::error::RefindError; +use self::backend::Refind; +use self::error::RefindError; mod backend; mod error; include!(concat!(env!("OUT_DIR"), "/layout.rs")); -/// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::AbiVersionFn`. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn abi_version() -> u32 { - BOOT_ABI_VERSION -} - -/// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::ProbeFn`. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn probe() -> i32 { - i32::from(Refind::probes()) +macro_rules! write_error { + ($err_out:expr, $error:expr) => { + if !$err_out.is_null() { + unsafe { *$err_out = $error.into() }; + } + }; } /// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::EspLoaderSourceFn`. +/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::BootPluginAbiVersionFn`. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn esp_loader_source() -> CSlice { - CSlice::from_slice(Refind::esp_loader_source().map(str::as_bytes)) +pub unsafe extern "C" fn boot_abi_version() -> u32 { + BOOT_ABI_VERSION } /// # Safety -/// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the +/// `request`, if non-null, must point to a valid, initialized `CBootPluginSetOneShotRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginSetOneShotRequest, err_out: *mut ErrorKind) -> i32 { if request.is_null() { - write_error(err_out, RefindError::InvalidRequest); + write_error!(err_out, RefindError::InvalidRequest); + return -1; } - let result = entry_name_from_request(unsafe { &*request }) - .and_then(|entry_name| Refind::new().and_then(|mut refind| refind.set_one_shot(&entry_name))); + let result = BootPluginSetOneShotRequest::try_from(unsafe { &*request }) + .map_err(|_| RefindError::InvalidRequest) + .and_then(|request| Refind::new().and_then(|mut refind| refind.set_one_shot(&request.entry_name))); match result { Ok(()) => 0, + Err(error) => { - write_error(err_out, error); + write_error!(err_out, error); + -1 } } } /// # Safety -/// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the +/// `request`, if non-null, must point to a valid, initialized `CBootPluginConfirmSuccsesBootRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn confirm_boot( + request: *const CBootPluginConfirmSuccsesBootRequest, err_out: *mut ErrorKind, +) -> i32 { if request.is_null() { - write_error(err_out, RefindError::InvalidRequest); + write_error!(err_out, RefindError::InvalidRequest); + return -1; } - let result = entry_name_from_request(unsafe { &*request }) - .and_then(|entry_name| Refind::new().and_then(|mut refind| refind.confirm_boot(&entry_name))); + let result = BootPluginConfirmSuccsesBootRequest::try_from(unsafe { &*request }) + .map_err(|_| RefindError::InvalidRequest) + .and_then(|request| { + Refind::new().and_then(|mut refind| refind.confirm_boot(&request.entry_name, &request.esp_mount_point)) + }); match result { Ok(()) => 0, + Err(error) => { - write_error(err_out, error); + write_error!(err_out, error); + -1 } } } -/// # Safety -/// Touches no pointers — rEFInd has no Boot#### entries to register, always succeeds. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, _err_out: *mut ErrorKind) -> i32 { - 0 -} - /// # Safety /// Touches no pointers — rEFInd has nothing to install onto a pre-existing ESP, always succeeds /// (its binary is copied from the source package tree via `esp_loader_source` instead). #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn install(_request: *const CBootPluginRequest, _err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn install(_request: *const CBootPluginInstallRequest, _err_out: *mut ErrorKind) -> i32 { 0 } - -fn entry_name_from_request(request: &CBootPluginRequest) -> Result { - let bytes = unsafe { request.value.as_borrowed() }; - - from_utf8(bytes) - .map(str::to_owned) - .map_err(|_| RefindError::InvalidRequest) -} - -fn write_error(err_out: *mut ErrorKind, error: RefindError) { - if !err_out.is_null() { - unsafe { *err_out = error.into() }; - } -} From 541cb7afe9686dbac317edbe37e2211a4838c3e4 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 01:41:58 +0400 Subject: [PATCH 070/148] fix: function naming and style corrections Co-Authored-By: Claude Sonnet 5 --- booters/grub/src/backend.rs | 4 ++-- booters/grub/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/booters/grub/src/backend.rs b/booters/grub/src/backend.rs index 8eb5621e..9a6867bc 100644 --- a/booters/grub/src/backend.rs +++ b/booters/grub/src/backend.rs @@ -10,8 +10,8 @@ use std::process::Command; use upac_types::traits::Booter; -use crate::error::GrubError; -use crate::grub::{ +use super::error::GrubError; +use super::grub::{ INSTALL_BIN_FALLBACK, INSTALL_BIN_PRIMARY, INSTALL_BOOTLOADER_ID, INSTALL_TARGET, REBOOT_BIN_FALLBACK, REBOOT_BIN_PRIMARY, SET_DEFAULT_BIN_FALLBACK, SET_DEFAULT_BIN_PRIMARY, }; diff --git a/booters/grub/src/lib.rs b/booters/grub/src/lib.rs index e7ea7335..9ead2b24 100644 --- a/booters/grub/src/lib.rs +++ b/booters/grub/src/lib.rs @@ -31,7 +31,7 @@ macro_rules! write_error { /// # Safety /// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::BootPluginAbiVersionFn`. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn abi_version() -> u32 { +pub unsafe extern "C" fn boot_abi_version() -> u32 { BOOT_ABI_VERSION } From b4b36177690a938a84b21372b85e63d01d7ce011 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 01:53:48 +0400 Subject: [PATCH 071/148] fix: rebuild upac-systemd-boot around current Booter shape, generate boot section not systemd_boot Co-Authored-By: Claude Sonnet 5 --- booters/booter.toml | 7 --- booters/systemd-boot/Cargo.toml | 1 + booters/systemd-boot/build.rs | 29 +++++---- booters/systemd-boot/src/backend.rs | 74 ++++++++-------------- booters/systemd-boot/src/error.rs | 28 ++++----- booters/systemd-boot/src/lib.rs | 97 +++++++++++++---------------- 6 files changed, 97 insertions(+), 139 deletions(-) diff --git a/booters/booter.toml b/booters/booter.toml index 30d5be4d..2028e0ef 100644 --- a/booters/booter.toml +++ b/booters/booter.toml @@ -51,13 +51,6 @@ efi_linux_real_path = "EFI/Linux" to_slot = "upac-to" from_slot = "upac-from" -# source is the fixed, package-convention path (source-tree-relative) where systemd's own -# packaging always installs its EFI binary — used by genesis to copy the loader onto a brand-new -# ESP that doesn't have one yet (install/update never need this, the binary is already on the ESP -# from genesis). -[systemd_boot] -source = "usr/lib/systemd/boot/efi/systemd-bootx64.efi" - # grub has no EFI-variable-based one-shot mechanism — it's file-based (grubenv), driven through # grub's own grub-reboot/grub-set-default tools rather than a hand-rolled binary-format writer. # Both the grubenv location and the tool names differ across distro packaging: Debian/Ubuntu/Arch diff --git a/booters/systemd-boot/Cargo.toml b/booters/systemd-boot/Cargo.toml index d2e1514f..1dd6f9ef 100644 --- a/booters/systemd-boot/Cargo.toml +++ b/booters/systemd-boot/Cargo.toml @@ -29,6 +29,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] upac-abi = { workspace = true } +upac-types = { workspace = true } nix = { workspace = true, features = ["ioctl"] } efivar = { workspace = true } diff --git a/booters/systemd-boot/build.rs b/booters/systemd-boot/build.rs index 23be7f05..62c06f36 100644 --- a/booters/systemd-boot/build.rs +++ b/booters/systemd-boot/build.rs @@ -19,26 +19,25 @@ fn main() -> Result<(), Box> { let raw = read_to_string(&source)?; let config: Value = from_str(&raw)?; - let mut generated = String::new(); - - let sections = config.as_table().ok_or("booter.toml: root must be a table")?; - for (section, entries) in sections { - generated.push_str(&format!("pub mod {section} {{\n")); + let section = "boot"; + let entries = config + .get(section) + .and_then(Value::as_table) + .ok_or_else(|| format!("booter.toml: [{section}] must be a table"))?; - let entries = entries - .as_table() - .ok_or_else(|| format!("booter.toml: [{section}] must be a table"))?; - for (key, value) in entries { - let value = value - .as_str() - .ok_or_else(|| format!("booter.toml: {section}.{key} must be a string"))?; + let mut generated = String::new(); + generated.push_str(&format!("pub mod {section} {{\n")); - generated.push_str(&format!(" pub const {}: &str = {value:?};\n", key.to_uppercase())); - } + for (key, value) in entries { + let value = value + .as_str() + .ok_or_else(|| format!("booter.toml: {section}.{key} must be a string"))?; - generated.push_str("}\n"); + generated.push_str(&format!(" pub const {}: &str = {value:?};\n", key.to_uppercase())); } + generated.push_str("}\n"); + let out = Path::new(&var("OUT_DIR")?).join("layout.rs"); write(out, generated)?; diff --git a/booters/systemd-boot/src/backend.rs b/booters/systemd-boot/src/backend.rs index 994b0ae0..56ec0938 100644 --- a/booters/systemd-boot/src/backend.rs +++ b/booters/systemd-boot/src/backend.rs @@ -16,62 +16,53 @@ use nix::{ioctl_read, ioctl_write_ptr}; use uuid::Uuid; -use upac_abi::boot::Booter; +use upac_types::traits::Booter; -use crate::boot::{ - EFIVARFS_PATH, LOADER_ENTRY_DEFAULT_VAR, LOADER_ENTRY_ONE_SHOT_VAR, LOADER_INFO_VAR, SD_BOOT_LOADER_GUID, -}; -use crate::error::BlsError; -use crate::systemd_boot::SOURCE; +use super::boot::{EFIVARFS_PATH, LOADER_ENTRY_DEFAULT_VAR, LOADER_ENTRY_ONE_SHOT_VAR, SD_BOOT_LOADER_GUID}; +use super::error::SystemdBootError; const FS_IMMUTABLE_FL: c_long = 0x0000_0010; ioctl_read!(fs_ioc_getflags, b'f', 1, c_long); ioctl_write_ptr!(fs_ioc_setflags, b'f', 2, c_long); -pub struct Bls { +macro_rules! encode_utf16_null { + ($value:expr) => {{ + let mut bytes: Vec = $value.encode_utf16().flat_map(u16::to_le_bytes).collect(); + bytes.extend_from_slice(&[0x00, 0x00]); + bytes + }}; +} + +pub struct SystemdBoot { manager: Box, } -impl Booter for Bls { - type Error = BlsError; +impl Booter for SystemdBoot { + type Error = SystemdBootError; - fn new() -> Result { + fn new() -> Result { Ok(Self { manager: catch_unwind(AssertUnwindSafe(efivar::system))?, }) } - fn probes() -> bool { - let Ok(manager) = catch_unwind(AssertUnwindSafe(efivar::system)) else { - return false; - }; - let Ok(guid) = Uuid::from_str(SD_BOOT_LOADER_GUID) else { - return false; - }; - - manager - .exists(&Variable::new_with_vendor(LOADER_INFO_VAR, guid)) - .unwrap_or(false) - } - - fn set_one_shot(&mut self, entry_name: &str) -> Result<(), BlsError> { + fn set_one_shot(&mut self, entry_name: &str) -> Result<(), SystemdBootError> { self.write_loader_variable(LOADER_ENTRY_ONE_SHOT_VAR, entry_name) } - fn confirm_boot(&mut self, entry_name: &str) -> Result<(), BlsError> { - self.write_loader_variable(LOADER_ENTRY_DEFAULT_VAR, entry_name) - } + fn confirm_boot(&mut self, entry_name: &str, esp_mount_point: &str) -> Result<(), SystemdBootError> { + let _ = esp_mount_point; - fn esp_loader_source() -> Option<&'static str> { - Some(SOURCE) + self.write_loader_variable(LOADER_ENTRY_DEFAULT_VAR, entry_name) } - fn register_boot_slots( - &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + fn install( + &mut self, esp_mount_point: &str, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, - ) -> Result<(), BlsError> { + ) -> Result<(), SystemdBootError> { let _ = ( + esp_mount_point, esp_partition_number, esp_starting_lba, esp_ending_lba, @@ -82,23 +73,17 @@ impl Booter for Bls { Ok(()) } - - fn install(&mut self, esp_mount_point: &str) -> Result<(), BlsError> { - let _ = esp_mount_point; - - Ok(()) - } } -impl Bls { - fn write_loader_variable(&mut self, name: &str, entry_name: &str) -> Result<(), BlsError> { +impl SystemdBoot { + fn write_loader_variable(&mut self, name: &str, entry_name: &str) -> Result<(), SystemdBootError> { let guid = Uuid::from_str(SD_BOOT_LOADER_GUID)?; let variable = Variable::new_with_vendor(name, guid); Self::clear_immutable(&variable); self.manager - .write(&variable, VariableFlags::default(), &encode_utf16_null(entry_name))?; + .write(&variable, VariableFlags::default(), &encode_utf16_null!(entry_name))?; Ok(()) } @@ -123,10 +108,3 @@ impl Bls { } } } - -fn encode_utf16_null(value: &str) -> Vec { - let mut bytes: Vec = value.encode_utf16().flat_map(u16::to_le_bytes).collect(); - bytes.extend_from_slice(&[0x00, 0x00]); - - bytes -} diff --git a/booters/systemd-boot/src/error.rs b/booters/systemd-boot/src/error.rs index ace8c742..72804775 100644 --- a/booters/systemd-boot/src/error.rs +++ b/booters/systemd-boot/src/error.rs @@ -12,41 +12,41 @@ use uuid::Error as UuidError; use upac_abi::error::ErrorKind; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BlsError { +pub enum SystemdBootError { EfiUnavailable, PermissionDenied, InvalidRequest, Unexpected, } -impl From for BlsError { +impl From for SystemdBootError { fn from(error: EfivarError) -> Self { match error { - EfivarError::PermissionDenied { .. } => BlsError::PermissionDenied, - _ => BlsError::Unexpected, + EfivarError::PermissionDenied { .. } => SystemdBootError::PermissionDenied, + _ => SystemdBootError::Unexpected, } } } -impl From for BlsError { +impl From for SystemdBootError { fn from(_: UuidError) -> Self { - BlsError::Unexpected + SystemdBootError::Unexpected } } -impl From> for BlsError { +impl From> for SystemdBootError { fn from(_: Box) -> Self { - BlsError::EfiUnavailable + SystemdBootError::EfiUnavailable } } -impl From for ErrorKind { - fn from(error: BlsError) -> Self { +impl From for ErrorKind { + fn from(error: SystemdBootError) -> Self { match error { - BlsError::EfiUnavailable => ErrorKind::NotInitialized, - BlsError::PermissionDenied => ErrorKind::PermissionDenied, - BlsError::InvalidRequest => ErrorKind::InvalidEntry, - BlsError::Unexpected => ErrorKind::Unexpected, + SystemdBootError::EfiUnavailable => ErrorKind::NotInitialized, + SystemdBootError::PermissionDenied => ErrorKind::PermissionDenied, + SystemdBootError::InvalidRequest => ErrorKind::InvalidEntry, + SystemdBootError::Unexpected => ErrorKind::Unexpected, } } } diff --git a/booters/systemd-boot/src/lib.rs b/booters/systemd-boot/src/lib.rs index 96529a94..09b3f616 100644 --- a/booters/systemd-boot/src/lib.rs +++ b/booters/systemd-boot/src/lib.rs @@ -3,111 +3,98 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::str::from_utf8; - use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{Booter, CBootPluginRequest, CBootSlotsRequest}; use upac_abi::error::ErrorKind; -use upac_abi::types::{CBorrowed, CSlice}; +use upac_abi::request::{ + CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, +}; + +use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginSetOneShotRequest}; +use upac_types::traits::Booter; -use crate::backend::Bls; -use crate::error::BlsError; +use self::backend::SystemdBoot; +use self::error::SystemdBootError; mod backend; mod error; include!(concat!(env!("OUT_DIR"), "/layout.rs")); -/// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::AbiVersionFn`. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn abi_version() -> u32 { - BOOT_ABI_VERSION -} - -/// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::ProbeFn`. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn probe() -> i32 { - i32::from(Bls::probes()) +macro_rules! write_error { + ($err_out:expr, $error:expr) => { + if !$err_out.is_null() { + unsafe { *$err_out = $error.into() }; + } + }; } /// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::EspLoaderSourceFn`. +/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::AbiVersionFn`. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn esp_loader_source() -> CSlice { - CSlice::from_slice(Bls::esp_loader_source().map(str::as_bytes)) +pub unsafe extern "C" fn boot_abi_version() -> u32 { + BOOT_ABI_VERSION } /// # Safety -/// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the +/// `request`, if non-null, must point to a valid, initialized `CBootPluginSetOneShotRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginSetOneShotRequest, err_out: *mut ErrorKind) -> i32 { if request.is_null() { - write_error(err_out, BlsError::InvalidRequest); + write_error!(err_out, SystemdBootError::InvalidRequest); return -1; } - let result = entry_name_from_request(unsafe { &*request }) - .and_then(|entry_name| Bls::new().and_then(|mut bls| bls.set_one_shot(&entry_name))); + let result = BootPluginSetOneShotRequest::try_from(unsafe { &*request }) + .map_err(|_| SystemdBootError::InvalidRequest) + .and_then(|request| SystemdBoot::new().and_then(|mut systemd| systemd.set_one_shot(&request.entry_name))); match result { Ok(()) => 0, + Err(error) => { - write_error(err_out, error); + write_error!(err_out, error); + -1 } } } /// # Safety -/// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the +/// `request`, if non-null, must point to a valid, initialized `CConfirmBootRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn confirm_boot( + request: *const CBootPluginConfirmSuccsesBootRequest, err_out: *mut ErrorKind, +) -> i32 { if request.is_null() { - write_error(err_out, BlsError::InvalidRequest); + write_error!(err_out, SystemdBootError::InvalidRequest); + return -1; } - let result = entry_name_from_request(unsafe { &*request }) - .and_then(|entry_name| Bls::new().and_then(|mut bls| bls.confirm_boot(&entry_name))); + let result = BootPluginConfirmSuccsesBootRequest::try_from(unsafe { &*request }) + .map_err(|_| SystemdBootError::InvalidRequest) + .and_then(|request| { + SystemdBoot::new() + .and_then(|mut systemd| systemd.confirm_boot(&request.entry_name, &request.esp_mount_point)) + }); match result { Ok(()) => 0, + Err(error) => { - write_error(err_out, error); + write_error!(err_out, error); + -1 } } } -/// # Safety -/// Touches no pointers — systemd-boot has no Boot#### entries to register, always succeeds. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn register_boot_slots(_request: *const CBootSlotsRequest, _err_out: *mut ErrorKind) -> i32 { - 0 -} - /// # Safety /// Touches no pointers — systemd-boot has nothing to install onto a pre-existing ESP, always /// succeeds (its binary is copied from the source package tree via `esp_loader_source` instead). #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn install(_request: *const CBootPluginRequest, _err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn install(_request: *const CBootPluginInstallRequest, _err_out: *mut ErrorKind) -> i32 { 0 } - -fn entry_name_from_request(request: &CBootPluginRequest) -> Result { - let bytes = unsafe { request.value.as_borrowed() }; - - from_utf8(bytes) - .map(str::to_owned) - .map_err(|_| BlsError::InvalidRequest) -} - -fn write_error(err_out: *mut ErrorKind, error: BlsError) { - if !err_out.is_null() { - unsafe { *err_out = error.into() }; - } -} From 3ce69b35a3b36da970d8f53d1ef621602fd733f1 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 02:12:00 +0400 Subject: [PATCH 072/148] fix: rebuild upac-uki around current Booter shape, restore slot registration in install Co-Authored-By: Claude Sonnet 5 --- booters/uki/Cargo.toml | 1 + booters/uki/build.rs | 38 ++++++----- booters/uki/src/backend.rs | 72 ++++++-------------- booters/uki/src/error.rs | 11 +++ booters/uki/src/lib.rs | 136 +++++++++++-------------------------- 5 files changed, 95 insertions(+), 163 deletions(-) diff --git a/booters/uki/Cargo.toml b/booters/uki/Cargo.toml index dfbe34a8..1fd695db 100644 --- a/booters/uki/Cargo.toml +++ b/booters/uki/Cargo.toml @@ -29,6 +29,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] upac-abi = { workspace = true } +upac-types = { workspace = true } nix = { workspace = true, features = ["ioctl"] } efivar = { workspace = true } diff --git a/booters/uki/build.rs b/booters/uki/build.rs index 23be7f05..b65b8a71 100644 --- a/booters/uki/build.rs +++ b/booters/uki/build.rs @@ -20,27 +20,33 @@ fn main() -> Result<(), Box> { let config: Value = from_str(&raw)?; let mut generated = String::new(); + generated.push_str(&generate_section(&config, "boot")?); + generated.push_str(&generate_section(&config, "uki")?); - let sections = config.as_table().ok_or("booter.toml: root must be a table")?; - for (section, entries) in sections { - generated.push_str(&format!("pub mod {section} {{\n")); + let out = Path::new(&var("OUT_DIR")?).join("layout.rs"); + write(out, generated)?; + + Ok(()) +} - let entries = entries - .as_table() - .ok_or_else(|| format!("booter.toml: [{section}] must be a table"))?; - for (key, value) in entries { - let value = value - .as_str() - .ok_or_else(|| format!("booter.toml: {section}.{key} must be a string"))?; +fn generate_section(config: &Value, section: &str) -> Result> { + let entries = config + .get(section) + .and_then(Value::as_table) + .ok_or_else(|| format!("booter.toml: [{section}] must be a table"))?; - generated.push_str(&format!(" pub const {}: &str = {value:?};\n", key.to_uppercase())); - } + let mut generated = String::new(); + generated.push_str(&format!("pub mod {section} {{\n")); + + for (key, value) in entries { + let value = value + .as_str() + .ok_or_else(|| format!("booter.toml: {section}.{key} must be a string"))?; - generated.push_str("}\n"); + generated.push_str(&format!(" pub const {}: &str = {value:?};\n", key.to_uppercase())); } - let out = Path::new(&var("OUT_DIR")?).join("layout.rs"); - write(out, generated)?; + generated.push_str("}\n"); - Ok(()) + Ok(generated) } diff --git a/booters/uki/src/backend.rs b/booters/uki/src/backend.rs index 3e494c9f..b32b2f17 100644 --- a/booters/uki/src/backend.rs +++ b/booters/uki/src/backend.rs @@ -3,12 +3,11 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::fs::OpenOptions; +use std::fs::{OpenOptions, copy}; use std::os::fd::AsRawFd; use std::os::raw::c_long; use std::panic::{AssertUnwindSafe, catch_unwind}; use std::path::Path; -use std::str::FromStr; use efivar::VarManager; use efivar::boot::{ @@ -20,13 +19,11 @@ use nix::{ioctl_read, ioctl_write_ptr}; use uuid::Uuid; -use upac_abi::boot::Booter; +use upac_types::traits::Booter; -use crate::boot::{BOOT_NEXT_VAR, BOOT_ORDER_VAR, EFI_SYSFS_PATH, EFIVARFS_PATH, LOADER_INFO_VAR, SD_BOOT_LOADER_GUID}; -use crate::error::UkiError; -use crate::grub::{GRUBENV_FALLBACK, GRUBENV_PRIMARY}; -use crate::refind::{PREVIOUS_BOOT_GUID, PREVIOUS_BOOT_VAR}; -use crate::uki::EFI_LINUX_DIR; +use super::boot::{BOOT_NEXT_VAR, BOOT_ORDER_VAR, EFIVARFS_PATH}; +use super::error::UkiError; +use super::uki::{EFI_LINUX_DIR, EFI_LINUX_REAL_PATH, FROM_SLOT, TO_SLOT}; const FS_IMMUTABLE_FL: c_long = 0x0000_0010; @@ -46,22 +43,6 @@ impl Booter for Uki { }) } - fn probes() -> bool { - if !Path::new(EFI_SYSFS_PATH).exists() { - return false; - } - if Path::new(GRUBENV_PRIMARY).exists() || Path::new(GRUBENV_FALLBACK).exists() { - return false; - } - - let Ok(manager) = catch_unwind(AssertUnwindSafe(efivar::system)) else { - return false; - }; - - !efi_variable_exists(manager.as_ref(), LOADER_INFO_VAR, SD_BOOT_LOADER_GUID) - && !efi_variable_exists(manager.as_ref(), PREVIOUS_BOOT_VAR, PREVIOUS_BOOT_GUID) - } - fn set_one_shot(&mut self, entry_name: &str) -> Result<(), UkiError> { let id = self.find_boot_id(entry_name)?; @@ -74,7 +55,7 @@ impl Booter for Uki { Ok(()) } - fn confirm_boot(&mut self, entry_name: &str) -> Result<(), UkiError> { + fn confirm_boot(&mut self, entry_name: &str, esp_mount_point: &str) -> Result<(), UkiError> { let id = self.find_boot_id(entry_name)?; let mut order = self.manager.get_boot_order()?; @@ -84,24 +65,33 @@ impl Booter for Uki { Self::clear_immutable(&Variable::new(BOOT_ORDER_VAR)); self.manager.set_boot_order(order)?; + if entry_name == TO_SLOT { + let efi_linux = Path::new(esp_mount_point).join(EFI_LINUX_REAL_PATH); + let to_path = efi_linux.join(format!("{TO_SLOT}.efi")); + let from_path = efi_linux.join(format!("{FROM_SLOT}.efi")); + copy(&to_path, &from_path)?; + } + Ok(()) } - fn register_boot_slots( - &mut self, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + fn install( + &mut self, esp_mount_point: &str, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, ) -> Result<(), UkiError> { - let partition_sig = Uuid::from_bytes_le(esp_unique_partition_guid); + let _ = esp_mount_point; + let partition_size = esp_ending_lba - esp_starting_lba + 1; + let partition_sig = Uuid::from_bytes_le(esp_unique_partition_guid); - let to_id = self.register_slot( + self.register_slot( esp_partition_number, esp_starting_lba, partition_size, partition_sig, to_slot, )?; - let from_id = self.register_slot( + self.register_slot( esp_partition_number, esp_starting_lba, partition_size, @@ -109,20 +99,6 @@ impl Booter for Uki { from_slot, )?; - let mut order = self.manager.get_boot_order().unwrap_or_default(); - order.retain(|&existing| existing != to_id && existing != from_id); - order.insert(0, from_id); - order.insert(0, to_id); - - Self::clear_immutable(&Variable::new(BOOT_ORDER_VAR)); - self.manager.set_boot_order(order)?; - - Ok(()) - } - - fn install(&mut self, esp_mount_point: &str) -> Result<(), UkiError> { - let _ = esp_mount_point; - Ok(()) } } @@ -203,11 +179,3 @@ impl Uki { } } } - -fn efi_variable_exists(manager: &dyn VarManager, name: &str, guid: &str) -> bool { - let Ok(guid) = Uuid::from_str(guid) else { - return false; - }; - - manager.exists(&Variable::new_with_vendor(name, guid)).unwrap_or(false) -} diff --git a/booters/uki/src/error.rs b/booters/uki/src/error.rs index 75dd7ac9..714a23da 100644 --- a/booters/uki/src/error.rs +++ b/booters/uki/src/error.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use std::any::Any; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use efivar::Error as EfivarError; @@ -28,6 +29,16 @@ impl From for UkiError { } } +impl From for UkiError { + fn from(error: IoError) -> Self { + match error.kind() { + IoErrorKind::NotFound => UkiError::EntryNotFound, + IoErrorKind::PermissionDenied => UkiError::PermissionDenied, + _ => UkiError::Unexpected, + } + } +} + impl From> for UkiError { fn from(_: Box) -> Self { UkiError::EfiUnavailable diff --git a/booters/uki/src/lib.rs b/booters/uki/src/lib.rs index b888bd81..4dc80b16 100644 --- a/booters/uki/src/lib.rs +++ b/booters/uki/src/lib.rs @@ -3,115 +3,89 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::str::from_utf8; - use upac_abi::BOOT_ABI_VERSION; -use upac_abi::boot::{Booter, CBootPluginRequest, CBootSlotsRequest}; use upac_abi::error::ErrorKind; -use upac_abi::types::{CBorrowed, CSlice}; +use upac_abi::request::{ + CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, +}; + +use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginSetOneShotRequest}; +use upac_types::traits::Booter; -use crate::backend::Uki; -use crate::error::UkiError; +use self::backend::Uki; +use self::error::UkiError; mod backend; mod error; include!(concat!(env!("OUT_DIR"), "/layout.rs")); -/// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::AbiVersionFn`. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn abi_version() -> u32 { - BOOT_ABI_VERSION -} - -/// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::ProbeFn`. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn probe() -> i32 { - i32::from(Uki::probes()) +macro_rules! write_error { + ($err_out:expr, $error:expr) => { + if !$err_out.is_null() { + unsafe { *$err_out = $error.into() }; + } + }; } /// # Safety -/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::EspLoaderSourceFn`. +/// Touches no pointers — `unsafe extern "C"` only to match `upac_abi::boot::AbiVersionFn`. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn esp_loader_source() -> CSlice { - CSlice::from_slice(Uki::esp_loader_source().map(str::as_bytes)) +pub unsafe extern "C" fn boot_abi_version() -> u32 { + BOOT_ABI_VERSION } /// # Safety -/// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the +/// `request`, if non-null, must point to a valid, initialized `CBootPluginSetOneShotRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginSetOneShotRequest, err_out: *mut ErrorKind) -> i32 { if request.is_null() { - write_error(err_out, UkiError::InvalidRequest); - return -1; - } - - let result = entry_name_from_request(unsafe { &*request }) - .and_then(|entry_name| Uki::new().and_then(|mut uki| uki.set_one_shot(&entry_name))); - - match result { - Ok(()) => 0, - Err(error) => { - write_error(err_out, error); - -1 - } - } -} + write_error!(err_out, UkiError::InvalidRequest); -/// # Safety -/// `request`, if non-null, must point to a valid, initialized `CBootPluginRequest` for the -/// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. -#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn confirm_boot(request: *const CBootPluginRequest, err_out: *mut ErrorKind) -> i32 { - if request.is_null() { - write_error(err_out, UkiError::InvalidRequest); return -1; } - let result = entry_name_from_request(unsafe { &*request }) - .and_then(|entry_name| Uki::new().and_then(|mut uki| uki.confirm_boot(&entry_name))); + let result = BootPluginSetOneShotRequest::try_from(unsafe { &*request }) + .map_err(|_| UkiError::InvalidRequest) + .and_then(|request| Uki::new().and_then(|mut uki| uki.set_one_shot(&request.entry_name))); match result { Ok(()) => 0, + Err(error) => { - write_error(err_out, error); + write_error!(err_out, error); + -1 } } } /// # Safety -/// `request`, if non-null, must point to a valid, initialized `CBootSlotsRequest` for the +/// `request`, if non-null, must point to a valid, initialized `CBootPluginConfirmSuccsesBootRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn register_boot_slots(request: *const CBootSlotsRequest, err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn confirm_boot( + request: *const CBootPluginConfirmSuccsesBootRequest, err_out: *mut ErrorKind, +) -> i32 { if request.is_null() { - write_error(err_out, UkiError::InvalidRequest); + write_error!(err_out, UkiError::InvalidRequest); + return -1; } - let request = unsafe { &*request }; - - let result = slots_from_request(request).and_then(|(to_slot, from_slot)| { - Uki::new().and_then(|mut uki| { - uki.register_boot_slots( - request.esp_partition_number, - request.esp_starting_lba, - request.esp_ending_lba, - request.esp_unique_partition_guid, - &to_slot, - &from_slot, - ) - }) - }); + let result = BootPluginConfirmSuccsesBootRequest::try_from(unsafe { &*request }) + .map_err(|_| UkiError::InvalidRequest) + .and_then(|request| { + Uki::new().and_then(|mut uki| uki.confirm_boot(&request.entry_name, &request.esp_mount_point)) + }); match result { Ok(()) => 0, + Err(error) => { - write_error(err_out, error); + write_error!(err_out, error); + -1 } } @@ -121,34 +95,6 @@ pub unsafe extern "C" fn register_boot_slots(request: *const CBootSlotsRequest, /// Touches no pointers — uki has nothing to install onto a pre-existing ESP, always succeeds /// (its binary is copied from the source package tree via `esp_loader_source` instead). #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn install(_request: *const CBootPluginRequest, _err_out: *mut ErrorKind) -> i32 { +pub unsafe extern "C" fn install(_request: *const CBootPluginInstallRequest, _err_out: *mut ErrorKind) -> i32 { 0 } - -fn entry_name_from_request(request: &CBootPluginRequest) -> Result { - let bytes = unsafe { request.value.as_borrowed() }; - - from_utf8(bytes) - .map(str::to_owned) - .map_err(|_| UkiError::InvalidRequest) -} - -fn slots_from_request(request: &CBootSlotsRequest) -> Result<(String, String), UkiError> { - let to_bytes = unsafe { request.to_slot.as_borrowed() }; - let from_bytes = unsafe { request.from_slot.as_borrowed() }; - - let to_slot = from_utf8(to_bytes) - .map(str::to_owned) - .map_err(|_| UkiError::InvalidRequest)?; - let from_slot = from_utf8(from_bytes) - .map(str::to_owned) - .map_err(|_| UkiError::InvalidRequest)?; - - Ok((to_slot, from_slot)) -} - -fn write_error(err_out: *mut ErrorKind, error: UkiError) { - if !err_out.is_null() { - unsafe { *err_out = error.into() }; - } -} From efbc11df021050dcbdd82d4767d8afa90b663148 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 02:15:04 +0400 Subject: [PATCH 073/148] fix: drop dead booter.toml keys (efi_sysfs_path, loader_info_var, refind.source), update docs Co-Authored-By: Claude Sonnet 5 --- booters/booter.toml | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/booters/booter.toml b/booters/booter.toml index 2028e0ef..9336646b 100644 --- a/booters/booter.toml +++ b/booters/booter.toml @@ -8,25 +8,21 @@ # own build.rs reads this same file and generates its own private layout::boot module; nothing # here is a Cargo dependency between plugins, just a shared data file. # -# efi_sysfs_path is the fixed Linux kernel sysfs mount point for UEFI firmware (never -# distro/deployment-configurable, unlike the ESP mount point). efivarfs_path is the fixed -# subdirectory where the kernel exposes individual EFI variables as files (`efivar`'s own crate -# never surfaces this path, so plugins that need to clear the immutable attribute on an existing -# variable file — kernel efivarfs sets it after first write, and `efivar` never clears it back off -# before overwriting — have to reconstruct the exact same path themselves). sd_boot_loader_guid is -# systemd's vendor GUID for its own boot-loader-interface EFI variables (LoaderInfo, -# LoaderEntryOneShot, LoaderEntryDefault), per systemd's BOOT_LOADER_INTERFACE.md. boot_next_var/ -# boot_order_var are UEFI global-namespace variables, no vendor GUID needed — boot_order_var is -# only needed to clear its immutable attribute before `VarManager::set_boot_order` touches it -# (that method writes the variable internally, so the uki plugin can't intercept the write() call -# itself the way it can for boot_next_var). +# efivarfs_path is the fixed subdirectory where the kernel exposes individual EFI variables as +# files (`efivar`'s own crate never surfaces this path, so plugins that need to clear the +# immutable attribute on an existing variable file — kernel efivarfs sets it after first write, +# and `efivar` never clears it back off before overwriting — have to reconstruct the exact same +# path themselves). sd_boot_loader_guid is systemd's vendor GUID for its own boot-loader-interface +# EFI variables (LoaderEntryOneShot, LoaderEntryDefault), per systemd's BOOT_LOADER_INTERFACE.md. +# boot_next_var/boot_order_var are UEFI global-namespace variables, no vendor GUID needed — +# boot_order_var is only needed to clear its immutable attribute before +# `VarManager::set_boot_order` touches it (that method writes the variable internally, so the uki +# plugin can't intercept the write() call itself the way it can for boot_next_var). [boot] -efi_sysfs_path = "/sys/firmware/efi" efivarfs_path = "/sys/firmware/efi/efivars" sd_boot_loader_guid = "4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" boot_next_var = "BootNext" boot_order_var = "BootOrder" -loader_info_var = "LoaderInfo" loader_entry_one_shot_var = "LoaderEntryOneShot" loader_entry_default_var = "LoaderEntryDefault" @@ -64,8 +60,8 @@ from_slot = "upac-from" # UEFI/x86_64-only throughout, matching systemd_boot/refind's own *_x64 binary names below). # install_bootloader_id is grub's `--bootloader-id`, used only to name the install's own # NVRAM-independent EFI/BOOT/BOOTX64.EFI fallback copy (see backend.rs's `--removable --no-nvram` -# — genesis relies on the same firmware fallback path uki/systemd-boot/refind already use via -# esp_loader_source, not a Boot#### entry, so a fresh disk boots without any NVRAM setup). +# — genesis relies on the same UEFI firmware fallback path uki/systemd-boot/refind's binaries +# already occupy, not a Boot#### entry, so a fresh disk boots without any NVRAM setup). [grub] reboot_bin_primary = "grub-reboot" reboot_bin_fallback = "grub2-reboot" @@ -86,9 +82,6 @@ install_bootloader_id = "upac" # entry through PreviousBoot only takes effect if refind.conf's `default_selection` starts with # `+` ("remember last boot"); that's a user/deployment-side rEFInd config choice this plugin has # no way to inspect or control. GUID/name per rEFInd's own documented EFI variable, not guessed. -# source is the fixed, package-convention path (source-tree-relative) where rEFInd's own packaging -# always installs its EFI binary — same genesis use as systemd_boot.source above. [refind] previous_boot_var = "PreviousBoot" previous_boot_guid = "36d08fa7-cf0b-42f5-8f14-68df73ed3740" -source = "usr/share/refind/refind_x64.efi" From fb30cea939f2c4a779cf30764d46844625a78737 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 05:00:16 +0400 Subject: [PATCH 074/148] fix: fix REUSE specification Co-Authored-By: Claude Sonnet 5 --- lib/types/src/traits.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/types/src/traits.rs b/lib/types/src/traits.rs index 15684c4d..2115ed52 100644 --- a/lib/types/src/traits.rs +++ b/lib/types/src/traits.rs @@ -1,3 +1,8 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + use upac_abi::hook::{CProgressEvent, HookAck}; use crate::error::DecodeError; From 6aa48650ee90fa88e009fd3b1e03d36816fb2001 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 06:08:21 +0400 Subject: [PATCH 075/148] fix: rebuild decoder plugin loading around DecoderPlugin, split dynamic/static link like boot Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/plugin/decoder/dynamic_link.rs | 55 ++++++ lib/lib/src/plugin/decoder/manifest.rs | 47 +++--- lib/lib/src/plugin/decoder/mod.rs | 188 +++------------------ lib/lib/src/plugin/decoder/static_link.rs | 69 ++++++++ lib/lib/src/plugin/decoder/unpack.rs | 114 +++++-------- 5 files changed, 214 insertions(+), 259 deletions(-) create mode 100644 lib/lib/src/plugin/decoder/dynamic_link.rs create mode 100644 lib/lib/src/plugin/decoder/static_link.rs diff --git a/lib/lib/src/plugin/decoder/dynamic_link.rs b/lib/lib/src/plugin/decoder/dynamic_link.rs new file mode 100644 index 00000000..b1175b4d --- /dev/null +++ b/lib/lib/src/plugin/decoder/dynamic_link.rs @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use libloading::Library; + +use upac_abi::{DECODER_ABI_VERSION, DecodeFn, DecodePluginAbiVersionFn}; + +use super::DecoderPlugin; +use super::error::DecoderError; +use super::manifest::DecoderManifests; + +macro_rules! load_symbol { + ($library:expr, $name:literal) => { + unsafe { load_symbol(&$library, $name)? } + }; +} + +unsafe fn load_symbol(library: &Library, name: &str) -> Result { + unsafe { library.get::(name.as_bytes()) } + .map(|symbol| *symbol) + .map_err(|_| DecoderError::Symbol) +} + +impl DecoderPlugin { + pub(super) fn load_plugin(library_name: &str) -> Result { + let library = unsafe { Library::new(library_name) }.map_err(|_| DecoderError::Load)?; + + let abi_version: DecodePluginAbiVersionFn = load_symbol!(library, "decode_abi_version"); + let decode: DecodeFn = load_symbol!(library, "decode"); + + let got_abi_version = unsafe { abi_version() }; + if got_abi_version != DECODER_ABI_VERSION { + return Err(DecoderError::AbiMismatch { + got: got_abi_version, + expected: DECODER_ABI_VERSION, + }); + } + + Ok(DecoderPlugin { + decode, + _library: Some(library), + }) + } +} + +pub(super) fn load_decoder_dynamic(manifests: &DecoderManifests, format: &str) -> Result { + let manifest = manifests + .0 + .get(format) + .ok_or_else(|| DecoderError::UnknownFormat(format.to_owned()))?; + + DecoderPlugin::load_plugin(&manifest.library) +} diff --git a/lib/lib/src/plugin/decoder/manifest.rs b/lib/lib/src/plugin/decoder/manifest.rs index 2ae36f5c..7c34e007 100644 --- a/lib/lib/src/plugin/decoder/manifest.rs +++ b/lib/lib/src/plugin/decoder/manifest.rs @@ -12,6 +12,7 @@ use mime::Mime; use serde::Deserialize; +use crate::layout::decoders::{DECODERS_DIR, MANIFEST_EXTENSION}; use crate::plugin::decoder::error::DecoderError; #[derive(Debug, Clone, Deserialize)] @@ -22,35 +23,37 @@ pub struct DecoderManifest { pub mime: String, } -pub fn load_decoder_manifests( - decoders_dir: &str, manifest_extension: &str, -) -> Result, DecoderError> { - let mut manifests = HashMap::new(); +pub struct DecoderManifests(pub HashMap); - let dir = match fs::read_dir(decoders_dir) { - Ok(dir) => dir, - Err(error) if error.kind() == ErrorKind::NotFound => return Ok(manifests), - Err(error) => return Err(error.into()), - }; +impl DecoderManifests { + pub fn new() -> Result { + let mut manifests = HashMap::new(); - for entry in dir { - let path = entry?.path(); + let dir = match fs::read_dir(DECODERS_DIR) { + Ok(dir) => dir, + Err(error) if error.kind() == ErrorKind::NotFound => return Ok(DecoderManifests(manifests)), + Err(error) => return Err(error.into()), + }; - if path.extension().and_then(|extension| extension.to_str()) != Some(manifest_extension) { - continue; - } + for entry in dir { + let path = entry?.path(); + + if path.extension().and_then(|extension| extension.to_str()) != Some(MANIFEST_EXTENSION) { + continue; + } - let raw = fs::read_to_string(&path)?; - let manifest: DecoderManifest = toml::from_str(&raw)?; + let raw = fs::read_to_string(&path)?; + let manifest: DecoderManifest = toml::from_str(&raw)?; - Mime::from_str(&manifest.mime)?; + Mime::from_str(&manifest.mime)?; - if manifests.contains_key(&manifest.format) { - return Err(DecoderError::DuplicateFormat(manifest.format)); + if manifests.contains_key(&manifest.format) { + return Err(DecoderError::DuplicateFormat(manifest.format)); + } + + manifests.insert(manifest.format.clone(), manifest); } - manifests.insert(manifest.format.clone(), manifest); + Ok(DecoderManifests(manifests)) } - - Ok(manifests) } diff --git a/lib/lib/src/plugin/decoder/mod.rs b/lib/lib/src/plugin/decoder/mod.rs index 7a2c7995..958f0869 100644 --- a/lib/lib/src/plugin/decoder/mod.rs +++ b/lib/lib/src/plugin/decoder/mod.rs @@ -3,133 +3,51 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_types::package::{PackageDependency, PackageMeta}; - -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] use std::mem::MaybeUninit; -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] -use std::str::from_utf8; - -#[cfg(feature = "dynamic-plugins")] -use libloading::Library; - -#[cfg(feature = "dynamic-plugins")] -use upac_abi::DECODER_ABI_VERSION; -#[cfg(feature = "dynamic-plugins")] -use upac_abi::DecodePluginAbiVersionFn; - -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] use upac_abi::DecodeFn; - -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] +use upac_abi::hook::CancelToken; use upac_abi::request::CDecodeRequest; +use upac_abi::response::CDecodeResponse; -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] -use upac_abi::response::CDecodePackageResponse; - -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] -use upac_abi::hook::CancelToken; +use upac_types::request::DecodeRequest; +use upac_types::response::DecodeResponse; -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] -use upac_abi::types::{CBorrowed, CSlice}; +#[cfg(feature = "dynamic-plugins")] +use libloading::Library; -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] use crate::plugin::decoder::error::DecoderError; -#[cfg(feature = "builtin-alpm")] -use upac_decoders_alpm::{decode as alpm_decode, manifest as alpm_manifest}; - -#[cfg(feature = "builtin-deb")] -use upac_decoders_deb::{decode as deb_decode, manifest as deb_manifest}; - -#[cfg(feature = "builtin-rpm")] -use upac_decoders_rpm::{decode as rpm_decode, manifest as rpm_manifest}; - -#[cfg(feature = "builtin-xbps")] -use upac_decoders_xbps::{decode as xbps_decode, manifest as xbps_manifest}; +#[cfg(all(feature = "dynamic-plugins", feature = "builtin-decoders"))] +compile_error!("dynamic-plugins and builtin-decoders are mutually exclusive"); +pub mod dynamic_link; pub mod error; pub mod manifest; +pub mod static_link; pub mod triggers; pub mod unpack; -/// A package decoded by a decoder plugin. -/// -/// Plain owned data — available in every build configuration, including ones -/// without `dynamic-plugins`/`builtin-decoders`, so that callers and error -/// types elsewhere in the crate keep compiling. -pub struct DecodedPackage { - pub meta: PackageMeta, - pub dependencies: Vec, - pub declarative_triggers: Vec, -} - -#[cfg(feature = "dynamic-plugins")] -unsafe fn load_symbol(library: &Library, name: &str) -> Result { - unsafe { library.get::(name.as_bytes()) } - .map(|symbol| *symbol) - .map_err(|_| DecoderError::Symbol) -} - -/// A decoder plugin, either loaded from a shared object at runtime (`dynamic-plugins`) or -/// compiled directly into this binary (`builtin-decoders`). -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] -pub struct Decoder { +pub struct DecoderPlugin { decode: DecodeFn, #[cfg(feature = "dynamic-plugins")] _library: Option, } -#[cfg(feature = "builtin-decoders")] -impl Decoder { - fn from_static(decode: DecodeFn) -> Self { - Decoder { - decode, - - #[cfg(feature = "dynamic-plugins")] - _library: None, - } - } -} - -#[cfg(feature = "dynamic-plugins")] -impl Decoder { - pub fn load(library_name: &str) -> Result { - let library = unsafe { Library::new(library_name) }.map_err(|_| DecoderError::Load)?; - - let abi_version: DecodePluginAbiVersionFn = unsafe { load_symbol(&library, "abi_version")? }; - let decode: DecodeFn = unsafe { load_symbol(&library, "decode")? }; - - let got = unsafe { abi_version() }; - if got != DECODER_ABI_VERSION { - return Err(DecoderError::AbiMismatch { - got, - expected: DECODER_ABI_VERSION, - }); - } - - Ok(Decoder { - decode, - _library: Some(library), - }) - } -} - -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] -impl Decoder { +impl DecoderPlugin { pub fn decode( &self, package_path: &str, output_dir: &str, checksum: [u8; 32], cancel: &CancelToken, - ) -> Result { - let request = CDecodeRequest::new( - CSlice::from_borrowed(package_path.as_bytes()), - CSlice::from_borrowed(output_dir.as_bytes()), + ) -> Result { + let request: CDecodeRequest = DecodeRequest { + package_path: package_path.to_owned(), + output_dir: output_dir.to_owned(), checksum, - cancel as *const CancelToken as *mut CancelToken, - ); + cancel_token: cancel as *const CancelToken as *mut CancelToken, + } + .into(); - let mut response = MaybeUninit::::uninit(); + let mut response = MaybeUninit::::uninit(); let code = unsafe { (self.decode)(&request, response.as_mut_ptr()) }; if code != 0 { @@ -138,70 +56,6 @@ impl Decoder { let response = unsafe { response.assume_init() }; - unsafe { response.validate() }?; - - let meta = PackageMeta::try_from(&response.meta)?; - - let dependencies = unsafe { response.dependencies.as_slice() } - .iter() - .map(PackageDependency::try_from) - .collect::, _>>()?; - - let declarative_triggers = unsafe { response.declarative_triggers.as_slice() } - .iter() - .map(|trigger| unsafe { trigger.as_borrowed() }) - .map(|bytes| from_utf8(bytes).map(str::to_owned)) - .collect::, _>>() - .map_err(|_| DecoderError::InvalidResponse)?; - - Ok(DecodedPackage { - meta, - dependencies, - declarative_triggers, - }) + Ok(DecodeResponse::try_from(&response)?) } } - -/// The decoders compiled directly into this binary, keyed by format name with their claimed -/// extensions — mirrors `plugin::boot::static_plugins`, adapted for extension-based dispatch -/// (a decoder is selected by the package file's extension, not by a `probe()` call). No ABI -/// version check: compiled from the same source tree by the same compiler, so the decoder's own -/// `DECODER_ABI_VERSION` matches by construction. -#[cfg(feature = "builtin-decoders")] -#[allow( - clippy::vec_init_then_push, - reason = "each push is independently cfg-gated, vec![] can't express that" -)] -pub(crate) fn static_decoders() -> Vec<(&'static str, &'static [&'static str], Decoder)> { - let mut decoders = Vec::new(); - - #[cfg(feature = "builtin-alpm")] - decoders.push(( - alpm_manifest::FORMAT, - alpm_manifest::EXTENSIONS, - Decoder::from_static(alpm_decode), - )); - - #[cfg(feature = "builtin-deb")] - decoders.push(( - deb_manifest::FORMAT, - deb_manifest::EXTENSIONS, - Decoder::from_static(deb_decode), - )); - - #[cfg(feature = "builtin-rpm")] - decoders.push(( - rpm_manifest::FORMAT, - rpm_manifest::EXTENSIONS, - Decoder::from_static(rpm_decode), - )); - - #[cfg(feature = "builtin-xbps")] - decoders.push(( - xbps_manifest::FORMAT, - xbps_manifest::EXTENSIONS, - Decoder::from_static(xbps_decode), - )); - - decoders -} diff --git a/lib/lib/src/plugin/decoder/static_link.rs b/lib/lib/src/plugin/decoder/static_link.rs new file mode 100644 index 00000000..9af9ff15 --- /dev/null +++ b/lib/lib/src/plugin/decoder/static_link.rs @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac_abi::DecodeFn; + +use super::DecoderPlugin; + +#[cfg(feature = "builtin-alpm")] +use upac_decoders_alpm::{decode as alpm_decode, manifest as alpm_manifest}; + +#[cfg(feature = "builtin-deb")] +use upac_decoders_deb::{decode as deb_decode, manifest as deb_manifest}; + +#[cfg(feature = "builtin-rpm")] +use upac_decoders_rpm::{decode as rpm_decode, manifest as rpm_manifest}; + +#[cfg(feature = "builtin-xbps")] +use upac_decoders_xbps::{decode as xbps_decode, manifest as xbps_manifest}; + +impl DecoderPlugin { + fn load_plugin_from_static(decode: DecodeFn) -> Self { + DecoderPlugin { + decode, + + #[cfg(feature = "dynamic-plugins")] + _library: None, + } + } +} + +#[allow( + clippy::vec_init_then_push, + reason = "each push is independently cfg-gated, vec![] can't express that" +)] +pub(super) fn static_decoders() -> Vec<(&'static str, &'static [&'static str], DecoderPlugin)> { + let mut decoders = Vec::new(); + + #[cfg(feature = "builtin-alpm")] + decoders.push(( + alpm_manifest::FORMAT, + alpm_manifest::EXTENSIONS, + DecoderPlugin::load_plugin_from_static(alpm_decode), + )); + + #[cfg(feature = "builtin-deb")] + decoders.push(( + deb_manifest::FORMAT, + deb_manifest::EXTENSIONS, + DecoderPlugin::load_plugin_from_static(deb_decode), + )); + + #[cfg(feature = "builtin-rpm")] + decoders.push(( + rpm_manifest::FORMAT, + rpm_manifest::EXTENSIONS, + DecoderPlugin::load_plugin_from_static(rpm_decode), + )); + + #[cfg(feature = "builtin-xbps")] + decoders.push(( + xbps_manifest::FORMAT, + xbps_manifest::EXTENSIONS, + DecoderPlugin::load_plugin_from_static(xbps_decode), + )); + + decoders +} diff --git a/lib/lib/src/plugin/decoder/unpack.rs b/lib/lib/src/plugin/decoder/unpack.rs index c3b6ae1a..08ca5df7 100644 --- a/lib/lib/src/plugin/decoder/unpack.rs +++ b/lib/lib/src/plugin/decoder/unpack.rs @@ -21,31 +21,45 @@ use std::path::Path; use sha2::{Digest, Sha256}; #[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] -use crate::plugin::decoder::Decoder; +use crate::plugin::decoder::DecoderPlugin; #[cfg(feature = "dynamic-plugins")] use std::collections::HashMap; #[cfg(feature = "dynamic-plugins")] -use crate::layout::decoders; +use crate::plugin::decoder::dynamic_link::load_decoder_dynamic; #[cfg(feature = "dynamic-plugins")] -use crate::plugin::decoder::manifest::{DecoderManifest, load_decoder_manifests}; +use crate::plugin::decoder::manifest::DecoderManifests; #[cfg(feature = "builtin-decoders")] -use crate::plugin::decoder::static_decoders; +use crate::plugin::decoder::static_link::static_decoders; + +#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] +fn checksum_of_file(path: &str) -> Result<[u8; 32], DecoderError> { + let mut file = File::open(path)?; + let mut hasher = Sha256::new(); + let mut buffer = [0u8; 65536]; + + loop { + let bytes_read = file.read(&mut buffer)?; + if bytes_read == 0 { + break; + } + hasher.update(&buffer[..bytes_read]); + } + + Ok(hasher.finalize().into()) +} pub struct PackageUnpacker { #[cfg(feature = "dynamic-plugins")] - manifests: HashMap, + manifests: DecoderManifests, #[cfg(feature = "dynamic-plugins")] - decoders: HashMap, + decoders: HashMap, - #[cfg(all(not(feature = "dynamic-plugins"), feature = "builtin-decoders"))] - decoders: Vec<(&'static str, &'static [&'static str], Decoder)>, - - #[cfg(all(feature = "dynamic-plugins", feature = "builtin-decoders"))] - static_decoders: Vec<(&'static str, &'static [&'static str], Decoder)>, + #[cfg(feature = "builtin-decoders")] + static_decoders: Vec<(&'static str, &'static [&'static str], DecoderPlugin)>, } #[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] @@ -85,14 +99,12 @@ impl PackageUnpacker { #[cfg(feature = "dynamic-plugins")] impl PackageUnpacker { pub fn new() -> Result { - let manifests = load_decoder_manifests(decoders::DECODERS_DIR, decoders::MANIFEST_EXTENSION)?; - Ok(Self { - manifests, + manifests: DecoderManifests::new()?, decoders: HashMap::new(), #[cfg(feature = "builtin-decoders")] - static_decoders: static_decoders(), + static_decoders: Vec::new(), }) } @@ -102,56 +114,35 @@ impl PackageUnpacker { .and_then(|extension| extension.to_str()) .ok_or_else(|| DecoderError::UnknownFormat(package_path.to_owned()))?; - if let Some(format) = self - .manifests + self.manifests + .0 .values() .find(|manifest| manifest.extensions.iter().any(|candidate| candidate == extension)) .map(|manifest| manifest.format.clone()) - { - return Ok(format); - } - - #[cfg(feature = "builtin-decoders")] - if let Some((format, ..)) = self - .static_decoders - .iter() - .find(|(_, extensions, _)| extensions.contains(&extension)) - { - return Ok((*format).to_owned()); - } - - Err(DecoderError::UnknownFormat(package_path.to_owned())) + .ok_or_else(|| DecoderError::UnknownFormat(package_path.to_owned())) } - fn decoder_for(&mut self, format: &str) -> Result<&Decoder, DecoderError> { - if self.decoders.contains_key(format) { - return Ok(&self.decoders[format]); - } - - if let Some(manifest) = self.manifests.get(format) { - let decoder = Decoder::load(&manifest.library)?; + fn decoder_for(&mut self, format: &str) -> Result<&DecoderPlugin, DecoderError> { + if !self.decoders.contains_key(format) { + let decoder = load_decoder_dynamic(&self.manifests, format)?; self.decoders.insert(format.to_owned(), decoder); - return Ok(&self.decoders[format]); } - #[cfg(feature = "builtin-decoders")] - if let Some((_, _, decoder)) = self.static_decoders.iter().find(|(name, _, _)| *name == format) { - return Ok(decoder); - } - - Err(DecoderError::UnknownFormat(format.to_owned())) + Ok(&self.decoders[format]) } } -/// Format resolution here never touches disk — the extension/format table comes straight from -/// each builtin decoder's own compiled-in manifest constants (`static_decoders`), not from -/// `/etc/upac.d/decoders/*.toml`. A build with `builtin-decoders` and no `dynamic-plugins` is -/// fully self-contained: no on-disk manifest is required for it to decode anything. #[cfg(all(not(feature = "dynamic-plugins"), feature = "builtin-decoders"))] impl PackageUnpacker { pub fn new() -> Result { Ok(Self { - decoders: static_decoders(), + #[cfg(feature = "dynamic-plugins")] + manifests: DecoderManifests::new()?, + + #[cfg(feature = "dynamic-plugins")] + decoders: HashMap::new(), + + static_decoders: static_decoders(), }) } @@ -161,15 +152,15 @@ impl PackageUnpacker { .and_then(|extension| extension.to_str()) .ok_or_else(|| DecoderError::UnknownFormat(package_path.to_owned()))?; - self.decoders + self.static_decoders .iter() .find(|(_, extensions, _)| extensions.contains(&extension)) .map(|(format, _, _)| (*format).to_owned()) .ok_or_else(|| DecoderError::UnknownFormat(package_path.to_owned())) } - fn decoder_for(&mut self, format: &str) -> Result<&Decoder, DecoderError> { - self.decoders + fn decoder_for(&mut self, format: &str) -> Result<&DecoderPlugin, DecoderError> { + self.static_decoders .iter() .find(|(name, _, _)| *name == format) .map(|(_, _, decoder)| decoder) @@ -190,20 +181,3 @@ impl PackageUnpacker { Err(DecoderError::NoDecoders) } } - -#[cfg(any(feature = "dynamic-plugins", feature = "builtin-decoders"))] -fn checksum_of_file(path: &str) -> Result<[u8; 32], DecoderError> { - let mut file = File::open(path)?; - let mut hasher = Sha256::new(); - let mut buffer = [0u8; 65536]; - - loop { - let bytes_read = file.read(&mut buffer)?; - if bytes_read == 0 { - break; - } - hasher.update(&buffer[..bytes_read]); - } - - Ok(hasher.finalize().into()) -} From 5e247b23d5ea1b46b586d492070133956c07a20a Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 06:08:46 +0400 Subject: [PATCH 076/148] fix: fix api for new decoder api Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/mutated/mime/preparing.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/lib/src/mutated/mime/preparing.rs b/lib/lib/src/mutated/mime/preparing.rs index 9cd5277b..0ebe1315 100644 --- a/lib/lib/src/mutated/mime/preparing.rs +++ b/lib/lib/src/mutated/mime/preparing.rs @@ -12,10 +12,10 @@ use upac_types::hook::ProgressEventBuilder; use super::{DesktopContent, MimeError}; use crate::errors::CommonError; -use crate::layout::{decoders, mime}; +use crate::layout::mime; use crate::orchestrator::context::Context; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::plugin::decoder::manifest::load_decoder_manifests; +use crate::plugin::decoder::manifest::DecoderManifests; pub struct PreparingStage; @@ -23,11 +23,10 @@ impl Stage for PreparingStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), MimeError> { - let manifests = load_decoder_manifests(decoders::DECODERS_DIR, decoders::MANIFEST_EXTENSION) - .map_err(CommonError::Decoder)?; + let manifests = DecoderManifests::new().map_err(CommonError::Decoder)?; let desktop_content = fs::read_to_string(mime::DESKTOP_FILE_PATH)?; - context.put(manifests); + context.put(manifests.0); context.put(DesktopContent(desktop_content)); Ok((progress, StageResult::Advance, Box::new(NoRollback))) From 588d38dc351c7283b7af9fda83d0b442c3775493 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 06:09:39 +0400 Subject: [PATCH 077/148] fix: fix symbol name Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/plugin/boot/dynamic_link.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/lib/src/plugin/boot/dynamic_link.rs b/lib/lib/src/plugin/boot/dynamic_link.rs index 6763c964..2723c790 100644 --- a/lib/lib/src/plugin/boot/dynamic_link.rs +++ b/lib/lib/src/plugin/boot/dynamic_link.rs @@ -27,7 +27,7 @@ impl BootPlugin { pub(super) fn load_plugin(library_name: &str) -> Result { let library = unsafe { Library::new(library_name) }.map_err(|_| BootPluginError::Load)?; - let booter_abi_version: BootPluginAbiVersionFn = load_symbol!(library, "abi_version"); + let booter_abi_version: BootPluginAbiVersionFn = load_symbol!(library, "boot_abi_version"); let set_one_shot: SetOneShotFn = load_symbol!(library, "set_one_shot"); let confirm_boot: ConfirmBootFn = load_symbol!(library, "confirm_boot"); let install: InstallFn = load_symbol!(library, "install"); From b2051b318c4aafe701579e0167900f99909c1b07 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 06:37:18 +0400 Subject: [PATCH 078/148] feat: add ContextValue derive for Deref/DerefMut/From on single-field context wrappers Co-Authored-By: Claude Sonnet 5 --- lib/macro/src/context_value/mod.rs | 51 ++++++++++++++++++++++++++++++ lib/macro/src/lib.rs | 7 ++++ 2 files changed, 58 insertions(+) create mode 100644 lib/macro/src/context_value/mod.rs diff --git a/lib/macro/src/context_value/mod.rs b/lib/macro/src/context_value/mod.rs new file mode 100644 index 00000000..94988896 --- /dev/null +++ b/lib/macro/src/context_value/mod.rs @@ -0,0 +1,51 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{Data, DeriveInput, Error, Fields, parse_macro_input}; + +pub(crate) fn expand(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + + let field = match &input.data { + Data::Struct(s) => match &s.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0], + _ => { + return Error::new_spanned(name, "ContextValue only supports single-field tuple structs") + .to_compile_error() + .into(); + } + }, + _ => { + return Error::new_spanned(name, "ContextValue only supports tuple structs") + .to_compile_error() + .into(); + } + }; + + let ty = &field.ty; + + let expanded: TokenStream2 = quote! { + impl std::ops::Deref for #name { + type Target = #ty; + + fn deref(&self) -> &#ty { + &self.0 + } + } + + impl std::ops::DerefMut for #name { + fn deref_mut(&mut self) -> &mut #ty { + &mut self.0 + } + } + + impl From<#ty> for #name { + fn from(value: #ty) -> Self { + #name(value) + } + } + }; + + expanded.into() +} diff --git a/lib/macro/src/lib.rs b/lib/macro/src/lib.rs index 9db7cd08..2215faa6 100644 --- a/lib/macro/src/lib.rs +++ b/lib/macro/src/lib.rs @@ -12,6 +12,7 @@ //! CTryToRust - C-ABI struct -> Rust domain type, fallible (inbound) //! CToRust - C-ABI struct -> Rust domain type, infallible (inbound) //! CValidate - unsafe validate() checking struct_size + every field +//! ContextValue - Deref/DerefMut/From for a single-field tuple struct //! FromStageIndex - orchestrator stage index -> enum variant (by position) //! StageKey - enum variant -> "stage_snake_case" gettext key (by name) //! RedbCodec - encode_into()/decode_from() for the redb key-value store @@ -29,6 +30,7 @@ mod c_to_rust; mod c_try_to_rust; mod c_validate; mod common; +mod context_value; mod from_stage_index; mod json_codec; mod redb_codec; @@ -65,6 +67,11 @@ pub fn derive_cvalidate(input: TokenStream) -> TokenStream { c_validate::expand(input) } +#[proc_macro_derive(ContextValue)] +pub fn derive_context_value(input: TokenStream) -> TokenStream { + context_value::expand(input) +} + #[proc_macro_derive(FromStageIndex)] pub fn derive_from_stage_index(input: TokenStream) -> TokenStream { from_stage_index::expand(input) From f031fb3e11ed2fffb644e8e28fd97d6d5f09ae82 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 09:36:24 +0400 Subject: [PATCH 079/148] fix: group orchestrator context values and apply ContextValue derive across all mutating pipelines - installer/uninstaller/update/files/gc/mime/commit/rollback/pin: consolidate co-occurring single-field context wrappers into named multi-field structs (NewState, CommitInfo, UnpackState, ImportProgress, ImportedState, WorkingState, RemoveProgress, DeployProgress, WriteProgress, FileProgress, ApplyTarget), apply #[derive(ContextValue)] uniformly to the remaining single-field tuple wrappers - fix real bugs surfaced along the way: uninstaller's Targets -> UninstallPackagesTargets, update/import.rs's undefined `pending` variable, uninstaller/remove.rs's stale WorkingState.0 field access, merge.rs config-path loop iterating the wrong collection - fix clippy unused_mut suggestion in decoder plugin's static_link.rs (mut only needed once at least one builtin-* decoder feature is enabled) - fix build: missing upac_pki::signature::{RootCertificate, HookSignature} import in scripts/mod.rs, and PackageUuidsToRemove move-out in uninstaller/open.rs (uuids.0.into_iter(), Deref doesn't allow moving out non-Copy fields) Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/export/unmutated/diff_prefix.rs | 4 +- lib/lib/src/mutated/commit/mod.rs | 12 ++- lib/lib/src/mutated/commit/transaction.rs | 9 +-- lib/lib/src/mutated/files/apply.rs | 74 +++++++++---------- lib/lib/src/mutated/files/checkout.rs | 10 ++- lib/lib/src/mutated/files/commit.rs | 21 +++--- lib/lib/src/mutated/files/mod.rs | 51 +++++++++---- lib/lib/src/mutated/files/open.rs | 14 +--- lib/lib/src/mutated/gc/cleaning.rs | 2 +- lib/lib/src/mutated/gc/collect.rs | 25 ++++--- lib/lib/src/mutated/gc/mod.rs | 10 ++- lib/lib/src/mutated/gc/pruning.rs | 7 +- lib/lib/src/mutated/installer/checkout.rs | 14 ++-- lib/lib/src/mutated/installer/commit.rs | 21 +++--- lib/lib/src/mutated/installer/import.rs | 47 ++++++------ lib/lib/src/mutated/installer/merge.rs | 27 +++---- lib/lib/src/mutated/installer/mod.rs | 60 +++++++++------ lib/lib/src/mutated/installer/open.rs | 10 ++- lib/lib/src/mutated/installer/preparation.rs | 36 ++++----- lib/lib/src/mutated/mime/mod.rs | 9 ++- lib/lib/src/mutated/mime/rendering.rs | 7 +- lib/lib/src/mutated/mime/writing.rs | 22 +++--- lib/lib/src/mutated/pin/mod.rs | 5 ++ lib/lib/src/mutated/pin/stage.rs | 2 +- lib/lib/src/mutated/rollback/checkout.rs | 8 +- lib/lib/src/mutated/rollback/merge.rs | 2 +- lib/lib/src/mutated/rollback/mod.rs | 6 ++ lib/lib/src/mutated/uninstaller/checkout.rs | 14 ++-- lib/lib/src/mutated/uninstaller/commit.rs | 18 ++--- lib/lib/src/mutated/uninstaller/merge.rs | 25 +++---- lib/lib/src/mutated/uninstaller/mod.rs | 42 ++++++++--- lib/lib/src/mutated/uninstaller/open.rs | 18 ++--- .../src/mutated/uninstaller/preparation.rs | 3 +- lib/lib/src/mutated/uninstaller/remove.rs | 51 ++++++------- lib/lib/src/mutated/update/checkout.rs | 14 ++-- lib/lib/src/mutated/update/commit.rs | 22 +++--- lib/lib/src/mutated/update/import.rs | 68 ++++++++--------- lib/lib/src/mutated/update/merge.rs | 30 +++----- lib/lib/src/mutated/update/mod.rs | 64 ++++++++++------ lib/lib/src/mutated/update/open.rs | 12 +-- lib/lib/src/mutated/update/preparation.rs | 36 ++++----- lib/lib/src/plugin/decoder/mod.rs | 2 + lib/lib/src/plugin/decoder/static_link.rs | 4 + lib/lib/src/scripts/file.rs | 2 +- lib/lib/src/scripts/mod.rs | 13 +++- lib/lib/src/unmutated/diff/mod.rs | 2 + 46 files changed, 521 insertions(+), 434 deletions(-) diff --git a/lib/lib/src/export/unmutated/diff_prefix.rs b/lib/lib/src/export/unmutated/diff_prefix.rs index ca4003db..57bda4d4 100644 --- a/lib/lib/src/export/unmutated/diff_prefix.rs +++ b/lib/lib/src/export/unmutated/diff_prefix.rs @@ -24,9 +24,7 @@ pub unsafe extern "C" fn diff_prefix( ) -> i32 { let diff_prefix_data = try_convert_abi!(DiffPrefixData::try_from(&request_c), err_out, DiffPrefixStateId); - let result = catch_unwind(AssertUnwindSafe(|| { - crate::unmutated::diff_prefix::run(diff_prefix_data) - })); + let result = catch_unwind(AssertUnwindSafe(|| run(diff_prefix_data))); match result { Ok(Ok(response)) => { diff --git a/lib/lib/src/mutated/commit/mod.rs b/lib/lib/src/mutated/commit/mod.rs index 144c29b9..3f139059 100644 --- a/lib/lib/src/mutated/commit/mod.rs +++ b/lib/lib/src/mutated/commit/mod.rs @@ -29,8 +29,10 @@ pub use self::error::CommitError; mod error; mod transaction; -pub(crate) struct Subject(pub String); -pub(crate) struct CommitMessage(pub Option); +pub(crate) struct CommitInfo { + pub subject: String, + pub message: Option, +} pub struct CommitData<'a> { pub tmp_path: &'a str, @@ -73,8 +75,10 @@ pub fn run(data: CommitData) -> Result<(), (CommitStateId, CommitError)> { let mut context = Context::new(); context.put(deploy); context.put(TmpPath(data.tmp_path.to_owned())); - context.put(Subject(data.subject.to_owned())); - context.put(CommitMessage(data.message.map(str::to_owned))); + context.put(CommitInfo { + subject: data.subject.to_owned(), + message: data.message.map(str::to_owned), + }); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); let orchestrator = assemble(); diff --git a/lib/lib/src/mutated/commit/transaction.rs b/lib/lib/src/mutated/commit/transaction.rs index 5904e5f0..230c452e 100644 --- a/lib/lib/src/mutated/commit/transaction.rs +++ b/lib/lib/src/mutated/commit/transaction.rs @@ -9,7 +9,7 @@ use composefs::repository::ImportContext; use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{CommitError, CommitMessage, Subject}; +use super::{CommitError, CommitInfo}; use crate::composefs::overlay::apply_overlay_upper; use crate::composefs::repository::commit_tree; @@ -27,8 +27,7 @@ impl Stage for TransactionStage { &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), CommitError> { let deploy = ctx_get!(context, Deploy); - let subject = ctx_get!(context, Subject); - let message = ctx_get!(context, CommitMessage); + let commit_info = ctx_get!(context, CommitInfo); let repository = deploy.open_repository()?; @@ -51,8 +50,8 @@ impl Stage for TransactionStage { written.extend(record_deploy.update_working_config( ¤t_record_dir, new_config_digest, - subject.0.clone(), - message.0.clone(), + commit_info.subject.clone(), + commit_info.message.clone(), )?); Ok((progress, StageResult::Advance, Box::new(written))) diff --git a/lib/lib/src/mutated/files/apply.rs b/lib/lib/src/mutated/files/apply.rs index d48b00cf..122adf48 100644 --- a/lib/lib/src/mutated/files/apply.rs +++ b/lib/lib/src/mutated/files/apply.rs @@ -17,10 +17,7 @@ use upac_abi::{DiffFileSource, FileDiffKind}; use upac_types::entry::{FileEntry, FileEntryScope}; use upac_types::hook::ProgressEventBuilder; -use super::{ - ConfigUpperDir, FilesError, PendingFiles, RequestedFileKind, RequestedFileScope, TargetUuid, TotalFiles, - WorkingDatabase, WorkingTree, -}; +use super::{ApplyTarget, FileProgress, FilesError, RequestedFileOperation, WorkingState}; use crate::composefs::error::RepoError; use crate::composefs::file::{FileHandle, stat_from_metadata}; @@ -38,33 +35,30 @@ impl Stage for ApplyFileStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), FilesError> { - let mut pending_files = ctx_take!(context, PendingFiles); - let mut woking_files_tree = ctx_take!(context, WorkingTree); - let mut woking_database = ctx_take!(context, WorkingDatabase); - let mut import_ctx = ctx_take!(context, ImportContext); - - let config_upper_dir = ctx_get!(context, ConfigUpperDir); - let uuid = ctx_get!(context, TargetUuid); - let file_kind = ctx_get!(context, RequestedFileKind); - let scope = ctx_get!(context, RequestedFileScope); - let total_files = ctx_get!(context, TotalFiles); + let mut file_progress = ctx_take!(context, FileProgress); + let mut woking_state = ctx_take!(context, WorkingState); + let mut imported_ctx = ctx_take!(context, ImportContext); + + let apply_target = ctx_get!(context, ApplyTarget); + let file_operation = ctx_get!(context, RequestedFileOperation); + let deploy = ctx_get!(context, Deploy); - let path = pending_files.0.pop_front().ok_or(CommonError::MissingResult)?; + let path = file_progress.pending.pop_front().ok_or(CommonError::MissingResult)?; - match scope.0 { + match file_operation.scope { DiffFileSource::Prefix => { let repository = deploy.open_repository()?; - match file_kind.0 { + match file_operation.kind { FileDiffKind::Removed => { - FileHandle::new(&path).remove_in_tree(&mut woking_files_tree.0)?; - woking_database.0.remove_user_file(uuid.0, &path)?; + FileHandle::new(&path).remove_in_tree(&mut woking_state.tree)?; + woking_state.database.remove_user_file(apply_target.uuid, &path)?; } FileDiffKind::Added | FileDiffKind::Modified => { - Self::add_file(&path, &repository, &mut woking_files_tree.0, &mut import_ctx)?; - woking_database.0.insert_package_file( - uuid.0, + Self::add_file(&path, &repository, &mut woking_state.tree, &mut imported_ctx)?; + woking_state.database.insert_package_file( + apply_target.uuid, &FileEntry { path: path.clone(), is_user: true, @@ -74,15 +68,15 @@ impl Stage for ApplyFileStage { } } } - DiffFileSource::Config => match file_kind.0 { + DiffFileSource::Config => match file_operation.kind { FileDiffKind::Removed => { - remove_file(config_upper_dir.0.join(&path)).map_err(RepoError::from)?; - woking_database.0.remove_user_file(uuid.0, &path)?; + remove_file(apply_target.config_upper_dir.join(&path)).map_err(RepoError::from)?; + woking_state.database.remove_user_file(apply_target.uuid, &path)?; } FileDiffKind::Added | FileDiffKind::Modified => { - Self::add_config_file(&path, &config_upper_dir.0)?; - woking_database.0.insert_package_file( - uuid.0, + Self::add_config_file(&path, &apply_target.config_upper_dir)?; + woking_state.database.insert_package_file( + apply_target.uuid, &FileEntry { path: path.clone(), is_user: true, @@ -93,20 +87,19 @@ impl Stage for ApplyFileStage { }, } - let remaining = pending_files.0.len() as u64; - let processed = total_files.0 - remaining; - progress = progress.subject(path).progress(processed, total_files.0); + let remaining = file_progress.pending.len() as u64; + let processed = file_progress.total - remaining; + progress = progress.subject(path).progress(processed, file_progress.total); - let result = if pending_files.0.is_empty() { + let result = if file_progress.pending.is_empty() { StageResult::Advance } else { StageResult::Repeat }; - context.put(pending_files); - context.put(woking_files_tree); - context.put(woking_database); - context.put(import_ctx); + context.put(file_progress); + context.put(woking_state); + context.put(imported_ctx); Ok((progress, result, Box::new(NoRollback))) } @@ -114,7 +107,8 @@ impl Stage for ApplyFileStage { impl ApplyFileStage { fn add_file( - path: &str, repository: &Repository, tree: &mut FileSystem, import_ctx: &mut ImportContext, + path: &str, repository: &Repository, tree: &mut FileSystem, + imported_ctx: &mut ImportContext, ) -> Result<(), FilesError> { let source_path = Path::new(path); let metadata = symlink_metadata(source_path).map_err(RepoError::from)?; @@ -145,17 +139,17 @@ impl ApplyFileStage { tree, &File::open(source_path).map_err(RepoError::from)?, stat, - import_ctx, + imported_ctx, )?; } Ok(()) } - fn add_config_file(path: &str, etc_upper_dir: &Path) -> Result<(), FilesError> { + fn add_config_file(path: &str, config_upper_dir: &Path) -> Result<(), FilesError> { let live_path = Path::new(LIVE_ETC_DIR).join(path); let metadata = symlink_metadata(&live_path).map_err(RepoError::from)?; - let dest_path = etc_upper_dir.join(path); + let dest_path = config_upper_dir.join(path); if let Some(parent) = dest_path.parent() { create_dir_all(parent).map_err(RepoError::from)?; diff --git a/lib/lib/src/mutated/files/checkout.rs b/lib/lib/src/mutated/files/checkout.rs index 294b4888..5acff554 100644 --- a/lib/lib/src/mutated/files/checkout.rs +++ b/lib/lib/src/mutated/files/checkout.rs @@ -21,17 +21,19 @@ impl Stage for CheckoutStage { &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), FilesError> { let new_prefix = ctx_get!(context, NewPrefixDigest); + let deploy = ctx_get!(context, Deploy); + let requested_boot_plugins = ctx_get!(context, RequestedBootPlugin); let repository = deploy.open_repository()?; - let deploy_tree = deploy.open_tree(&new_prefix.0)?; - let digest = object_id_from_hex(&new_prefix.0)?; + let deploy_tree = deploy.open_tree(&new_prefix)?; + let digest = object_id_from_hex(&new_prefix)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &deploy_tree, digest, &esp_mount, &new_prefix.0)?; + let entry_name = write_boot_entry(&repository, &deploy_tree, digest, &esp_mount, &new_prefix)?; - let plugin = BootPlugins::new()?.load(&requested_boot_plugins.0)?; + let plugin = BootPlugins::new()?.load(&requested_boot_plugins)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/files/commit.rs b/lib/lib/src/mutated/files/commit.rs index f4897fc2..227f9dab 100644 --- a/lib/lib/src/mutated/files/commit.rs +++ b/lib/lib/src/mutated/files/commit.rs @@ -15,7 +15,7 @@ use upac_abi::hook::CancelToken; use upac_types::TmpPath; use upac_types::hook::ProgressEventBuilder; -use super::{CommitMessage, FilesError, NewPrefixDigest, Subject, WorkingDatabase, WorkingTree}; +use super::{CommitInfo, FilesError, NewPrefixDigest, WorkingState}; use crate::composefs::error::RepoError; use crate::composefs::file::FileHandle; @@ -35,19 +35,18 @@ impl Stage for CommitTransactionStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), FilesError> { - let working_tree = ctx_take!(context, WorkingTree); - let working_database = ctx_take!(context, WorkingDatabase); - let mut import_ctx = ctx_take!(context, ImportContext); + let working_state = ctx_take!(context, WorkingState); + let mut imported_ctx = ctx_take!(context, ImportContext); + let commit_info = ctx_get!(context, CommitInfo); let tmp_path = ctx_get!(context, TmpPath); + let deploy = ctx_get!(context, Deploy); - let subject = ctx_get!(context, Subject); - let message = ctx_get!(context, CommitMessage); let repository = deploy.open_repository()?; - let mut tree = working_tree.0; + let mut tree = working_state.tree; - let database_bytes = working_database.0.into_bytes()?; + let database_bytes = working_state.database.into_bytes()?; let database_scratch_path = Path::new(tmp_path.as_ref()).join(FILES_SCRATCH_FILENAME); write(&database_scratch_path, &database_bytes).map_err(RepoError::from)?; @@ -56,7 +55,7 @@ impl Stage for CommitTransactionStage { &mut tree, &File::open(&database_scratch_path).map_err(RepoError::from)?, Stat::uninitialized(), - &mut import_ctx, + &mut imported_ctx, )?; let digest = commit_tree(&repository, tree)?; @@ -73,8 +72,8 @@ impl Stage for CommitTransactionStage { let record = DeployRecord { prefix_digest: new_prefix.clone(), - subject: subject.0.clone(), - message: message.0.clone(), + subject: commit_info.subject.clone(), + message: commit_info.message.clone(), seq: DeployRecord::allocate_seq(&deploy.next_seq_path())?, timestamp: DeployRecord::now_secs(), config_history: current_record.config_history.clone(), diff --git a/lib/lib/src/mutated/files/mod.rs b/lib/lib/src/mutated/files/mod.rs index 0cdab519..4bde5b85 100644 --- a/lib/lib/src/mutated/files/mod.rs +++ b/lib/lib/src/mutated/files/mod.rs @@ -23,6 +23,8 @@ use upac_types::hook::Message; use upac_types::states::FilesStateId; use upac_types::traits::MessageHook; +use upac_macro::ContextValue; + use self::apply::ApplyFileStage; use self::checkout::CheckoutStage; use self::commit::CommitTransactionStage; @@ -48,28 +50,45 @@ mod error; mod open; mod swap; -pub(crate) struct RequestedFileKind(pub FileDiffKind); -pub(crate) struct RequestedFileScope(pub DiffFileSource); +pub(crate) struct RequestedFileOperation { + pub kind: FileDiffKind, + pub scope: DiffFileSource, +} pub(crate) struct RequestedFilePackage { pub name: String, pub arch: String, pub arch_sub: Option, } + +#[derive(ContextValue)] pub(crate) struct NewPrefixDigest(pub String); -pub(crate) struct Subject(pub String); -pub(crate) struct CommitMessage(pub Option); + +pub(crate) struct CommitInfo { + pub subject: String, + pub message: Option, +} + +#[derive(ContextValue)] pub(crate) struct RequestedBootPlugin(pub String); pub(crate) struct ResolvedBootEntry { pub plugin: BootPlugin, pub entry_name: String, } -pub(crate) struct PendingFiles(pub VecDeque); -pub(crate) struct TotalFiles(pub u64); -pub(crate) struct WorkingTree(pub FileSystem); -pub(crate) struct WorkingDatabase(pub MemoryDatabase); -pub(crate) struct TargetUuid(pub Uuid); -pub(crate) struct ConfigUpperDir(pub PathBuf); +pub(crate) struct FileProgress { + pub pending: VecDeque, + pub total: u64, +} + +pub(crate) struct WorkingState { + pub tree: FileSystem, + pub database: MemoryDatabase, +} + +pub(crate) struct ApplyTarget { + pub uuid: Uuid, + pub config_upper_dir: PathBuf, +} pub struct FilesPackage<'a> { pub name: &'a str, @@ -154,16 +173,20 @@ pub fn run(data: FilesData) -> Result<(), (FilesStateId, FilesError)> { .map(|path| (*path).to_owned()) .collect::>(), ); - context.put(RequestedFileKind(data.file_kind)); - context.put(RequestedFileScope(data.scope)); + context.put(RequestedFileOperation { + kind: data.file_kind, + scope: data.scope, + }); context.put(RequestedFilePackage { name: data.file_package.name.to_owned(), arch: data.file_package.arch.to_owned(), arch_sub: data.file_package.arch_sub.map(str::to_owned), }); context.put(TmpPath(data.tmp_path.to_owned())); - context.put(Subject(data.subject.to_owned())); - context.put(CommitMessage(data.message.map(str::to_owned))); + context.put(CommitInfo { + subject: data.subject.to_owned(), + message: data.message.map(str::to_owned), + }); context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); diff --git a/lib/lib/src/mutated/files/open.rs b/lib/lib/src/mutated/files/open.rs index abfc57f9..ece80f06 100644 --- a/lib/lib/src/mutated/files/open.rs +++ b/lib/lib/src/mutated/files/open.rs @@ -10,10 +10,7 @@ use composefs::repository::ImportContext; use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{ - ConfigUpperDir, FilesError, PendingFiles, RequestedFilePackage, TargetUuid, TotalFiles, WorkingDatabase, - WorkingTree, -}; +use super::{ApplyTarget, FileProgress, FilesError, RequestedFilePackage, WorkingState}; use crate::composefs::file::FileHandle; use crate::database::meta::MetaStore; @@ -53,13 +50,10 @@ impl Stage for OpenTransactionStage { let total = files.len() as u64; let pending: VecDeque<_> = files.into_iter().collect(); - context.put(WorkingTree(tree)); - context.put(WorkingDatabase(database)); + context.put(WorkingState { tree, database }); context.put(ImportContext::default()); - context.put(ConfigUpperDir(config_upper_dir)); - context.put(TargetUuid(uuid)); - context.put(PendingFiles(pending)); - context.put(TotalFiles(total)); + context.put(ApplyTarget { uuid, config_upper_dir }); + context.put(FileProgress { pending, total }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/gc/cleaning.rs b/lib/lib/src/mutated/gc/cleaning.rs index 0c3173f6..5f96ab39 100644 --- a/lib/lib/src/mutated/gc/cleaning.rs +++ b/lib/lib/src/mutated/gc/cleaning.rs @@ -23,7 +23,7 @@ impl Stage for CleaningStage { let deploy = ctx_take!(context, Deploy); let repository = deploy.open_repository()?; - let root_refs: Vec<&str> = roots.0.iter().map(String::as_str).collect(); + let root_refs: Vec<&str> = roots.iter().map(String::as_str).collect(); gc(&repository, &root_refs)?; Ok((progress, StageResult::Advance, Box::new(NoRollback))) diff --git a/lib/lib/src/mutated/gc/collect.rs b/lib/lib/src/mutated/gc/collect.rs index ae512300..aa6c8470 100644 --- a/lib/lib/src/mutated/gc/collect.rs +++ b/lib/lib/src/mutated/gc/collect.rs @@ -6,7 +6,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{CollectedRoots, GcError, PendingDeploys, TotalDeploys}; +use super::{CollectedRoots, DeployProgress, GcError}; use crate::database::record::DeployRecord; use crate::deploy::Deploy; @@ -20,35 +20,36 @@ impl Stage for CollectRootsStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), GcError> { - let mut pending_deploys = ctx_take!(context, PendingDeploys); + let mut deploy_progress = ctx_take!(context, DeployProgress); let mut roots = ctx_take!(context, CollectedRoots); - let total_deploys = ctx_get!(context, TotalDeploys); let deploy = ctx_get!(context, Deploy); - let prefix_digest = pending_deploys.0.pop_front().ok_or(CommonError::MissingResult)?; + let prefix_digest = deploy_progress.pending.pop_front().ok_or(CommonError::MissingResult)?; let record = DeployRecord::read(&deploy.deploy(&prefix_digest))?; - roots.0.push(record.prefix_digest); + roots.push(record.prefix_digest); if !record.working_config.is_empty() { - roots.0.push(record.working_config); + roots.push(record.working_config); } for entry in record.config_history { - roots.0.push(entry.config_digest); + roots.push(entry.config_digest); } - let remaining = pending_deploys.0.len() as u64; - let processed = total_deploys.0 - remaining; - progress = progress.subject(prefix_digest).progress(processed, total_deploys.0); + let remaining = deploy_progress.pending.len() as u64; + let processed = deploy_progress.total - remaining; + progress = progress + .subject(prefix_digest) + .progress(processed, deploy_progress.total); - let stage_result = if pending_deploys.0.is_empty() { + let stage_result = if deploy_progress.pending.is_empty() { StageResult::Advance } else { StageResult::Repeat }; - context.put(pending_deploys); + context.put(deploy_progress); context.put(roots); Ok((progress, stage_result, Box::new(NoRollback))) diff --git a/lib/lib/src/mutated/gc/mod.rs b/lib/lib/src/mutated/gc/mod.rs index 4274da70..be15a483 100644 --- a/lib/lib/src/mutated/gc/mod.rs +++ b/lib/lib/src/mutated/gc/mod.rs @@ -16,6 +16,8 @@ use upac_types::traits::MessageHook; use upac_types::states::GcStateId; +use upac_macro::ContextValue; + use self::cleaning::CleaningStage; use self::collect::CollectRootsStage; use self::pruning::PruneStage; @@ -31,8 +33,12 @@ mod collect; mod error; mod pruning; -pub(crate) struct PendingDeploys(pub VecDeque); -pub(crate) struct TotalDeploys(pub u64); +pub(crate) struct DeployProgress { + pub pending: VecDeque, + pub total: u64, +} + +#[derive(ContextValue)] pub(crate) struct CollectedRoots(pub Vec); pub struct GcData<'a> { diff --git a/lib/lib/src/mutated/gc/pruning.rs b/lib/lib/src/mutated/gc/pruning.rs index 6ab08a85..d1f89117 100644 --- a/lib/lib/src/mutated/gc/pruning.rs +++ b/lib/lib/src/mutated/gc/pruning.rs @@ -9,7 +9,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; use crate::deploy::Deploy; -use crate::mutated::gc::{CollectedRoots, GcError, PendingDeploys, TotalDeploys}; +use crate::mutated::gc::{CollectedRoots, DeployProgress, GcError}; use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; @@ -24,11 +24,10 @@ impl Stage for PruneStage { deploy.prune_deploys()?; let deploys = deploy.deploys()?; - let total_deploys_count = deploys.len() as u64; + let total = deploys.len() as u64; let pending: VecDeque<_> = deploys.into_iter().collect(); - context.put(PendingDeploys(pending)); - context.put(TotalDeploys(total_deploys_count)); + context.put(DeployProgress { pending, total }); context.put(CollectedRoots(Vec::new())); Ok((progress, StageResult::Advance, Box::new(NoRollback))) diff --git a/lib/lib/src/mutated/installer/checkout.rs b/lib/lib/src/mutated/installer/checkout.rs index 674353e8..c2677cfa 100644 --- a/lib/lib/src/mutated/installer/checkout.rs +++ b/lib/lib/src/mutated/installer/checkout.rs @@ -7,7 +7,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{InstallError, NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry}; +use super::{InstallError, NewState, RequestedBootPlugin, ResolvedBootEntry}; use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; @@ -23,18 +23,18 @@ impl Stage for CheckoutStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), InstallError> { - let new_prefix = ctx_get!(context, NewPrefixDigest); + let new_state = ctx_get!(context, NewState); let deploy = ctx_get!(context, Deploy); - let requested = ctx_get!(context, RequestedBootPlugin); + let requested_boot_plugins = ctx_get!(context, RequestedBootPlugin); let repository = deploy.open_repository()?; - let tree = deploy.open_tree(&new_prefix.0)?; - let digest = object_id_from_hex(&new_prefix.0)?; + let tree = deploy.open_tree(&new_state.prefix_digest)?; + let digest = object_id_from_hex(&new_state.prefix_digest)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_prefix.0)?; + let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; - let plugin = BootPlugins::new()?.load(&requested_boot_plugins.0)?; + let plugin = BootPlugins::new()?.load(&requested_boot_plugins)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/installer/commit.rs b/lib/lib/src/mutated/installer/commit.rs index 511d7cf8..571ef599 100644 --- a/lib/lib/src/mutated/installer/commit.rs +++ b/lib/lib/src/mutated/installer/commit.rs @@ -15,7 +15,7 @@ use upac_abi::hook::CancelToken; use upac_types::TmpPath; use upac_types::hook::ProgressEventBuilder; -use super::{ImportedConfigDefaults, ImportedDatabase, ImportedTree, InstallError, NewConfigDefaults, NewPrefixDigest}; +use super::{ImportedState, InstallError, NewState}; use crate::composefs::error::RepoError; use crate::composefs::file::FileHandle; @@ -32,19 +32,18 @@ impl Stage for CommitTransactionStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), InstallError> { - let imported_tree = ctx_take!(context, ImportedTree); - let config_defaults = ctx_take!(context, ImportedConfigDefaults); - let database = ctx_take!(context, ImportedDatabase); - let mut import_ctx = ctx_take!(context, ImportContext); + let imported_state = ctx_take!(context, ImportedState); + let mut imported_ctx = ctx_take!(context, ImportContext); let tmp_path = ctx_get!(context, TmpPath); let deploy = ctx_get!(context, Deploy); let repository = deploy.open_repository()?; - let mut tree = imported_tree.0; + let mut tree = imported_state.tree; - let database_bytes = database.0.into_bytes()?; + let database_bytes = imported_state.database.into_bytes()?; let database_scratch_path = Path::new(tmp_path.as_ref()).join(INSTALLER_SCRATCH_FILENAME); + write(&database_scratch_path, &database_bytes).map_err(RepoError::from)?; FileHandle::new(DATABASE_PATH).insert_file( @@ -52,13 +51,15 @@ impl Stage for CommitTransactionStage { &mut tree, &File::open(&database_scratch_path).map_err(RepoError::from)?, Stat::uninitialized(), - &mut import_ctx, + &mut imported_ctx, )?; let digest = commit_tree(&repository, tree)?; - context.put(NewPrefixDigest(digest.to_hex())); - context.put(NewConfigDefaults(config_defaults.0)); + context.put(NewState { + prefix_digest: digest.to_hex(), + config_defaults: imported_state.config_defaults, + }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/installer/import.rs b/lib/lib/src/mutated/installer/import.rs index 0358455b..c0cbe766 100644 --- a/lib/lib/src/mutated/installer/import.rs +++ b/lib/lib/src/mutated/installer/import.rs @@ -12,7 +12,7 @@ use upac_abi::hook::CancelToken; use upac_types::entry::{FileEntry, FileEntryScope}; use upac_types::hook::ProgressEventBuilder; -use super::{ImportedConfigDefaults, ImportedDatabase, ImportedTree, InstallError, PendingPackages, TotalPackages}; +use super::{ImportedState, InstallError, InstallProgress}; use crate::composefs::file::import_if_dir; use crate::database::files::FileStoreMut; @@ -29,16 +29,13 @@ impl Stage for ImportPackageStage { fn run( &self, context: &mut Context, cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), InstallError> { - let mut pending_packages = ctx_take!(context, PendingPackages); - let mut imported_tree = ctx_take!(context, ImportedTree); - let mut config_defaults = ctx_take!(context, ImportedConfigDefaults); - let mut database = ctx_take!(context, ImportedDatabase); - let mut import_ctx = ctx_take!(context, ImportContext); + let mut install_progress = ctx_take!(context, InstallProgress); + let mut imported_state = ctx_take!(context, ImportedState); + let mut imported_ctx = ctx_take!(context, ImportContext); - let total = ctx_get!(context, TotalPackages); let deploy = ctx_get!(context, Deploy); - let (package, trigger) = pending_packages.0.pop_front().ok_or(CommonError::MissingResult)?; + let (package, trigger) = install_progress.pending.pop_front().ok_or(CommonError::MissingResult)?; let repository = deploy.open_repository()?; let source_root = Path::new(&package.temp_package_path); @@ -46,26 +43,26 @@ impl Stage for ImportPackageStage { let prefix_source = source_root.join("usr"); let imported = import_if_dir!( &repository, - &mut imported_tree.0, + &mut imported_state.tree, &prefix_source, - &mut import_ctx, + &mut imported_ctx, cancel ); let config_source = source_root.join("etc"); let imported_config = import_if_dir!( &repository, - &mut config_defaults.0, + &mut imported_state.config_defaults, &config_source, - &mut import_ctx, + &mut imported_ctx, cancel ); - let uuid = database.0.insert_package_meta(&package.meta)?; - database.0.set_declarative_triggers(uuid, &trigger)?; + let uuid = imported_state.database.insert_package_meta(&package.meta)?; + imported_state.database.set_declarative_triggers(uuid, &trigger)?; for path in imported { - database.0.insert_package_file( + imported_state.database.insert_package_file( uuid, &FileEntry { path: path.to_string_lossy().into_owned(), @@ -76,7 +73,7 @@ impl Stage for ImportPackageStage { } for path in imported_config { - database.0.insert_package_file( + imported_state.database.insert_package_file( uuid, &FileEntry { path: path.to_string_lossy().into_owned(), @@ -86,21 +83,21 @@ impl Stage for ImportPackageStage { )?; } - let remaining = pending_packages.0.len() as u64; - let processed = total.0 - remaining; - progress = progress.subject(package.meta.name.clone()).progress(processed, total.0); + let remaining = install_progress.pending.len() as u64; + let processed = install_progress.total - remaining; + progress = progress + .subject(package.meta.name.clone()) + .progress(processed, install_progress.total); - let stage_result = if pending_packages.0.is_empty() { + let stage_result = if install_progress.pending.is_empty() { StageResult::Advance } else { StageResult::Repeat }; - context.put(pending_packages); - context.put(imported_tree); - context.put(config_defaults); - context.put(database); - context.put(import_ctx); + context.put(install_progress); + context.put(imported_state); + context.put(imported_ctx); Ok((progress, stage_result, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/installer/merge.rs b/lib/lib/src/mutated/installer/merge.rs index 5dab1ca5..2cf91ae3 100644 --- a/lib/lib/src/mutated/installer/merge.rs +++ b/lib/lib/src/mutated/installer/merge.rs @@ -12,7 +12,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{AllowConflictFiles, CommitMessage, InstallError, NewConfigDefaults, NewPrefixDigest, Subject}; +use super::{CommitInfo, InstallError, NewState}; use crate::composefs::overlay::{apply_overlay_upper, apply_tree_overlay}; use crate::composefs::repository::commit_tree; @@ -22,7 +22,7 @@ use crate::database::record::DeployRecord; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::deployment::CONFIG_DIR_NAME; -use crate::orchestrator::context::{Context, ctx_get, ctx_take}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; pub struct MergeStage; @@ -31,13 +31,10 @@ impl Stage for MergeStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), InstallError> { - let new_config_defaults = ctx_take!(context, NewConfigDefaults); + let new_state = ctx_get!(context, NewState); - let new_prefix = ctx_get!(context, NewPrefixDigest); let deploy = ctx_get!(context, Deploy); - let subject = ctx_get!(context, Subject); - let message = ctx_get!(context, CommitMessage); - let allow_conflict_files = ctx_get!(context, AllowConflictFiles); + let commit_info = ctx_get!(context, CommitInfo); let repository = deploy.open_repository()?; @@ -57,13 +54,13 @@ impl Stage for MergeStage { let mut new_config_layout = base_config_layout.clone(); - apply_tree_overlay(&mut new_config_layout, &new_config_defaults.0)?; + apply_tree_overlay(&mut new_config_layout, &new_state.config_defaults)?; let merge_result = merge_config( &base_config_layout, &new_config_layout, &live_config_layout, - allow_conflict_files.0, + commit_info.allow_conflict_files, )?; let new_config_digest = commit_tree(&repository, merge_result.tree)?.to_hex(); @@ -74,16 +71,16 @@ impl Stage for MergeStage { context.send_progress(&progress); } - let new_record_dir = deploy.deploy(&new_prefix.0); + let new_record_dir = deploy.deploy(&new_state.prefix_digest); let mut record_deploy = match DeployRecord::read(&new_record_dir) { Ok(existing) => existing, Err(DeployRecordError::NotFound) => { create_dir_all(&new_record_dir).map_err(DeployRecordError::from)?; DeployRecord { - prefix_digest: new_prefix.0.clone(), - subject: subject.0.clone(), - message: message.0.clone(), + prefix_digest: new_state.prefix_digest.clone(), + subject: commit_info.subject.clone(), + message: commit_info.message.clone(), seq: DeployRecord::allocate_seq(&deploy.next_seq_path())?, timestamp: DeployRecord::now_secs(), config_history: Vec::new(), @@ -98,8 +95,8 @@ impl Stage for MergeStage { written.extend(record_deploy.update_working_config( &new_record_dir, new_config_digest, - subject.0.clone(), - message.0.clone(), + commit_info.subject.clone(), + commit_info.message.clone(), )?); Ok((progress, StageResult::Advance, Box::new(written))) diff --git a/lib/lib/src/mutated/installer/mod.rs b/lib/lib/src/mutated/installer/mod.rs index 06085a99..de660ce4 100644 --- a/lib/lib/src/mutated/installer/mod.rs +++ b/lib/lib/src/mutated/installer/mod.rs @@ -20,6 +20,8 @@ use upac_types::package::PackageTemp; use upac_types::states::InstallStateId; use upac_types::traits::MessageHook; +use upac_macro::ContextValue; + use self::checkout::CheckoutStage; use self::commit::CommitTransactionStage; use self::fetching::FetchingStage; @@ -53,27 +55,39 @@ mod open; mod preparation; mod swap; -pub(crate) struct NewPrefixDigest(pub String); -pub(crate) struct NewConfigDefaults(pub FileSystem); -pub(crate) struct Subject(pub String); -pub(crate) struct CommitMessage(pub Option); -pub(crate) struct AllowConflictFiles(pub bool); +pub(crate) struct NewState { + pub prefix_digest: String, + pub config_defaults: FileSystem, +} + +pub(crate) struct CommitInfo { + pub subject: String, + pub message: Option, + pub allow_conflict_files: bool, +} +#[derive(ContextValue)] pub(crate) struct RequestedBootPlugin(pub String); pub(crate) struct ResolvedBootEntry { pub plugin: BootPlugin, pub entry_name: String, } -pub(crate) struct PendingPackagePaths(pub VecDeque); -pub(crate) struct UnpackerState(pub PackageUnpacker); +pub(crate) struct UnpackState { + pub pending_paths: VecDeque, + pub unpacker: PackageUnpacker, +} -pub(crate) struct PendingPackages(pub VecDeque<(PackageTemp, DeclarativeTrigger)>); -pub(crate) struct TotalPackages(pub u64); +pub(crate) struct InstallProgress { + pub pending: VecDeque<(PackageTemp, DeclarativeTrigger)>, + pub total: u64, +} -pub(crate) struct ImportedTree(pub FileSystem); -pub(crate) struct ImportedConfigDefaults(pub FileSystem); -pub(crate) struct ImportedDatabase(pub MemoryDatabase); +pub(crate) struct ImportedState { + pub tree: FileSystem, + pub config_defaults: FileSystem, + pub database: MemoryDatabase, +} pub struct InstallData<'a> { pub packages: Vec<&'a str>, @@ -131,17 +145,21 @@ pub fn run(data: InstallData) -> Result<(), (InstallStateId, InstallError)> { let mut context = Context::new(); context.put(deploy); - context.put(UnpackerState(unpacker)); - context.put(PendingPackagePaths( - data.packages.iter().map(|path| (*path).to_owned()).collect(), - )); - context.put(PendingPackages(VecDeque::new())); - context.put(TotalPackages(total_packages)); + context.put(UnpackState { + pending_paths: data.packages.iter().map(|path| (*path).to_owned()).collect(), + unpacker, + }); + context.put(InstallProgress { + pending: VecDeque::new(), + total: total_packages, + }); context.put(TmpPath(data.tmp_path.to_owned())); - context.put(Subject(data.subject.to_owned())); - context.put(CommitMessage(data.message.map(str::to_owned))); + context.put(CommitInfo { + subject: data.subject.to_owned(), + message: data.message.map(str::to_owned), + allow_conflict_files: data.allow_conflict_files, + }); context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); - context.put(AllowConflictFiles(data.allow_conflict_files)); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); let orchestrator = assemble(); diff --git a/lib/lib/src/mutated/installer/open.rs b/lib/lib/src/mutated/installer/open.rs index e6baef6b..ab781e00 100644 --- a/lib/lib/src/mutated/installer/open.rs +++ b/lib/lib/src/mutated/installer/open.rs @@ -11,7 +11,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{ImportedConfigDefaults, ImportedDatabase, ImportedTree, InstallError}; +use super::{ImportedState, InstallError}; use crate::composefs::file::FileHandle; use crate::database::{InMemory, MemoryDatabase}; @@ -36,9 +36,11 @@ impl Stage for OpenTransactionStage { let database_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &tree)?; let database = MemoryDatabase::open_in_memory(database_bytes)?; - context.put(ImportedTree(tree)); - context.put(ImportedConfigDefaults(FileSystem::new(Stat::uninitialized()))); - context.put(ImportedDatabase(database)); + context.put(ImportedState { + tree, + config_defaults: FileSystem::new(Stat::uninitialized()), + database, + }); context.put(ImportContext::default()); Ok((progress, StageResult::Advance, Box::new(NoRollback))) diff --git a/lib/lib/src/mutated/installer/preparation.rs b/lib/lib/src/mutated/installer/preparation.rs index 8ee89cf5..694cfa21 100644 --- a/lib/lib/src/mutated/installer/preparation.rs +++ b/lib/lib/src/mutated/installer/preparation.rs @@ -12,7 +12,7 @@ use upac_abi::hook::CancelToken; use upac_types::TmpPath; use upac_types::hook::ProgressEventBuilder; -use super::{InstallError, PendingPackagePaths, PendingPackages, TotalPackages, UnpackerState}; +use super::{InstallError, InstallProgress, UnpackState}; use crate::errors::CommonError; use crate::orchestrator::context::{Context, ctx_get, ctx_take}; @@ -26,38 +26,40 @@ impl Stage for PreparationStage { fn run( &self, context: &mut Context, cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), InstallError> { - let mut pending_paths = ctx_take!(context, PendingPackagePaths); - let mut unpacker = ctx_take!(context, UnpackerState); - let mut pending_packages = ctx_take!(context, PendingPackages); + let mut unpack_state = ctx_take!(context, UnpackState); + let mut install_progress = ctx_take!(context, InstallProgress); let tmp_path = ctx_get!(context, TmpPath); - let total_packages = ctx_get!(context, TotalPackages); - let package_path = pending_paths.0.pop_front().ok_or(CommonError::MissingResult)?; - let index = pending_packages.0.len(); + let package_path = unpack_state + .pending_paths + .pop_front() + .ok_or(CommonError::MissingResult)?; + let index = install_progress.pending.len(); - let (package, trigger) = unpacker - .0 + let (package, trigger) = unpack_state + .unpacker .unpack_one(&package_path, index, tmp_path.as_ref(), cancel) .map_err(CommonError::Decoder)?; let guard = UnpackedPackageDir(PathBuf::from(&package.temp_package_path)); - pending_packages.0.push_back((package, trigger)); + install_progress.pending.push_back((package, trigger)); - let remaining = pending_paths.0.len() as u64; - let processed = total_packages.0 - remaining; - progress = progress.subject(package_path).progress(processed, total_packages.0); + let remaining = unpack_state.pending_paths.len() as u64; + let processed = install_progress.total - remaining; + progress = progress + .subject(package_path) + .progress(processed, install_progress.total); - let result = if pending_paths.0.is_empty() { + let result = if unpack_state.pending_paths.is_empty() { StageResult::Advance } else { StageResult::Repeat }; - context.put(pending_paths); - context.put(unpacker); - context.put(pending_packages); + context.put(unpack_state); + context.put(install_progress); Ok((progress, result, Box::new(guard))) } diff --git a/lib/lib/src/mutated/mime/mod.rs b/lib/lib/src/mutated/mime/mod.rs index 9b63e0c7..f461045d 100644 --- a/lib/lib/src/mutated/mime/mod.rs +++ b/lib/lib/src/mutated/mime/mod.rs @@ -15,6 +15,8 @@ use upac_types::hook::Message; use upac_types::states::MimeStateId; use upac_types::traits::MessageHook; +use upac_macro::ContextValue; + use self::preparing::PreparingStage; use self::rendering::RenderingStage; use self::writing::WritingStage; @@ -29,10 +31,13 @@ mod preparing; mod rendering; mod writing; +#[derive(ContextValue)] pub(crate) struct DesktopContent(pub String); -pub(crate) struct PendingWrites(pub VecDeque<(&'static str, String)>); -pub(crate) struct TotalWrites(pub u64); +pub(crate) struct WriteProgress { + pub pending: VecDeque<(&'static str, String)>, + pub total: u64, +} pub struct MimeData<'a> { pub hook_message: Option, diff --git a/lib/lib/src/mutated/mime/rendering.rs b/lib/lib/src/mutated/mime/rendering.rs index 120b2a90..03a2a4c6 100644 --- a/lib/lib/src/mutated/mime/rendering.rs +++ b/lib/lib/src/mutated/mime/rendering.rs @@ -13,7 +13,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{DesktopContent, MimeError, PendingWrites, TotalWrites}; +use super::{DesktopContent, MimeError, WriteProgress}; use crate::layout::mime::{DESKTOP_FILE_PATH, MIME_XML_PATH, SHARED_MIME_INFO_XMLNS}; use crate::orchestrator::context::{Context, ctx_take}; @@ -31,12 +31,11 @@ impl Stage for RenderingStage { let mime_xml = Self::render_mime_xml(&manifests)?; let mime_type_line = Self::render_mime_type_line(&manifests); - let desktop_content = Self::rewrite_desktop_mime_type(&desktop_content.0, &mime_type_line)?; + let desktop_content = Self::rewrite_desktop_mime_type(&desktop_content, &mime_type_line)?; let pending = VecDeque::from([(MIME_XML_PATH, mime_xml), (DESKTOP_FILE_PATH, desktop_content)]); - context.put(PendingWrites(pending)); - context.put(TotalWrites(2)); + context.put(WriteProgress { pending, total: 2 }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/mime/writing.rs b/lib/lib/src/mutated/mime/writing.rs index 4783aa57..40b7bd90 100644 --- a/lib/lib/src/mutated/mime/writing.rs +++ b/lib/lib/src/mutated/mime/writing.rs @@ -10,12 +10,12 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{MimeError, PendingWrites, TotalWrites}; +use super::{MimeError, WriteProgress}; use crate::errors::CommonError; use crate::fs::WrittenFile; use crate::layout::mime::{APPLICATIONS_DIR, MIME_DB_DIR, UPDATE_DESKTOP_DATABASE_BIN, UPDATE_MIME_DATABASE_BIN}; -use crate::orchestrator::context::{Context, ctx_get, ctx_take}; +use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; pub struct WritingStage; @@ -24,19 +24,19 @@ impl Stage for WritingStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), MimeError> { - let mut pending = ctx_take!(context, PendingWrites); + let mut write_progress = ctx_take!(context, WriteProgress); - let total = ctx_get!(context, TotalWrites); - - let (path, content) = pending.0.pop_front().ok_or(CommonError::MissingResult)?; + let (path, content) = write_progress.pending.pop_front().ok_or(CommonError::MissingResult)?; let written_file = WrittenFile::write(Path::new(path), content.as_bytes())?; - let remaining = pending.0.len() as u64; - let processed = total.0 - remaining; - progress = progress.subject(path.to_owned()).progress(processed, total.0); + let remaining = write_progress.pending.len() as u64; + let processed = write_progress.total - remaining; + progress = progress + .subject(path.to_owned()) + .progress(processed, write_progress.total); - let result = if pending.0.is_empty() { + let result = if write_progress.pending.is_empty() { let _ = Command::new(UPDATE_MIME_DATABASE_BIN).arg(MIME_DB_DIR).status(); let _ = Command::new(UPDATE_DESKTOP_DATABASE_BIN).arg(APPLICATIONS_DIR).status(); @@ -45,7 +45,7 @@ impl Stage for WritingStage { StageResult::Repeat }; - context.put(pending); + context.put(write_progress); Ok((progress, result, Box::new(vec![written_file]))) } diff --git a/lib/lib/src/mutated/pin/mod.rs b/lib/lib/src/mutated/pin/mod.rs index 23cde075..680168d4 100644 --- a/lib/lib/src/mutated/pin/mod.rs +++ b/lib/lib/src/mutated/pin/mod.rs @@ -14,6 +14,8 @@ use upac_types::traits::MessageHook; use upac_types::states::PinStateId; +use upac_macro::ContextValue; + use self::stage::SetPinnedStage; use crate::deploy::{Deploy, DeployMode}; @@ -25,7 +27,10 @@ pub use self::error::PinError; mod error; mod stage; +#[derive(ContextValue)] pub(crate) struct RequestedPrefixDigest(pub String); + +#[derive(ContextValue)] pub(crate) struct RequestedPinned(pub bool); pub struct PinData<'a> { diff --git a/lib/lib/src/mutated/pin/stage.rs b/lib/lib/src/mutated/pin/stage.rs index 693d1b02..e4933ff2 100644 --- a/lib/lib/src/mutated/pin/stage.rs +++ b/lib/lib/src/mutated/pin/stage.rs @@ -26,7 +26,7 @@ impl Stage for SetPinnedStage { let prefix_digest = ctx_get!(context, RequestedPrefixDigest); let pinned = ctx_get!(context, RequestedPinned); - let record_dir = deploy.deploy(&prefix_digest.0); + let record_dir = deploy.deploy(&prefix_digest); let mut record = DeployRecord::read(&record_dir)?; let mut written = Vec::new(); diff --git a/lib/lib/src/mutated/rollback/checkout.rs b/lib/lib/src/mutated/rollback/checkout.rs index 9f943e2f..f935cbb4 100644 --- a/lib/lib/src/mutated/rollback/checkout.rs +++ b/lib/lib/src/mutated/rollback/checkout.rs @@ -27,13 +27,13 @@ impl Stage for CheckoutStage { let requested_boot_plugins = ctx_get!(context, RequestedBootPlugin); let repository = deploy.open_repository()?; - let tree = deploy.open_tree(&target.0)?; - let digest = object_id_from_hex(&target.0)?; + let tree = deploy.open_tree(&target)?; + let digest = object_id_from_hex(&target)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &target.0)?; + let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &target)?; - let plugin = BootPlugins::new()?.load(&requested_boot_plugins.0)?; + let plugin = BootPlugins::new()?.load(&requested_boot_plugins)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/rollback/merge.rs b/lib/lib/src/mutated/rollback/merge.rs index 238dd003..3761b4ab 100644 --- a/lib/lib/src/mutated/rollback/merge.rs +++ b/lib/lib/src/mutated/rollback/merge.rs @@ -23,7 +23,7 @@ impl Stage for MergeStage { let requested = ctx_get!(context, RequestedConfigDigest); let deploy = ctx_get!(context, Deploy); - let (config_digest, prefix_digest) = DeployRecord::resolve_config_digest(deploy, Some(&requested.0))?; + let (config_digest, prefix_digest) = DeployRecord::resolve_config_digest(deploy, Some(&requested))?; let record_dir = deploy.deploy(&prefix_digest); let mut record = DeployRecord::read(&record_dir)?; diff --git a/lib/lib/src/mutated/rollback/mod.rs b/lib/lib/src/mutated/rollback/mod.rs index fb8f7c55..ea993bc0 100644 --- a/lib/lib/src/mutated/rollback/mod.rs +++ b/lib/lib/src/mutated/rollback/mod.rs @@ -15,6 +15,8 @@ use upac_types::hook::Message; use upac_types::states::RollbackStateId; use upac_types::traits::MessageHook; +use upac_macro::ContextValue; + use self::checkout::CheckoutStage; use self::merge::MergeStage; use self::swap::SwapStage; @@ -34,9 +36,13 @@ mod error; mod merge; mod swap; +#[derive(ContextValue)] pub(crate) struct RequestedConfigDigest(pub String); + +#[derive(ContextValue)] pub(crate) struct TargetPrefixDigest(pub String); +#[derive(ContextValue)] pub(crate) struct RequestedBootPlugin(pub String); pub(crate) struct ResolvedBootEntry { diff --git a/lib/lib/src/mutated/uninstaller/checkout.rs b/lib/lib/src/mutated/uninstaller/checkout.rs index 327bafd9..73578955 100644 --- a/lib/lib/src/mutated/uninstaller/checkout.rs +++ b/lib/lib/src/mutated/uninstaller/checkout.rs @@ -7,7 +7,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry, UninstallError}; +use super::{NewState, RequestedBootPlugin, ResolvedBootEntry, UninstallError}; use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; @@ -22,18 +22,18 @@ impl Stage for CheckoutStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), UninstallError> { - let new_prefix = ctx_get!(context, NewPrefixDigest); + let new_state = ctx_get!(context, NewState); let deploy = ctx_get!(context, Deploy); - let requested_boot_plugins = ctx_get!(context, RequestedBootPlugin); + let requested_boot_plugin = ctx_get!(context, RequestedBootPlugin); let repository = deploy.open_repository()?; - let tree = deploy.open_tree(&new_prefix.0)?; - let digest = object_id_from_hex(&new_prefix.0)?; + let tree = deploy.open_tree(&new_state.prefix_digest)?; + let digest = object_id_from_hex(&new_state.prefix_digest)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_prefix.0)?; + let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; - let plugin = BootPlugins::new()?.load(&requested_boot_plugins.0)?; + let plugin = BootPlugins::new()?.load(&requested_boot_plugin)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/uninstaller/commit.rs b/lib/lib/src/mutated/uninstaller/commit.rs index 42cef5f2..0cfef3b0 100644 --- a/lib/lib/src/mutated/uninstaller/commit.rs +++ b/lib/lib/src/mutated/uninstaller/commit.rs @@ -15,9 +15,7 @@ use upac_abi::hook::CancelToken; use upac_types::TmpPath; use upac_types::hook::ProgressEventBuilder; -use super::{ - NewPrefixDigest, RemovedConfigPaths, UninstallError, WorkingDatabase, WorkingRemovedConfigPaths, WorkingTree, -}; +use super::{NewState, UninstallError, WorkingState}; use crate::composefs::error::RepoError; use crate::composefs::file::FileHandle; @@ -34,17 +32,15 @@ impl Stage for CommitTransactionStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), UninstallError> { - let working_tree = ctx_take!(context, WorkingTree); - let working_database = ctx_take!(context, WorkingDatabase); - let removed_config_paths = ctx_take!(context, WorkingRemovedConfigPaths); + let working_state = ctx_take!(context, WorkingState); let tmp_path = ctx_get!(context, TmpPath); let deploy = ctx_get!(context, Deploy); let repository = deploy.open_repository()?; - let mut tree = working_tree.0; + let mut tree = working_state.tree; - let database_bytes = working_database.0.into_bytes()?; + let database_bytes = working_state.database.into_bytes()?; let database_scratch_path = Path::new(tmp_path.as_ref()).join(UNINSTALL_SCRATCH_FILENAME); write(&database_scratch_path, &database_bytes).map_err(RepoError::from)?; @@ -58,8 +54,10 @@ impl Stage for CommitTransactionStage { let digest = commit_tree(&repository, tree)?; - context.put(NewPrefixDigest(digest.to_hex())); - context.put(RemovedConfigPaths(removed_config_paths.0)); + context.put(NewState { + prefix_digest: digest.to_hex(), + removed_config_paths: working_state.removed_config_paths, + }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/uninstaller/merge.rs b/lib/lib/src/mutated/uninstaller/merge.rs index 022bd544..1498361a 100644 --- a/lib/lib/src/mutated/uninstaller/merge.rs +++ b/lib/lib/src/mutated/uninstaller/merge.rs @@ -12,7 +12,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{CommitMessage, NewPrefixDigest, RemovedConfigPaths, Subject, UninstallError}; +use super::{CommitInfo, NewState, UninstallError}; use crate::composefs::file::FileHandle; use crate::composefs::overlay::apply_overlay_upper; @@ -23,7 +23,7 @@ use crate::database::record::DeployRecord; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::deployment::CONFIG_DIR_NAME; -use crate::orchestrator::context::{Context, ctx_get, ctx_take}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; pub struct MergeStage; @@ -32,12 +32,9 @@ impl Stage for MergeStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), UninstallError> { - let removed_config_paths = ctx_take!(context, RemovedConfigPaths); - - let new_prefix = ctx_get!(context, NewPrefixDigest); + let new_state = ctx_get!(context, NewState); let deploy = ctx_get!(context, Deploy); - let subject = ctx_get!(context, Subject); - let message = ctx_get!(context, CommitMessage); + let commit_info = ctx_get!(context, CommitInfo); let repository = deploy.open_repository()?; @@ -55,23 +52,23 @@ impl Stage for MergeStage { let mut new = base_config_layout.clone(); - for path in &removed_config_paths.0 { + for path in &new_state.removed_config_paths { FileHandle::new(path).remove_in_tree(&mut new)?; } let merge_result = merge_config(&base_config_layout, &new, &live_config_layout, true)?; let new_config_digest = commit_tree(&repository, merge_result.tree)?.to_hex(); - let new_record_dir = deploy.deploy(&new_prefix.0); + let new_record_dir = deploy.deploy(&new_state.prefix_digest); let mut record = match DeployRecord::read(&new_record_dir) { Ok(existing) => existing, Err(DeployRecordError::NotFound) => { create_dir_all(&new_record_dir).map_err(DeployRecordError::from)?; DeployRecord { - prefix_digest: new_prefix.0.clone(), - subject: subject.0.clone(), - message: message.0.clone(), + prefix_digest: new_state.prefix_digest.clone(), + subject: commit_info.subject.clone(), + message: commit_info.message.clone(), seq: DeployRecord::allocate_seq(&deploy.next_seq_path())?, timestamp: DeployRecord::now_secs(), config_history: Vec::new(), @@ -86,8 +83,8 @@ impl Stage for MergeStage { written.extend(record.update_working_config( &new_record_dir, new_config_digest, - subject.0.clone(), - message.0.clone(), + commit_info.subject.clone(), + commit_info.message.clone(), )?); Ok((progress, StageResult::Advance, Box::new(written))) diff --git a/lib/lib/src/mutated/uninstaller/mod.rs b/lib/lib/src/mutated/uninstaller/mod.rs index 1dd9ecfe..0153a3d5 100644 --- a/lib/lib/src/mutated/uninstaller/mod.rs +++ b/lib/lib/src/mutated/uninstaller/mod.rs @@ -22,6 +22,8 @@ use upac_types::states::UninstallStateId; use upac_types::traits::MessageHook; use upac_types::{TmpPath, UninstallPackagesTargets}; +use upac_macro::ContextValue; + use self::checkout::CheckoutStage; use self::commit::CommitTransactionStage; use self::merge::MergeStage; @@ -51,25 +53,39 @@ mod preparation; mod remove; mod swap; +#[derive(ContextValue)] pub(crate) struct PackageUuidsToRemove(pub Vec); -pub(crate) struct NewPrefixDigest(pub String); -pub(crate) struct RemovedConfigPaths(pub Vec); -pub(crate) struct Subject(pub String); -pub(crate) struct CommitMessage(pub Option); +pub(crate) struct NewState { + pub prefix_digest: String, + pub removed_config_paths: Vec, +} + +pub(crate) struct CommitInfo { + pub subject: String, + pub message: Option, +} + +#[derive(ContextValue)] pub(crate) struct Purge(pub bool); +#[derive(ContextValue)] pub(crate) struct RequestedBootPlugin(pub String); pub(crate) struct ResolvedBootEntry { pub plugin: BootPlugin, pub entry_name: String, } -pub(crate) struct PendingUuids(pub VecDeque); -pub(crate) struct TotalPackages(pub u64); -pub(crate) struct WorkingTree(pub FileSystem); -pub(crate) struct WorkingDatabase(pub MemoryDatabase); -pub(crate) struct WorkingRemovedConfigPaths(pub Vec); +pub(crate) struct RemoveProgress { + pub pending: VecDeque, + pub total: u64, +} + +pub(crate) struct WorkingState { + pub tree: FileSystem, + pub database: MemoryDatabase, + pub removed_config_paths: Vec, +} pub struct UninstallPackage<'a> { pub name: &'a str, @@ -141,7 +157,7 @@ pub fn run(data: UninstallData) -> Result<(), (UninstallStateId, UninstallError) let deploy = Deploy::new(DeployMode::ReadWrite).map_err(|error| (UninstallStateId::Setup, UninstallError::from(error)))?; - let targets = Targets( + let targets = UninstallPackagesTargets( data.packages .iter() .map(|package| PackageEntry { @@ -156,8 +172,10 @@ pub fn run(data: UninstallData) -> Result<(), (UninstallStateId, UninstallError) context.put(targets); context.put(deploy); context.put(TmpPath(data.tmp_path.to_owned())); - context.put(Subject(data.subject.to_owned())); - context.put(CommitMessage(data.message.map(str::to_owned))); + context.put(CommitInfo { + subject: data.subject.to_owned(), + message: data.message.map(str::to_owned), + }); context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); context.put(Purge(data.purge)); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); diff --git a/lib/lib/src/mutated/uninstaller/open.rs b/lib/lib/src/mutated/uninstaller/open.rs index 5a759055..ddeb84c2 100644 --- a/lib/lib/src/mutated/uninstaller/open.rs +++ b/lib/lib/src/mutated/uninstaller/open.rs @@ -9,10 +9,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{ - PackageUuidsToRemove, PendingUuids, TotalPackages, UninstallError, WorkingDatabase, WorkingRemovedConfigPaths, - WorkingTree, -}; +use super::{PackageUuidsToRemove, RemoveProgress, UninstallError, WorkingState}; use crate::composefs::file::FileHandle; use crate::database::{InMemory, MemoryDatabase}; @@ -39,14 +36,15 @@ impl Stage for OpenTransactionStage { let database_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &tree)?; let database = MemoryDatabase::open_in_memory(database_bytes)?; - let total = uuids.0.len() as u64; + let total = uuids.len() as u64; let pending: VecDeque<_> = uuids.0.into_iter().collect(); - context.put(WorkingTree(tree)); - context.put(WorkingDatabase(database)); - context.put(WorkingRemovedConfigPaths(Vec::new())); - context.put(PendingUuids(pending)); - context.put(TotalPackages(total)); + context.put(WorkingState { + tree, + database, + removed_config_paths: Vec::new(), + }); + context.put(RemoveProgress { total, pending }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/uninstaller/preparation.rs b/lib/lib/src/mutated/uninstaller/preparation.rs index 06cc3d83..2ff9ffd6 100644 --- a/lib/lib/src/mutated/uninstaller/preparation.rs +++ b/lib/lib/src/mutated/uninstaller/preparation.rs @@ -5,6 +5,7 @@ use upac_abi::hook::CancelToken; +use upac_types::UninstallPackagesTargets; use upac_types::decoder::DeclarativeTrigger; use upac_types::hook::ProgressEventBuilder; @@ -26,7 +27,7 @@ impl Stage for PreparationStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), UninstallError> { - let targets = ctx_get!(context, Targets); + let targets = ctx_get!(context, UninstallPackagesTargets); let deploy = ctx_get!(context, Deploy); let current_prefix = current_prefix_digest()?; diff --git a/lib/lib/src/mutated/uninstaller/remove.rs b/lib/lib/src/mutated/uninstaller/remove.rs index fe8593f5..5fed6c55 100644 --- a/lib/lib/src/mutated/uninstaller/remove.rs +++ b/lib/lib/src/mutated/uninstaller/remove.rs @@ -9,9 +9,7 @@ use upac_types::hook::ProgressEventBuilder; use upac_types::entry::FileEntryScope; -use super::{ - PendingUuids, Purge, TotalPackages, UninstallError, WorkingDatabase, WorkingRemovedConfigPaths, WorkingTree, -}; +use super::{Purge, RemoveProgress, UninstallError, WorkingState}; use crate::composefs::file::FileHandle; use crate::database::files::{FileStore, FileStoreMut}; @@ -27,23 +25,20 @@ impl Stage for RemovePackageStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), UninstallError> { - let mut pending = ctx_take!(context, PendingUuids); - let mut woking_tree = ctx_take!(context, WorkingTree); - let mut woking_database = ctx_take!(context, WorkingDatabase); - let mut removed_config_paths = ctx_take!(context, WorkingRemovedConfigPaths); + let mut woking_state = ctx_take!(context, WorkingState); + let mut remove_progress = ctx_take!(context, RemoveProgress); - let total_packages = ctx_get!(context, TotalPackages); let purge = ctx_get!(context, Purge); - let uuid = pending.0.pop_front().ok_or(CommonError::MissingResult)?; + let uuid = remove_progress.pending.pop_front().ok_or(CommonError::MissingResult)?; - let subject = woking_database - .0 + let subject = woking_state + .database .get_package_meta(uuid)? .map(|meta| meta.name) .unwrap_or_default(); - let files = woking_database.0.list_package_files(uuid)?; + let files = woking_state.database.list_package_files(uuid)?; for entry in files { if entry.is_user && !purge.0 { @@ -52,43 +47,41 @@ impl Stage for RemovePackageStage { match entry.scope { FileEntryScope::Prefix => { - FileHandle::new(&entry.path).remove_in_tree(&mut woking_tree.0)?; + FileHandle::new(&entry.path).remove_in_tree(&mut woking_state.tree)?; } FileEntryScope::Config => { - removed_config_paths.0.push(entry.path.clone()); + woking_state.removed_config_paths.push(entry.path.clone()); } } if entry.is_user { - woking_database.0.remove_user_file(uuid, &entry.path)?; + woking_state.database.remove_user_file(uuid, &entry.path)?; } else { - woking_database.0.remove_package_file(uuid, &entry.path)?; + woking_state.database.remove_package_file(uuid, &entry.path)?; } } - let meta = woking_database - .0 + let meta = woking_state + .database .get_package_meta(uuid)? .ok_or(UninstallError::PackageNotFound)?; - woking_database - .0 + woking_state + .database .remove_package_meta(&meta.name, &meta.arch, meta.arch_sub.as_deref())?; - woking_database.0.remove_declarative_triggers(uuid)?; + woking_state.database.remove_declarative_triggers(uuid)?; - let remaining = pending.0.len() as u64; - let processed = total_packages.0 - remaining; - progress = progress.subject(subject).progress(processed, total_packages.0); + let remaining = remove_progress.pending.len() as u64; + let processed = remove_progress.total - remaining; + progress = progress.subject(subject).progress(processed, remove_progress.total); - let result = if pending.0.is_empty() { + let result = if remove_progress.pending.is_empty() { StageResult::Advance } else { StageResult::Repeat }; - context.put(pending); - context.put(woking_tree); - context.put(woking_database); - context.put(removed_config_paths); + context.put(remove_progress); + context.put(woking_state); Ok((progress, result, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/update/checkout.rs b/lib/lib/src/mutated/update/checkout.rs index edeb9202..4e5e2aae 100644 --- a/lib/lib/src/mutated/update/checkout.rs +++ b/lib/lib/src/mutated/update/checkout.rs @@ -7,7 +7,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{NewPrefixDigest, RequestedBootPlugin, ResolvedBootEntry, UpdateError}; +use super::{NewState, RequestedBootPlugin, ResolvedBootEntry, UpdateError}; use crate::boot::write_boot_entry; use crate::composefs::repository::object_id_from_hex; @@ -22,18 +22,18 @@ impl Stage for CheckoutStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), UpdateError> { - let new_prefix = ctx_get!(context, NewPrefixDigest); + let new_state = ctx_get!(context, NewState); let deploy = ctx_get!(context, Deploy); - let requested_boot_plugins = ctx_get!(context, RequestedBootPlugin); + let requested_boot_plugin = ctx_get!(context, RequestedBootPlugin); let repository = deploy.open_repository()?; - let tree = deploy.open_tree(&new_prefix.0)?; - let digest = object_id_from_hex(&new_prefix.0)?; + let tree = deploy.open_tree(&new_state.prefix_digest)?; + let digest = object_id_from_hex(&new_state.prefix_digest)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_prefix.0)?; + let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; - let plugin = BootPlugins::new()?.load(&requested_boot_plugins.0)?; + let plugin = BootPlugins::new()?.load(&requested_boot_plugin)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/update/commit.rs b/lib/lib/src/mutated/update/commit.rs index 6393c444..42ec74d8 100644 --- a/lib/lib/src/mutated/update/commit.rs +++ b/lib/lib/src/mutated/update/commit.rs @@ -15,10 +15,7 @@ use upac_abi::hook::CancelToken; use upac_types::TmpPath; use upac_types::hook::ProgressEventBuilder; -use super::{ - ImportedConfigDefaults, ImportedDatabase, ImportedRemovedConfigPaths, ImportedTree, NewConfigDefaults, - NewPrefixDigest, RemovedConfigPaths, UpdateError, -}; +use super::{ImportedState, NewState, UpdateError}; use crate::composefs::error::RepoError; use crate::composefs::file::FileHandle; @@ -35,19 +32,16 @@ impl Stage for CommitTransactionStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), UpdateError> { - let tree = ctx_take!(context, ImportedTree); - let config_defaults = ctx_take!(context, ImportedConfigDefaults); - let database = ctx_take!(context, ImportedDatabase); - let removed_config_paths = ctx_take!(context, ImportedRemovedConfigPaths); + let imported_state = ctx_take!(context, ImportedState); let mut import_ctx = ctx_take!(context, ImportContext); let tmp_path = ctx_get!(context, TmpPath); let deploy = ctx_get!(context, Deploy); let repository = deploy.open_repository()?; - let mut tree = tree.0; + let mut tree = imported_state.tree; - let database_bytes = database.0.into_bytes()?; + let database_bytes = imported_state.database.into_bytes()?; let database_scratch_path = Path::new(tmp_path.as_ref()).join(UPDATE_SCRATCH_FILENAME); write(&database_scratch_path, &database_bytes).map_err(RepoError::from)?; @@ -61,9 +55,11 @@ impl Stage for CommitTransactionStage { let digest = commit_tree(&repository, tree)?; - context.put(NewPrefixDigest(digest.to_hex())); - context.put(NewConfigDefaults(config_defaults.0)); - context.put(RemovedConfigPaths(removed_config_paths.0)); + context.put(NewState { + prefix_digest: digest.to_hex(), + config_defaults: imported_state.config_defaults, + removed_config_paths: imported_state.removed_config_paths, + }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/update/import.rs b/lib/lib/src/mutated/update/import.rs index ec1639b7..3fc816ff 100644 --- a/lib/lib/src/mutated/update/import.rs +++ b/lib/lib/src/mutated/update/import.rs @@ -18,10 +18,7 @@ use crate::database::meta::{MetaStore, MetaStoreMut}; use crate::database::triggers::TriggerStoreMut; use crate::deploy::Deploy; use crate::errors::CommonError; -use crate::mutated::update::{ - AllowDowngrade, ImportedConfigDefaults, ImportedDatabase, ImportedRemovedConfigPaths, ImportedTree, - PendingPackages, TotalPackages, UpdateError, -}; +use crate::mutated::update::{AllowDowngrade, ImportProgress, ImportedState, UpdateError}; use crate::orchestrator::context::{Context, ctx_get, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; @@ -31,68 +28,74 @@ impl Stage for ImportPackageStage { fn run( &self, context: &mut Context, cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), UpdateError> { - let mut pending = ctx_take!(context, PendingPackages); - let mut tree = ctx_take!(context, ImportedTree); - let mut config_defaults = ctx_take!(context, ImportedConfigDefaults); - let mut database = ctx_take!(context, ImportedDatabase); - let mut removed_config_paths = ctx_take!(context, ImportedRemovedConfigPaths); + let mut import_progress = ctx_take!(context, ImportProgress); + let mut imported_state = ctx_take!(context, ImportedState); let mut import_ctx = ctx_take!(context, ImportContext); - let total = ctx_get!(context, TotalPackages); let allow_downgrade = ctx_get!(context, AllowDowngrade); + let deploy = ctx_get!(context, Deploy); - let (package, trigger) = pending.0.pop_front().ok_or(CommonError::MissingResult)?; + let (package, trigger) = import_progress.pending.pop_front().ok_or(CommonError::MissingResult)?; let repository = deploy.open_repository()?; - let uuid = database - .0 + let uuid = imported_state + .database .find_package_uuid(&package.meta.name, &package.meta.arch, package.meta.arch_sub.as_deref())? .ok_or(UpdateError::PackageNotFound)?; if !allow_downgrade.0 { - let current_meta = database.0.get_package_meta(uuid)?.ok_or(UpdateError::PackageNotFound)?; + let current_meta = imported_state + .database + .get_package_meta(uuid)? + .ok_or(UpdateError::PackageNotFound)?; if package.meta.version < current_meta.version { return Err(UpdateError::DowngradeNotAllowed); } } - let old_files = database.0.list_package_files(uuid)?; + let old_files = imported_state.database.list_package_files(uuid)?; for entry in old_files { match entry.scope { FileEntryScope::Prefix => { - FileHandle::new(&entry.path).remove_in_tree(&mut tree.0)?; + FileHandle::new(&entry.path).remove_in_tree(&mut imported_state.tree)?; } FileEntryScope::Config => { - removed_config_paths.0.push(entry.path.clone()); + imported_state.removed_config_paths.push(entry.path.clone()); } } - database.0.remove_package_file(uuid, &entry.path)?; + imported_state.database.remove_package_file(uuid, &entry.path)?; } let source_root = Path::new(&package.temp_package_path); let usr_source = source_root.join("usr"); - let imported = import_if_dir!(&repository, &mut tree.0, &usr_source, &mut import_ctx, cancel); + let imported = import_if_dir!( + &repository, + &mut imported_state.tree, + &usr_source, + &mut import_ctx, + cancel + ); let config_source = source_root.join("etc"); let imported_config = import_if_dir!( &repository, - &mut config_defaults.0, + &mut imported_state.config_defaults, &config_source, &mut import_ctx, cancel ); - database.0.update_package_meta(&package.meta)?; - database.0.set_declarative_triggers(uuid, &trigger)?; + imported_state.database.update_package_meta(&package.meta)?; + imported_state.database.set_declarative_triggers(uuid, &trigger)?; for path in imported { - database.0.insert_package_file( + imported_state.database.insert_package_file( uuid, &FileEntry { path: path.to_string_lossy().into_owned(), @@ -103,7 +106,7 @@ impl Stage for ImportPackageStage { } for path in imported_config { - database.0.insert_package_file( + imported_state.database.insert_package_file( uuid, &FileEntry { path: path.to_string_lossy().into_owned(), @@ -113,21 +116,20 @@ impl Stage for ImportPackageStage { )?; } - let remaining = pending.0.len() as u64; - let processed = total.0 - remaining; - progress = progress.subject(package.meta.name.clone()).progress(processed, total.0); + let remaining = import_progress.pending.len() as u64; + let processed = import_progress.total - remaining; + progress = progress + .subject(package.meta.name.clone()) + .progress(processed, import_progress.total); - let result = if pending.0.is_empty() { + let result = if import_progress.pending.is_empty() { StageResult::Advance } else { StageResult::Repeat }; - context.put(pending); - context.put(tree); - context.put(config_defaults); - context.put(database); - context.put(removed_config_paths); + context.put(import_progress); + context.put(imported_state); context.put(import_ctx); Ok((progress, result, Box::new(NoRollback))) diff --git a/lib/lib/src/mutated/update/merge.rs b/lib/lib/src/mutated/update/merge.rs index 2c912d09..bcd3427d 100644 --- a/lib/lib/src/mutated/update/merge.rs +++ b/lib/lib/src/mutated/update/merge.rs @@ -12,9 +12,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{ - AllowConflictFiles, CommitMessage, NewConfigDefaults, NewPrefixDigest, RemovedConfigPaths, Subject, UpdateError, -}; +use super::{AllowConflictFiles, CommitInfo, NewState, UpdateError}; use crate::composefs::file::FileHandle; use crate::composefs::overlay::{apply_overlay_upper, apply_tree_overlay}; @@ -25,7 +23,7 @@ use crate::database::record::DeployRecord; use crate::deploy::Deploy; use crate::deploy::digest::current_prefix_digest; use crate::layout::deployment::CONFIG_DIR_NAME; -use crate::orchestrator::context::{Context, ctx_get, ctx_take}; +use crate::orchestrator::context::{Context, ctx_get}; use crate::orchestrator::stage::{RollbackGuard, Stage, StageResult}; pub struct MergeStage; @@ -34,13 +32,9 @@ impl Stage for MergeStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), UpdateError> { - let new_config_defaults = ctx_take!(context, NewConfigDefaults); - let removed_config_paths = ctx_take!(context, RemovedConfigPaths); - - let new_prefix = ctx_get!(context, NewPrefixDigest); + let new_state = ctx_get!(context, NewState); let deploy = ctx_get!(context, Deploy); - let subject = ctx_get!(context, Subject); - let message = ctx_get!(context, CommitMessage); + let commit_info = ctx_get!(context, CommitInfo); let allow_conflict_files = ctx_get!(context, AllowConflictFiles); let repository = deploy.open_repository()?; @@ -58,10 +52,10 @@ impl Stage for MergeStage { let mut new = base.clone(); - for path in &removed_config_paths.0 { + for path in &new_state.removed_config_paths { FileHandle::new(path).remove_in_tree(&mut new)?; } - apply_tree_overlay(&mut new, &new_config_defaults.0)?; + apply_tree_overlay(&mut new, &new_state.config_defaults)?; let merge_result = merge_config(&base, &new, &live, allow_conflict_files.0)?; let new_config_digest = commit_tree(&repository, merge_result.tree)?.to_hex(); @@ -72,16 +66,16 @@ impl Stage for MergeStage { context.send_progress(&progress); } - let new_record_dir = deploy.deploy(&new_prefix.0); + let new_record_dir = deploy.deploy(&new_state.prefix_digest); let mut record = match DeployRecord::read(&new_record_dir) { Ok(existing) => existing, Err(DeployRecordError::NotFound) => { create_dir_all(&new_record_dir).map_err(DeployRecordError::from)?; DeployRecord { - prefix_digest: new_prefix.0.clone(), - subject: subject.0.clone(), - message: message.0.clone(), + prefix_digest: new_state.prefix_digest.clone(), + subject: commit_info.subject.clone(), + message: commit_info.message.clone(), seq: DeployRecord::allocate_seq(&deploy.next_seq_path())?, timestamp: DeployRecord::now_secs(), config_history: Vec::new(), @@ -96,8 +90,8 @@ impl Stage for MergeStage { written.extend(record.update_working_config( &new_record_dir, new_config_digest, - subject.0.clone(), - message.0.clone(), + commit_info.subject.clone(), + commit_info.message.clone(), )?); Ok((progress, StageResult::Advance, Box::new(written))) diff --git a/lib/lib/src/mutated/update/mod.rs b/lib/lib/src/mutated/update/mod.rs index 5d46476c..00205421 100644 --- a/lib/lib/src/mutated/update/mod.rs +++ b/lib/lib/src/mutated/update/mod.rs @@ -20,6 +20,8 @@ use upac_types::package::PackageTemp; use upac_types::states::UpdateStateId; use upac_types::traits::MessageHook; +use upac_macro::ContextValue; + use self::checkout::CheckoutStage; use self::commit::CommitTransactionStage; use self::fetching::FetchingStage; @@ -53,29 +55,45 @@ mod open; mod preparation; mod swap; -pub(crate) struct NewPrefixDigest(pub String); -pub(crate) struct NewConfigDefaults(pub FileSystem); -pub(crate) struct RemovedConfigPaths(pub Vec); -pub(crate) struct Subject(pub String); -pub(crate) struct CommitMessage(pub Option); +pub(crate) struct NewState { + pub prefix_digest: String, + pub config_defaults: FileSystem, + pub removed_config_paths: Vec, +} +pub(crate) struct CommitInfo { + pub subject: String, + pub message: Option, +} + +#[derive(ContextValue)] pub(crate) struct RequestedBootPlugin(pub String); pub(crate) struct ResolvedBootEntry { pub plugin: BootPlugin, pub entry_name: String, } +#[derive(ContextValue)] pub(crate) struct AllowDowngrade(pub bool); +#[derive(ContextValue)] pub(crate) struct AllowConflictFiles(pub bool); -pub(crate) struct PendingPackagePaths(pub VecDeque); -pub(crate) struct UnpackerState(pub PackageUnpacker); -pub(crate) struct PendingPackages(pub VecDeque<(PackageTemp, DeclarativeTrigger)>); -pub(crate) struct TotalPackages(pub u64); -pub(crate) struct ImportedTree(pub FileSystem); -pub(crate) struct ImportedConfigDefaults(pub FileSystem); -pub(crate) struct ImportedDatabase(pub MemoryDatabase); -pub(crate) struct ImportedRemovedConfigPaths(pub Vec); +pub(crate) struct UnpackState { + pub pending_paths: VecDeque, + pub unpacker: PackageUnpacker, +} + +pub(crate) struct ImportProgress { + pub pending: VecDeque<(PackageTemp, DeclarativeTrigger)>, + pub total: u64, +} + +pub(crate) struct ImportedState { + pub tree: FileSystem, + pub config_defaults: FileSystem, + pub database: MemoryDatabase, + pub removed_config_paths: Vec, +} pub struct UpdateData<'a> { pub packages: Vec<&'a str>, @@ -135,15 +153,19 @@ pub fn run(data: UpdateData) -> Result<(), (UpdateStateId, UpdateError)> { let mut context = Context::new(); context.put(deploy); - context.put(UnpackerState(unpacker)); - context.put(PendingPackagePaths( - data.packages.iter().map(|path| (*path).to_owned()).collect(), - )); - context.put(PendingPackages(VecDeque::new())); - context.put(TotalPackages(total_packages)); + context.put(UnpackState { + pending_paths: data.packages.iter().map(|path| (*path).to_owned()).collect(), + unpacker, + }); + context.put(ImportProgress { + pending: VecDeque::new(), + total: total_packages, + }); context.put(TmpPath(data.tmp_path.to_owned())); - context.put(Subject(data.subject.to_owned())); - context.put(CommitMessage(data.message.map(str::to_owned))); + context.put(CommitInfo { + subject: data.subject.to_owned(), + message: data.message.map(str::to_owned), + }); context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); context.put(AllowDowngrade(data.allow_downgrade)); context.put(AllowConflictFiles(data.allow_conflict_files)); diff --git a/lib/lib/src/mutated/update/open.rs b/lib/lib/src/mutated/update/open.rs index ba9f4bce..16357813 100644 --- a/lib/lib/src/mutated/update/open.rs +++ b/lib/lib/src/mutated/update/open.rs @@ -11,7 +11,7 @@ use upac_abi::hook::CancelToken; use upac_types::hook::ProgressEventBuilder; -use super::{ImportedConfigDefaults, ImportedDatabase, ImportedRemovedConfigPaths, ImportedTree, UpdateError}; +use super::{ImportedState, UpdateError}; use crate::composefs::file::FileHandle; use crate::database::{InMemory, MemoryDatabase}; @@ -36,10 +36,12 @@ impl Stage for OpenTransactionStage { let database_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &tree)?; let database = MemoryDatabase::open_in_memory(database_bytes)?; - context.put(ImportedTree(tree)); - context.put(ImportedConfigDefaults(FileSystem::new(Stat::uninitialized()))); - context.put(ImportedDatabase(database)); - context.put(ImportedRemovedConfigPaths(Vec::new())); + context.put(ImportedState { + tree, + config_defaults: FileSystem::new(Stat::uninitialized()), + database, + removed_config_paths: Vec::new(), + }); context.put(ImportContext::default()); Ok((progress, StageResult::Advance, Box::new(NoRollback))) diff --git a/lib/lib/src/mutated/update/preparation.rs b/lib/lib/src/mutated/update/preparation.rs index 7cea0522..f721e19c 100644 --- a/lib/lib/src/mutated/update/preparation.rs +++ b/lib/lib/src/mutated/update/preparation.rs @@ -12,7 +12,7 @@ use upac_abi::hook::CancelToken; use upac_types::TmpPath; use upac_types::hook::ProgressEventBuilder; -use super::{PendingPackagePaths, PendingPackages, TotalPackages, UnpackerState, UpdateError}; +use super::{ImportProgress, UnpackState, UpdateError}; use crate::errors::CommonError; use crate::orchestrator::context::{Context, ctx_get, ctx_take}; @@ -26,38 +26,40 @@ impl Stage for PreparationStage { fn run( &self, context: &mut Context, cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), UpdateError> { - let mut pending_paths = ctx_take!(context, PendingPackagePaths); - let mut unpacker = ctx_take!(context, UnpackerState); - let mut pending_packages = ctx_take!(context, PendingPackages); + let mut unpack_state = ctx_take!(context, UnpackState); + let mut import_progress = ctx_take!(context, ImportProgress); let tmp_path = ctx_get!(context, TmpPath); - let total_packages = ctx_get!(context, TotalPackages); - let package_path = pending_paths.0.pop_front().ok_or(CommonError::MissingResult)?; - let index = pending_packages.0.len(); + let package_path = unpack_state + .pending_paths + .pop_front() + .ok_or(CommonError::MissingResult)?; + let index = import_progress.pending.len(); - let (package, trigger) = unpacker - .0 + let (package, trigger) = unpack_state + .unpacker .unpack_one(&package_path, index, tmp_path.as_ref(), cancel) .map_err(CommonError::Decoder)?; let guard = UnpackedPackageDir(PathBuf::from(&package.temp_package_path)); - pending_packages.0.push_back((package, trigger)); + import_progress.pending.push_back((package, trigger)); - let remaining = pending_paths.0.len() as u64; - let processed = total_packages.0 - remaining; - progress = progress.subject(package_path).progress(processed, total_packages.0); + let remaining = unpack_state.pending_paths.len() as u64; + let processed = import_progress.total - remaining; + progress = progress + .subject(package_path) + .progress(processed, import_progress.total); - let result = if pending_paths.0.is_empty() { + let result = if unpack_state.pending_paths.is_empty() { StageResult::Advance } else { StageResult::Repeat }; - context.put(pending_paths); - context.put(unpacker); - context.put(pending_packages); + context.put(unpack_state); + context.put(import_progress); Ok((progress, result, Box::new(guard))) } diff --git a/lib/lib/src/plugin/decoder/mod.rs b/lib/lib/src/plugin/decoder/mod.rs index 958f0869..6ad75850 100644 --- a/lib/lib/src/plugin/decoder/mod.rs +++ b/lib/lib/src/plugin/decoder/mod.rs @@ -21,9 +21,11 @@ use crate::plugin::decoder::error::DecoderError; #[cfg(all(feature = "dynamic-plugins", feature = "builtin-decoders"))] compile_error!("dynamic-plugins and builtin-decoders are mutually exclusive"); +#[cfg(feature = "dynamic-plugins")] pub mod dynamic_link; pub mod error; pub mod manifest; +#[cfg(feature = "builtin-decoders")] pub mod static_link; pub mod triggers; pub mod unpack; diff --git a/lib/lib/src/plugin/decoder/static_link.rs b/lib/lib/src/plugin/decoder/static_link.rs index 9af9ff15..6e3013c7 100644 --- a/lib/lib/src/plugin/decoder/static_link.rs +++ b/lib/lib/src/plugin/decoder/static_link.rs @@ -35,6 +35,10 @@ impl DecoderPlugin { reason = "each push is independently cfg-gated, vec![] can't express that" )] pub(super) fn static_decoders() -> Vec<(&'static str, &'static [&'static str], DecoderPlugin)> { + #[allow( + unused_mut, + reason = "mut is only needed when at least one builtin-* decoder feature is enabled" + )] let mut decoders = Vec::new(); #[cfg(feature = "builtin-alpm")] diff --git a/lib/lib/src/scripts/file.rs b/lib/lib/src/scripts/file.rs index 50dd8ce8..5e7a2651 100644 --- a/lib/lib/src/scripts/file.rs +++ b/lib/lib/src/scripts/file.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use serde::Deserialize; -use upac_abi::hook::ProgressEventBuilder; +use upac_types::hook::ProgressEventBuilder; use crate::errors::CommonError; use crate::orchestrator::stage::{ConcurrentStage, RollbackGuard, StageResult}; diff --git a/lib/lib/src/scripts/mod.rs b/lib/lib/src/scripts/mod.rs index 1a97a6ab..e06364a1 100644 --- a/lib/lib/src/scripts/mod.rs +++ b/lib/lib/src/scripts/mod.rs @@ -4,12 +4,17 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use std::collections::{HashMap, HashSet}; +use std::fs::{read, read_dir}; +use std::str::from_utf8; use upac_abi::hook::CancelToken; + use upac_types::hook::ProgressEventBuilder; use upac_types::decoder::DeclarativeTrigger; +use upac_pki::signature::{HookSignature, RootCertificate}; + use crate::errors::CommonError; use crate::layout::hooks::{HOOK_EXTENSION, HOOKS_DIR, ROOT_CERT_PATH, SIGNATURE_EXTENSION}; use crate::orchestrator::context::Context; @@ -88,12 +93,12 @@ impl + Send + 'static> Stage for HookStage { pub fn load_hooks( hooks_dir: &str, root_cert_path: &str, hook_extension: &str, signature_extension: &str, ) -> Result, HookError> { - let root_bytes = fs::read(root_cert_path)?; + let root_bytes = read(root_cert_path)?; let root_certificate = RootCertificate::from_bytes(&root_bytes)?; let mut hooks = Vec::new(); - for entry in fs::read_dir(hooks_dir)? { + for entry in read_dir(hooks_dir)? { let path = entry?.path(); if path.extension().and_then(|extension| extension.to_str()) != Some(hook_extension) { @@ -104,8 +109,8 @@ pub fn load_hooks( signature_path.push("."); signature_path.push(signature_extension); - let hook_bytes = fs::read(&path)?; - let signature_bytes = fs::read(&signature_path)?; + let hook_bytes = read(&path)?; + let signature_bytes = read(&signature_path)?; let signature = HookSignature::from_bytes(&signature_bytes)?; signature.verify(&hook_bytes, &root_certificate)?; diff --git a/lib/lib/src/unmutated/diff/mod.rs b/lib/lib/src/unmutated/diff/mod.rs index e3d801d2..6aba3d83 100644 --- a/lib/lib/src/unmutated/diff/mod.rs +++ b/lib/lib/src/unmutated/diff/mod.rs @@ -35,7 +35,9 @@ mod preparing; struct DiffSnapshot { from_packages: Vec, to_packages: Vec, + changed_files: Vec<(String, FileDiffKind, DiffFileSource)>, + from_database: MemoryDatabase, to_database: MemoryDatabase, } From 73a2693bd8297103c67258abd589f1a97264c031 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 09:39:35 +0400 Subject: [PATCH 080/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/TODO.md b/TODO.md index d92834dc..e8c2b4df 100644 --- a/TODO.md +++ b/TODO.md @@ -36,14 +36,27 @@ match upac's on-disk layout exactly (repo at `composefs/`, per-deploy state at ` Still unresolved: whether upac ships/packages the `composefs-setup-root` binary itself or expects it to already exist on the source distro (same open question as the systemd-boot/rEFInd binaries). -**Boot confirmation service, generalized to all 4 plugins (not just UKI)**: `Booter::confirm_boot -(entry_name)` is already implemented for every plugin — grub (`grub-set-default`, promotes the -one-shot `grub-reboot` selection to persistent default), systemd-boot (writes `LoaderEntryDefault`), -rEFInd (writes `PreviousBoot`) all already do the right thing for their own one-shot mechanism; uki -still needs its to/from swap + persistent NVRAM boot order designed. But nothing anywhere calls -`confirm_boot` for any of them after a successful boot. Needs its own small service + unit, shipped -the same way as `composefs-setup-root.service` — via `system/`, built and dropped in by whoever -assembles `--source`, not embedded in upac itself. Open design question, now needed generically -(not just for UKI's to/from case): how does the service determine which `entry_name` was actually -booted (`/proc/cmdline`? the loaded image's own filename? grubenv's own state?) — needs deciding -before writing any code. +**Booter ABI redesign — decided this session, execution in progress file-by-file under direct +supervision (no batch edits).** Four canonical plugin responsibilities: + +1. Plugin sets itself for one-time boot (`set_one_shot`) — done. +2. Plugin sets itself for persistent boot (`confirm_boot`) — done, including UKI's `to.efi`↔ + `from.efi` file swap (needed a new `esp_mount_point` parameter on `confirm_boot`, added this + session; the swap only fires when the confirmed `entry_name` is the `to` slot specifically). +3. Plugin installs itself onto the ESP (`install`) — done for grub (real `grub-install + --removable --no-nvram` + a minimal `blscfg` `grub.cfg`), no-op for the other 3. +4. Plugin declares where its own pre-built loader binary lives in the source package tree + (`esp_loader_source`) — done, stays a separate passive query (only genesis can reach the + composefs tree to copy the bytes out, plugins can't do this step themselves). + +**`write_boot_entry` must search only for the resource type the selected plugin needs, not +autonomously scan everything and guess.** Right now (`lib/lib/src/boot/mod.rs`) it calls +`get_boot_resources` unconditionally, takes whichever single boot resource exists in the tree +(Type1/Type2/`UsrLibModulesVmLinuz`), and only errors if more than one is found total — completely +independent of which plugin was actually selected. This is the case in all 6 call sites: +`lib/lib/src/boot/mod.rs` itself, `mutated/{files,installer,uninstaller,update,rollback}/checkout.rs`, +and `lib/setup/src/genesis/entry.rs`. Needs to take the (now always-explicit) plugin name and require +specifically: `uki` → `Type2` only; `grub`/`systemd-boot`/`rEFInd` → `Type1`/`UsrLibModulesVmLinuz` +only — hard error if that type isn't present, even if a different type is. This also means +`resolve_boot_plugin` must run before `write_boot_entry` everywhere — today the 5 ordinary +`checkout.rs` stages call it after (only genesis already has the order right). From 7210aa279c6852c552e52d8c27b27f7f0fe78776 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 09:52:44 +0400 Subject: [PATCH 081/148] fix: add shared upac_types::decoder::verify checksum helper, identical across all 4 decoder crates Co-Authored-By: Claude Sonnet 5 --- lib/types/Cargo.toml | 1 + lib/types/src/decoder.rs | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/types/Cargo.toml b/lib/types/Cargo.toml index 38eac821..8b34b0cb 100644 --- a/lib/types/Cargo.toml +++ b/lib/types/Cargo.toml @@ -30,4 +30,5 @@ upac-macro = { workspace = true } upac-abi = { workspace = true } serde = { workspace = true } +sha2 = { workspace = true } toml = { workspace = true } diff --git a/lib/types/src/decoder.rs b/lib/types/src/decoder.rs index 97af9502..a2573e47 100644 --- a/lib/types/src/decoder.rs +++ b/lib/types/src/decoder.rs @@ -3,12 +3,18 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::io::Read; +use std::fs::File; +use std::io::{BufReader, Read}; +use sha2::{Digest, Sha256}; + +use upac_abi::hook::CancelToken; use upac_macro::RedbCodec; use super::error::DecodeError; +const VERIFY_CHUNK_SIZE: usize = 65536; + #[derive(Debug, Clone, RedbCodec)] pub struct DeclarativeTrigger { pub format: String, @@ -49,3 +55,30 @@ pub fn read_to_string(reader: &mut R) -> Result { String::from_utf8(bytes).map_err(|_| DecodeError::InvalidUtf8) } + +pub fn verify(package_path: &str, expected_checksum: [u8; 32], cancel: &CancelToken) -> Result<(), DecodeError> { + let file = File::open(package_path)?; + let mut reader = BufReader::new(file); + + let mut hasher = Sha256::new(); + let mut buffer = [0u8; VERIFY_CHUNK_SIZE]; + + loop { + if cancel.is_cancelled() { + return Err(DecodeError::Cancelled); + } + + let bytes_read = reader.read(&mut buffer)?; + if bytes_read == 0 { + break; + } + + hasher.update(&buffer[..bytes_read]); + } + + if hasher.finalize().as_slice() != expected_checksum.as_slice() { + return Err(DecodeError::ChecksumMismatch); + } + + Ok(()) +} From 582c867e6a891c3c4a5cc1f0cb3ddaaa2be2ea12 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 10:03:35 +0400 Subject: [PATCH 082/148] fix: add reuse head Co-Authored-By: Claude Sonnet 5 --- lib/macro/src/context_value/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/macro/src/context_value/mod.rs b/lib/macro/src/context_value/mod.rs index 94988896..d3e06184 100644 --- a/lib/macro/src/context_value/mod.rs +++ b/lib/macro/src/context_value/mod.rs @@ -1,3 +1,13 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +//! `#[derive(ContextValue)]` — generates `Deref`/`DerefMut`/`From` for a +//! single-field tuple struct, so an orchestrator `Context` wrapper type can be +//! used (and constructed) like its inner value without hand-written +//! boilerplate for each one. + use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; From 22299e2a64c2ed436bdc727e1e344bc497887946 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 10:09:42 +0400 Subject: [PATCH 083/148] fix: add shared upac_types::decoder::build_decode_response, identical CDecodeResponse assembly duplicated across all 4 decoder crates Co-Authored-By: Claude Sonnet 5 --- lib/types/src/decoder.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/types/src/decoder.rs b/lib/types/src/decoder.rs index a2573e47..5945aa6e 100644 --- a/lib/types/src/decoder.rs +++ b/lib/types/src/decoder.rs @@ -8,10 +8,16 @@ use std::io::{BufReader, Read}; use sha2::{Digest, Sha256}; +use upac_abi::FreeDecodeResponseFn; use upac_abi::hook::CancelToken; +use upac_abi::package::{CPackageDependency, CPackageMeta}; +use upac_abi::response::CDecodeResponse; +use upac_abi::types::{COwned, CSlice, CVec}; + use upac_macro::RedbCodec; use super::error::DecodeError; +use super::package::DecodedPackageMeta; const VERIFY_CHUNK_SIZE: usize = 65536; @@ -82,3 +88,26 @@ pub fn verify(package_path: &str, expected_checksum: [u8; 32], cancel: &CancelTo Ok(()) } + +pub fn build_decode_response( + decoded: DecodedPackageMeta, declarative_triggers: Vec, free: FreeDecodeResponseFn, +) -> CDecodeResponse { + let DecodedPackageMeta { meta, dependencies } = decoded; + + let dependencies = dependencies + .into_iter() + .map(CPackageDependency::from) + .collect::>(); + + let declarative_triggers = declarative_triggers + .into_iter() + .map(|trigger| CSlice::from_owned(trigger.into_bytes())) + .collect::>(); + + CDecodeResponse::new( + CPackageMeta::from(meta), + CVec::from_owned(dependencies), + CVec::from_owned(declarative_triggers), + free, + ) +} From eefa95b86009745b7351e756a79067c42e44294e Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 10:12:36 +0400 Subject: [PATCH 084/148] fix: rebuild upac-decoder-alpm around current upac_abi/upac_types shapes, dedupe verify/build_decode_response into upac_types::decoder Co-Authored-By: Claude Sonnet 5 --- decoders/alpm/Cargo.toml | 1 - decoders/alpm/src/extract.rs | 5 ++-- decoders/alpm/src/lib.rs | 44 +++++++++++------------------------ decoders/alpm/src/pkginfo.rs | 25 ++++++++++---------- decoders/alpm/src/triggers.rs | 4 ++-- decoders/alpm/src/verify.rs | 41 -------------------------------- 6 files changed, 31 insertions(+), 89 deletions(-) delete mode 100644 decoders/alpm/src/verify.rs diff --git a/decoders/alpm/Cargo.toml b/decoders/alpm/Cargo.toml index fcd01bcb..38cdab9e 100644 --- a/decoders/alpm/Cargo.toml +++ b/decoders/alpm/Cargo.toml @@ -32,7 +32,6 @@ upac-abi = { workspace = true } upac-types = { workspace = true } flate2 = { workspace = true } -sha2 = { workspace = true } tar = { workspace = true } xz2 = { workspace = true } zstd = { workspace = true } diff --git a/decoders/alpm/src/extract.rs b/decoders/alpm/src/extract.rs index e755930d..6c937975 100644 --- a/decoders/alpm/src/extract.rs +++ b/decoders/alpm/src/extract.rs @@ -11,11 +11,12 @@ use tar::Archive; use xz2::read::XzDecoder; use zstd::stream::read::Decoder as ZstdDecoder; -use upac_abi::decoder::DecodeError; use upac_abi::hook::CancelToken; + use upac_types::decoder::read_to_string; +use upac_types::error::DecodeError; -use crate::alpm::{BUILDINFO_ENTRY, CHANGELOG_ENTRY, INSTALL_ENTRY, MTREE_ENTRY, PKGINFO_ENTRY}; +use super::alpm::{BUILDINFO_ENTRY, CHANGELOG_ENTRY, INSTALL_ENTRY, MTREE_ENTRY, PKGINFO_ENTRY}; const JUNK_ENTRIES: [&str; 3] = [BUILDINFO_ENTRY, MTREE_ENTRY, CHANGELOG_ENTRY]; diff --git a/decoders/alpm/src/lib.rs b/decoders/alpm/src/lib.rs index d69540ed..27bff897 100644 --- a/decoders/alpm/src/lib.rs +++ b/decoders/alpm/src/lib.rs @@ -6,12 +6,13 @@ use std::str::from_utf8; use upac_abi::DECODER_ABI_VERSION; -use upac_abi::decoder::{CDecodeRequest, CDecodeResponse, CDependency, DecodeError}; use upac_abi::memory::{free_cslice, free_cvec_owning}; -use upac_abi::package::CPackageMeta; -use upac_abi::types::COwned; -use upac_abi::types::{CSlice, CVec}; -use upac_types::decoder::{DecodeMeta, DecodedMeta}; +use upac_abi::request::CDecodeRequest; +use upac_abi::response::CDecodeResponse; + +use upac_types::decoder::{build_decode_response, verify}; +use upac_types::error::DecodeError; +use upac_types::traits::DecodeMeta; use crate::extract::ExtractedMetadata; use crate::pkginfo::PkgInfo; @@ -20,14 +21,13 @@ pub mod pkginfo; pub mod triggers; mod extract; -mod verify; include!(concat!(env!("OUT_DIR"), "/layout.rs")); /// # Safety /// Touches no pointers. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn abi_version() -> u32 { +pub unsafe extern "C" fn decode_abi_version() -> u32 { DECODER_ABI_VERSION } @@ -78,34 +78,16 @@ fn decode_package(request: &CDecodeRequest) -> Result) -> CDecodeResponse { - let DecodedMeta { meta, dependencies } = decoded; - - let dependencies = dependencies.into_iter().map(CDependency::from).collect::>(); - - let declarative_triggers = declarative_triggers - .into_iter() - .map(|trigger| CSlice::from_owned(trigger.into_bytes())) - .collect::>(); - - CDecodeResponse { - struct_size: size_of::(), - - meta: CPackageMeta::from(meta), - - dependencies: CVec::from_owned(dependencies), - declarative_triggers: CVec::from_owned(declarative_triggers), - - free: free_decode_response, - } + Ok(build_decode_response( + decoded, + declarative_triggers, + free_decode_response, + )) } diff --git a/decoders/alpm/src/pkginfo.rs b/decoders/alpm/src/pkginfo.rs index 5128ccb5..f79bbf9b 100644 --- a/decoders/alpm/src/pkginfo.rs +++ b/decoders/alpm/src/pkginfo.rs @@ -5,13 +5,14 @@ use std::collections::HashMap; -use upac_abi::decoder::{ - CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS, DecodeError, parse_constraint_prefix, -}; -use upac_types::decoder::{DecodeMeta, DecodedMeta}; -use upac_types::{Dependency, PackageMeta, Version}; +use upac_abi::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS}; + +use upac_types::decoder::parse_constraint_prefix; +use upac_types::error::DecodeError; +use upac_types::package::{DecodedPackageMeta, PackageDependency, PackageMeta, Version}; +use upac_types::traits::DecodeMeta; -use crate::alpm::{ +use super::alpm::{ PKGINFO_ARCH_KEY, PKGINFO_DEPEND_KEY, PKGINFO_DESCRIPTION_KEY, PKGINFO_EPOCH_KEY, PKGINFO_LICENSE_KEY, PKGINFO_MAINTAINER_KEY, PKGINFO_NAME_KEY, PKGINFO_RELEASE_KEY, PKGINFO_SIZE_KEY, PKGINFO_URL_KEY, PKGINFO_VERSION_KEY, @@ -43,7 +44,7 @@ const OPERATORS: [(&[u8], u8); 5] = [ pub struct PkgInfo<'a>(pub &'a str); impl DecodeMeta for PkgInfo<'_> { - fn decode(&self, sha256: [u8; 32]) -> Result { + fn decode(&self, sha256: [u8; 32]) -> Result { let (mut fields, dependencies) = self.parse_fields(); let name = required_field!(fields, PKGINFO_NAME_KEY); @@ -73,12 +74,12 @@ impl DecodeMeta for PkgInfo<'_> { installed_size, }; - Ok(DecodedMeta { meta, dependencies }) + Ok(DecodedPackageMeta { meta, dependencies }) } } impl PkgInfo<'_> { - fn parse_fields(&self) -> (HashMap<&str, String>, Vec) { + fn parse_fields(&self) -> (HashMap<&str, String>, Vec) { let mut fields: HashMap<&str, String> = HashMap::new(); let mut dependencies = Vec::new(); @@ -102,7 +103,7 @@ impl PkgInfo<'_> { (fields, dependencies) } - fn parse_dependency(value: &str) -> Dependency { + fn parse_dependency(value: &str) -> PackageDependency { let bytes = value.as_bytes(); for index in 0..bytes.len() { @@ -110,14 +111,14 @@ impl PkgInfo<'_> { continue; }; - return Dependency { + return PackageDependency { name: value[..index].to_owned(), constraint, version: Version::parse(&value[index + operator_len..]), }; } - Dependency { + PackageDependency { name: value.to_owned(), constraint: CONSTRAINT_ANY, version: Version::default(), diff --git a/decoders/alpm/src/triggers.rs b/decoders/alpm/src/triggers.rs index 82e4a9cf..abaf1315 100644 --- a/decoders/alpm/src/triggers.rs +++ b/decoders/alpm/src/triggers.rs @@ -3,9 +3,9 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_types::DecoderTrigger; +use upac_types::decoder::DecoderTrigger; -use crate::alpm::{POST_INSTALL_FN, POST_REMOVE_FN, POST_UPGRADE_FN, PRE_INSTALL_FN, PRE_REMOVE_FN, PRE_UPGRADE_FN}; +use super::alpm::{POST_INSTALL_FN, POST_REMOVE_FN, POST_UPGRADE_FN, PRE_INSTALL_FN, PRE_REMOVE_FN, PRE_UPGRADE_FN}; pub fn scan(content: &str) -> Vec { DecoderTrigger::ALL diff --git a/decoders/alpm/src/verify.rs b/decoders/alpm/src/verify.rs deleted file mode 100644 index aae6636c..00000000 --- a/decoders/alpm/src/verify.rs +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::File; -use std::io::{BufReader, Read}; - -use sha2::{Digest, Sha256}; - -use upac_abi::decoder::DecodeError; -use upac_abi::hook::CancelToken; - -const READ_CHUNK_SIZE: usize = 65536; - -pub fn verify(package_path: &str, expected_checksum: [u8; 32], cancel: &CancelToken) -> Result<(), DecodeError> { - let file = File::open(package_path)?; - let mut reader = BufReader::new(file); - - let mut hasher = Sha256::new(); - let mut buffer = [0u8; READ_CHUNK_SIZE]; - - loop { - if cancel.is_cancelled() { - return Err(DecodeError::Cancelled); - } - - let bytes_read = reader.read(&mut buffer)?; - if bytes_read == 0 { - break; - } - - hasher.update(&buffer[..bytes_read]); - } - - if hasher.finalize().as_slice() != expected_checksum.as_slice() { - return Err(DecodeError::ChecksumMismatch); - } - - Ok(()) -} From bb3cc246b071f102bf481a5349e50bb30e841583 Mon Sep 17 00:00:00 2001 From: JustPav Date: Thu, 10 Sep 2026 10:14:16 +0400 Subject: [PATCH 085/148] fix: fix test Co-Authored-By: Claude Sonnet 5 --- decoders/alpm/tests/pkginfo.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/decoders/alpm/tests/pkginfo.rs b/decoders/alpm/tests/pkginfo.rs index bbc0dae2..0d5a54f2 100644 --- a/decoders/alpm/tests/pkginfo.rs +++ b/decoders/alpm/tests/pkginfo.rs @@ -3,9 +3,12 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::decoder::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS, DecodeError}; +use upac_abi::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS}; + use upac_decoder_alpm::pkginfo::PkgInfo; -use upac_types::decoder::DecodeMeta; + +use upac_types::error::DecodeError; +use upac_types::traits::DecodeMeta; const CHECKSUM: [u8; 32] = [7; 32]; From 2b014b27f50e389a0ab4bd8a5a824dab7ddbbc6d Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 07:17:18 +0400 Subject: [PATCH 086/148] fix: rebuild upac-decoder-deb around current upac_abi/upac_types shapes, dedupe verify/build_decode_response into upac_types::decoder Co-Authored-By: Claude Sonnet 5 --- decoders/deb/src/control.rs | 27 ++++++++++---------- decoders/deb/src/extract.rs | 5 ++-- decoders/deb/src/lib.rs | 48 +++++++++++------------------------ decoders/deb/src/triggers.rs | 4 +-- decoders/deb/src/verify.rs | 41 ------------------------------ decoders/deb/tests/control.rs | 7 +++-- 6 files changed, 39 insertions(+), 93 deletions(-) delete mode 100644 decoders/deb/src/verify.rs diff --git a/decoders/deb/src/control.rs b/decoders/deb/src/control.rs index 4b9720c0..99f80027 100644 --- a/decoders/deb/src/control.rs +++ b/decoders/deb/src/control.rs @@ -5,13 +5,14 @@ use std::collections::HashMap; -use upac_abi::decoder::{ - CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS, DecodeError, parse_constraint_prefix, -}; -use upac_types::decoder::{DecodeMeta, DecodedMeta}; -use upac_types::{Dependency, PackageMeta, Version}; +use upac_abi::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS}; + +use upac_types::decoder::parse_constraint_prefix; +use upac_types::error::DecodeError; +use upac_types::package::{DecodedPackageMeta, PackageDependency, PackageMeta, Version}; +use upac_types::traits::DecodeMeta; -use crate::deb::{ +use super::deb::{ CONTROL_ARCH_KEY, CONTROL_DEPENDS_KEY, CONTROL_DESCRIPTION_KEY, CONTROL_INSTALLED_SIZE_KEY, CONTROL_MAINTAINER_KEY, CONTROL_NAME_KEY, CONTROL_URL_KEY, CONTROL_VERSION_KEY, }; @@ -45,7 +46,7 @@ pub struct ControlFile<'a> { } impl DecodeMeta for ControlFile<'_> { - fn decode(&self, sha256: [u8; 32]) -> Result { + fn decode(&self, sha256: [u8; 32]) -> Result { let (mut fields, dependencies) = self.parse_fields(); let name = required_field!(fields, CONTROL_NAME_KEY); @@ -66,12 +67,12 @@ impl DecodeMeta for ControlFile<'_> { installed_size, }; - Ok(DecodedMeta { meta, dependencies }) + Ok(DecodedPackageMeta { meta, dependencies }) } } impl ControlFile<'_> { - fn parse_fields(&self) -> (HashMap<&str, String>, Vec) { + fn parse_fields(&self) -> (HashMap<&str, String>, Vec) { let mut fields: HashMap<&str, String> = HashMap::new(); let mut dependencies = Vec::new(); @@ -95,7 +96,7 @@ impl ControlFile<'_> { (fields, dependencies) } - fn parse_depends(value: &str) -> Vec { + fn parse_depends(value: &str) -> Vec { value .split(',') .filter_map(|group| group.split('|').next()) @@ -103,7 +104,7 @@ impl ControlFile<'_> { .collect() } - fn parse_dependency(raw: &str) -> Dependency { + fn parse_dependency(raw: &str) -> PackageDependency { let raw = raw.trim(); let bytes = raw.as_bytes(); @@ -120,14 +121,14 @@ impl ControlFile<'_> { None => version_part, }; - return Dependency { + return PackageDependency { name, constraint, version: Version::parse(version_str), }; } - Dependency { + PackageDependency { name: raw.to_owned(), constraint: CONSTRAINT_ANY, version: Version::default(), diff --git a/decoders/deb/src/extract.rs b/decoders/deb/src/extract.rs index 1e702d1a..5bac96dd 100644 --- a/decoders/deb/src/extract.rs +++ b/decoders/deb/src/extract.rs @@ -13,11 +13,12 @@ use tar::Archive as TarArchive; use xz2::read::XzDecoder; use zstd::stream::read::Decoder as ZstdDecoder; -use upac_abi::decoder::DecodeError; use upac_abi::hook::CancelToken; + use upac_types::decoder::read_to_string; +use upac_types::error::DecodeError; -use crate::deb::{ +use super::deb::{ CONTROL_ENTRY, CONTROL_TAR_PREFIX, COPYRIGHT_DIR_PREFIX, COPYRIGHT_ENTRY_SUFFIX, DATA_TAR_PREFIX, POSTINST_FILE, POSTRM_FILE, PREINST_FILE, PRERM_FILE, }; diff --git a/decoders/deb/src/lib.rs b/decoders/deb/src/lib.rs index 6c6c29ec..9f93cdc5 100644 --- a/decoders/deb/src/lib.rs +++ b/decoders/deb/src/lib.rs @@ -6,28 +6,28 @@ use std::str::from_utf8; use upac_abi::DECODER_ABI_VERSION; -use upac_abi::decoder::{CDecodeRequest, CDecodeResponse, CDependency, DecodeError}; use upac_abi::memory::{free_cslice, free_cvec_owning}; -use upac_abi::package::CPackageMeta; -use upac_abi::types::COwned; -use upac_abi::types::{CSlice, CVec}; -use upac_types::decoder::{DecodeMeta, DecodedMeta}; +use upac_abi::request::CDecodeRequest; +use upac_abi::response::CDecodeResponse; -use crate::control::ControlFile; -use crate::extract::ExtractedMetadata; +use upac_types::decoder::{build_decode_response, verify}; +use upac_types::error::DecodeError; +use upac_types::traits::DecodeMeta; + +use self::control::ControlFile; +use self::extract::ExtractedMetadata; pub mod control; pub mod triggers; mod extract; -mod verify; include!(concat!(env!("OUT_DIR"), "/layout.rs")); /// # Safety /// Touches no pointers. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn abi_version() -> u32 { +pub unsafe extern "C" fn decode_abi_version() -> u32 { DECODER_ABI_VERSION } @@ -78,7 +78,7 @@ fn decode_package(request: &CDecodeRequest) -> Result Result) -> CDecodeResponse { - let DecodedMeta { meta, dependencies } = decoded; - - let dependencies = dependencies.into_iter().map(CDependency::from).collect::>(); - - let declarative_triggers = declarative_triggers - .into_iter() - .map(|trigger| CSlice::from_owned(trigger.into_bytes())) - .collect::>(); - - CDecodeResponse { - struct_size: size_of::(), - - meta: CPackageMeta::from(meta), - - dependencies: CVec::from_owned(dependencies), - declarative_triggers: CVec::from_owned(declarative_triggers), - - free: free_decode_response, - } + Ok(build_decode_response( + decoded, + declarative_triggers, + free_decode_response, + )) } diff --git a/decoders/deb/src/triggers.rs b/decoders/deb/src/triggers.rs index 212f7178..dc81c7a8 100644 --- a/decoders/deb/src/triggers.rs +++ b/decoders/deb/src/triggers.rs @@ -3,9 +3,9 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_types::DecoderTrigger; +use upac_types::decoder::DecoderTrigger; -use crate::deb::{POSTINST_FILE, POSTRM_FILE, PREINST_FILE, PRERM_FILE}; +use super::deb::{POSTINST_FILE, POSTRM_FILE, PREINST_FILE, PRERM_FILE}; pub fn scan(scripts_present: &[String]) -> Vec { let mut names: Vec = Vec::new(); diff --git a/decoders/deb/src/verify.rs b/decoders/deb/src/verify.rs deleted file mode 100644 index aae6636c..00000000 --- a/decoders/deb/src/verify.rs +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::File; -use std::io::{BufReader, Read}; - -use sha2::{Digest, Sha256}; - -use upac_abi::decoder::DecodeError; -use upac_abi::hook::CancelToken; - -const READ_CHUNK_SIZE: usize = 65536; - -pub fn verify(package_path: &str, expected_checksum: [u8; 32], cancel: &CancelToken) -> Result<(), DecodeError> { - let file = File::open(package_path)?; - let mut reader = BufReader::new(file); - - let mut hasher = Sha256::new(); - let mut buffer = [0u8; READ_CHUNK_SIZE]; - - loop { - if cancel.is_cancelled() { - return Err(DecodeError::Cancelled); - } - - let bytes_read = reader.read(&mut buffer)?; - if bytes_read == 0 { - break; - } - - hasher.update(&buffer[..bytes_read]); - } - - if hasher.finalize().as_slice() != expected_checksum.as_slice() { - return Err(DecodeError::ChecksumMismatch); - } - - Ok(()) -} diff --git a/decoders/deb/tests/control.rs b/decoders/deb/tests/control.rs index 60b837b1..a81318fe 100644 --- a/decoders/deb/tests/control.rs +++ b/decoders/deb/tests/control.rs @@ -3,9 +3,12 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::decoder::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS, DecodeError}; +use upac_abi::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS}; + use upac_decoder_deb::control::ControlFile; -use upac_types::decoder::DecodeMeta; + +use upac_types::error::DecodeError; +use upac_types::traits::DecodeMeta; const CHECKSUM: [u8; 32] = [7; 32]; From 152e865af0f96a39d52639d9b507b1efde729c97 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 07:22:28 +0400 Subject: [PATCH 087/148] fix: add DecodeError::from(Utf8Error), dedupe repeated map_err(InvalidRequest) at every decoder's C-ABI boundary Co-Authored-By: Claude Sonnet 5 --- lib/types/src/error.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/types/src/error.rs b/lib/types/src/error.rs index 758348a6..6bb83fef 100644 --- a/lib/types/src/error.rs +++ b/lib/types/src/error.rs @@ -5,6 +5,7 @@ use std::io::Error as IoError; use std::io::ErrorKind as IoErrorKind; +use std::str::Utf8Error; use upac_abi::error::ErrorDomain; @@ -26,6 +27,12 @@ impl From for DecodeError { } } +impl From for DecodeError { + fn from(_: Utf8Error) -> Self { + DecodeError::InvalidRequest + } +} + impl DecodeError { pub fn code(self) -> i32 { match self { From ee317570f595cb2421fde768a9f66a554d4d6ace Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 07:22:58 +0400 Subject: [PATCH 088/148] fix: use ? instead .map_err Co-Authored-By: Claude Sonnet 5 --- decoders/alpm/src/lib.rs | 5 ++--- decoders/deb/src/lib.rs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/decoders/alpm/src/lib.rs b/decoders/alpm/src/lib.rs index 27bff897..705e8dba 100644 --- a/decoders/alpm/src/lib.rs +++ b/decoders/alpm/src/lib.rs @@ -73,9 +73,8 @@ unsafe extern "C" fn free_decode_response(response: *mut CDecodeResponse) { } fn decode_package(request: &CDecodeRequest) -> Result { - let package_path = - from_utf8(unsafe { request.package_path.as_slice() }).map_err(|_| DecodeError::InvalidRequest)?; - let output_dir = from_utf8(unsafe { request.output_dir.as_slice() }).map_err(|_| DecodeError::InvalidRequest)?; + let package_path = from_utf8(unsafe { request.package_path.as_slice() })?; + let output_dir = from_utf8(unsafe { request.output_dir.as_slice() })?; let cancel = unsafe { request.cancel_token.as_ref() }.ok_or(DecodeError::InvalidRequest)?; verify(package_path, request.checksum, cancel)?; diff --git a/decoders/deb/src/lib.rs b/decoders/deb/src/lib.rs index 9f93cdc5..3364f4c6 100644 --- a/decoders/deb/src/lib.rs +++ b/decoders/deb/src/lib.rs @@ -73,9 +73,8 @@ unsafe extern "C" fn free_decode_response(response: *mut CDecodeResponse) { } fn decode_package(request: &CDecodeRequest) -> Result { - let package_path = - from_utf8(unsafe { request.package_path.as_slice() }).map_err(|_| DecodeError::InvalidRequest)?; - let output_dir = from_utf8(unsafe { request.output_dir.as_slice() }).map_err(|_| DecodeError::InvalidRequest)?; + let package_path = from_utf8(unsafe { request.package_path.as_slice() })?; + let output_dir = from_utf8(unsafe { request.output_dir.as_slice() })?; let cancel = unsafe { request.cancel_token.as_ref() }.ok_or(DecodeError::InvalidRequest)?; verify(package_path, request.checksum, cancel)?; From 21c4e2a972f9ec391e9943799975e4abcf5a267b Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 07:38:37 +0400 Subject: [PATCH 089/148] fix: teach CFree's cvec_free to own-free CVec fields (free_cvec_owning + free_cslice), mirrors CValidate's existing CSlice special-case Co-Authored-By: Claude Sonnet 5 --- lib/macro/src/c_free/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/macro/src/c_free/mod.rs b/lib/macro/src/c_free/mod.rs index e3df09a8..1451ff75 100644 --- a/lib/macro/src/c_free/mod.rs +++ b/lib/macro/src/c_free/mod.rs @@ -9,8 +9,9 @@ //! //! Dispatch is by field TYPE, decided at compile time: //! CSlice -> free_cslice(&self.field) -//! CVec -> free_cvec(&self.field) +//! CVec -> free_cvec_owning(&self.field, |entry| free_cslice(entry)) //! CVec -> free_cvec_owning(&self.field, |entry| entry.free()) +//! CVec -> free_cvec(&self.field) //! primitive (u32, [u8;32], bool, ...) -> owns nothing, skipped //! other named type (composite) -> self.field.free() //! Add a new owned field and it's handled automatically — no list to maintain. @@ -32,6 +33,9 @@ fn composite_free(ident: &Ident) -> TokenStream2 { fn cvec_free(ident: &Ident, segment: &PathSegment) -> TokenStream2 { match generic_arg(segment).and_then(segment_name) { + Some(name) if name == "CSlice" => quote! { + free_cvec_owning(&self.#ident, |entry| free_cslice(entry)); + }, Some(name) if VALIDATABLE_COMPOSITES.contains(&name.as_str()) => quote! { free_cvec_owning(&self.#ident, |entry| entry.free()); }, From 56103992afcc67017cde77a2d268b96cb5c6deb6 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 07:38:45 +0400 Subject: [PATCH 090/148] fix: derive CFree on CDecodeResponse now that cvec_free handles CVec, collapse hand-written free_decode_response in alpm/deb/rpm to response.free() Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/abi/src/response.rs b/lib/abi/src/response.rs index 61757160..45008407 100644 --- a/lib/abi/src/response.rs +++ b/lib/abi/src/response.rs @@ -198,7 +198,7 @@ pub struct CDiffResponse { } #[repr(C)] -#[derive(CValidate, CNew)] +#[derive(CFree, CValidate, CNew)] pub struct CDecodeResponse { pub struct_size: usize, From 3623bf7a4d0611fdb1a49b6f39218b4c21507768 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 07:39:31 +0400 Subject: [PATCH 091/148] fix: collapse free_decode_response to response.free() in alpm/deb/rpm now that CDecodeResponse derives CFree Co-Authored-By: Claude Sonnet 5 --- decoders/alpm/src/lib.rs | 14 +------------- decoders/deb/src/lib.rs | 14 +------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/decoders/alpm/src/lib.rs b/decoders/alpm/src/lib.rs index 705e8dba..3740eedc 100644 --- a/decoders/alpm/src/lib.rs +++ b/decoders/alpm/src/lib.rs @@ -6,7 +6,6 @@ use std::str::from_utf8; use upac_abi::DECODER_ABI_VERSION; -use upac_abi::memory::{free_cslice, free_cvec_owning}; use upac_abi::request::CDecodeRequest; use upac_abi::response::CDecodeResponse; @@ -58,18 +57,7 @@ unsafe extern "C" fn free_decode_response(response: *mut CDecodeResponse) { return; } - let response = unsafe { &*response }; - - unsafe { - response.meta.free(); - - free_cvec_owning(&response.dependencies, |dependency| { - free_cslice(&dependency.name); - dependency.version.free(); - }); - - free_cvec_owning(&response.declarative_triggers, |slice| free_cslice(slice)); - } + unsafe { (&*response).free() }; } fn decode_package(request: &CDecodeRequest) -> Result { diff --git a/decoders/deb/src/lib.rs b/decoders/deb/src/lib.rs index 3364f4c6..29214b30 100644 --- a/decoders/deb/src/lib.rs +++ b/decoders/deb/src/lib.rs @@ -6,7 +6,6 @@ use std::str::from_utf8; use upac_abi::DECODER_ABI_VERSION; -use upac_abi::memory::{free_cslice, free_cvec_owning}; use upac_abi::request::CDecodeRequest; use upac_abi::response::CDecodeResponse; @@ -58,18 +57,7 @@ unsafe extern "C" fn free_decode_response(response: *mut CDecodeResponse) { return; } - let response = unsafe { &*response }; - - unsafe { - response.meta.free(); - - free_cvec_owning(&response.dependencies, |dependency| { - free_cslice(&dependency.name); - dependency.version.free(); - }); - - free_cvec_owning(&response.declarative_triggers, |slice| free_cslice(slice)); - } + unsafe { (&*response).free() }; } fn decode_package(request: &CDecodeRequest) -> Result { From a8fe9d5598f8b3f3e5a0272b29d2e6798272d09c Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 08:32:13 +0400 Subject: [PATCH 092/148] fix: restructure rpm's header.rs around self-parsing types (SectionHeader/TagEntry/RawData/Header), eliminate all free functions Co-Authored-By: Claude Sonnet 5 --- decoders/rpm/src/extract.rs | 29 +++-- decoders/rpm/src/header.rs | 196 ++++++++++++++++++--------------- decoders/rpm/src/lib.rs | 74 ++++--------- decoders/rpm/src/meta.rs | 40 +++---- decoders/rpm/src/triggers.rs | 6 +- decoders/rpm/src/verify.rs | 41 ------- decoders/rpm/tests/meta.rs | 12 +- decoders/rpm/tests/triggers.rs | 4 +- 8 files changed, 176 insertions(+), 226 deletions(-) delete mode 100644 decoders/rpm/src/verify.rs diff --git a/decoders/rpm/src/extract.rs b/decoders/rpm/src/extract.rs index 41eaafb4..c0f9f0ce 100644 --- a/decoders/rpm/src/extract.rs +++ b/decoders/rpm/src/extract.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::fs::{self, File}; +use std::fs::{File, create_dir_all}; use std::io::Read; use std::os::unix::fs::symlink; use std::path::{Component, Path}; @@ -13,9 +13,10 @@ use flate2::read::GzDecoder; use xz2::read::XzDecoder; use zstd::stream::read::Decoder as ZstdDecoder; -use upac_abi::decoder::DecodeError; use upac_abi::hook::CancelToken; +use upac_types::error::DecodeError; + use crate::header::Header; use crate::rpm::{PAYLOAD_COMPRESSOR_TAG, PAYLOAD_FORMAT_TAG}; @@ -34,7 +35,13 @@ pub fn extract(file: File, header: &Header, output_dir: &str, cancel: &CancelTok .string(PAYLOAD_COMPRESSOR_TAG)? .unwrap_or_else(|| "gzip".to_owned()); - let mut reader = open_decompressor(&compressor, file)?; + let mut reader: Box = match compressor.as_str() { + "gzip" => Box::new(GzDecoder::new(file)), + "xz" => Box::new(XzDecoder::new(file)), + "zstd" => Box::new(ZstdDecoder::new(file)?), + "none" => Box::new(file), + _ => return Err(DecodeError::UnsupportedFormat), + }; loop { if cancel.is_cancelled() { @@ -65,7 +72,7 @@ pub fn extract(file: File, header: &Header, output_dir: &str, cancel: &CancelTok reader = match entry.mode() & MODE_TYPE_MASK { MODE_TYPE_DIRECTORY => { - fs::create_dir_all(&target_path)?; + create_dir_all(&target_path)?; entry_reader.finish()? } MODE_TYPE_SYMLINK => { @@ -74,7 +81,7 @@ pub fn extract(file: File, header: &Header, output_dir: &str, cancel: &CancelTok let link_target = String::from_utf8(link_target).map_err(|_| DecodeError::InvalidUtf8)?; if let Some(parent) = target_path.parent() { - fs::create_dir_all(parent)?; + create_dir_all(parent)?; } symlink(link_target, &target_path)?; @@ -82,7 +89,7 @@ pub fn extract(file: File, header: &Header, output_dir: &str, cancel: &CancelTok } MODE_TYPE_REGULAR => { if let Some(parent) = target_path.parent() { - fs::create_dir_all(parent)?; + create_dir_all(parent)?; } let mut out = File::create(&target_path)?; @@ -94,13 +101,3 @@ pub fn extract(file: File, header: &Header, output_dir: &str, cancel: &CancelTok Ok(()) } - -fn open_decompressor(compressor: &str, file: File) -> Result, DecodeError> { - match compressor { - "gzip" => Ok(Box::new(GzDecoder::new(file))), - "xz" => Ok(Box::new(XzDecoder::new(file))), - "zstd" => Ok(Box::new(ZstdDecoder::new(file)?)), - "none" => Ok(Box::new(file)), - _ => Err(DecodeError::UnsupportedFormat), - } -} diff --git a/decoders/rpm/src/header.rs b/decoders/rpm/src/header.rs index 4b352be1..e0c36a77 100644 --- a/decoders/rpm/src/header.rs +++ b/decoders/rpm/src/header.rs @@ -5,9 +5,9 @@ use std::io::{Read, Seek, SeekFrom}; -use upac_abi::decoder::DecodeError; +use upac_types::error::DecodeError; -use crate::rpm::LEAD_SIZE; +use super::rpm::LEAD_SIZE; const LEAD_MAGIC: [u8; 4] = [0xED, 0xAB, 0xEE, 0xDB]; const SECTION_MAGIC: [u8; 3] = [0x8E, 0xAD, 0xE8]; @@ -17,28 +17,97 @@ struct SectionHeader { data_size: u32, } +impl SectionHeader { + fn read(reader: &mut R) -> Result { + let mut buffer = [0u8; 16]; + reader + .read_exact(&mut buffer) + .map_err(|_| DecodeError::MalformedMetadata)?; + + if buffer[0..3] != SECTION_MAGIC { + return Err(DecodeError::MalformedMetadata); + } + + Ok(SectionHeader { + tag_count: u32::from_be_bytes([buffer[8], buffer[9], buffer[10], buffer[11]]), + data_size: u32::from_be_bytes([buffer[12], buffer[13], buffer[14], buffer[15]]), + }) + } + + fn skip_body(&self, reader: &mut R) -> Result<(), DecodeError> { + let total_size = u64::from(self.tag_count) * 16 + u64::from(self.data_size); + reader.seek(SeekFrom::Current(total_size as i64))?; + + let remainder = total_size % 8; + if remainder != 0 { + reader.seek(SeekFrom::Current((8 - remainder) as i64))?; + } + + Ok(()) + } +} + struct TagEntry { tag: u32, offset: u32, count: u32, } -pub struct Header { - entries: Vec, - data: Vec, +impl TagEntry { + fn from_bytes(chunk: &[u8; 16]) -> Self { + TagEntry { + tag: u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]), + offset: u32::from_be_bytes([chunk[8], chunk[9], chunk[10], chunk[11]]), + count: u32::from_be_bytes([chunk[12], chunk[13], chunk[14], chunk[15]]), + } + } } -pub fn read(reader: &mut R) -> Result { - skip_lead(reader)?; - skip_signature(reader)?; - read_main_header(reader) +struct RawData(Vec); + +impl RawData { + fn read(reader: &mut R, size: usize) -> Result { + let mut bytes = vec![0u8; size]; + reader + .read_exact(&mut bytes) + .map_err(|_| DecodeError::MalformedMetadata)?; + + Ok(RawData(bytes)) + } + + fn string_at(&self, offset: usize) -> Result { + let slice = self.0.get(offset..).ok_or(DecodeError::MalformedMetadata)?; + let end = slice + .iter() + .position(|&byte| byte == 0) + .ok_or(DecodeError::MalformedMetadata)?; + + String::from_utf8(slice[..end].to_vec()).map_err(|_| DecodeError::InvalidUtf8) + } + + fn i32_at(&self, offset: usize) -> Result { + let bytes = self.0.get(offset..offset + 4).ok_or(DecodeError::MalformedMetadata)?; + + Ok(i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])) + } +} + +pub struct Header { + entries: Vec, + data: RawData, } impl Header { + pub fn read(reader: &mut R) -> Result { + Self::skip_lead(reader)?; + SectionHeader::read(reader)?.skip_body(reader)?; + Self::read_main(reader) + } + pub fn string(&self, tag: u32) -> Result, DecodeError> { let Some(entry) = self.find(tag) else { return Ok(None) }; - read_string_at(&self.data, entry.offset as usize).map(Some) + self.data.string_at(entry.offset as usize).map(Some) } pub fn string_array(&self, tag: u32) -> Result, DecodeError> { @@ -49,7 +118,7 @@ impl Header { let mut values = Vec::with_capacity(entry.count as usize); let mut cursor = entry.offset as usize; for _ in 0..entry.count { - let value = read_string_at(&self.data, cursor)?; + let value = self.data.string_at(cursor)?; cursor += value.len() + 1; values.push(value); } @@ -60,7 +129,7 @@ impl Header { pub fn int32(&self, tag: u32) -> Result, DecodeError> { let Some(entry) = self.find(tag) else { return Ok(None) }; - read_i32_at(&self.data, entry.offset as usize).map(Some) + self.data.i32_at(entry.offset as usize).map(Some) } pub fn int32_array(&self, tag: u32) -> Result, DecodeError> { @@ -69,7 +138,7 @@ impl Header { }; (0..entry.count as usize) - .map(|index| read_i32_at(&self.data, entry.offset as usize + index * 4)) + .map(|index| self.data.i32_at(entry.offset as usize + index * 4)) .collect() } @@ -80,92 +149,39 @@ impl Header { fn find(&self, tag: u32) -> Option<&TagEntry> { self.entries.iter().find(|entry| entry.tag == tag) } -} - -fn skip_lead(reader: &mut R) -> Result<(), DecodeError> { - let mut magic = [0u8; 4]; - reader - .read_exact(&mut magic) - .map_err(|_| DecodeError::UnsupportedFormat)?; - - if magic != LEAD_MAGIC { - return Err(DecodeError::UnsupportedFormat); - } - reader.seek(SeekFrom::Start(u64::from(LEAD_SIZE)))?; - - Ok(()) -} + fn skip_lead(reader: &mut R) -> Result<(), DecodeError> { + let mut magic = [0u8; 4]; + reader + .read_exact(&mut magic) + .map_err(|_| DecodeError::UnsupportedFormat)?; -fn skip_signature(reader: &mut R) -> Result<(), DecodeError> { - let header = read_section_header(reader)?; + if magic != LEAD_MAGIC { + return Err(DecodeError::UnsupportedFormat); + } - let total_size = u64::from(header.tag_count) * 16 + u64::from(header.data_size); - reader.seek(SeekFrom::Current(total_size as i64))?; + reader.seek(SeekFrom::Start(u64::from(LEAD_SIZE)))?; - let remainder = total_size % 8; - if remainder != 0 { - reader.seek(SeekFrom::Current((8 - remainder) as i64))?; + Ok(()) } - Ok(()) -} - -fn read_main_header(reader: &mut R) -> Result { - let header = read_section_header(reader)?; + fn read_main(reader: &mut R) -> Result { + let header = SectionHeader::read(reader)?; - let mut index_bytes = vec![0u8; header.tag_count as usize * 16]; - reader - .read_exact(&mut index_bytes) - .map_err(|_| DecodeError::MalformedMetadata)?; + let mut index_bytes = vec![0u8; header.tag_count as usize * 16]; + reader + .read_exact(&mut index_bytes) + .map_err(|_| DecodeError::MalformedMetadata)?; - let mut data = vec![0u8; header.data_size as usize]; - reader - .read_exact(&mut data) - .map_err(|_| DecodeError::MalformedMetadata)?; + let data = RawData::read(reader, header.data_size as usize)?; - let entries = index_bytes - .as_chunks::<16>() - .0 - .iter() - .map(|chunk| TagEntry { - tag: u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]), - offset: u32::from_be_bytes([chunk[8], chunk[9], chunk[10], chunk[11]]), - count: u32::from_be_bytes([chunk[12], chunk[13], chunk[14], chunk[15]]), - }) - .collect(); - - Ok(Header { entries, data }) -} - -fn read_section_header(reader: &mut R) -> Result { - let mut buffer = [0u8; 16]; - reader - .read_exact(&mut buffer) - .map_err(|_| DecodeError::MalformedMetadata)?; + let entries = index_bytes + .as_chunks::<16>() + .0 + .iter() + .map(TagEntry::from_bytes) + .collect(); - if buffer[0..3] != SECTION_MAGIC { - return Err(DecodeError::MalformedMetadata); + Ok(Header { entries, data }) } - - Ok(SectionHeader { - tag_count: u32::from_be_bytes([buffer[8], buffer[9], buffer[10], buffer[11]]), - data_size: u32::from_be_bytes([buffer[12], buffer[13], buffer[14], buffer[15]]), - }) -} - -fn read_string_at(data: &[u8], offset: usize) -> Result { - let slice = data.get(offset..).ok_or(DecodeError::MalformedMetadata)?; - let end = slice - .iter() - .position(|&byte| byte == 0) - .ok_or(DecodeError::MalformedMetadata)?; - - String::from_utf8(slice[..end].to_vec()).map_err(|_| DecodeError::InvalidUtf8) -} - -fn read_i32_at(data: &[u8], offset: usize) -> Result { - let bytes = data.get(offset..offset + 4).ok_or(DecodeError::MalformedMetadata)?; - - Ok(i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])) } diff --git a/decoders/rpm/src/lib.rs b/decoders/rpm/src/lib.rs index 10deca54..9376ab48 100644 --- a/decoders/rpm/src/lib.rs +++ b/decoders/rpm/src/lib.rs @@ -7,26 +7,29 @@ use std::fs::File; use std::str::from_utf8; use upac_abi::DECODER_ABI_VERSION; -use upac_abi::decoder::{CDecodeRequest, CDecodeResponse, CDependency, DecodeError}; -use upac_abi::memory::{free_cslice, free_cvec_owning}; -use upac_abi::package::CPackageMeta; -use upac_abi::types::COwned; -use upac_abi::types::{CSlice, CVec}; -use upac_types::decoder::{DecodeMeta, DecodedMeta}; +use upac_abi::request::CDecodeRequest; +use upac_abi::response::CDecodeResponse; + +use upac_types::decoder::{build_decode_response, verify}; +use upac_types::error::DecodeError; +use upac_types::traits::DecodeMeta; + +use self::extract::extract; +use self::header::Header; +use self::triggers::scan; pub mod header; pub mod meta; pub mod triggers; mod extract; -mod verify; include!(concat!(env!("OUT_DIR"), "/layout.rs")); /// # Safety /// Touches no pointers. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn abi_version() -> u32 { +pub unsafe extern "C" fn decode_abi_version() -> u32 { DECODER_ABI_VERSION } @@ -57,57 +60,28 @@ unsafe extern "C" fn free_decode_response(response: *mut CDecodeResponse) { return; } - let response = unsafe { &*response }; - - unsafe { - response.meta.free(); - - free_cvec_owning(&response.dependencies, |dependency| { - free_cslice(&dependency.name); - dependency.version.free(); - }); - - free_cvec_owning(&response.declarative_triggers, |slice| free_cslice(slice)); - } + unsafe { (&*response).free() }; } fn decode_package(request: &CDecodeRequest) -> Result { - let package_path = - from_utf8(unsafe { request.package_path.as_slice() }).map_err(|_| DecodeError::InvalidRequest)?; - let output_dir = from_utf8(unsafe { request.output_dir.as_slice() }).map_err(|_| DecodeError::InvalidRequest)?; + let package_path = from_utf8(unsafe { request.package_path.as_slice() })?; + let output_dir = from_utf8(unsafe { request.output_dir.as_slice() })?; + let cancel = unsafe { request.cancel_token.as_ref() }.ok_or(DecodeError::InvalidRequest)?; - verify::verify(package_path, request.checksum, cancel)?; + verify(package_path, request.checksum, cancel)?; let mut file = File::open(package_path)?; - let header = header::read(&mut file)?; + let header = Header::read(&mut file)?; - extract::extract(file, &header, output_dir, cancel)?; + extract(file, &header, output_dir, cancel)?; - let declarative_triggers = triggers::scan(&header); + let declarative_triggers = scan(&header); let decoded = header.decode(request.checksum)?; - Ok(build_response(decoded, declarative_triggers)) -} - -fn build_response(decoded: DecodedMeta, declarative_triggers: Vec) -> CDecodeResponse { - let DecodedMeta { meta, dependencies } = decoded; - - let dependencies = dependencies.into_iter().map(CDependency::from).collect::>(); - - let declarative_triggers = declarative_triggers - .into_iter() - .map(|trigger| CSlice::from_owned(trigger.into_bytes())) - .collect::>(); - - CDecodeResponse { - struct_size: size_of::(), - - meta: CPackageMeta::from(meta), - - dependencies: CVec::from_owned(dependencies), - declarative_triggers: CVec::from_owned(declarative_triggers), - - free: free_decode_response, - } + Ok(build_decode_response( + decoded, + declarative_triggers, + free_decode_response, + )) } diff --git a/decoders/rpm/src/meta.rs b/decoders/rpm/src/meta.rs index 80e9e466..c933c883 100644 --- a/decoders/rpm/src/meta.rs +++ b/decoders/rpm/src/meta.rs @@ -3,12 +3,14 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::decoder::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS, DecodeError}; -use upac_types::decoder::{DecodeMeta, DecodedMeta}; -use upac_types::{Dependency, PackageMeta, Version}; +use upac_abi::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS}; -use crate::header::Header; -use crate::rpm::{ +use upac_types::error::DecodeError; +use upac_types::package::{DecodedPackageMeta, PackageDependency, PackageMeta, Version}; +use upac_types::traits::DecodeMeta; + +use super::header::Header; +use super::rpm::{ ARCH_TAG, LICENSE_TAG, NAME_TAG, PACKAGER_TAG, RELEASE_TAG, REQUIRE_FLAGS_TAG, REQUIRE_NAME_TAG, REQUIRE_VERSION_TAG, SIZE_TAG, SUMMARY_TAG, URL_TAG, VERSION_TAG, }; @@ -18,8 +20,14 @@ const SENSE_GREATER: i32 = 0x04; const SENSE_EQUAL: i32 = 0x08; const SENSE_RPMLIB: i32 = 0x0100_0000; +const SENSE_FLAGS: [(i32, u8); 3] = [ + (SENSE_LESS, CONSTRAINT_LESS), + (SENSE_GREATER, CONSTRAINT_GREATER), + (SENSE_EQUAL, CONSTRAINT_EQUAL), +]; + impl DecodeMeta for Header { - fn decode(&self, sha256: [u8; 32]) -> Result { + fn decode(&self, sha256: [u8; 32]) -> Result { let name = self.string(NAME_TAG)?.ok_or(DecodeError::MalformedMetadata)?; let version = self.string(VERSION_TAG)?.ok_or(DecodeError::MalformedMetadata)?; @@ -44,7 +52,7 @@ impl DecodeMeta for Header { installed_size, }; - Ok(DecodedMeta { + Ok(DecodedPackageMeta { meta, dependencies: self.parse_dependencies()?, }) @@ -52,7 +60,7 @@ impl DecodeMeta for Header { } impl Header { - fn parse_dependencies(&self) -> Result, DecodeError> { + fn parse_dependencies(&self) -> Result, DecodeError> { let names = self.string_array(REQUIRE_NAME_TAG)?; let versions = self.string_array(REQUIRE_VERSION_TAG)?; let flags = self.int32_array(REQUIRE_FLAGS_TAG)?; @@ -66,7 +74,7 @@ impl Header { let raw_version = versions.get(index).cloned().unwrap_or_default(); - dependencies.push(Dependency { + dependencies.push(PackageDependency { name, constraint: Self::sense_to_constraint(flag), version: Version::parse(&raw_version), @@ -77,16 +85,10 @@ impl Header { } fn sense_to_constraint(flag: i32) -> u8 { - let mut constraint = 0; - if flag & SENSE_LESS != 0 { - constraint |= CONSTRAINT_LESS; - } - if flag & SENSE_GREATER != 0 { - constraint |= CONSTRAINT_GREATER; - } - if flag & SENSE_EQUAL != 0 { - constraint |= CONSTRAINT_EQUAL; - } + let constraint = SENSE_FLAGS + .iter() + .filter(|(sense, _)| flag & sense != 0) + .fold(0, |acc, (_, constraint)| acc | constraint); if constraint == 0 { CONSTRAINT_ANY } else { constraint } } diff --git a/decoders/rpm/src/triggers.rs b/decoders/rpm/src/triggers.rs index 6b39233d..7571ff83 100644 --- a/decoders/rpm/src/triggers.rs +++ b/decoders/rpm/src/triggers.rs @@ -3,10 +3,10 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_types::DecoderTrigger; +use upac_types::decoder::DecoderTrigger; -use crate::header::Header; -use crate::rpm::{POSTIN_NAME, POSTIN_TAG, POSTUN_NAME, POSTUN_TAG, PREIN_NAME, PREIN_TAG, PREUN_NAME, PREUN_TAG}; +use super::header::Header; +use super::rpm::{POSTIN_NAME, POSTIN_TAG, POSTUN_NAME, POSTUN_TAG, PREIN_NAME, PREIN_TAG, PREUN_NAME, PREUN_TAG}; pub fn scan(header: &Header) -> Vec { let mut names: Vec = Vec::new(); diff --git a/decoders/rpm/src/verify.rs b/decoders/rpm/src/verify.rs deleted file mode 100644 index aae6636c..00000000 --- a/decoders/rpm/src/verify.rs +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::File; -use std::io::{BufReader, Read}; - -use sha2::{Digest, Sha256}; - -use upac_abi::decoder::DecodeError; -use upac_abi::hook::CancelToken; - -const READ_CHUNK_SIZE: usize = 65536; - -pub fn verify(package_path: &str, expected_checksum: [u8; 32], cancel: &CancelToken) -> Result<(), DecodeError> { - let file = File::open(package_path)?; - let mut reader = BufReader::new(file); - - let mut hasher = Sha256::new(); - let mut buffer = [0u8; READ_CHUNK_SIZE]; - - loop { - if cancel.is_cancelled() { - return Err(DecodeError::Cancelled); - } - - let bytes_read = reader.read(&mut buffer)?; - if bytes_read == 0 { - break; - } - - hasher.update(&buffer[..bytes_read]); - } - - if hasher.finalize().as_slice() != expected_checksum.as_slice() { - return Err(DecodeError::ChecksumMismatch); - } - - Ok(()) -} diff --git a/decoders/rpm/tests/meta.rs b/decoders/rpm/tests/meta.rs index 596565ed..e60fdb3c 100644 --- a/decoders/rpm/tests/meta.rs +++ b/decoders/rpm/tests/meta.rs @@ -5,14 +5,16 @@ use std::io::Cursor; -use upac_abi::decoder::DecodeError; -use upac_abi::decoder::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS}; -use upac_decoder_rpm::header::{self, Header}; +use upac_abi::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS}; + +use upac_decoder_rpm::header::Header; use upac_decoder_rpm::rpm::{ ARCH_TAG, LICENSE_TAG, NAME_TAG, PACKAGER_TAG, RELEASE_TAG, REQUIRE_FLAGS_TAG, REQUIRE_NAME_TAG, REQUIRE_VERSION_TAG, SIZE_TAG, SUMMARY_TAG, URL_TAG, VERSION_TAG, }; -use upac_types::decoder::DecodeMeta; + +use upac_types::error::DecodeError; +use upac_types::traits::DecodeMeta; const CHECKSUM: [u8; 32] = [7; 32]; @@ -70,7 +72,7 @@ fn build_header(entries: &[(u32, RawValue)]) -> Header { bytes.extend_from_slice(&data_block); let mut cursor = Cursor::new(bytes); - header::read(&mut cursor).unwrap() + Header::read(&mut cursor).unwrap() } fn section_header(tag_count: u32, data_size: u32) -> [u8; 16] { diff --git a/decoders/rpm/tests/triggers.rs b/decoders/rpm/tests/triggers.rs index bc87bc2a..b609410a 100644 --- a/decoders/rpm/tests/triggers.rs +++ b/decoders/rpm/tests/triggers.rs @@ -5,7 +5,7 @@ use std::io::Cursor; -use upac_decoder_rpm::header::{self, Header}; +use upac_decoder_rpm::header::Header; use upac_decoder_rpm::rpm::{NAME_TAG, POSTIN_TAG, POSTUN_TAG, PREIN_TAG, PREUN_TAG}; use upac_decoder_rpm::triggers; @@ -43,7 +43,7 @@ fn build_header(entries: &[(u32, RawValue)]) -> Header { bytes.extend_from_slice(&data_block); let mut cursor = Cursor::new(bytes); - header::read(&mut cursor).unwrap() + Header::read(&mut cursor).unwrap() } fn section_header(tag_count: u32, data_size: u32) -> [u8; 16] { From 5b9d870ef9581a25eab161dbbce96782125a7fb1 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 08:38:17 +0400 Subject: [PATCH 093/148] fix: rebuild upac-decoder-xbps around current upac_abi/upac_types shapes, dedupe verify/build_decode_response into upac_types::decoder Co-Authored-By: Claude Sonnet 5 --- decoders/xbps/src/extract.rs | 7 ++-- decoders/xbps/src/lib.rs | 71 +++++++++++------------------------ decoders/xbps/src/meta.rs | 23 ++++++------ decoders/xbps/src/triggers.rs | 4 +- decoders/xbps/src/verify.rs | 41 -------------------- decoders/xbps/tests/meta.rs | 7 +++- 6 files changed, 43 insertions(+), 110 deletions(-) delete mode 100644 decoders/xbps/src/verify.rs diff --git a/decoders/xbps/src/extract.rs b/decoders/xbps/src/extract.rs index d000d499..13316b2b 100644 --- a/decoders/xbps/src/extract.rs +++ b/decoders/xbps/src/extract.rs @@ -11,11 +11,12 @@ use tar::Archive; use xz2::read::XzDecoder; use zstd::stream::read::Decoder as ZstdDecoder; -use upac_abi::decoder::DecodeError; use upac_abi::hook::CancelToken; + use upac_types::decoder::read_to_string; +use upac_types::error::DecodeError; -use crate::xbps::{FILES_ENTRY, INSTALL_ENTRY, PROPS_ENTRY, REMOVE_ENTRY}; +use super::xbps::{FILES_ENTRY, INSTALL_ENTRY, PROPS_ENTRY, REMOVE_ENTRY}; const ZSTD_MAGIC: [u8; 4] = [0x28, 0xB5, 0x2F, 0xFD]; const XZ_MAGIC: [u8; 6] = [0xFD, b'7', b'z', b'X', b'Z', 0x00]; @@ -76,8 +77,6 @@ impl ExtractedMetadata { .ok_or(DecodeError::MissingMetadata) } - /// `.xbps` filenames carry no compression suffix (unlike alpm's `.pkg.tar.{zst,xz,gz}`) — the - /// compression filter is sniffed from the file's own magic bytes instead. fn open_reader(mut file: File) -> Result, DecodeError> { let mut magic = [0u8; 6]; let bytes_read = file.read(&mut magic)?; diff --git a/decoders/xbps/src/lib.rs b/decoders/xbps/src/lib.rs index e501fccf..c9ba8e5b 100644 --- a/decoders/xbps/src/lib.rs +++ b/decoders/xbps/src/lib.rs @@ -6,28 +6,28 @@ use std::str::from_utf8; use upac_abi::DECODER_ABI_VERSION; -use upac_abi::decoder::{CDecodeRequest, CDecodeResponse, CDependency, DecodeError}; -use upac_abi::memory::{free_cslice, free_cvec_owning}; -use upac_abi::package::CPackageMeta; -use upac_abi::types::COwned; -use upac_abi::types::{CSlice, CVec}; -use upac_types::decoder::{DecodeMeta, DecodedMeta}; +use upac_abi::request::CDecodeRequest; +use upac_abi::response::CDecodeResponse; -use crate::extract::ExtractedMetadata; -use crate::meta::Props; +use upac_types::decoder::{build_decode_response, verify}; +use upac_types::error::DecodeError; +use upac_types::traits::DecodeMeta; + +use self::extract::ExtractedMetadata; +use self::meta::Props; +use self::triggers::scan; pub mod meta; pub mod triggers; mod extract; -mod verify; include!(concat!(env!("OUT_DIR"), "/layout.rs")); /// # Safety /// Touches no pointers. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn abi_version() -> u32 { +pub unsafe extern "C" fn decode_abi_version() -> u32 { DECODER_ABI_VERSION } @@ -58,54 +58,25 @@ unsafe extern "C" fn free_decode_response(response: *mut CDecodeResponse) { return; } - let response = unsafe { &*response }; - - unsafe { - response.meta.free(); - - free_cvec_owning(&response.dependencies, |dependency| { - free_cslice(&dependency.name); - dependency.version.free(); - }); - - free_cvec_owning(&response.declarative_triggers, |slice| free_cslice(slice)); - } + unsafe { (&*response).free() }; } fn decode_package(request: &CDecodeRequest) -> Result { - let package_path = - from_utf8(unsafe { request.package_path.as_slice() }).map_err(|_| DecodeError::InvalidRequest)?; - let output_dir = from_utf8(unsafe { request.output_dir.as_slice() }).map_err(|_| DecodeError::InvalidRequest)?; + let package_path = from_utf8(unsafe { request.package_path.as_slice() })?; + let output_dir = from_utf8(unsafe { request.output_dir.as_slice() })?; + let cancel = unsafe { request.cancel_token.as_ref() }.ok_or(DecodeError::InvalidRequest)?; - verify::verify(package_path, request.checksum, cancel)?; + verify(package_path, request.checksum, cancel)?; let extracted = ExtractedMetadata::extract(package_path, output_dir, cancel)?; - let declarative_triggers = triggers::scan(extracted.install_present, extracted.remove_present); + let declarative_triggers = scan(extracted.install_present, extracted.remove_present); let decoded = Props(&extracted.props).decode(request.checksum)?; - Ok(build_response(decoded, declarative_triggers)) -} - -fn build_response(decoded: DecodedMeta, declarative_triggers: Vec) -> CDecodeResponse { - let DecodedMeta { meta, dependencies } = decoded; - - let dependencies = dependencies.into_iter().map(CDependency::from).collect::>(); - - let declarative_triggers = declarative_triggers - .into_iter() - .map(|trigger| CSlice::from_owned(trigger.into_bytes())) - .collect::>(); - - CDecodeResponse { - struct_size: size_of::(), - - meta: CPackageMeta::from(meta), - - dependencies: CVec::from_owned(dependencies), - declarative_triggers: CVec::from_owned(declarative_triggers), - - free: free_decode_response, - } + Ok(build_decode_response( + decoded, + declarative_triggers, + free_decode_response, + )) } diff --git a/decoders/xbps/src/meta.rs b/decoders/xbps/src/meta.rs index 3658c3d3..91841576 100644 --- a/decoders/xbps/src/meta.rs +++ b/decoders/xbps/src/meta.rs @@ -10,13 +10,14 @@ use quick_xml::escape::resolve_xml_entity; use quick_xml::events::Event; use quick_xml::reader::Reader; -use upac_abi::decoder::{ - CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS, DecodeError, parse_constraint_prefix, -}; -use upac_types::decoder::{DecodeMeta, DecodedMeta}; -use upac_types::{Dependency, PackageMeta, Version}; +use upac_abi::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS}; + +use upac_types::decoder::parse_constraint_prefix; +use upac_types::error::DecodeError; +use upac_types::package::{DecodedPackageMeta, PackageDependency, PackageMeta, Version}; +use upac_types::traits::DecodeMeta; -use crate::xbps::{ +use super::xbps::{ ARCHITECTURE_KEY, HOMEPAGE_KEY, INSTALLED_SIZE_KEY, LICENSE_KEY, MAINTAINER_KEY, PKGNAME_KEY, RUN_DEPENDS_KEY, SHORT_DESC_KEY, VERSION_KEY, }; @@ -44,7 +45,7 @@ const OPERATORS: [(&[u8], u8); 2] = [ pub struct Props<'a>(pub &'a str); impl DecodeMeta for Props<'_> { - fn decode(&self, sha256: [u8; 32]) -> Result { + fn decode(&self, sha256: [u8; 32]) -> Result { let (mut fields, run_depends) = Self::parse_plist(self.0)?; let name = required_field!(fields, PKGNAME_KEY); @@ -67,7 +68,7 @@ impl DecodeMeta for Props<'_> { let dependencies = run_depends.iter().map(|dep| Self::parse_dependency(dep)).collect(); - Ok(DecodedMeta { meta, dependencies }) + Ok(DecodedPackageMeta { meta, dependencies }) } } @@ -122,7 +123,7 @@ impl Props<'_> { Ok((fields, run_depends)) } - fn parse_dependency(raw: &str) -> Dependency { + fn parse_dependency(raw: &str) -> PackageDependency { let bytes = raw.as_bytes(); for index in 0..bytes.len() { @@ -133,14 +134,14 @@ impl Props<'_> { let name = raw[..index].to_owned(); let version = raw[index + operator_len..].to_owned(); - return Dependency { + return PackageDependency { name, constraint, version: Version::parse(&version), }; } - Dependency { + PackageDependency { name: raw.to_owned(), constraint: CONSTRAINT_ANY, version: Version::default(), diff --git a/decoders/xbps/src/triggers.rs b/decoders/xbps/src/triggers.rs index d92d42d9..42a8074b 100644 --- a/decoders/xbps/src/triggers.rs +++ b/decoders/xbps/src/triggers.rs @@ -3,9 +3,9 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_types::DecoderTrigger; +use upac_types::decoder::DecoderTrigger; -use crate::xbps::{INSTALL_ENTRY, REMOVE_ENTRY}; +use super::xbps::{INSTALL_ENTRY, REMOVE_ENTRY}; pub fn scan(install_present: bool, remove_present: bool) -> Vec { let mut names: Vec = Vec::new(); diff --git a/decoders/xbps/src/verify.rs b/decoders/xbps/src/verify.rs deleted file mode 100644 index aae6636c..00000000 --- a/decoders/xbps/src/verify.rs +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::File; -use std::io::{BufReader, Read}; - -use sha2::{Digest, Sha256}; - -use upac_abi::decoder::DecodeError; -use upac_abi::hook::CancelToken; - -const READ_CHUNK_SIZE: usize = 65536; - -pub fn verify(package_path: &str, expected_checksum: [u8; 32], cancel: &CancelToken) -> Result<(), DecodeError> { - let file = File::open(package_path)?; - let mut reader = BufReader::new(file); - - let mut hasher = Sha256::new(); - let mut buffer = [0u8; READ_CHUNK_SIZE]; - - loop { - if cancel.is_cancelled() { - return Err(DecodeError::Cancelled); - } - - let bytes_read = reader.read(&mut buffer)?; - if bytes_read == 0 { - break; - } - - hasher.update(&buffer[..bytes_read]); - } - - if hasher.finalize().as_slice() != expected_checksum.as_slice() { - return Err(DecodeError::ChecksumMismatch); - } - - Ok(()) -} diff --git a/decoders/xbps/tests/meta.rs b/decoders/xbps/tests/meta.rs index 366be9de..cb82f7be 100644 --- a/decoders/xbps/tests/meta.rs +++ b/decoders/xbps/tests/meta.rs @@ -3,9 +3,12 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::decoder::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS, DecodeError}; +use upac_abi::{CONSTRAINT_ANY, CONSTRAINT_EQUAL, CONSTRAINT_GREATER, CONSTRAINT_LESS}; + use upac_decoder_xbps::meta::Props; -use upac_types::decoder::DecodeMeta; + +use upac_types::error::DecodeError; +use upac_types::traits::DecodeMeta; const CHECKSUM: [u8; 32] = [7; 32]; From e399dce413484b70314f0599b6fdc184f121a566 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 08:50:46 +0400 Subject: [PATCH 094/148] fix: silence clippy too_many_arguments on Booter::install with an explanatory allow Co-Authored-By: Claude Sonnet 5 --- lib/types/src/traits.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/types/src/traits.rs b/lib/types/src/traits.rs index 2115ed52..5270096e 100644 --- a/lib/types/src/traits.rs +++ b/lib/types/src/traits.rs @@ -15,6 +15,12 @@ pub trait Booter: Sized { fn set_one_shot(&mut self, entry_name: &str) -> Result<(), Self::Error>; fn confirm_boot(&mut self, entry_name: &str, esp_mount_point: &str) -> Result<(), Self::Error>; + #[allow( + clippy::too_many_arguments, + reason = "each param is its own ESP/partition fact every Booter impl needs individually (mount point, \ + partition number, LBA range, GUID, slot names) — grouping them into a request type here would \ + just be indirection, not a real builder-shaped API" + )] fn install( &mut self, esp_mount_point: &str, esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, From d1827dd246d1bc4422022412a02702fccaa96698 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 08:56:30 +0400 Subject: [PATCH 095/148] fix: addressed Clippy suggestions Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/mutated/files/checkout.rs | 8 ++++---- lib/lib/src/mutated/installer/checkout.rs | 2 +- lib/lib/src/mutated/pin/stage.rs | 2 +- lib/lib/src/mutated/rollback/checkout.rs | 8 ++++---- lib/lib/src/mutated/rollback/merge.rs | 2 +- lib/lib/src/mutated/uninstaller/checkout.rs | 2 +- lib/lib/src/mutated/update/checkout.rs | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/lib/src/mutated/files/checkout.rs b/lib/lib/src/mutated/files/checkout.rs index 5acff554..49bb854e 100644 --- a/lib/lib/src/mutated/files/checkout.rs +++ b/lib/lib/src/mutated/files/checkout.rs @@ -27,13 +27,13 @@ impl Stage for CheckoutStage { let requested_boot_plugins = ctx_get!(context, RequestedBootPlugin); let repository = deploy.open_repository()?; - let deploy_tree = deploy.open_tree(&new_prefix)?; - let digest = object_id_from_hex(&new_prefix)?; + let deploy_tree = deploy.open_tree(new_prefix)?; + let digest = object_id_from_hex(new_prefix)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &deploy_tree, digest, &esp_mount, &new_prefix)?; + let entry_name = write_boot_entry(&repository, &deploy_tree, digest, &esp_mount, new_prefix)?; - let plugin = BootPlugins::new()?.load(&requested_boot_plugins)?; + let plugin = BootPlugins::new()?.load(requested_boot_plugins)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/installer/checkout.rs b/lib/lib/src/mutated/installer/checkout.rs index c2677cfa..5bb9e3b2 100644 --- a/lib/lib/src/mutated/installer/checkout.rs +++ b/lib/lib/src/mutated/installer/checkout.rs @@ -34,7 +34,7 @@ impl Stage for CheckoutStage { let esp_mount = find_esp_mount()?; let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; - let plugin = BootPlugins::new()?.load(&requested_boot_plugins)?; + let plugin = BootPlugins::new()?.load(requested_boot_plugins)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/pin/stage.rs b/lib/lib/src/mutated/pin/stage.rs index e4933ff2..35f3feea 100644 --- a/lib/lib/src/mutated/pin/stage.rs +++ b/lib/lib/src/mutated/pin/stage.rs @@ -26,7 +26,7 @@ impl Stage for SetPinnedStage { let prefix_digest = ctx_get!(context, RequestedPrefixDigest); let pinned = ctx_get!(context, RequestedPinned); - let record_dir = deploy.deploy(&prefix_digest); + let record_dir = deploy.deploy(prefix_digest); let mut record = DeployRecord::read(&record_dir)?; let mut written = Vec::new(); diff --git a/lib/lib/src/mutated/rollback/checkout.rs b/lib/lib/src/mutated/rollback/checkout.rs index f935cbb4..84d080d2 100644 --- a/lib/lib/src/mutated/rollback/checkout.rs +++ b/lib/lib/src/mutated/rollback/checkout.rs @@ -27,13 +27,13 @@ impl Stage for CheckoutStage { let requested_boot_plugins = ctx_get!(context, RequestedBootPlugin); let repository = deploy.open_repository()?; - let tree = deploy.open_tree(&target)?; - let digest = object_id_from_hex(&target)?; + let tree = deploy.open_tree(target)?; + let digest = object_id_from_hex(target)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &target)?; + let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, target)?; - let plugin = BootPlugins::new()?.load(&requested_boot_plugins)?; + let plugin = BootPlugins::new()?.load(requested_boot_plugins)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/rollback/merge.rs b/lib/lib/src/mutated/rollback/merge.rs index 3761b4ab..9bfcb190 100644 --- a/lib/lib/src/mutated/rollback/merge.rs +++ b/lib/lib/src/mutated/rollback/merge.rs @@ -23,7 +23,7 @@ impl Stage for MergeStage { let requested = ctx_get!(context, RequestedConfigDigest); let deploy = ctx_get!(context, Deploy); - let (config_digest, prefix_digest) = DeployRecord::resolve_config_digest(deploy, Some(&requested))?; + let (config_digest, prefix_digest) = DeployRecord::resolve_config_digest(deploy, Some(requested))?; let record_dir = deploy.deploy(&prefix_digest); let mut record = DeployRecord::read(&record_dir)?; diff --git a/lib/lib/src/mutated/uninstaller/checkout.rs b/lib/lib/src/mutated/uninstaller/checkout.rs index 73578955..7251d3fb 100644 --- a/lib/lib/src/mutated/uninstaller/checkout.rs +++ b/lib/lib/src/mutated/uninstaller/checkout.rs @@ -33,7 +33,7 @@ impl Stage for CheckoutStage { let esp_mount = find_esp_mount()?; let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; - let plugin = BootPlugins::new()?.load(&requested_boot_plugin)?; + let plugin = BootPlugins::new()?.load(requested_boot_plugin)?; context.put(ResolvedBootEntry { plugin, entry_name }); diff --git a/lib/lib/src/mutated/update/checkout.rs b/lib/lib/src/mutated/update/checkout.rs index 4e5e2aae..a5b8bebe 100644 --- a/lib/lib/src/mutated/update/checkout.rs +++ b/lib/lib/src/mutated/update/checkout.rs @@ -33,7 +33,7 @@ impl Stage for CheckoutStage { let esp_mount = find_esp_mount()?; let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; - let plugin = BootPlugins::new()?.load(&requested_boot_plugin)?; + let plugin = BootPlugins::new()?.load(requested_boot_plugin)?; context.put(ResolvedBootEntry { plugin, entry_name }); From d6cf10ab92dab0d2049a96f3cceb095b17eca728 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 09:19:14 +0400 Subject: [PATCH 096/148] fix: fixed clippy suggestions Co-Authored-By: Claude Sonnet 5 --- user/upac-cli/src/commands/file/add.rs | 2 +- user/upac-cli/src/commands/file/remove.rs | 2 +- user/upac-cli/src/commands/package/install.rs | 2 +- user/upac-cli/src/commands/package/update.rs | 2 +- user/upac-cli/src/commands/rollback.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/user/upac-cli/src/commands/file/add.rs b/user/upac-cli/src/commands/file/add.rs index 45e02616..3c8cc3a3 100644 --- a/user/upac-cli/src/commands/file/add.rs +++ b/user/upac-cli/src/commands/file/add.rs @@ -72,7 +72,7 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { file_kind: FileDiffKind::Added, scope: args.scope.into(), file_package: &package, - boot_plugin: boot_plugin, + boot_plugin, } .into(); diff --git a/user/upac-cli/src/commands/file/remove.rs b/user/upac-cli/src/commands/file/remove.rs index ec8d4ee5..25dc3167 100644 --- a/user/upac-cli/src/commands/file/remove.rs +++ b/user/upac-cli/src/commands/file/remove.rs @@ -72,7 +72,7 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { file_kind: FileDiffKind::Removed, scope: args.scope.into(), file_package: &package, - boot_plugin: boot_plugin, + boot_plugin, } .into(); diff --git a/user/upac-cli/src/commands/package/install.rs b/user/upac-cli/src/commands/package/install.rs index e5c7b0a2..453c1a6c 100644 --- a/user/upac-cli/src/commands/package/install.rs +++ b/user/upac-cli/src/commands/package/install.rs @@ -62,7 +62,7 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { subject: "install".to_owned(), message: args.message, packages, - boot_plugin: boot_plugin, + boot_plugin, allow_conflict_files: !args.no_conflict_files, } .into(); diff --git a/user/upac-cli/src/commands/package/update.rs b/user/upac-cli/src/commands/package/update.rs index f499d0c7..206e3449 100644 --- a/user/upac-cli/src/commands/package/update.rs +++ b/user/upac-cli/src/commands/package/update.rs @@ -64,7 +64,7 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { subject: "update".to_owned(), message: args.message, packages, - boot_plugin: boot_plugin, + boot_plugin, allow_downgrade: args.allow_downgrade, allow_conflict_files: !args.no_conflict_files, } diff --git a/user/upac-cli/src/commands/rollback.rs b/user/upac-cli/src/commands/rollback.rs index 5f74899c..45599508 100644 --- a/user/upac-cli/src/commands/rollback.rs +++ b/user/upac-cli/src/commands/rollback.rs @@ -44,7 +44,7 @@ pub fn run(args: Args, ctx: CommandContext) -> Result<()> { }, tmp_path: ctx.tmp_path.to_string_lossy().into_owned(), config_digest: args.commit, - boot_plugin: boot_plugin, + boot_plugin, } .into(); From ecf37d973ba35b3db8bee809090ce5a86aa7c876 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 13:39:39 +0400 Subject: [PATCH 097/148] fix: split CI clippy/test/build into upac-cli's dynamic-default vs upac-setup's hardcoded-static workspace graphs to stop Cargo unifying their conflicting upac-lib feature requests, replace cargo-hack link-modes sweep with two explicit builds, add missing cdylib build coverage for standalone decoder/booter plugins Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci-rust.yml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-rust.yml b/.github/workflows/ci-rust.yml index 9f39ae6a..ba1041c2 100644 --- a/.github/workflows/ci-rust.yml +++ b/.github/workflows/ci-rust.yml @@ -36,13 +36,17 @@ jobs: run: cargo fmt --all -- --check - name: Clippy - run: cargo clippy --workspace --all-targets -- -D warnings + run: | + cargo clippy --workspace --all-targets --exclude upac-setup --exclude upac-setup-cli -- -D warnings + cargo clippy -p upac-setup -p upac-setup-cli --all-targets -- -D warnings - name: Lint style run: cargo xtask lint-style - name: Test - run: cargo test --workspace --lib --tests + run: | + cargo test --workspace --lib --tests --exclude upac-setup --exclude upac-setup-cli + cargo test -p upac-setup -p upac-setup-cli --lib --tests link-modes: name: Link modes @@ -56,11 +60,15 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable - - name: Install cargo-hack - uses: taiki-e/install-action@cargo-hack + - name: Build fully dynamic (dlopen upac-lib + booters/decoders) + run: cargo build -p upac-cli -p upac-lib + + - name: Build fully static (all booters/decoders built in) + run: | + cargo build -p upac-cli -p upac-lib --no-default-features \ + --features upac-cli/builtin-all,upac-lib/builtin-all - - name: Build every dynamic-plugins/static-link/builtin-* combination + - name: Build booters/decoders as standalone dlopen'd plugins run: | - cargo hack build -p upac-cli -p upac-lib --feature-powerset \ - --mutually-exclusive-features dynamic-plugins,static-link \ - --at-least-one-of dynamic-plugins,static-link + cargo build -p upac-grub -p upac-refind -p upac-systemd-boot -p upac-uki \ + -p alpm -p deb -p rpm -p xbps --features cdylib From 07b080d456eb72305a5bc8b621b978af6076f7b2 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 17:49:56 +0400 Subject: [PATCH 098/148] =?UTF-8?q?fix:=20rename=20bare=20'a/'b=20lifetime?= =?UTF-8?q?s=20to=20descriptive=20names=20across=20lib/lib=20=E2=80=94=20T?= =?UTF-8?q?reeDiff=20->=20'tree,=20the=20three=20redb=20Value=20impls=20(m?= =?UTF-8?q?eta/files/triggers)=20->=20'bytes/'value,=20and=20every=20reque?= =?UTF-8?q?st-conversion=20XxxData/XxxPackage=20struct=20in=20mutated/*=20?= =?UTF-8?q?and=20unmutated/*=20->=20'data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/composefs/diff.rs | 12 +++---- lib/lib/src/database/files.rs | 12 +++---- lib/lib/src/database/meta.rs | 14 ++++---- lib/lib/src/database/triggers.rs | 12 +++---- lib/lib/src/mutated/commit/mod.rs | 14 ++++---- lib/lib/src/mutated/files/mod.rs | 32 +++++++++---------- lib/lib/src/mutated/gc/mod.rs | 8 ++--- lib/lib/src/mutated/installer/mod.rs | 18 +++++------ lib/lib/src/mutated/mime/mod.rs | 8 ++--- lib/lib/src/mutated/pin/mod.rs | 10 +++--- lib/lib/src/mutated/rollback/mod.rs | 14 ++++---- lib/lib/src/mutated/uninstaller/mod.rs | 30 ++++++++--------- lib/lib/src/mutated/update/mod.rs | 18 +++++------ lib/lib/src/unmutated/diff/mod.rs | 16 +++++----- lib/lib/src/unmutated/diff_config/mod.rs | 12 +++---- lib/lib/src/unmutated/diff_packages/mod.rs | 12 +++---- lib/lib/src/unmutated/diff_prefix/mod.rs | 12 +++---- lib/lib/src/unmutated/list_config/mod.rs | 10 +++--- lib/lib/src/unmutated/list_history/mod.rs | 8 ++--- lib/lib/src/unmutated/list_packages/mod.rs | 8 ++--- lib/lib/src/unmutated/list_prefix/mod.rs | 8 ++--- lib/lib/src/unmutated/search_files/mod.rs | 10 +++--- lib/lib/src/unmutated/search_in_meta/mod.rs | 16 +++++----- .../unmutated/search_in_package_files/mod.rs | 16 +++++----- lib/lib/src/unmutated/search_meta/mod.rs | 10 +++--- 25 files changed, 169 insertions(+), 171 deletions(-) diff --git a/lib/lib/src/composefs/diff.rs b/lib/lib/src/composefs/diff.rs index 7dac4af6..dff63387 100644 --- a/lib/lib/src/composefs/diff.rs +++ b/lib/lib/src/composefs/diff.rs @@ -34,14 +34,14 @@ impl Side { } } -pub struct TreeDiff<'a> { - from_leaves: &'a [Leaf], - to_leaves: &'a [Leaf], +pub struct TreeDiff<'tree> { + from_leaves: &'tree [Leaf], + to_leaves: &'tree [Leaf], changes: Vec<(String, FileDiffKind)>, } -impl<'a> TreeDiff<'a> { - pub fn run(from: &'a FileSystem, to: &'a FileSystem) -> Vec<(String, FileDiffKind)> { +impl<'tree> TreeDiff<'tree> { + pub fn run(from: &'tree FileSystem, to: &'tree FileSystem) -> Vec<(String, FileDiffKind)> { let mut differ = Self { from_leaves: &from.leaves, to_leaves: &to.leaves, @@ -126,7 +126,7 @@ impl<'a> TreeDiff<'a> { } } - fn leaves(&self, side: Side) -> &'a [Leaf] { + fn leaves(&self, side: Side) -> &'tree [Leaf] { match side { Side::From => self.from_leaves, Side::To => self.to_leaves, diff --git a/lib/lib/src/database/files.rs b/lib/lib/src/database/files.rs index b32e7181..99cefdbc 100644 --- a/lib/lib/src/database/files.rs +++ b/lib/lib/src/database/files.rs @@ -163,25 +163,25 @@ impl StoredFileEntry { } impl RedbValue for StoredFileEntry { - type AsBytes<'a> = Vec; - type SelfType<'a> = StoredFileEntry; + type AsBytes<'bytes> = Vec; + type SelfType<'bytes> = StoredFileEntry; fn fixed_width() -> Option { None } - fn from_bytes<'a>(data: &'a [u8]) -> StoredFileEntry + fn from_bytes<'bytes>(data: &'bytes [u8]) -> StoredFileEntry where - Self: 'a, + Self: 'bytes, { let mut offset = 0; StoredFileEntry(FileEntry::redb_decode(data, &mut offset)) } - fn as_bytes<'a, 'b: 'a>(value: &'a StoredFileEntry) -> Vec + fn as_bytes<'bytes, 'value: 'bytes>(value: &'bytes StoredFileEntry) -> Vec where - Self: 'b, + Self: 'value, { let mut buf = Vec::new(); diff --git a/lib/lib/src/database/meta.rs b/lib/lib/src/database/meta.rs index 36abfb06..0531662a 100644 --- a/lib/lib/src/database/meta.rs +++ b/lib/lib/src/database/meta.rs @@ -137,8 +137,6 @@ impl MetaStoreMut for MemoryDatabase { } } -// Wraps `PackageMeta` (defined in the external `upac-types` crate) so `redb::Value` can be -// implemented for it here without violating the orphan rule. #[derive(Debug)] #[repr(transparent)] pub(crate) struct StoredPackageMeta(pub(crate) PackageMeta); @@ -152,25 +150,25 @@ impl StoredPackageMeta { } impl RedbValue for StoredPackageMeta { - type AsBytes<'a> = Vec; - type SelfType<'a> = StoredPackageMeta; + type AsBytes<'bytes> = Vec; + type SelfType<'bytes> = StoredPackageMeta; fn fixed_width() -> Option { None } - fn from_bytes<'a>(data: &'a [u8]) -> StoredPackageMeta + fn from_bytes<'bytes>(data: &'bytes [u8]) -> StoredPackageMeta where - Self: 'a, + Self: 'bytes, { let mut offset = 0; StoredPackageMeta(PackageMeta::redb_decode(data, &mut offset)) } - fn as_bytes<'a, 'b: 'a>(value: &'a StoredPackageMeta) -> Vec + fn as_bytes<'bytes, 'value: 'bytes>(value: &'bytes StoredPackageMeta) -> Vec where - Self: 'b, + Self: 'value, { let mut buf = Vec::new(); diff --git a/lib/lib/src/database/triggers.rs b/lib/lib/src/database/triggers.rs index 544e59f8..18b539d2 100644 --- a/lib/lib/src/database/triggers.rs +++ b/lib/lib/src/database/triggers.rs @@ -70,25 +70,25 @@ impl StoredTriggers { } impl RedbValue for StoredTriggers { - type AsBytes<'a> = Vec; - type SelfType<'a> = StoredTriggers; + type AsBytes<'bytes> = Vec; + type SelfType<'bytes> = StoredTriggers; fn fixed_width() -> Option { None } - fn from_bytes<'a>(data: &'a [u8]) -> StoredTriggers + fn from_bytes<'bytes>(data: &'bytes [u8]) -> StoredTriggers where - Self: 'a, + Self: 'bytes, { let mut offset = 0; StoredTriggers(DeclarativeTrigger::redb_decode(data, &mut offset)) } - fn as_bytes<'a, 'b: 'a>(value: &'a StoredTriggers) -> Vec + fn as_bytes<'bytes, 'value: 'bytes>(value: &'bytes StoredTriggers) -> Vec where - Self: 'b, + Self: 'value, { let mut buf = Vec::new(); diff --git a/lib/lib/src/mutated/commit/mod.rs b/lib/lib/src/mutated/commit/mod.rs index 3f139059..c68e8b48 100644 --- a/lib/lib/src/mutated/commit/mod.rs +++ b/lib/lib/src/mutated/commit/mod.rs @@ -34,22 +34,22 @@ pub(crate) struct CommitInfo { pub message: Option, } -pub struct CommitData<'a> { - pub tmp_path: &'a str, +pub struct CommitData<'data> { + pub tmp_path: &'data str, - pub subject: &'a str, - pub message: Option<&'a str>, + pub subject: &'data str, + pub message: Option<&'data str>, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CCommitRequest> for CommitData<'a> { +impl<'data> TryFrom<&'data CCommitRequest> for CommitData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CCommitRequest) -> Result { + fn try_from(request: &'data CCommitRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/mutated/files/mod.rs b/lib/lib/src/mutated/files/mod.rs index 4bde5b85..4fc9e22d 100644 --- a/lib/lib/src/mutated/files/mod.rs +++ b/lib/lib/src/mutated/files/mod.rs @@ -90,16 +90,16 @@ pub(crate) struct ApplyTarget { pub config_upper_dir: PathBuf, } -pub struct FilesPackage<'a> { - pub name: &'a str, - pub arch: &'a str, - pub arch_sub: Option<&'a str>, +pub struct FilesPackage<'data> { + pub name: &'data str, + pub arch: &'data str, + pub arch_sub: Option<&'data str>, } -impl<'a> TryFrom<&'a CPackageInfo> for FilesPackage<'a> { +impl<'data> TryFrom<&'data CPackageInfo> for FilesPackage<'data> { type Error = ErrorKind; - fn try_from(info: &'a CPackageInfo) -> Result { + fn try_from(info: &'data CPackageInfo) -> Result { unsafe { info.validate()? }; Ok(FilesPackage { @@ -110,30 +110,30 @@ impl<'a> TryFrom<&'a CPackageInfo> for FilesPackage<'a> { } } -pub struct FilesData<'a> { +pub struct FilesData<'data> { pub scope: DiffFileSource, - pub files: Vec<&'a str>, + pub files: Vec<&'data str>, pub file_kind: FileDiffKind, - pub file_package: FilesPackage<'a>, + pub file_package: FilesPackage<'data>, - pub boot_plugin: &'a str, + pub boot_plugin: &'data str, - pub tmp_path: &'a str, + pub tmp_path: &'data str, - pub subject: &'a str, - pub message: Option<&'a str>, + pub subject: &'data str, + pub message: Option<&'data str>, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CFilesRequest> for FilesData<'a> { +impl<'data> TryFrom<&'data CFilesRequest> for FilesData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CFilesRequest) -> Result { + fn try_from(request: &'data CFilesRequest) -> Result { unsafe { request.validate()? }; let file_package = unsafe { request.file_package.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; diff --git a/lib/lib/src/mutated/gc/mod.rs b/lib/lib/src/mutated/gc/mod.rs index be15a483..a46707dc 100644 --- a/lib/lib/src/mutated/gc/mod.rs +++ b/lib/lib/src/mutated/gc/mod.rs @@ -41,17 +41,17 @@ pub(crate) struct DeployProgress { #[derive(ContextValue)] pub(crate) struct CollectedRoots(pub Vec); -pub struct GcData<'a> { +pub struct GcData<'data> { pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CGcRequest> for GcData<'a> { +impl<'data> TryFrom<&'data CGcRequest> for GcData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CGcRequest) -> Result { + fn try_from(request: &'data CGcRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/mutated/installer/mod.rs b/lib/lib/src/mutated/installer/mod.rs index de660ce4..c136cbae 100644 --- a/lib/lib/src/mutated/installer/mod.rs +++ b/lib/lib/src/mutated/installer/mod.rs @@ -89,28 +89,28 @@ pub(crate) struct ImportedState { pub database: MemoryDatabase, } -pub struct InstallData<'a> { - pub packages: Vec<&'a str>, +pub struct InstallData<'data> { + pub packages: Vec<&'data str>, pub allow_conflict_files: bool, - pub boot_plugin: &'a str, + pub boot_plugin: &'data str, - pub tmp_path: &'a str, + pub tmp_path: &'data str, - pub subject: &'a str, - pub message: Option<&'a str>, + pub subject: &'data str, + pub message: Option<&'data str>, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CInstallRequest> for InstallData<'a> { +impl<'data> TryFrom<&'data CInstallRequest> for InstallData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CInstallRequest) -> Result { + fn try_from(request: &'data CInstallRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/mutated/mime/mod.rs b/lib/lib/src/mutated/mime/mod.rs index f461045d..3110469e 100644 --- a/lib/lib/src/mutated/mime/mod.rs +++ b/lib/lib/src/mutated/mime/mod.rs @@ -39,17 +39,17 @@ pub(crate) struct WriteProgress { pub total: u64, } -pub struct MimeData<'a> { +pub struct MimeData<'data> { pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CMimeSyncRequest> for MimeData<'a> { +impl<'data> TryFrom<&'data CMimeSyncRequest> for MimeData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CMimeSyncRequest) -> Result { + fn try_from(request: &'data CMimeSyncRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/mutated/pin/mod.rs b/lib/lib/src/mutated/pin/mod.rs index 680168d4..3cb3d735 100644 --- a/lib/lib/src/mutated/pin/mod.rs +++ b/lib/lib/src/mutated/pin/mod.rs @@ -33,20 +33,20 @@ pub(crate) struct RequestedPrefixDigest(pub String); #[derive(ContextValue)] pub(crate) struct RequestedPinned(pub bool); -pub struct PinData<'a> { - pub prefix_digest: &'a str, +pub struct PinData<'data> { + pub prefix_digest: &'data str, pub pinned: bool, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CPinRequest> for PinData<'a> { +impl<'data> TryFrom<&'data CPinRequest> for PinData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CPinRequest) -> Result { + fn try_from(request: &'data CPinRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/mutated/rollback/mod.rs b/lib/lib/src/mutated/rollback/mod.rs index ea993bc0..8f1f851a 100644 --- a/lib/lib/src/mutated/rollback/mod.rs +++ b/lib/lib/src/mutated/rollback/mod.rs @@ -50,23 +50,23 @@ pub(crate) struct ResolvedBootEntry { pub entry_name: String, } -pub struct RollbackData<'a> { - pub config_digest: &'a str, +pub struct RollbackData<'data> { + pub config_digest: &'data str, - pub boot_plugin: &'a str, + pub boot_plugin: &'data str, - pub tmp_path: &'a str, + pub tmp_path: &'data str, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CRollbackRequest> for RollbackData<'a> { +impl<'data> TryFrom<&'data CRollbackRequest> for RollbackData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CRollbackRequest) -> Result { + fn try_from(request: &'data CRollbackRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/mutated/uninstaller/mod.rs b/lib/lib/src/mutated/uninstaller/mod.rs index 0153a3d5..9b8dd33e 100644 --- a/lib/lib/src/mutated/uninstaller/mod.rs +++ b/lib/lib/src/mutated/uninstaller/mod.rs @@ -87,16 +87,16 @@ pub(crate) struct WorkingState { pub removed_config_paths: Vec, } -pub struct UninstallPackage<'a> { - pub name: &'a str, - pub arch: &'a str, - pub arch_sub: Option<&'a str>, +pub struct UninstallPackage<'data> { + pub name: &'data str, + pub arch: &'data str, + pub arch_sub: Option<&'data str>, } -impl<'a> TryFrom<&'a CPackageInfo> for UninstallPackage<'a> { +impl<'data> TryFrom<&'data CPackageInfo> for UninstallPackage<'data> { type Error = ErrorKind; - fn try_from(info: &'a CPackageInfo) -> Result { + fn try_from(info: &'data CPackageInfo) -> Result { unsafe { info.validate()? }; Ok(UninstallPackage { @@ -107,28 +107,28 @@ impl<'a> TryFrom<&'a CPackageInfo> for UninstallPackage<'a> { } } -pub struct UninstallData<'a> { - pub packages: Vec>, +pub struct UninstallData<'data> { + pub packages: Vec>, pub purge: bool, - pub boot_plugin: &'a str, + pub boot_plugin: &'data str, - pub tmp_path: &'a str, + pub tmp_path: &'data str, - pub subject: &'a str, - pub message: Option<&'a str>, + pub subject: &'data str, + pub message: Option<&'data str>, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CUninstallRequest> for UninstallData<'a> { +impl<'data> TryFrom<&'data CUninstallRequest> for UninstallData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CUninstallRequest) -> Result { + fn try_from(request: &'data CUninstallRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/mutated/update/mod.rs b/lib/lib/src/mutated/update/mod.rs index 00205421..950a47ce 100644 --- a/lib/lib/src/mutated/update/mod.rs +++ b/lib/lib/src/mutated/update/mod.rs @@ -95,29 +95,29 @@ pub(crate) struct ImportedState { pub removed_config_paths: Vec, } -pub struct UpdateData<'a> { - pub packages: Vec<&'a str>, +pub struct UpdateData<'data> { + pub packages: Vec<&'data str>, - pub boot_plugin: &'a str, + pub boot_plugin: &'data str, pub allow_downgrade: bool, pub allow_conflict_files: bool, - pub tmp_path: &'a str, + pub tmp_path: &'data str, - pub subject: &'a str, - pub message: Option<&'a str>, + pub subject: &'data str, + pub message: Option<&'data str>, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CUpdateRequest> for UpdateData<'a> { +impl<'data> TryFrom<&'data CUpdateRequest> for UpdateData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CUpdateRequest) -> Result { + fn try_from(request: &'data CUpdateRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/diff/mod.rs b/lib/lib/src/unmutated/diff/mod.rs index 6aba3d83..d1281ac6 100644 --- a/lib/lib/src/unmutated/diff/mod.rs +++ b/lib/lib/src/unmutated/diff/mod.rs @@ -42,22 +42,22 @@ struct DiffSnapshot { to_database: MemoryDatabase, } -pub struct DiffData<'a> { - pub from_prefix_digest: Option<&'a str>, - pub to_prefix_digest: Option<&'a str>, - pub from_config_digest: Option<&'a str>, - pub to_config_digest: Option<&'a str>, +pub struct DiffData<'data> { + pub from_prefix_digest: Option<&'data str>, + pub to_prefix_digest: Option<&'data str>, + pub from_config_digest: Option<&'data str>, + pub to_config_digest: Option<&'data str>, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CDiffRequest> for DiffData<'a> { +impl<'data> TryFrom<&'data CDiffRequest> for DiffData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CDiffRequest) -> Result { + fn try_from(request: &'data CDiffRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/diff_config/mod.rs b/lib/lib/src/unmutated/diff_config/mod.rs index 89f68af6..663501df 100644 --- a/lib/lib/src/unmutated/diff_config/mod.rs +++ b/lib/lib/src/unmutated/diff_config/mod.rs @@ -37,20 +37,20 @@ struct DiffConfigSnapshot { to_database: MemoryDatabase, } -pub struct DiffConfigData<'a> { - pub from_config_digest: Option<&'a str>, - pub to_config_digest: Option<&'a str>, +pub struct DiffConfigData<'data> { + pub from_config_digest: Option<&'data str>, + pub to_config_digest: Option<&'data str>, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CDiffConfigRequest> for DiffConfigData<'a> { +impl<'data> TryFrom<&'data CDiffConfigRequest> for DiffConfigData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CDiffConfigRequest) -> Result { + fn try_from(request: &'data CDiffConfigRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/diff_packages/mod.rs b/lib/lib/src/unmutated/diff_packages/mod.rs index e302bec9..78268a6c 100644 --- a/lib/lib/src/unmutated/diff_packages/mod.rs +++ b/lib/lib/src/unmutated/diff_packages/mod.rs @@ -29,20 +29,20 @@ mod comparing; mod error; mod preparing; -pub struct DiffPackagesData<'a> { - pub from_prefix_digest: Option<&'a str>, - pub to_prefix_digest: Option<&'a str>, +pub struct DiffPackagesData<'data> { + pub from_prefix_digest: Option<&'data str>, + pub to_prefix_digest: Option<&'data str>, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CDiffPackagesRequest> for DiffPackagesData<'a> { +impl<'data> TryFrom<&'data CDiffPackagesRequest> for DiffPackagesData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CDiffPackagesRequest) -> Result { + fn try_from(request: &'data CDiffPackagesRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/diff_prefix/mod.rs b/lib/lib/src/unmutated/diff_prefix/mod.rs index 8dd5a44c..1c200120 100644 --- a/lib/lib/src/unmutated/diff_prefix/mod.rs +++ b/lib/lib/src/unmutated/diff_prefix/mod.rs @@ -37,20 +37,20 @@ struct DiffPrefixSnapshot { to_database: MemoryDatabase, } -pub struct DiffPrefixData<'a> { - pub from_prefix_digest: Option<&'a str>, - pub to_prefix_digest: Option<&'a str>, +pub struct DiffPrefixData<'data> { + pub from_prefix_digest: Option<&'data str>, + pub to_prefix_digest: Option<&'data str>, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CDiffPrefixRequest> for DiffPrefixData<'a> { +impl<'data> TryFrom<&'data CDiffPrefixRequest> for DiffPrefixData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CDiffPrefixRequest) -> Result { + fn try_from(request: &'data CDiffPrefixRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/list_config/mod.rs b/lib/lib/src/unmutated/list_config/mod.rs index 754736ab..0b71ae6e 100644 --- a/lib/lib/src/unmutated/list_config/mod.rs +++ b/lib/lib/src/unmutated/list_config/mod.rs @@ -27,19 +27,19 @@ pub use self::error::ListConfigError; mod error; mod fetching; -pub struct ListConfigData<'a> { - pub prefix_digest: Option<&'a str>, +pub struct ListConfigData<'data> { + pub prefix_digest: Option<&'data str>, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CListConfigRequest> for ListConfigData<'a> { +impl<'data> TryFrom<&'data CListConfigRequest> for ListConfigData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CListConfigRequest) -> Result { + fn try_from(request: &'data CListConfigRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/list_history/mod.rs b/lib/lib/src/unmutated/list_history/mod.rs index 264f320f..eac0132f 100644 --- a/lib/lib/src/unmutated/list_history/mod.rs +++ b/lib/lib/src/unmutated/list_history/mod.rs @@ -26,17 +26,17 @@ pub use self::error::ListHistoryError; mod error; mod fetching; -pub struct ListHistoryData<'a> { +pub struct ListHistoryData<'data> { pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CListHistoryRequest> for ListHistoryData<'a> { +impl<'data> TryFrom<&'data CListHistoryRequest> for ListHistoryData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CListHistoryRequest) -> Result { + fn try_from(request: &'data CListHistoryRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/list_packages/mod.rs b/lib/lib/src/unmutated/list_packages/mod.rs index 2201ed22..2dfb8bf3 100644 --- a/lib/lib/src/unmutated/list_packages/mod.rs +++ b/lib/lib/src/unmutated/list_packages/mod.rs @@ -26,17 +26,17 @@ pub use self::error::ListPackagesError; mod error; mod fetching; -pub struct ListPackagesData<'a> { +pub struct ListPackagesData<'data> { pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CListPackagesRequest> for ListPackagesData<'a> { +impl<'data> TryFrom<&'data CListPackagesRequest> for ListPackagesData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CListPackagesRequest) -> Result { + fn try_from(request: &'data CListPackagesRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/list_prefix/mod.rs b/lib/lib/src/unmutated/list_prefix/mod.rs index aae03e52..1bcb74e7 100644 --- a/lib/lib/src/unmutated/list_prefix/mod.rs +++ b/lib/lib/src/unmutated/list_prefix/mod.rs @@ -26,17 +26,17 @@ pub use self::error::ListPrefixError; mod error; mod fetching; -pub struct ListPrefixData<'a> { +pub struct ListPrefixData<'data> { pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CListPrefixRequest> for ListPrefixData<'a> { +impl<'data> TryFrom<&'data CListPrefixRequest> for ListPrefixData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CListPrefixRequest) -> Result { + fn try_from(request: &'data CListPrefixRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/search_files/mod.rs b/lib/lib/src/unmutated/search_files/mod.rs index 2791043d..1612d893 100644 --- a/lib/lib/src/unmutated/search_files/mod.rs +++ b/lib/lib/src/unmutated/search_files/mod.rs @@ -27,20 +27,20 @@ pub use self::error::SearchFilesError; mod error; mod searching; -pub struct SearchFilesData<'a> { - pub search: &'a str, +pub struct SearchFilesData<'data> { + pub search: &'data str, pub is_regex: bool, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CSearchFilesRequest> for SearchFilesData<'a> { +impl<'data> TryFrom<&'data CSearchFilesRequest> for SearchFilesData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CSearchFilesRequest) -> Result { + fn try_from(request: &'data CSearchFilesRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/search_in_meta/mod.rs b/lib/lib/src/unmutated/search_in_meta/mod.rs index cb622dfc..d81f19de 100644 --- a/lib/lib/src/unmutated/search_in_meta/mod.rs +++ b/lib/lib/src/unmutated/search_in_meta/mod.rs @@ -27,23 +27,23 @@ pub use self::error::SearchInMetaError; mod error; mod searching; -pub struct SearchInMetaData<'a> { - pub name: &'a str, - pub arch: &'a str, - pub arch_sub: Option<&'a str>, - pub search: &'a str, +pub struct SearchInMetaData<'data> { + pub name: &'data str, + pub arch: &'data str, + pub arch_sub: Option<&'data str>, + pub search: &'data str, pub is_regex: bool, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CSearchInMetaRequest> for SearchInMetaData<'a> { +impl<'data> TryFrom<&'data CSearchInMetaRequest> for SearchInMetaData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CSearchInMetaRequest) -> Result { + fn try_from(request: &'data CSearchInMetaRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/search_in_package_files/mod.rs b/lib/lib/src/unmutated/search_in_package_files/mod.rs index aedecf1c..43c42ca1 100644 --- a/lib/lib/src/unmutated/search_in_package_files/mod.rs +++ b/lib/lib/src/unmutated/search_in_package_files/mod.rs @@ -28,23 +28,23 @@ pub use self::error::SearchInPackageFilesError; mod error; mod searching; -pub struct SearchInPackageFilesData<'a> { - pub name: &'a str, - pub arch: &'a str, - pub arch_sub: Option<&'a str>, - pub search: &'a str, +pub struct SearchInPackageFilesData<'data> { + pub name: &'data str, + pub arch: &'data str, + pub arch_sub: Option<&'data str>, + pub search: &'data str, pub is_regex: bool, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CSearchInPackageFilesRequest> for SearchInPackageFilesData<'a> { +impl<'data> TryFrom<&'data CSearchInPackageFilesRequest> for SearchInPackageFilesData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CSearchInPackageFilesRequest) -> Result { + fn try_from(request: &'data CSearchInPackageFilesRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; diff --git a/lib/lib/src/unmutated/search_meta/mod.rs b/lib/lib/src/unmutated/search_meta/mod.rs index d32c7478..9b5155d6 100644 --- a/lib/lib/src/unmutated/search_meta/mod.rs +++ b/lib/lib/src/unmutated/search_meta/mod.rs @@ -27,20 +27,20 @@ pub use self::error::SearchMetaError; mod error; mod searching; -pub struct SearchMetaData<'a> { - pub search: &'a str, +pub struct SearchMetaData<'data> { + pub search: &'data str, pub is_regex: bool, pub hook_message: Option, pub hook_message_context: *mut c_void, - pub cancel_token: &'a CancelToken, + pub cancel_token: &'data CancelToken, } -impl<'a> TryFrom<&'a CSearchMetaRequest> for SearchMetaData<'a> { +impl<'data> TryFrom<&'data CSearchMetaRequest> for SearchMetaData<'data> { type Error = ErrorKind; - fn try_from(request: &'a CSearchMetaRequest) -> Result { + fn try_from(request: &'data CSearchMetaRequest) -> Result { unsafe { request.validate()? }; let cancel_token = unsafe { &*request.base.cancel_token }; From b2e67d2e104b697b154427d1b43598eca8028321 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 17:52:26 +0400 Subject: [PATCH 099/148] =?UTF-8?q?fix:=20rename=20bare=20'a=20lifetimes?= =?UTF-8?q?=20to=20descriptive=20names=20in=20lib/abi/src/types.rs=20?= =?UTF-8?q?=E2=80=94=20CSlice's=20TryFrom=20impls=20->=20'slice,=20CVec?= =?UTF-8?q?'s=20TryFrom=20impl=20->=20'vec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/types.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/abi/src/types.rs b/lib/abi/src/types.rs index 778b2a31..a7bc6d65 100644 --- a/lib/abi/src/types.rs +++ b/lib/abi/src/types.rs @@ -112,18 +112,18 @@ impl CBorrowed for CSlice { } } -impl<'a> TryFrom<&'a CSlice> for &'a str { +impl<'slice> TryFrom<&'slice CSlice> for &'slice str { type Error = ErrorKind; - fn try_from(slice: &'a CSlice) -> Result { + fn try_from(slice: &'slice CSlice) -> Result { unsafe { slice.as_str() } } } -impl<'a> TryFrom<&'a CSlice> for Option<&'a str> { +impl<'slice> TryFrom<&'slice CSlice> for Option<&'slice str> { type Error = ErrorKind; - fn try_from(slice: &'a CSlice) -> Result { + fn try_from(slice: &'slice CSlice) -> Result { if slice.ptr.is_null() { return Ok(None); } @@ -216,13 +216,13 @@ impl CBorrowed for CVec { } } -impl<'a, T, U> TryFrom<&'a CVec> for Vec +impl<'vec, T, U> TryFrom<&'vec CVec> for Vec where - U: TryFrom<&'a T, Error = ErrorKind>, + U: TryFrom<&'vec T, Error = ErrorKind>, { type Error = ErrorKind; - fn try_from(vec: &'a CVec) -> Result { + fn try_from(vec: &'vec CVec) -> Result { unsafe { vec.validate()? }; unsafe { vec.as_slice() }.iter().map(U::try_from).collect() } From 8b252fb856fbe271a5b726efb7125f0aad8993c4 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 17:54:25 +0400 Subject: [PATCH 100/148] =?UTF-8?q?fix:=20rename=20bare=20'a=20lifetimes?= =?UTF-8?q?=20to=20descriptive=20names=20in=20lib/types=20=E2=80=94=20Vers?= =?UTF-8?q?ionToken=20->=20'raw,=20read=5Flen=5Fprefixed=20->=20'bytes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- lib/types/src/codec.rs | 2 +- lib/types/src/package.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/types/src/codec.rs b/lib/types/src/codec.rs index 1045aee0..4fd948a7 100644 --- a/lib/types/src/codec.rs +++ b/lib/types/src/codec.rs @@ -135,7 +135,7 @@ pub(crate) fn read_u64(data: &[u8], offset: &mut usize) -> u64 { value } -pub(crate) fn read_len_prefixed<'a>(data: &'a [u8], offset: &mut usize) -> &'a [u8] { +pub(crate) fn read_len_prefixed<'bytes>(data: &'bytes [u8], offset: &mut usize) -> &'bytes [u8] { let len = read_u32(data, offset) as usize; let bytes = &data[*offset..*offset + len]; diff --git a/lib/types/src/package.rs b/lib/types/src/package.rs index eb8ad1b8..f1b0061c 100644 --- a/lib/types/src/package.rs +++ b/lib/types/src/package.rs @@ -16,8 +16,8 @@ use upac_macro::{CTryToRust, RedbCodec, RustToC}; // ── Version ───────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -enum VersionToken<'a> { - Alpha(&'a str), +enum VersionToken<'raw> { + Alpha(&'raw str), Numeric(u64), } From 167013c68be8ab2b445fc948785be59dca42f11e Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 18:11:48 +0400 Subject: [PATCH 101/148] fix: register CPartitionMount as a validatable composite in lib/macro so CValidate checks its CSlice fields when nested inside CVec Co-Authored-By: Claude Sonnet 5 --- lib/macro/src/common.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/macro/src/common.rs b/lib/macro/src/common.rs index 7feb428c..1b0e5716 100644 --- a/lib/macro/src/common.rs +++ b/lib/macro/src/common.rs @@ -29,6 +29,7 @@ pub(crate) const VALIDATABLE_COMPOSITES: &[&str] = &[ "CHistoryEntry", "CRequestBase", "CPackageDependency", + "CPartitionMount", ]; pub(crate) fn generic_arg(segment: &PathSegment) -> Option<&Type> { From 6cd7cd6dddbc562c0e21be3a6d8044ac3065dfc1 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 18:12:19 +0400 Subject: [PATCH 102/148] =?UTF-8?q?feat:=20add=20CPartitionMount=20and=20C?= =?UTF-8?q?SetupExistingRequest=20to=20lib/abi=20=E2=80=94=20a=20real=20C-?= =?UTF-8?q?ABI=20request=20surface=20for=20upac-setup's=20existing-partiti?= =?UTF-8?q?ons=20genesis=20path=20(boot=5Fplugin=20required,=20not=20optio?= =?UTF-8?q?nal,=20so=20callers=20must=20pick=20a=20plugin=20explicitly)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/request.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/abi/src/request.rs b/lib/abi/src/request.rs index e25f57de..1b185c68 100644 --- a/lib/abi/src/request.rs +++ b/lib/abi/src/request.rs @@ -12,7 +12,7 @@ use crate::error::ErrorKind; use crate::hook::CancelToken; use crate::package::CPackageInfo; use crate::types::{CSlice, CVec, check_size}; -use crate::{DiffFileSource, FileDiffKind}; +use crate::{DiffFileSource, FileDiffKind, FsKind}; #[repr(C)] #[derive(CNew, CValidate)] @@ -317,3 +317,32 @@ pub struct CBootPluginInstallRequest { pub to_slot: CSlice, pub from_slot: CSlice, } + +#[repr(C)] +#[derive(CNew, CValidate)] +pub struct CPartitionMount { + pub struct_size: usize, + + pub mount_path: CSlice, + pub device_path: CSlice, + pub fs_kind: FsKind, +} + +#[repr(C)] +#[derive(CNew, CValidate)] +pub struct CSetupExistingRequest { + pub struct_size: usize, + pub base: CRequestBase, + + pub esp_device: CSlice, + pub deploy_device: CSlice, + pub deploy_fs: FsKind, + pub extra_mounts: CVec, + + #[optional] + pub mount_point: CSlice, + pub source: CSlice, + pub empty_config: bool, + pub pinned: bool, + pub boot_plugin: CSlice, +} From e2348077048e2b7340021f79e9704feca0895e02 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 18:30:21 +0400 Subject: [PATCH 103/148] =?UTF-8?q?feat:=20add=20CPartitionSpec,=20CGptLay?= =?UTF-8?q?out,=20CBtrfsOptions,=20and=20CSetupWholeDiskRequest=20to=20lib?= =?UTF-8?q?/abi=20=E2=80=94=20restores=20a=20real=20C-ABI=20request=20surf?= =?UTF-8?q?ace=20for=20upac-setup's=20whole-disk=20genesis=20path,=20mirro?= =?UTF-8?q?ring=20CSetupExistingRequest's=20flat=20base:=20CRequestBase=20?= =?UTF-8?q?shape,=20boot=5Fplugin=20required?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/request.rs | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/lib/abi/src/request.rs b/lib/abi/src/request.rs index 1b185c68..0a502531 100644 --- a/lib/abi/src/request.rs +++ b/lib/abi/src/request.rs @@ -346,3 +346,52 @@ pub struct CSetupExistingRequest { pub pinned: bool, pub boot_plugin: CSlice, } + +#[repr(C)] +#[derive(CNew, CValidate)] +pub struct CPartitionSpec { + pub struct_size: usize, + + pub mount_path: CSlice, + pub size_mib: u64, + pub fs_kind: FsKind, +} + +#[repr(C)] +#[derive(CNew, CValidate)] +pub struct CGptLayout { + pub struct_size: usize, + + pub esp_size_mib: u64, + pub deploy_fs: FsKind, + pub deploy_size_mib: u64, + pub extra_partitions: CVec, + pub force_wipe: bool, +} + +#[repr(C)] +#[derive(CNew, CValidate)] +pub struct CBtrfsOptions { + pub struct_size: usize, + + pub node_size: u32, + pub sector_size: u32, +} + +#[repr(C)] +#[derive(CNew, CValidate)] +pub struct CSetupWholeDiskRequest { + pub struct_size: usize, + pub base: CRequestBase, + + pub device_path: CSlice, + pub gpt: CGptLayout, + pub btrfs: CBtrfsOptions, + + #[optional] + pub mount_point: CSlice, + pub source: CSlice, + pub empty_config: bool, + pub pinned: bool, + pub boot_plugin: CSlice, +} From 34c3f77487ce509f42ec44f0722c0a2a2d0a534d Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 18:30:39 +0400 Subject: [PATCH 104/148] fix: register CPartitionSpec/CGptLayout/CBtrfsOptions as validatable composites in lib/macro so CValidate cascades into them from CSetupWholeDiskRequest and CVec Co-Authored-By: Claude Sonnet 5 --- lib/macro/src/common.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/macro/src/common.rs b/lib/macro/src/common.rs index 1b0e5716..53bd89a8 100644 --- a/lib/macro/src/common.rs +++ b/lib/macro/src/common.rs @@ -30,6 +30,9 @@ pub(crate) const VALIDATABLE_COMPOSITES: &[&str] = &[ "CRequestBase", "CPackageDependency", "CPartitionMount", + "CPartitionSpec", + "CGptLayout", + "CBtrfsOptions", ]; pub(crate) fn generic_arg(segment: &PathSegment) -> Option<&Type> { From 88f1f56debab5d41d22402a2d975f54da4668795 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 18:37:52 +0400 Subject: [PATCH 105/148] feat: add SetupExistingRequest/SetupWholeDiskRequest Rust-domain mirrors to lib/types, give PartitionMount/PartitionSpec/GptLayout/BtrfsOptions the RustToC direction too so genesis's bootstrap-setup types can round-trip through the C-ABI like every other request Co-Authored-By: Claude Sonnet 5 --- lib/types/src/lib.rs | 32 ------------------ lib/types/src/request.rs | 73 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 38 deletions(-) diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index beefaf38..cb2b4643 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -3,8 +3,6 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::FsKind; - use self::package::{PackageEntry, PackageMeta}; pub mod codec; @@ -57,33 +55,3 @@ pub struct DiffPackagesSnapshot { pub from: Vec, pub to: Vec, } - -// ── PartitionMount / PartitionSpec (bootstrap setup) ──────────────────────── -#[derive(Debug, Clone)] -pub struct PartitionMount { - pub mount_path: String, - pub device_path: String, - pub fs_kind: FsKind, -} - -#[derive(Debug, Clone)] -pub struct PartitionSpec { - pub mount_path: String, - pub size_mib: u64, - pub fs_kind: FsKind, -} - -#[derive(Debug, Clone)] -pub struct GptLayout { - pub esp_size_mib: u64, - pub deploy_fs: FsKind, - pub deploy_size_mib: u64, - pub extra_partitions: Vec, - pub force_wipe: bool, -} - -#[derive(Debug, Clone)] -pub struct BtrfsOptions { - pub node_size: u32, - pub sector_size: u32, -} diff --git a/lib/types/src/request.rs b/lib/types/src/request.rs index e863fa11..d2d91f55 100644 --- a/lib/types/src/request.rs +++ b/lib/types/src/request.rs @@ -11,14 +11,15 @@ use upac_abi::error::ErrorKind; use upac_abi::hook::CancelToken; use upac_abi::package::CPackageInfo; use upac_abi::request::{ - CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, CCommitRequest, - CDecodeRequest, CDiffConfigRequest, CDiffPackagesRequest, CDiffPrefixRequest, CDiffRequest, CFilesRequest, - CGcRequest, CInstallRequest, CListConfigRequest, CListHistoryRequest, CListPackagesRequest, CListPrefixRequest, - CMimeSyncRequest, CPinRequest, CRequestBase, CRollbackRequest, CSearchFilesRequest, CSearchInMetaRequest, - CSearchInPackageFilesRequest, CSearchMetaRequest, CUninstallRequest, CUpdateRequest, + CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, CBtrfsOptions, + CCommitRequest, CDecodeRequest, CDiffConfigRequest, CDiffPackagesRequest, CDiffPrefixRequest, CDiffRequest, + CFilesRequest, CGcRequest, CGptLayout, CInstallRequest, CListConfigRequest, CListHistoryRequest, + CListPackagesRequest, CListPrefixRequest, CMimeSyncRequest, CPartitionMount, CPartitionSpec, CPinRequest, + CRequestBase, CRollbackRequest, CSearchFilesRequest, CSearchInMetaRequest, CSearchInPackageFilesRequest, + CSearchMetaRequest, CSetupExistingRequest, CSetupWholeDiskRequest, CUninstallRequest, CUpdateRequest, }; use upac_abi::types::{COwned, CSlice, CVec}; -use upac_abi::{DiffFileSource, FileDiffKind}; +use upac_abi::{DiffFileSource, FileDiffKind, FsKind}; use upac_macro::{CTryToRust, RustToC}; @@ -241,3 +242,63 @@ pub struct BootPluginInstallRequest { pub to_slot: String, pub from_slot: String, } + +#[derive(Debug, Clone, CTryToRust, RustToC)] +pub struct PartitionMount { + pub mount_path: String, + pub device_path: String, + pub fs_kind: FsKind, +} + +#[derive(Debug, Clone, CTryToRust, RustToC)] +pub struct PartitionSpec { + pub mount_path: String, + pub size_mib: u64, + pub fs_kind: FsKind, +} + +#[derive(Debug, Clone, CTryToRust, RustToC)] +pub struct GptLayout { + pub esp_size_mib: u64, + pub deploy_fs: FsKind, + pub deploy_size_mib: u64, + pub extra_partitions: Vec, + pub force_wipe: bool, +} + +#[derive(Debug, Clone, CTryToRust, RustToC)] +pub struct BtrfsOptions { + pub node_size: u32, + pub sector_size: u32, +} + +#[derive(Debug, Clone, RustToC)] +pub struct SetupExistingRequest { + pub base: RequestBase, + + pub esp_device: String, + pub deploy_device: String, + pub deploy_fs: FsKind, + pub extra_mounts: Vec, + + pub mount_point: Option, + pub source: String, + pub empty_config: bool, + pub pinned: bool, + pub boot_plugin: String, +} + +#[derive(Debug, Clone, RustToC)] +pub struct SetupWholeDiskRequest { + pub base: RequestBase, + + pub device_path: String, + pub gpt: GptLayout, + pub btrfs: BtrfsOptions, + + pub mount_point: Option, + pub source: String, + pub empty_config: bool, + pub pinned: bool, + pub boot_plugin: String, +} From 34b436231a3b515c434c3869afc9ed4c0f3882a1 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 18:53:24 +0400 Subject: [PATCH 106/148] =?UTF-8?q?feat:=20add=20ErrorDomain::Setup=20and?= =?UTF-8?q?=20move=20GenesisStage=20into=20upac=5Ftypes::states=20as=20Set?= =?UTF-8?q?upStateId=20=E2=80=94=20genesis=20now=20shares=20the=20same=20C?= =?UTF-8?q?ommandState=20machinery=20as=20every=20other=20command,=20since?= =?UTF-8?q?=20it's=20gaining=20a=20real=20C-ABI=20request=20surface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- lib/types/src/states.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/types/src/states.rs b/lib/types/src/states.rs index bea4cf3c..f50535aa 100644 --- a/lib/types/src/states.rs +++ b/lib/types/src/states.rs @@ -277,3 +277,19 @@ pub enum CommitStateId { } impl_command_state!(CommitStateId, Commit); + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromStageIndex, StageKey)] +pub enum SetupStateId { + PrepareSource = 0, + EnumeratePackages = 1, + UnpackPackage = 2, + ImportPackage = 3, + ImportSystem = 4, + EmbedDatabase = 5, + WriteDeployRecord = 6, + StageBoot = 7, + Setup = 8, +} + +impl_command_state!(SetupStateId, Setup); From f787d919a17838cfb89642f7767cb9ccffce5b47 Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 18:53:54 +0400 Subject: [PATCH 107/148] =?UTF-8?q?feat:=20add=20Setup=20variant=20to=20Er?= =?UTF-8?q?rorDomain=20in=20lib/abi=20=E2=80=94=20genesis's=20C-ABI=20requ?= =?UTF-8?q?ests=20now=20have=20a=20domain=20to=20report=20errors=20under,?= =?UTF-8?q?=20matching=20every=20other=20commandA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/abi/src/error.rs b/lib/abi/src/error.rs index 80f9e942..0cc7dae2 100644 --- a/lib/abi/src/error.rs +++ b/lib/abi/src/error.rs @@ -30,6 +30,7 @@ pub enum ErrorDomain { SearchFiles, SearchInMeta, SearchInPackageFiles, + Setup, } #[repr(u32)] From 8fc55fd2014953254f892ceb17c823901a0695cd Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 19:37:27 +0400 Subject: [PATCH 108/148] fix: slightly reorganized the error structure; errors now share common functions across the two crates fix: moved the error trait to the traits module Co-Authored-By: Claude Sonnet 5 --- lib/types/src/entry.rs | 11 ----------- lib/types/src/error.rs | 33 ++++++++++++++++++++++++++++----- lib/types/src/hook.rs | 2 +- lib/types/src/package.rs | 3 --- lib/types/src/states.rs | 2 +- lib/types/src/traits.rs | 12 ++++++++++-- 6 files changed, 40 insertions(+), 23 deletions(-) diff --git a/lib/types/src/entry.rs b/lib/types/src/entry.rs index 6c34f6f9..066f7daa 100644 --- a/lib/types/src/entry.rs +++ b/lib/types/src/entry.rs @@ -18,7 +18,6 @@ use upac_macro::{RedbCodec, RustToC}; use crate::codec::RedbCodable; use crate::package::Version; -// ── FileEntryScope ────────────────────────────────────────────────────────── #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FileEntryScope { @@ -42,7 +41,6 @@ impl RedbCodable for FileEntryScope { } } -// ── FileEntry ─────────────────────────────────────────────────────────────── #[derive(Debug, Clone, RedbCodec)] pub struct FileEntry { pub path: String, @@ -50,7 +48,6 @@ pub struct FileEntry { pub scope: FileEntryScope, } -// ── SearchFileEntry ───────────────────────────────────────────────────────── #[derive(Debug, Clone, RustToC)] pub struct SearchFileEntry { pub path: String, @@ -58,7 +55,6 @@ pub struct SearchFileEntry { pub is_user: bool, } -// ── PrefixEntry ───────────────────────────────────────────────────────────── #[derive(Debug, Clone, RustToC)] pub struct PrefixEntry { pub prefix_digest: String, @@ -71,7 +67,6 @@ pub struct PrefixEntry { pub working_config: Option, } -// ── ConfigCommitEntry ───────────────────────────────────────────────────────────── #[derive(Debug, Clone, RustToC)] pub struct ConfigCommitEntry { pub config_digest: String, @@ -80,7 +75,6 @@ pub struct ConfigCommitEntry { pub message: Option, } -// ── HistoryEntry ──────────────────────────────────────────────────────────── #[derive(Debug, Clone, RustToC)] pub struct HistoryEntry { pub prefix_digest: String, @@ -94,14 +88,12 @@ pub struct HistoryEntry { pub config_history: Vec, } -// ── DiffFileEntryCommon ────────────────────────────────────────────────────── #[derive(Debug, Clone, RustToC)] pub struct DiffFileEntryCommon { pub path: String, pub kind: FileDiffKind, } -// ── DiffPrefixFileEntry ───────────────────────────────────────────────────── #[derive(Debug, Clone, RustToC)] pub struct DiffPrefixFileEntry { pub common: DiffFileEntryCommon, @@ -110,14 +102,12 @@ pub struct DiffPrefixFileEntry { pub is_user: bool, } -// ── DiffConfigFileEntry ───────────────────────────────────────────────────── #[derive(Debug, Clone, RustToC)] pub struct DiffConfigFileEntry { pub common: DiffFileEntryCommon, pub package_name: Option, } -// ── DiffPackageEntry ──────────────────────────────────────────────────────── #[derive(Debug, Clone, RustToC)] pub struct DiffPackageEntry { pub name: String, @@ -127,7 +117,6 @@ pub struct DiffPackageEntry { pub files: Vec, } -// ── DiffUntrackedFileEntry ────────────────────────────────────────────────── #[derive(Debug, Clone, RustToC)] pub struct DiffUntrackedFileEntry { pub common: DiffFileEntryCommon, diff --git a/lib/types/src/error.rs b/lib/types/src/error.rs index 6bb83fef..fa647d88 100644 --- a/lib/types/src/error.rs +++ b/lib/types/src/error.rs @@ -7,7 +7,9 @@ use std::io::Error as IoError; use std::io::ErrorKind as IoErrorKind; use std::str::Utf8Error; -use upac_abi::error::ErrorDomain; +use upac_abi::error::{CError, ErrorKind}; + +use super::traits::CommandState; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DecodeError { @@ -48,9 +50,30 @@ impl DecodeError { } } -pub trait CommandState: Copy { - const DOMAIN: ErrorDomain; - const VALIDATION: Self; +pub unsafe fn write_error(err_out: *mut CError, state: S, error: ErrorKind) { + if !err_out.is_null() { + unsafe { + *err_out = CError { + domain: S::DOMAIN, + state: state.as_u32(), + error, + }; + } + } +} - fn as_u32(self) -> u32; +pub fn write_abi_error(error: ErrorKind, err_out: *mut CError) -> i32 { + unsafe { write_error(err_out, S::VALIDATION, error) }; + -1 +} + +#[macro_export] +macro_rules! try_convert_abi { + ($expr:expr, $err_out:expr, $state:ty) => { + match $expr { + Ok(value) => value, + Err(error) => return upac_types::error::write_abi_error::<$state>(error, $err_out), + } + }; } +pub use try_convert_abi; diff --git a/lib/types/src/hook.rs b/lib/types/src/hook.rs index 64bad63a..a8c41e46 100644 --- a/lib/types/src/hook.rs +++ b/lib/types/src/hook.rs @@ -11,7 +11,7 @@ use upac_abi::HookMessageFn; use upac_abi::hook::{CProgressEvent, HookAck}; use upac_abi::types::CSlice; -use crate::traits::MessageHook; +use super::traits::MessageHook; pub struct ProgressEventBuilder { stage: u32, diff --git a/lib/types/src/package.rs b/lib/types/src/package.rs index f1b0061c..4661bc60 100644 --- a/lib/types/src/package.rs +++ b/lib/types/src/package.rs @@ -14,7 +14,6 @@ use upac_abi::types::{COwned, CSlice}; use upac_macro::{CTryToRust, RedbCodec, RustToC}; -// ── Version ───────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum VersionToken<'raw> { Alpha(&'raw str), @@ -127,7 +126,6 @@ impl Version { } } -// ── Package ───────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct PackageTemp { pub meta: PackageMeta, @@ -149,7 +147,6 @@ pub struct PackageMeta { pub installed_size: u64, } -// ── PackageEntry ──────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct PackageEntry { pub name: String, diff --git a/lib/types/src/states.rs b/lib/types/src/states.rs index f50535aa..008e5776 100644 --- a/lib/types/src/states.rs +++ b/lib/types/src/states.rs @@ -7,7 +7,7 @@ use upac_abi::error::ErrorDomain; use upac_macro::{FromStageIndex, StageKey}; -use crate::error::CommandState; +use super::traits::CommandState; macro_rules! impl_command_state { ($name:ident, $domain:ident) => { diff --git a/lib/types/src/traits.rs b/lib/types/src/traits.rs index 5270096e..c01a1c85 100644 --- a/lib/types/src/traits.rs +++ b/lib/types/src/traits.rs @@ -3,10 +3,18 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception +use upac_abi::error::ErrorDomain; use upac_abi::hook::{CProgressEvent, HookAck}; -use crate::error::DecodeError; -use crate::package::DecodedPackageMeta; +use super::error::DecodeError; +use super::package::DecodedPackageMeta; + +pub trait CommandState: Copy { + const DOMAIN: ErrorDomain; + const VALIDATION: Self; + + fn as_u32(self) -> u32; +} pub trait Booter: Sized { type Error; From 2a0b7b4359105ef523fc8862cda5fd69ba792bac Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 19:38:08 +0400 Subject: [PATCH 109/148] fix: fixed import errors after moving items to `types` fix: context macros are now exported Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/export/mod.rs | 30 ------------------- lib/lib/src/export/mutated/commit.rs | 2 +- lib/lib/src/export/mutated/files.rs | 2 +- lib/lib/src/export/mutated/gc.rs | 2 +- lib/lib/src/export/mutated/installer.rs | 2 +- lib/lib/src/export/mutated/mime.rs | 2 +- lib/lib/src/export/mutated/pin.rs | 2 +- lib/lib/src/export/mutated/rollback.rs | 2 +- lib/lib/src/export/mutated/uninstaller.rs | 2 +- lib/lib/src/export/mutated/update.rs | 2 +- lib/lib/src/export/unmutated/diff.rs | 2 +- lib/lib/src/export/unmutated/diff_config.rs | 2 +- lib/lib/src/export/unmutated/diff_packages.rs | 6 ++-- lib/lib/src/export/unmutated/diff_prefix.rs | 6 ++-- lib/lib/src/export/unmutated/list_config.rs | 6 ++-- lib/lib/src/export/unmutated/list_history.rs | 6 ++-- lib/lib/src/export/unmutated/list_packages.rs | 6 ++-- lib/lib/src/export/unmutated/list_prefix.rs | 6 ++-- lib/lib/src/export/unmutated/search_files.rs | 6 ++-- .../src/export/unmutated/search_in_meta.rs | 6 ++-- .../unmutated/search_in_package_files.rs | 6 ++-- lib/lib/src/export/unmutated/search_meta.rs | 6 ++-- lib/lib/src/orchestrator/context.rs | 6 ++-- 23 files changed, 45 insertions(+), 73 deletions(-) diff --git a/lib/lib/src/export/mod.rs b/lib/lib/src/export/mod.rs index 632f9636..f7c06c72 100644 --- a/lib/lib/src/export/mod.rs +++ b/lib/lib/src/export/mod.rs @@ -4,11 +4,8 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use upac_abi::LIB_ABI_VERSION; -use upac_abi::error::{CError, ErrorKind}; use upac_abi::hook::CancelToken; -use upac_types::error::CommandState; - pub mod mutated; pub mod unmutated; @@ -29,30 +26,3 @@ pub unsafe extern "C" fn cancel(token: *mut CancelToken) { } unsafe { (*token).cancel() }; } - -pub(crate) unsafe fn write_error(err_out: *mut CError, state: S, error: ErrorKind) { - if !err_out.is_null() { - unsafe { - *err_out = CError { - domain: S::DOMAIN, - state: state.as_u32(), - error, - }; - } - } -} - -pub(crate) fn write_abi_error(error: ErrorKind, err_out: *mut CError) -> i32 { - unsafe { write_error(err_out, S::VALIDATION, error) }; - -1 -} - -macro_rules! try_convert_abi { - ($expr:expr, $err_out:expr, $state:ty) => { - match $expr { - Ok(value) => value, - Err(error) => return crate::export::write_abi_error::<$state>(error, $err_out), - } - }; -} -pub(crate) use try_convert_abi; diff --git a/lib/lib/src/export/mutated/commit.rs b/lib/lib/src/export/mutated/commit.rs index e49fe1ca..99c435c0 100644 --- a/lib/lib/src/export/mutated/commit.rs +++ b/lib/lib/src/export/mutated/commit.rs @@ -8,9 +8,9 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CCommitRequest; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::CommitStateId; -use crate::export::{try_convert_abi, write_error}; use crate::mutated::commit::{CommitData, run}; /// # Safety diff --git a/lib/lib/src/export/mutated/files.rs b/lib/lib/src/export/mutated/files.rs index 0c076217..e96ea3a3 100644 --- a/lib/lib/src/export/mutated/files.rs +++ b/lib/lib/src/export/mutated/files.rs @@ -8,9 +8,9 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CFilesRequest; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::FilesStateId; -use crate::export::{try_convert_abi, write_error}; use crate::mutated::files::{FilesData, run}; /// # Safety diff --git a/lib/lib/src/export/mutated/gc.rs b/lib/lib/src/export/mutated/gc.rs index 5843e4c6..cf449493 100644 --- a/lib/lib/src/export/mutated/gc.rs +++ b/lib/lib/src/export/mutated/gc.rs @@ -8,9 +8,9 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CGcRequest; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::GcStateId; -use crate::export::{try_convert_abi, write_error}; use crate::mutated::gc::{GcData, run}; /// # Safety diff --git a/lib/lib/src/export/mutated/installer.rs b/lib/lib/src/export/mutated/installer.rs index 25fc7824..f2ad348e 100644 --- a/lib/lib/src/export/mutated/installer.rs +++ b/lib/lib/src/export/mutated/installer.rs @@ -8,9 +8,9 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CInstallRequest; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::InstallStateId; -use crate::export::{try_convert_abi, write_error}; use crate::mutated::installer::{InstallData, run}; /// # Safety diff --git a/lib/lib/src/export/mutated/mime.rs b/lib/lib/src/export/mutated/mime.rs index 5db3fa6c..362bd293 100644 --- a/lib/lib/src/export/mutated/mime.rs +++ b/lib/lib/src/export/mutated/mime.rs @@ -8,9 +8,9 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CMimeSyncRequest; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::MimeStateId; -use crate::export::{try_convert_abi, write_error}; use crate::mutated::mime::{MimeData, run}; /// # Safety diff --git a/lib/lib/src/export/mutated/pin.rs b/lib/lib/src/export/mutated/pin.rs index dd96ed26..d15dc291 100644 --- a/lib/lib/src/export/mutated/pin.rs +++ b/lib/lib/src/export/mutated/pin.rs @@ -8,9 +8,9 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CPinRequest; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::PinStateId; -use crate::export::{try_convert_abi, write_error}; use crate::mutated::pin::{PinData, run}; /// # Safety diff --git a/lib/lib/src/export/mutated/rollback.rs b/lib/lib/src/export/mutated/rollback.rs index 614c43ac..a80756ee 100644 --- a/lib/lib/src/export/mutated/rollback.rs +++ b/lib/lib/src/export/mutated/rollback.rs @@ -8,9 +8,9 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CRollbackRequest; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::RollbackStateId; -use crate::export::{try_convert_abi, write_error}; use crate::mutated::rollback::{RollbackData, run}; /// # Safety diff --git a/lib/lib/src/export/mutated/uninstaller.rs b/lib/lib/src/export/mutated/uninstaller.rs index f7f6627c..891c8741 100644 --- a/lib/lib/src/export/mutated/uninstaller.rs +++ b/lib/lib/src/export/mutated/uninstaller.rs @@ -8,9 +8,9 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CUninstallRequest; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::UninstallStateId; -use crate::export::{try_convert_abi, write_error}; use crate::mutated::uninstaller::{UninstallData, run}; /// # Safety diff --git a/lib/lib/src/export/mutated/update.rs b/lib/lib/src/export/mutated/update.rs index 23d90aae..469c4380 100644 --- a/lib/lib/src/export/mutated/update.rs +++ b/lib/lib/src/export/mutated/update.rs @@ -8,9 +8,9 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CUpdateRequest; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::UpdateStateId; -use crate::export::{try_convert_abi, write_error}; use crate::mutated::update::{UpdateData, run}; /// # Safety diff --git a/lib/lib/src/export/unmutated/diff.rs b/lib/lib/src/export/unmutated/diff.rs index db6554e0..0ead280f 100644 --- a/lib/lib/src/export/unmutated/diff.rs +++ b/lib/lib/src/export/unmutated/diff.rs @@ -9,9 +9,9 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffRequest; use upac_abi::response::CDiffResponse; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::DiffStateId; -use crate::export::{try_convert_abi, write_error}; use crate::unmutated::diff::{DiffData, run}; /// # Safety diff --git a/lib/lib/src/export/unmutated/diff_config.rs b/lib/lib/src/export/unmutated/diff_config.rs index f6cdaaf8..9a6b1192 100644 --- a/lib/lib/src/export/unmutated/diff_config.rs +++ b/lib/lib/src/export/unmutated/diff_config.rs @@ -9,9 +9,9 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffConfigRequest; use upac_abi::response::CDiffConfigResponse; +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::DiffConfigStateId; -use crate::export::{try_convert_abi, write_error}; use crate::unmutated::diff_config::{DiffConfigData, run}; /// # Safety diff --git a/lib/lib/src/export/unmutated/diff_packages.rs b/lib/lib/src/export/unmutated/diff_packages.rs index f9a8c920..8569ce24 100644 --- a/lib/lib/src/export/unmutated/diff_packages.rs +++ b/lib/lib/src/export/unmutated/diff_packages.rs @@ -9,11 +9,11 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffPackagesRequest; use upac_abi::response::CDiffPackagesResponse; -use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::diff_packages::{DiffPackagesData, run}; - +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::DiffPackagesStateId; +use crate::unmutated::diff_packages::{DiffPackagesData, run}; + /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the /// call. `response_out` and `err_out`, if non-null, must each point to writable storage of the diff --git a/lib/lib/src/export/unmutated/diff_prefix.rs b/lib/lib/src/export/unmutated/diff_prefix.rs index 57bda4d4..c65d4a0f 100644 --- a/lib/lib/src/export/unmutated/diff_prefix.rs +++ b/lib/lib/src/export/unmutated/diff_prefix.rs @@ -9,11 +9,11 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffPrefixRequest; use upac_abi::response::CDiffPrefixResponse; -use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::diff_prefix::{DiffPrefixData, run}; - +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::DiffPrefixStateId; +use crate::unmutated::diff_prefix::{DiffPrefixData, run}; + /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the /// call. `response_out` and `err_out`, if non-null, must each point to writable storage of the diff --git a/lib/lib/src/export/unmutated/list_config.rs b/lib/lib/src/export/unmutated/list_config.rs index 056c2f9a..7b17aaef 100644 --- a/lib/lib/src/export/unmutated/list_config.rs +++ b/lib/lib/src/export/unmutated/list_config.rs @@ -9,11 +9,11 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CListConfigRequest; use upac_abi::response::CListConfigResponse; -use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::list_config::{ListConfigData, run}; - +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::ListConfigStateId; +use crate::unmutated::list_config::{ListConfigData, run}; + /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the /// call. `response_out` and `err_out`, if non-null, must each point to writable storage of the diff --git a/lib/lib/src/export/unmutated/list_history.rs b/lib/lib/src/export/unmutated/list_history.rs index ed3f0fdb..3e2d9434 100644 --- a/lib/lib/src/export/unmutated/list_history.rs +++ b/lib/lib/src/export/unmutated/list_history.rs @@ -9,11 +9,11 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CListHistoryRequest; use upac_abi::response::CListHistoryResponse; -use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::list_history::{ListHistoryData, run}; - +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::ListHistoryStateId; +use crate::unmutated::list_history::{ListHistoryData, run}; + /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the /// call. `response_out` and `err_out`, if non-null, must each point to writable storage of the diff --git a/lib/lib/src/export/unmutated/list_packages.rs b/lib/lib/src/export/unmutated/list_packages.rs index dbf0dfc5..f10c6d31 100644 --- a/lib/lib/src/export/unmutated/list_packages.rs +++ b/lib/lib/src/export/unmutated/list_packages.rs @@ -9,11 +9,11 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CListPackagesRequest; use upac_abi::response::CListPackagesResponse; -use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::list_packages::{ListPackagesData, run}; - +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::ListPackagesStateId; +use crate::unmutated::list_packages::{ListPackagesData, run}; + /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the /// call. `response_out` and `err_out`, if non-null, must each point to writable storage of the diff --git a/lib/lib/src/export/unmutated/list_prefix.rs b/lib/lib/src/export/unmutated/list_prefix.rs index 2e519f2a..59f5214b 100644 --- a/lib/lib/src/export/unmutated/list_prefix.rs +++ b/lib/lib/src/export/unmutated/list_prefix.rs @@ -9,11 +9,11 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CListPrefixRequest; use upac_abi::response::CListPrefixResponse; -use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::list_prefix::{ListPrefixData, run}; - +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::ListPrefixStateId; +use crate::unmutated::list_prefix::{ListPrefixData, run}; + /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the /// call. `response_out` and `err_out`, if non-null, must each point to writable storage of the diff --git a/lib/lib/src/export/unmutated/search_files.rs b/lib/lib/src/export/unmutated/search_files.rs index 7c29dd83..98298eac 100644 --- a/lib/lib/src/export/unmutated/search_files.rs +++ b/lib/lib/src/export/unmutated/search_files.rs @@ -9,11 +9,11 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CSearchFilesRequest; use upac_abi::response::CSearchFilesResponse; -use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::search_files::{SearchFilesData, run}; - +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::SearchFilesStateId; +use crate::unmutated::search_files::{SearchFilesData, run}; + /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the /// call. `response_out` and `err_out`, if non-null, must each point to writable storage of the diff --git a/lib/lib/src/export/unmutated/search_in_meta.rs b/lib/lib/src/export/unmutated/search_in_meta.rs index a0396b77..191d9d26 100644 --- a/lib/lib/src/export/unmutated/search_in_meta.rs +++ b/lib/lib/src/export/unmutated/search_in_meta.rs @@ -9,11 +9,11 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CSearchInMetaRequest; use upac_abi::response::CSearchInMetaResponse; -use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::search_in_meta::{SearchInMetaData, run}; - +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::SearchInMetaStateId; +use crate::unmutated::search_in_meta::{SearchInMetaData, run}; + /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the /// call. `response_out` and `err_out`, if non-null, must each point to writable storage of the diff --git a/lib/lib/src/export/unmutated/search_in_package_files.rs b/lib/lib/src/export/unmutated/search_in_package_files.rs index 0dedaa3c..859a3bc9 100644 --- a/lib/lib/src/export/unmutated/search_in_package_files.rs +++ b/lib/lib/src/export/unmutated/search_in_package_files.rs @@ -9,11 +9,11 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CSearchInPackageFilesRequest; use upac_abi::response::CSearchInPackageFilesResponse; -use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::search_in_package_files::{SearchInPackageFilesData, run}; - +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::SearchInPackageFilesStateId; +use crate::unmutated::search_in_package_files::{SearchInPackageFilesData, run}; + /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the /// call. `response_out` and `err_out`, if non-null, must each point to writable storage of the diff --git a/lib/lib/src/export/unmutated/search_meta.rs b/lib/lib/src/export/unmutated/search_meta.rs index 8c140513..007bb50c 100644 --- a/lib/lib/src/export/unmutated/search_meta.rs +++ b/lib/lib/src/export/unmutated/search_meta.rs @@ -9,11 +9,11 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CSearchMetaRequest; use upac_abi::response::CSearchMetaResponse; -use crate::export::{try_convert_abi, write_error}; -use crate::unmutated::search_meta::{SearchMetaData, run}; - +use upac_types::error::{try_convert_abi, write_abi_error, write_error}; use upac_types::states::SearchMetaStateId; +use crate::unmutated::search_meta::{SearchMetaData, run}; + /// # Safety /// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the /// call. `response_out` and `err_out`, if non-null, must each point to writable storage of the diff --git a/lib/lib/src/orchestrator/context.rs b/lib/lib/src/orchestrator/context.rs index 5655a580..068f650c 100644 --- a/lib/lib/src/orchestrator/context.rs +++ b/lib/lib/src/orchestrator/context.rs @@ -16,6 +16,7 @@ use upac_types::traits::MessageHook; use crate::orchestrator::stage::RollbackGuard; +#[macro_export] macro_rules! ctx_get { ($context:expr, $ty:ty) => { $context @@ -23,8 +24,9 @@ macro_rules! ctx_get { .ok_or($crate::errors::CommonError::MissingResult)? }; } -pub(crate) use ctx_get; +pub use ctx_get; +#[macro_export] macro_rules! ctx_take { ($context:expr, $ty:ty) => { $context @@ -32,7 +34,7 @@ macro_rules! ctx_take { .ok_or($crate::errors::CommonError::MissingResult)? }; } -pub(crate) use ctx_take; +pub use ctx_take; pub struct Context { slots: HashMap>, From 842f751291bb8eded299dfaaa40d4958f39dbc6e Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 19:42:51 +0400 Subject: [PATCH 110/148] fix: added a new variant for the errorA Co-Authored-By: Claude Sonnet 5 --- user/upac-cli/src/types/errors.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/user/upac-cli/src/types/errors.rs b/user/upac-cli/src/types/errors.rs index 3bab1099..ef6db9ec 100644 --- a/user/upac-cli/src/types/errors.rs +++ b/user/upac-cli/src/types/errors.rs @@ -14,7 +14,7 @@ use upac_types::states::{ CommitStateId, DiffConfigStateId, DiffPackagesStateId, DiffPrefixStateId, DiffStateId, FilesStateId, GcStateId, InstallStateId, ListConfigStateId, ListHistoryStateId, ListPackagesStateId, ListPrefixStateId, MimeStateId, PinStateId, RollbackStateId, SearchFilesStateId, SearchInMetaStateId, SearchInPackageFilesStateId, - SearchMetaStateId, UninstallStateId, UpdateStateId, + SearchMetaStateId, SetupStateId, UninstallStateId, UpdateStateId, }; use crate::locale::LOADER; @@ -99,6 +99,7 @@ impl Display for StageName { ErrorDomain::SearchFiles => SearchFilesStateId::from_stage_index(state).stage_key(), ErrorDomain::SearchInMeta => SearchInMetaStateId::from_stage_index(state).stage_key(), ErrorDomain::SearchInPackageFiles => SearchInPackageFilesStateId::from_stage_index(state).stage_key(), + ErrorDomain::Setup => SetupStateId::from_stage_index(state).stage_key(), }; write!(formatter, "{}", LOADER.get(key)) From 6f91d7944b28429630533be91132607d93a3082e Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 23:51:57 +0400 Subject: [PATCH 111/148] fix: fixed build errors and Clippy suggestions Co-Authored-By: Claude Sonnet 5 --- lib/setup/tests/inline/archive.rs | 117 ++++++++++++++++++++++++++++++ lib/types/src/error.rs | 8 +- 2 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 lib/setup/tests/inline/archive.rs diff --git a/lib/setup/tests/inline/archive.rs b/lib/setup/tests/inline/archive.rs new file mode 100644 index 00000000..39ed8c57 --- /dev/null +++ b/lib/setup/tests/inline/archive.rs @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::fs::{self, File}; +use std::io::Write; +use std::path::Path; + +use tar::{Builder as TarBuilder, Header as TarHeader}; +use tempfile::TempDir; +use xz2::write::XzEncoder; +use zip::ZipWriter; +use zip::write::SimpleFileOptions; + +use super::SourceArchive; + +fn write_plain_tar(path: &Path, entry_name: &str, content: &[u8]) { + let file = File::create(path).unwrap(); + let mut builder = TarBuilder::new(file); + + let mut header = TarHeader::new_gnu(); + header.set_size(content.len() as u64); + header.set_cksum(); + builder.append_data(&mut header, entry_name, content).unwrap(); + builder.finish().unwrap(); +} + +fn assert_round_trip(archive_path: &Path, entry_name: &str, content: &[u8]) { + let archive = SourceArchive::sniff(archive_path).unwrap(); + + let destination = TempDir::new().unwrap(); + archive.extract(destination.path()).unwrap(); + + let extracted = fs::read(destination.path().join(entry_name)).unwrap(); + assert_eq!(extracted, content); +} + +#[test] +fn sniff_and_extract_round_trip_plain_tar() { + let scratch = TempDir::new().unwrap(); + let archive_path = scratch.path().join("source.tar"); + write_plain_tar(&archive_path, "hello.txt", b"hello from plain tar"); + + assert_round_trip(&archive_path, "hello.txt", b"hello from plain tar"); +} + +#[test] +fn sniff_and_extract_round_trip_gzip_tar() { + let scratch = TempDir::new().unwrap(); + let tar_path = scratch.path().join("source.tar"); + write_plain_tar(&tar_path, "hello.txt", b"hello from gzip tar"); + let tar_bytes = fs::read(&tar_path).unwrap(); + + let archive_path = scratch.path().join("source.tar.gz"); + let out = File::create(&archive_path).unwrap(); + let mut encoder = flate2::write::GzEncoder::new(out, flate2::Compression::default()); + encoder.write_all(&tar_bytes).unwrap(); + encoder.finish().unwrap(); + + assert_round_trip(&archive_path, "hello.txt", b"hello from gzip tar"); +} + +#[test] +fn sniff_and_extract_round_trip_xz_tar() { + let scratch = TempDir::new().unwrap(); + let tar_path = scratch.path().join("source.tar"); + write_plain_tar(&tar_path, "hello.txt", b"hello from xz tar"); + let tar_bytes = fs::read(&tar_path).unwrap(); + + let archive_path = scratch.path().join("source.tar.xz"); + let out = File::create(&archive_path).unwrap(); + let mut encoder = XzEncoder::new(out, 6); + encoder.write_all(&tar_bytes).unwrap(); + encoder.finish().unwrap(); + + assert_round_trip(&archive_path, "hello.txt", b"hello from xz tar"); +} + +#[test] +fn sniff_and_extract_round_trip_zstd_tar() { + let scratch = TempDir::new().unwrap(); + let tar_path = scratch.path().join("source.tar"); + write_plain_tar(&tar_path, "hello.txt", b"hello from zstd tar"); + let tar_bytes = fs::read(&tar_path).unwrap(); + + let archive_path = scratch.path().join("source.tar.zst"); + let compressed = zstd::encode_all(&tar_bytes[..], 0).unwrap(); + fs::write(&archive_path, compressed).unwrap(); + + assert_round_trip(&archive_path, "hello.txt", b"hello from zstd tar"); +} + +#[test] +fn sniff_and_extract_round_trip_zip() { + let scratch = TempDir::new().unwrap(); + let archive_path = scratch.path().join("source.zip"); + + let file = File::create(&archive_path).unwrap(); + let mut writer = ZipWriter::new(file); + writer.start_file("hello.txt", SimpleFileOptions::default()).unwrap(); + writer.write_all(b"hello from zip").unwrap(); + writer.finish().unwrap(); + + assert_round_trip(&archive_path, "hello.txt", b"hello from zip"); +} + +#[test] +fn sniff_detects_sevenzip_magic_without_a_full_archive() { + let scratch = TempDir::new().unwrap(); + let archive_path = scratch.path().join("source.7z"); + fs::write(&archive_path, [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C, 0x00, 0x04]).unwrap(); + + let archive = SourceArchive::sniff(&archive_path).unwrap(); + + assert!(matches!(archive, SourceArchive::SevenZip(_))); +} diff --git a/lib/types/src/error.rs b/lib/types/src/error.rs index fa647d88..a2e9b1e0 100644 --- a/lib/types/src/error.rs +++ b/lib/types/src/error.rs @@ -50,6 +50,8 @@ impl DecodeError { } } +/// # Safety +/// `err_out`, if non-null, must point to writable `CError` storage. pub unsafe fn write_error(err_out: *mut CError, state: S, error: ErrorKind) { if !err_out.is_null() { unsafe { @@ -62,7 +64,9 @@ pub unsafe fn write_error(err_out: *mut CError, state: S, error } } -pub fn write_abi_error(error: ErrorKind, err_out: *mut CError) -> i32 { +/// # Safety +/// `err_out`, if non-null, must point to writable `CError` storage. +pub unsafe fn write_abi_error(error: ErrorKind, err_out: *mut CError) -> i32 { unsafe { write_error(err_out, S::VALIDATION, error) }; -1 } @@ -72,7 +76,7 @@ macro_rules! try_convert_abi { ($expr:expr, $err_out:expr, $state:ty) => { match $expr { Ok(value) => value, - Err(error) => return upac_types::error::write_abi_error::<$state>(error, $err_out), + Err(error) => return unsafe { upac_types::error::write_abi_error::<$state>(error, $err_out) }, } }; } From d84a10728e53aa5caf68e26c492cb560bfe535df Mon Sep 17 00:00:00 2001 From: JustPav Date: Fri, 11 Sep 2026 23:52:03 +0400 Subject: [PATCH 112/148] fix: fixed build errors and Clippy suggestions Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/export/mutated/commit.rs | 2 +- lib/lib/src/export/mutated/files.rs | 2 +- lib/lib/src/export/mutated/gc.rs | 2 +- lib/lib/src/export/mutated/installer.rs | 2 +- lib/lib/src/export/mutated/mime.rs | 2 +- lib/lib/src/export/mutated/pin.rs | 2 +- lib/lib/src/export/mutated/rollback.rs | 2 +- lib/lib/src/export/mutated/uninstaller.rs | 2 +- lib/lib/src/export/mutated/update.rs | 2 +- lib/lib/src/export/unmutated/diff.rs | 2 +- lib/lib/src/export/unmutated/diff_config.rs | 2 +- lib/lib/src/export/unmutated/diff_packages.rs | 2 +- lib/lib/src/export/unmutated/diff_prefix.rs | 2 +- lib/lib/src/export/unmutated/list_config.rs | 2 +- lib/lib/src/export/unmutated/list_history.rs | 2 +- lib/lib/src/export/unmutated/list_prefix.rs | 2 +- lib/lib/src/export/unmutated/search_files.rs | 2 +- lib/lib/src/export/unmutated/search_in_meta.rs | 2 +- lib/lib/src/export/unmutated/search_in_package_files.rs | 2 +- lib/lib/src/export/unmutated/search_meta.rs | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/lib/src/export/mutated/commit.rs b/lib/lib/src/export/mutated/commit.rs index 99c435c0..3b6c3f70 100644 --- a/lib/lib/src/export/mutated/commit.rs +++ b/lib/lib/src/export/mutated/commit.rs @@ -8,7 +8,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CCommitRequest; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::CommitStateId; use crate::mutated::commit::{CommitData, run}; diff --git a/lib/lib/src/export/mutated/files.rs b/lib/lib/src/export/mutated/files.rs index e96ea3a3..3a817ef7 100644 --- a/lib/lib/src/export/mutated/files.rs +++ b/lib/lib/src/export/mutated/files.rs @@ -8,7 +8,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CFilesRequest; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::FilesStateId; use crate::mutated::files::{FilesData, run}; diff --git a/lib/lib/src/export/mutated/gc.rs b/lib/lib/src/export/mutated/gc.rs index cf449493..dc5680cb 100644 --- a/lib/lib/src/export/mutated/gc.rs +++ b/lib/lib/src/export/mutated/gc.rs @@ -8,7 +8,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CGcRequest; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::GcStateId; use crate::mutated::gc::{GcData, run}; diff --git a/lib/lib/src/export/mutated/installer.rs b/lib/lib/src/export/mutated/installer.rs index f2ad348e..17868d9e 100644 --- a/lib/lib/src/export/mutated/installer.rs +++ b/lib/lib/src/export/mutated/installer.rs @@ -8,7 +8,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CInstallRequest; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::InstallStateId; use crate::mutated::installer::{InstallData, run}; diff --git a/lib/lib/src/export/mutated/mime.rs b/lib/lib/src/export/mutated/mime.rs index 362bd293..b6c60531 100644 --- a/lib/lib/src/export/mutated/mime.rs +++ b/lib/lib/src/export/mutated/mime.rs @@ -8,7 +8,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CMimeSyncRequest; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::MimeStateId; use crate::mutated::mime::{MimeData, run}; diff --git a/lib/lib/src/export/mutated/pin.rs b/lib/lib/src/export/mutated/pin.rs index d15dc291..6782c6e0 100644 --- a/lib/lib/src/export/mutated/pin.rs +++ b/lib/lib/src/export/mutated/pin.rs @@ -8,7 +8,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CPinRequest; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::PinStateId; use crate::mutated::pin::{PinData, run}; diff --git a/lib/lib/src/export/mutated/rollback.rs b/lib/lib/src/export/mutated/rollback.rs index a80756ee..4aac3666 100644 --- a/lib/lib/src/export/mutated/rollback.rs +++ b/lib/lib/src/export/mutated/rollback.rs @@ -8,7 +8,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CRollbackRequest; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::RollbackStateId; use crate::mutated::rollback::{RollbackData, run}; diff --git a/lib/lib/src/export/mutated/uninstaller.rs b/lib/lib/src/export/mutated/uninstaller.rs index 891c8741..996e5b80 100644 --- a/lib/lib/src/export/mutated/uninstaller.rs +++ b/lib/lib/src/export/mutated/uninstaller.rs @@ -8,7 +8,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CUninstallRequest; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::UninstallStateId; use crate::mutated::uninstaller::{UninstallData, run}; diff --git a/lib/lib/src/export/mutated/update.rs b/lib/lib/src/export/mutated/update.rs index 469c4380..48073b19 100644 --- a/lib/lib/src/export/mutated/update.rs +++ b/lib/lib/src/export/mutated/update.rs @@ -8,7 +8,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CUpdateRequest; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::UpdateStateId; use crate::mutated::update::{UpdateData, run}; diff --git a/lib/lib/src/export/unmutated/diff.rs b/lib/lib/src/export/unmutated/diff.rs index 0ead280f..15a2a55e 100644 --- a/lib/lib/src/export/unmutated/diff.rs +++ b/lib/lib/src/export/unmutated/diff.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffRequest; use upac_abi::response::CDiffResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::DiffStateId; use crate::unmutated::diff::{DiffData, run}; diff --git a/lib/lib/src/export/unmutated/diff_config.rs b/lib/lib/src/export/unmutated/diff_config.rs index 9a6b1192..2863d34b 100644 --- a/lib/lib/src/export/unmutated/diff_config.rs +++ b/lib/lib/src/export/unmutated/diff_config.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffConfigRequest; use upac_abi::response::CDiffConfigResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::DiffConfigStateId; use crate::unmutated::diff_config::{DiffConfigData, run}; diff --git a/lib/lib/src/export/unmutated/diff_packages.rs b/lib/lib/src/export/unmutated/diff_packages.rs index 8569ce24..0e3994b6 100644 --- a/lib/lib/src/export/unmutated/diff_packages.rs +++ b/lib/lib/src/export/unmutated/diff_packages.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffPackagesRequest; use upac_abi::response::CDiffPackagesResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::DiffPackagesStateId; use crate::unmutated::diff_packages::{DiffPackagesData, run}; diff --git a/lib/lib/src/export/unmutated/diff_prefix.rs b/lib/lib/src/export/unmutated/diff_prefix.rs index c65d4a0f..874737d5 100644 --- a/lib/lib/src/export/unmutated/diff_prefix.rs +++ b/lib/lib/src/export/unmutated/diff_prefix.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffPrefixRequest; use upac_abi::response::CDiffPrefixResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::DiffPrefixStateId; use crate::unmutated::diff_prefix::{DiffPrefixData, run}; diff --git a/lib/lib/src/export/unmutated/list_config.rs b/lib/lib/src/export/unmutated/list_config.rs index 7b17aaef..1b9f1f4c 100644 --- a/lib/lib/src/export/unmutated/list_config.rs +++ b/lib/lib/src/export/unmutated/list_config.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CListConfigRequest; use upac_abi::response::CListConfigResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::ListConfigStateId; use crate::unmutated::list_config::{ListConfigData, run}; diff --git a/lib/lib/src/export/unmutated/list_history.rs b/lib/lib/src/export/unmutated/list_history.rs index 3e2d9434..2c89aa41 100644 --- a/lib/lib/src/export/unmutated/list_history.rs +++ b/lib/lib/src/export/unmutated/list_history.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CListHistoryRequest; use upac_abi::response::CListHistoryResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::ListHistoryStateId; use crate::unmutated::list_history::{ListHistoryData, run}; diff --git a/lib/lib/src/export/unmutated/list_prefix.rs b/lib/lib/src/export/unmutated/list_prefix.rs index 59f5214b..f377ab0f 100644 --- a/lib/lib/src/export/unmutated/list_prefix.rs +++ b/lib/lib/src/export/unmutated/list_prefix.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CListPrefixRequest; use upac_abi::response::CListPrefixResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::ListPrefixStateId; use crate::unmutated::list_prefix::{ListPrefixData, run}; diff --git a/lib/lib/src/export/unmutated/search_files.rs b/lib/lib/src/export/unmutated/search_files.rs index 98298eac..5a897490 100644 --- a/lib/lib/src/export/unmutated/search_files.rs +++ b/lib/lib/src/export/unmutated/search_files.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CSearchFilesRequest; use upac_abi::response::CSearchFilesResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::SearchFilesStateId; use crate::unmutated::search_files::{SearchFilesData, run}; diff --git a/lib/lib/src/export/unmutated/search_in_meta.rs b/lib/lib/src/export/unmutated/search_in_meta.rs index 191d9d26..aa23c74f 100644 --- a/lib/lib/src/export/unmutated/search_in_meta.rs +++ b/lib/lib/src/export/unmutated/search_in_meta.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CSearchInMetaRequest; use upac_abi::response::CSearchInMetaResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::SearchInMetaStateId; use crate::unmutated::search_in_meta::{SearchInMetaData, run}; diff --git a/lib/lib/src/export/unmutated/search_in_package_files.rs b/lib/lib/src/export/unmutated/search_in_package_files.rs index 859a3bc9..a92cda61 100644 --- a/lib/lib/src/export/unmutated/search_in_package_files.rs +++ b/lib/lib/src/export/unmutated/search_in_package_files.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CSearchInPackageFilesRequest; use upac_abi::response::CSearchInPackageFilesResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::SearchInPackageFilesStateId; use crate::unmutated::search_in_package_files::{SearchInPackageFilesData, run}; diff --git a/lib/lib/src/export/unmutated/search_meta.rs b/lib/lib/src/export/unmutated/search_meta.rs index 007bb50c..c6457379 100644 --- a/lib/lib/src/export/unmutated/search_meta.rs +++ b/lib/lib/src/export/unmutated/search_meta.rs @@ -9,7 +9,7 @@ use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CSearchMetaRequest; use upac_abi::response::CSearchMetaResponse; -use upac_types::error::{try_convert_abi, write_abi_error, write_error}; +use upac_types::error::{try_convert_abi, write_error}; use upac_types::states::SearchMetaStateId; use crate::unmutated::search_meta::{SearchMetaData, run}; From 8aa87bf3b07bd4cab669a2ead3b5db64d53f427f Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 12 Sep 2026 00:37:16 +0400 Subject: [PATCH 113/148] fix: add macro to type for stages Co-Authored-By: Claude Sonnet 5 --- lib/types/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index cb2b4643..3e677bf7 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -3,6 +3,8 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception +use upac_macro::ContextValue; + use self::package::{PackageEntry, PackageMeta}; pub mod codec; @@ -27,6 +29,7 @@ macro_rules! as_str_method { }; } +#[derive(ContextValue)] pub struct UninstallPackagesTargets(pub Vec); impl UninstallPackagesTargets { @@ -35,10 +38,12 @@ impl UninstallPackagesTargets { } } +#[derive(ContextValue)] pub struct TmpPath(pub String); as_str_method!(TmpPath); +#[derive(ContextValue)] pub struct RequestedPrefixDigest(pub Option); pub struct RequestedPrefixDigestRange { From 651bd9dbcf1671b3e140fa2d3f9cc045300965d3 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 12 Sep 2026 00:37:52 +0400 Subject: [PATCH 114/148] fix: repair uninstaller's open.rs move-out-through-Deref bug (stray `*` instead of `.0`), fix mime rendering.rs's Context key mismatch (was storing DecoderManifests but reading back a raw HashMap), and derive ContextValue for DecoderManifests so its `.0` access can drop out at call sites Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/fs.rs | 10 +++------- lib/lib/src/mutated/mime/preparing.rs | 2 +- lib/lib/src/mutated/mime/rendering.rs | 4 ++-- lib/lib/src/mutated/pin/stage.rs | 2 +- lib/lib/src/mutated/uninstaller/remove.rs | 2 +- lib/lib/src/mutated/update/import.rs | 2 +- lib/lib/src/mutated/update/merge.rs | 2 +- lib/lib/src/plugin/decoder/manifest.rs | 3 +++ lib/lib/src/unmutated/list_config/fetching.rs | 2 +- 9 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/lib/src/fs.rs b/lib/lib/src/fs.rs index 82827a9b..4ecf2192 100644 --- a/lib/lib/src/fs.rs +++ b/lib/lib/src/fs.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::fs; +use std::fs::{read, remove_file}; use std::io::{Error as IoError, ErrorKind as IoErrorKind, Write as IoWrite}; use std::path::{Path, PathBuf}; @@ -13,10 +13,6 @@ use upac_abi::error::ErrorKind; use crate::orchestrator::stage::RollbackGuard; -/// A file written via [`atomic_write`], remembering its previous content (if any) so a group of -/// writes can be undone as a unit — push each successfully written file into a `Vec` -/// and call `.rollback()` on it (manually, on a later write's failure, or via the orchestrator's -/// own [`RollbackGuard`] machinery if a later stage fails). pub struct WrittenFile { path: PathBuf, previous: Option>, @@ -24,7 +20,7 @@ pub struct WrittenFile { impl WrittenFile { pub fn write(path: &Path, content: &[u8]) -> Result { - let previous = fs::read(path).ok(); + let previous = read(path).ok(); let written = WrittenFile { path: path.to_owned(), previous, @@ -37,7 +33,7 @@ impl WrittenFile { fn restore(&self) -> Result<(), IoError> { match &self.previous { Some(bytes) => self.atomic_write(bytes), - None => match fs::remove_file(&self.path) { + None => match remove_file(&self.path) { Ok(()) => Ok(()), Err(error) if error.kind() == IoErrorKind::NotFound => Ok(()), Err(error) => Err(error), diff --git a/lib/lib/src/mutated/mime/preparing.rs b/lib/lib/src/mutated/mime/preparing.rs index 0ebe1315..20045063 100644 --- a/lib/lib/src/mutated/mime/preparing.rs +++ b/lib/lib/src/mutated/mime/preparing.rs @@ -26,7 +26,7 @@ impl Stage for PreparingStage { let manifests = DecoderManifests::new().map_err(CommonError::Decoder)?; let desktop_content = fs::read_to_string(mime::DESKTOP_FILE_PATH)?; - context.put(manifests.0); + context.put(manifests); context.put(DesktopContent(desktop_content)); Ok((progress, StageResult::Advance, Box::new(NoRollback))) diff --git a/lib/lib/src/mutated/mime/rendering.rs b/lib/lib/src/mutated/mime/rendering.rs index 03a2a4c6..80a3c894 100644 --- a/lib/lib/src/mutated/mime/rendering.rs +++ b/lib/lib/src/mutated/mime/rendering.rs @@ -18,7 +18,7 @@ use super::{DesktopContent, MimeError, WriteProgress}; use crate::layout::mime::{DESKTOP_FILE_PATH, MIME_XML_PATH, SHARED_MIME_INFO_XMLNS}; use crate::orchestrator::context::{Context, ctx_take}; use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use crate::plugin::decoder::manifest::DecoderManifest; +use crate::plugin::decoder::manifest::{DecoderManifest, DecoderManifests}; pub struct RenderingStage; @@ -26,7 +26,7 @@ impl Stage for RenderingStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), MimeError> { - let manifests = ctx_take!(context, HashMap); + let manifests = ctx_take!(context, DecoderManifests); let desktop_content = ctx_take!(context, DesktopContent); let mime_xml = Self::render_mime_xml(&manifests)?; diff --git a/lib/lib/src/mutated/pin/stage.rs b/lib/lib/src/mutated/pin/stage.rs index 35f3feea..3a73c7f9 100644 --- a/lib/lib/src/mutated/pin/stage.rs +++ b/lib/lib/src/mutated/pin/stage.rs @@ -30,7 +30,7 @@ impl Stage for SetPinnedStage { let mut record = DeployRecord::read(&record_dir)?; let mut written = Vec::new(); - if replace(&mut record.pinned, pinned.0) != record.pinned { + if replace(&mut record.pinned, **pinned) != record.pinned { written.push(record.write(&record_dir)?); } diff --git a/lib/lib/src/mutated/uninstaller/remove.rs b/lib/lib/src/mutated/uninstaller/remove.rs index 5fed6c55..03c941d8 100644 --- a/lib/lib/src/mutated/uninstaller/remove.rs +++ b/lib/lib/src/mutated/uninstaller/remove.rs @@ -41,7 +41,7 @@ impl Stage for RemovePackageStage { let files = woking_state.database.list_package_files(uuid)?; for entry in files { - if entry.is_user && !purge.0 { + if entry.is_user && !**purge { continue; } diff --git a/lib/lib/src/mutated/update/import.rs b/lib/lib/src/mutated/update/import.rs index 3fc816ff..9065751a 100644 --- a/lib/lib/src/mutated/update/import.rs +++ b/lib/lib/src/mutated/update/import.rs @@ -45,7 +45,7 @@ impl Stage for ImportPackageStage { .find_package_uuid(&package.meta.name, &package.meta.arch, package.meta.arch_sub.as_deref())? .ok_or(UpdateError::PackageNotFound)?; - if !allow_downgrade.0 { + if !**allow_downgrade { let current_meta = imported_state .database .get_package_meta(uuid)? diff --git a/lib/lib/src/mutated/update/merge.rs b/lib/lib/src/mutated/update/merge.rs index bcd3427d..3cf52c7b 100644 --- a/lib/lib/src/mutated/update/merge.rs +++ b/lib/lib/src/mutated/update/merge.rs @@ -57,7 +57,7 @@ impl Stage for MergeStage { } apply_tree_overlay(&mut new, &new_state.config_defaults)?; - let merge_result = merge_config(&base, &new, &live, allow_conflict_files.0)?; + let merge_result = merge_config(&base, &new, &live, **allow_conflict_files)?; let new_config_digest = commit_tree(&repository, merge_result.tree)?.to_hex(); let conflicts_total = merge_result.conflicts.len() as u64; diff --git a/lib/lib/src/plugin/decoder/manifest.rs b/lib/lib/src/plugin/decoder/manifest.rs index 7c34e007..5f9f7e3a 100644 --- a/lib/lib/src/plugin/decoder/manifest.rs +++ b/lib/lib/src/plugin/decoder/manifest.rs @@ -12,6 +12,8 @@ use mime::Mime; use serde::Deserialize; +use upac_macro::ContextValue; + use crate::layout::decoders::{DECODERS_DIR, MANIFEST_EXTENSION}; use crate::plugin::decoder::error::DecoderError; @@ -23,6 +25,7 @@ pub struct DecoderManifest { pub mime: String, } +#[derive(ContextValue)] pub struct DecoderManifests(pub HashMap); impl DecoderManifests { diff --git a/lib/lib/src/unmutated/list_config/fetching.rs b/lib/lib/src/unmutated/list_config/fetching.rs index 156f1375..d1809b16 100644 --- a/lib/lib/src/unmutated/list_config/fetching.rs +++ b/lib/lib/src/unmutated/list_config/fetching.rs @@ -25,7 +25,7 @@ impl Stage for FetchingStage { ) -> Result<(ProgressEventBuilder, StageResult, Box), ListConfigError> { let requested = ctx_get!(context, RequestedPrefixDigest); - let prefix_digest = match &requested.0 { + let prefix_digest = match &**requested { Some(prefix_digest) => prefix_digest.clone(), None => current_prefix_digest()?, }; From 1f36ee9c203992224a0ada44f40b437679915d20 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 12 Sep 2026 00:42:35 +0400 Subject: [PATCH 115/148] fix: add new stages Co-Authored-By: Claude Sonnet 5 --- lib/types/src/states.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/types/src/states.rs b/lib/types/src/states.rs index 008e5776..16733636 100644 --- a/lib/types/src/states.rs +++ b/lib/types/src/states.rs @@ -288,8 +288,9 @@ pub enum SetupStateId { ImportSystem = 4, EmbedDatabase = 5, WriteDeployRecord = 6, - StageBoot = 7, - Setup = 8, + Kernel = 7, + StageBoot = 8, + Setup = 9, } impl_command_state!(SetupStateId, Setup); From 427c6bc9eb9ab13733160a0c756f5f9b6a4376c4 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 12 Sep 2026 04:50:42 +0400 Subject: [PATCH 116/148] fix: fixed standalone progress functions Co-Authored-By: Claude Sonnet 5 --- user/upac-cli/src/types/progress.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/user/upac-cli/src/types/progress.rs b/user/upac-cli/src/types/progress.rs index 1fc53775..780017b8 100644 --- a/user/upac-cli/src/types/progress.rs +++ b/user/upac-cli/src/types/progress.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: GPL-3.0-only use std::os::raw::c_void; +use std::ptr::from_mut; use std::time::Duration; use indicatif::{ProgressBar, ProgressStyle}; @@ -46,7 +47,8 @@ impl ProgressState { let settings = RuntimeSettings::load().progress; let bar = ProgressBar::new_spinner(); - bar.set_style(spinner_style(&settings.spinner_template)); + + bar.set_style(Self::spinner_style(&settings.spinner_template)); bar.enable_steady_tick(Duration::from_millis(settings.tick_interval_ms)); ProgressState { @@ -58,7 +60,7 @@ impl ProgressState { } pub fn ctx_ptr(&mut self) -> *mut c_void { - std::ptr::from_mut(self).cast() + from_mut(self).cast() } pub fn finish(&self) { @@ -71,7 +73,7 @@ impl ProgressState { if event.total > 0 { if !self.is_bar { - self.bar.set_style(bar_style(&self.settings.bar_template)); + self.bar.set_style(self.bar_style()); self.is_bar = true; } self.bar.set_length(event.total); @@ -87,10 +89,12 @@ impl ProgressState { } } -fn spinner_style(template: &str) -> ProgressStyle { - ProgressStyle::with_template(template).unwrap_or_else(|_| ProgressStyle::default_spinner()) -} +impl ProgressState { + fn spinner_style(template: &str) -> ProgressStyle { + ProgressStyle::with_template(template).unwrap_or_else(|_| ProgressStyle::default_spinner()) + } -fn bar_style(template: &str) -> ProgressStyle { - ProgressStyle::with_template(template).unwrap_or_else(|_| ProgressStyle::default_bar()) + fn bar_style(&self) -> ProgressStyle { + ProgressStyle::with_template(&self.settings.bar_template).unwrap_or_else(|_| ProgressStyle::default_bar()) + } } From 111a1b25555864fc0e395a267bcc719658a713a6 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 13 Sep 2026 07:23:19 +0400 Subject: [PATCH 117/148] fix: split CI configuration into multiple files to separate tasks and display them individually Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci-rust/ci-rust-clippy.yml | 44 +++++++++++++++++++ .../{ci-rust.yml => ci-rust/ci-rust-link.yml} | 30 ------------- .github/workflows/ci-rust/ci-rust-tests.yml | 38 ++++++++++++++++ 3 files changed, 82 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/ci-rust/ci-rust-clippy.yml rename .github/workflows/{ci-rust.yml => ci-rust/ci-rust-link.yml} (58%) create mode 100644 .github/workflows/ci-rust/ci-rust-tests.yml diff --git a/.github/workflows/ci-rust/ci-rust-clippy.yml b/.github/workflows/ci-rust/ci-rust-clippy.yml new file mode 100644 index 00000000..67c6bdb5 --- /dev/null +++ b/.github/workflows/ci-rust/ci-rust-clippy.yml @@ -0,0 +1,44 @@ +name: Rust CI +on: + push: + branches: [main] + paths-ignore: ["**.md", "doc/**", "LICENSES/**", "REUSE.toml"] + pull_request: + branches: [main] + paths-ignore: ["**.md", "doc/**", "LICENSES/**", "REUSE.toml"] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.head.ref || github.ref_name }} + cancel-in-progress: true + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + name: Build, Test, and Lints + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libblkid-dev libmount-dev clang mold gettext + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Clippy + run: | + cargo clippy --workspace --all-targets --exclude upac-setup --exclude upac-setup-cli -- -D warnings + cargo clippy -p upac-setup -p upac-setup-cli --all-targets -- -D warnings + + - name: Lint style + run: cargo xtask lint-style diff --git a/.github/workflows/ci-rust.yml b/.github/workflows/ci-rust/ci-rust-link.yml similarity index 58% rename from .github/workflows/ci-rust.yml rename to .github/workflows/ci-rust/ci-rust-link.yml index ba1041c2..d987fc18 100644 --- a/.github/workflows/ci-rust.yml +++ b/.github/workflows/ci-rust/ci-rust-link.yml @@ -18,36 +18,6 @@ env: CARGO_TERM_COLOR: always jobs: - test: - name: Build, Test, and Lints - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y libblkid-dev libmount-dev clang mold gettext - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - components: clippy, rustfmt - - - name: Check formatting - run: cargo fmt --all -- --check - - - name: Clippy - run: | - cargo clippy --workspace --all-targets --exclude upac-setup --exclude upac-setup-cli -- -D warnings - cargo clippy -p upac-setup -p upac-setup-cli --all-targets -- -D warnings - - - name: Lint style - run: cargo xtask lint-style - - - name: Test - run: | - cargo test --workspace --lib --tests --exclude upac-setup --exclude upac-setup-cli - cargo test -p upac-setup -p upac-setup-cli --lib --tests - link-modes: name: Link modes runs-on: ubuntu-latest diff --git a/.github/workflows/ci-rust/ci-rust-tests.yml b/.github/workflows/ci-rust/ci-rust-tests.yml new file mode 100644 index 00000000..88e926c8 --- /dev/null +++ b/.github/workflows/ci-rust/ci-rust-tests.yml @@ -0,0 +1,38 @@ +name: Rust CI +on: + push: + branches: [main] + paths-ignore: ["**.md", "doc/**", "LICENSES/**", "REUSE.toml"] + pull_request: + branches: [main] + paths-ignore: ["**.md", "doc/**", "LICENSES/**", "REUSE.toml"] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.head.ref || github.ref_name }} + cancel-in-progress: true + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + name: Build, Test, and Lints + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libblkid-dev libmount-dev clang mold gettext + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - name: Test + run: | + cargo test --workspace --tests + cargo test -p upac-setup -p upac-setup-cli --tests From e4faca3baedea2f81ca58282d8179136b17c1e25 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 13 Sep 2026 07:23:52 +0400 Subject: [PATCH 118/148] fux: fix build for setup Co-Authored-By: Claude Sonnet 5 --- lib/setup/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/setup/Cargo.toml b/lib/setup/Cargo.toml index 6b5f70c8..acb38ee4 100644 --- a/lib/setup/Cargo.toml +++ b/lib/setup/Cargo.toml @@ -24,7 +24,7 @@ categories.workspace = true workspace = true [dependencies] -upac-lib = { workspace = true, features = ["builtin-all-booters", "builtin-all-decoders"] } +upac-lib = { workspace = true, default-features = false, features = ["builtin-all-booters", "builtin-all-decoders"] } upac-types = { workspace = true } upac-abi = { workspace = true } upac-macro = { workspace = true } From bd1a0758a1f34b222cc9cf414044b207bd4eb3d9 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 13 Sep 2026 07:31:30 +0400 Subject: [PATCH 119/148] fix: fixed the location, as required by GitHub Co-Authored-By: Claude Sonnet 5 --- .github/workflows/{ci-rust => }/ci-rust-clippy.yml | 0 .github/workflows/{ci-rust => }/ci-rust-link.yml | 0 .github/workflows/{ci-rust => }/ci-rust-tests.yml | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{ci-rust => }/ci-rust-clippy.yml (100%) rename .github/workflows/{ci-rust => }/ci-rust-link.yml (100%) rename .github/workflows/{ci-rust => }/ci-rust-tests.yml (100%) diff --git a/.github/workflows/ci-rust/ci-rust-clippy.yml b/.github/workflows/ci-rust-clippy.yml similarity index 100% rename from .github/workflows/ci-rust/ci-rust-clippy.yml rename to .github/workflows/ci-rust-clippy.yml diff --git a/.github/workflows/ci-rust/ci-rust-link.yml b/.github/workflows/ci-rust-link.yml similarity index 100% rename from .github/workflows/ci-rust/ci-rust-link.yml rename to .github/workflows/ci-rust-link.yml diff --git a/.github/workflows/ci-rust/ci-rust-tests.yml b/.github/workflows/ci-rust-tests.yml similarity index 100% rename from .github/workflows/ci-rust/ci-rust-tests.yml rename to .github/workflows/ci-rust-tests.yml From dae883331f41b6b39f8addfaf44ec252f5d63248 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 13 Sep 2026 07:33:22 +0400 Subject: [PATCH 120/148] fix: fix names of CI Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci-rust-clippy.yml | 2 +- .github/workflows/ci-rust-link.yml | 2 +- .github/workflows/ci-rust-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-rust-clippy.yml b/.github/workflows/ci-rust-clippy.yml index 67c6bdb5..bc3c7349 100644 --- a/.github/workflows/ci-rust-clippy.yml +++ b/.github/workflows/ci-rust-clippy.yml @@ -1,4 +1,4 @@ -name: Rust CI +name: Rust CI Clippy and lint on: push: branches: [main] diff --git a/.github/workflows/ci-rust-link.yml b/.github/workflows/ci-rust-link.yml index d987fc18..a2aa2a8c 100644 --- a/.github/workflows/ci-rust-link.yml +++ b/.github/workflows/ci-rust-link.yml @@ -1,4 +1,4 @@ -name: Rust CI +name: Rust CI link test on: push: branches: [main] diff --git a/.github/workflows/ci-rust-tests.yml b/.github/workflows/ci-rust-tests.yml index 88e926c8..e75046ab 100644 --- a/.github/workflows/ci-rust-tests.yml +++ b/.github/workflows/ci-rust-tests.yml @@ -1,4 +1,4 @@ -name: Rust CI +name: Rust CI tests on: push: branches: [main] From 6d0ea3e95ba8ad2966281f95d03186a1bdc53d03 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 15 Sep 2026 04:02:02 +0400 Subject: [PATCH 121/148] fix: let grub-install register its own NVRAM Boot#### entry instead of forcing --removable --no-nvram, matching systemd-boot/refind's own install tools; update booter.toml's stale rationale comment Co-Authored-By: Claude Sonnet 5 --- booters/booter.toml | 9 +++++---- booters/grub/src/backend.rs | 2 -- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/booters/booter.toml b/booters/booter.toml index 9336646b..19776c9f 100644 --- a/booters/booter.toml +++ b/booters/booter.toml @@ -58,10 +58,11 @@ from_slot = "upac-from" # reboot/set-default tools above, for the one-time `grub-install` genesis runs against a freshly # partitioned ESP. install_target is grub's own `--target` platform name (this project is # UEFI/x86_64-only throughout, matching systemd_boot/refind's own *_x64 binary names below). -# install_bootloader_id is grub's `--bootloader-id`, used only to name the install's own -# NVRAM-independent EFI/BOOT/BOOTX64.EFI fallback copy (see backend.rs's `--removable --no-nvram` -# — genesis relies on the same UEFI firmware fallback path uki/systemd-boot/refind's binaries -# already occupy, not a Boot#### entry, so a fresh disk boots without any NVRAM setup). +# install_bootloader_id is grub's `--bootloader-id`, naming both the ESP's EFI// install +# directory and the NVRAM Boot#### entry `grub-install` registers for it — genesis lets +# `grub-install` manage its own NVRAM entry the same way systemd-boot/refind do through their own +# install tools; only uki has no install utility to shell out to and has to build its own Boot#### +# entries by hand (see `Uki::install`). [grub] reboot_bin_primary = "grub-reboot" reboot_bin_fallback = "grub2-reboot" diff --git a/booters/grub/src/backend.rs b/booters/grub/src/backend.rs index 9a6867bc..68625726 100644 --- a/booters/grub/src/backend.rs +++ b/booters/grub/src/backend.rs @@ -57,8 +57,6 @@ impl Booter for Grub { &format!("--efi-directory={esp_mount_point}"), &format!("--boot-directory={esp_mount_point}"), &format!("--bootloader-id={INSTALL_BOOTLOADER_ID}"), - "--removable", - "--no-nvram", ], )?; From 426b2aeb3a9531c3f6f235520489ccbccf405b53 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 15 Sep 2026 04:02:28 +0400 Subject: [PATCH 122/148] fix: replace write_boot_entry's bare String return with a WrittenBootEntry enum (Bls/Uki) so callers branch on the actual boot-resource kind instead of comparing entry_name against a magic UKI slot constant Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/boot/mod.rs | 39 +++++++++++++++++++---- lib/lib/src/mutated/installer/checkout.rs | 7 ++-- lib/lib/src/mutated/rollback/checkout.rs | 7 ++-- lib/lib/src/mutated/update/checkout.rs | 7 ++-- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/lib/lib/src/boot/mod.rs b/lib/lib/src/boot/mod.rs index 9cfe143b..8c92f5c7 100644 --- a/lib/lib/src/boot/mod.rs +++ b/lib/lib/src/boot/mod.rs @@ -21,10 +21,29 @@ use crate::layout::boot::UPAC_UKI_TO_SLOT; pub mod error; +pub enum WrittenBootEntry { + Bls(String), + Uki(String), +} + +impl WrittenBootEntry { + pub fn entry_name(&self) -> &str { + match self { + WrittenBootEntry::Bls(name) | WrittenBootEntry::Uki(name) => name, + } + } + + pub fn into_entry_name(self) -> String { + match self { + WrittenBootEntry::Bls(name) | WrittenBootEntry::Uki(name) => name, + } + } +} + pub fn write_boot_entry( repository: &Repository, tree: &FileSystem, digest: ObjectID, boot_partition: &Path, prefix_digest: &str, -) -> Result { +) -> Result { let rooted_tree = wrap_under_usr(tree); let mut entries = get_boot_resources(&rooted_tree, repository)?; @@ -33,15 +52,23 @@ pub fn write_boot_entry( } let entry = entries.pop().ok_or(BootError::NoBootResource)?; - let entry_name = match &entry { - BootEntry::Type1(_) | BootEntry::UsrLibModulesVmLinuz(_) => prefix_digest.to_owned(), - BootEntry::Type2(_) => UPAC_UKI_TO_SLOT.to_owned(), + let written = match &entry { + BootEntry::Type1(_) | BootEntry::UsrLibModulesVmLinuz(_) => WrittenBootEntry::Bls(prefix_digest.to_owned()), + BootEntry::Type2(_) => WrittenBootEntry::Uki(UPAC_UKI_TO_SLOT.to_owned()), }; let karg = ComposefsCmdline::new_v2(digest, false); - write_boot_simple(repository, entry, &karg, boot_partition, None, Some(&entry_name), &[])?; + write_boot_simple( + repository, + entry, + &karg, + boot_partition, + None, + Some(written.entry_name()), + &[], + )?; - Ok(entry_name) + Ok(written) } fn wrap_under_usr(tree: &FileSystem) -> FileSystem { diff --git a/lib/lib/src/mutated/installer/checkout.rs b/lib/lib/src/mutated/installer/checkout.rs index 5bb9e3b2..b22b7285 100644 --- a/lib/lib/src/mutated/installer/checkout.rs +++ b/lib/lib/src/mutated/installer/checkout.rs @@ -32,11 +32,14 @@ impl Stage for CheckoutStage { let digest = object_id_from_hex(&new_state.prefix_digest)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; + let written = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; let plugin = BootPlugins::new()?.load(requested_boot_plugins)?; - context.put(ResolvedBootEntry { plugin, entry_name }); + context.put(ResolvedBootEntry { + plugin, + entry_name: written.into_entry_name(), + }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/rollback/checkout.rs b/lib/lib/src/mutated/rollback/checkout.rs index 84d080d2..880188e6 100644 --- a/lib/lib/src/mutated/rollback/checkout.rs +++ b/lib/lib/src/mutated/rollback/checkout.rs @@ -31,11 +31,14 @@ impl Stage for CheckoutStage { let digest = object_id_from_hex(target)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, target)?; + let written = write_boot_entry(&repository, &tree, digest, &esp_mount, target)?; let plugin = BootPlugins::new()?.load(requested_boot_plugins)?; - context.put(ResolvedBootEntry { plugin, entry_name }); + context.put(ResolvedBootEntry { + plugin, + entry_name: written.into_entry_name(), + }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/update/checkout.rs b/lib/lib/src/mutated/update/checkout.rs index a5b8bebe..b5a41926 100644 --- a/lib/lib/src/mutated/update/checkout.rs +++ b/lib/lib/src/mutated/update/checkout.rs @@ -31,11 +31,14 @@ impl Stage for CheckoutStage { let digest = object_id_from_hex(&new_state.prefix_digest)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; + let written = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; let plugin = BootPlugins::new()?.load(requested_boot_plugin)?; - context.put(ResolvedBootEntry { plugin, entry_name }); + context.put(ResolvedBootEntry { + plugin, + entry_name: written.into_entry_name(), + }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } From 54ef884ce52cf7ec25947fbfc11e323c18b162f4 Mon Sep 17 00:00:00 2001 From: JustPav Date: Tue, 15 Sep 2026 05:21:31 +0400 Subject: [PATCH 123/148] feat: implement Refind/SystemdBoot install() via their own native installers (refind-install/bootctl), matching grub's already-real install() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also wires up install()'s C-ABI entry point for uki/refind/systemd-boot, which was a hardcoded stub always returning 0 without ever calling the backend — plugin.install() goes exclusively through this extern "C" fn in both static and dynamic link modes, so uki's existing register_slot NVRAM logic never actually ran either. booter.toml gains install_bin for both plugins; systemd-boot's build.rs now generates both the shared [boot] and its own [systemd_boot] section like refind already did. refactor: propagate the real ErrorKind through boot-plugin request-conversion errors instead of discarding it to a hardcoded InvalidRequest Adds From for GrubError/UkiError/RefindError/SystemdBootError (PermissionDenied passes through, everything else still falls back to InvalidRequest) so try_from(...).map_err(XxxError::from) can reuse it instead of a closure that threw the conversion error away. Co-Authored-By: Claude Sonnet 5 --- booters/booter.toml | 13 +++++++++ booters/grub/src/error.rs | 9 ++++++ booters/grub/src/lib.rs | 6 ++-- booters/refind/src/backend.rs | 11 ++++++-- booters/refind/src/error.rs | 22 +++++++++++++++ booters/refind/src/lib.rs | 44 ++++++++++++++++++++++++----- booters/systemd-boot/build.rs | 17 +++++++---- booters/systemd-boot/src/backend.rs | 15 ++++++++-- booters/systemd-boot/src/error.rs | 22 +++++++++++++++ booters/systemd-boot/src/lib.rs | 44 ++++++++++++++++++++++++----- booters/uki/src/error.rs | 9 ++++++ booters/uki/src/lib.rs | 44 ++++++++++++++++++++++++----- 12 files changed, 223 insertions(+), 33 deletions(-) diff --git a/booters/booter.toml b/booters/booter.toml index 19776c9f..1f5a2bba 100644 --- a/booters/booter.toml +++ b/booters/booter.toml @@ -83,6 +83,19 @@ install_bootloader_id = "upac" # entry through PreviousBoot only takes effect if refind.conf's `default_selection` starts with # `+` ("remember last boot"); that's a user/deployment-side rEFInd config choice this plugin has # no way to inspect or control. GUID/name per rEFInd's own documented EFI variable, not guessed. +# +# install_bin is rEFInd's own installer — single canonical name across distros (unlike grub's +# primary/fallback split). Run with no extra flags (besides `--yes` for non-interactive use): +# `refind-install` on its own finds the mounted ESP itself and registers its own NVRAM entry, +# same as `grub-install`/`bootctl install` do for their own loaders. [refind] previous_boot_var = "PreviousBoot" previous_boot_guid = "36d08fa7-cf0b-42f5-8f14-68df73ed3740" + +install_bin = "refind-install" + +# systemd-boot's own installer, single canonical name. `bootctl install --esp-path=` copies +# the binary to both `EFI/systemd/` and the `EFI/BOOT/BOOTX64.EFI` removable-fallback path, and +# registers its own NVRAM entry — no extra flags needed beyond pointing it at the right ESP. +[systemd_boot] +install_bin = "bootctl" diff --git a/booters/grub/src/error.rs b/booters/grub/src/error.rs index 5b1c7b2b..552b086a 100644 --- a/booters/grub/src/error.rs +++ b/booters/grub/src/error.rs @@ -25,6 +25,15 @@ impl From for GrubError { } } +impl From for GrubError { + fn from(error: ErrorKind) -> Self { + match error { + ErrorKind::PermissionDenied => GrubError::PermissionDenied, + _ => GrubError::InvalidRequest, + } + } +} + impl From for ErrorKind { fn from(error: GrubError) -> Self { match error { diff --git a/booters/grub/src/lib.rs b/booters/grub/src/lib.rs index 9ead2b24..62e03b9e 100644 --- a/booters/grub/src/lib.rs +++ b/booters/grub/src/lib.rs @@ -47,7 +47,7 @@ pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginSetOneShotReque } let result = BootPluginSetOneShotRequest::try_from(unsafe { &*request }) - .map_err(|_| GrubError::InvalidRequest) + .map_err(GrubError::from) .and_then(|request| Grub::new().and_then(|mut grub| grub.set_one_shot(&request.entry_name))); match result { @@ -75,7 +75,7 @@ pub unsafe extern "C" fn confirm_boot( } let result = BootPluginConfirmSuccsesBootRequest::try_from(unsafe { &*request }) - .map_err(|_| GrubError::InvalidRequest) + .map_err(GrubError::from) .and_then(|request| { Grub::new().and_then(|mut grub| grub.confirm_boot(&request.entry_name, &request.esp_mount_point)) }); @@ -103,7 +103,7 @@ pub unsafe extern "C" fn install(request: *const CBootPluginInstallRequest, err_ } let result = BootPluginInstallRequest::try_from(unsafe { &*request }) - .map_err(|_| GrubError::InvalidRequest) + .map_err(GrubError::from) .and_then(|request| { Grub::new().and_then(|mut grub| { grub.install( diff --git a/booters/refind/src/backend.rs b/booters/refind/src/backend.rs index d1abd87e..a29b174a 100644 --- a/booters/refind/src/backend.rs +++ b/booters/refind/src/backend.rs @@ -4,9 +4,11 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use std::fs::OpenOptions; +use std::io::ErrorKind as IoErrorKind; use std::os::fd::AsRawFd; use std::os::raw::c_long; use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::process::Command; use std::str::FromStr; use efivar::VarManager; @@ -20,7 +22,7 @@ use upac_types::traits::Booter; use super::boot::EFIVARFS_PATH; use super::error::RefindError; -use super::refind::{PREVIOUS_BOOT_GUID, PREVIOUS_BOOT_VAR}; +use super::refind::{INSTALL_BIN, PREVIOUS_BOOT_GUID, PREVIOUS_BOOT_VAR}; const FS_IMMUTABLE_FL: c_long = 0x0000_0010; @@ -72,7 +74,12 @@ impl Booter for Refind { from_slot, ); - Ok(()) + match Command::new(INSTALL_BIN).arg("--yes").status() { + Ok(status) if status.success() => Ok(()), + Ok(_) => Err(RefindError::Unexpected), + Err(error) if error.kind() == IoErrorKind::NotFound => Err(RefindError::ToolNotFound), + Err(error) => Err(RefindError::from(error)), + } } } diff --git a/booters/refind/src/error.rs b/booters/refind/src/error.rs index 70f99d16..62a8fa14 100644 --- a/booters/refind/src/error.rs +++ b/booters/refind/src/error.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use std::any::Any; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use efivar::Error as EfivarError; @@ -14,6 +15,7 @@ use upac_abi::error::ErrorKind; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RefindError { EfiUnavailable, + ToolNotFound, PermissionDenied, InvalidRequest, Unexpected, @@ -28,6 +30,16 @@ impl From for RefindError { } } +impl From for RefindError { + fn from(error: IoError) -> Self { + match error.kind() { + IoErrorKind::NotFound => RefindError::ToolNotFound, + IoErrorKind::PermissionDenied => RefindError::PermissionDenied, + _ => RefindError::Unexpected, + } + } +} + impl From for RefindError { fn from(_: UuidError) -> Self { RefindError::Unexpected @@ -40,10 +52,20 @@ impl From> for RefindError { } } +impl From for RefindError { + fn from(error: ErrorKind) -> Self { + match error { + ErrorKind::PermissionDenied => RefindError::PermissionDenied, + _ => RefindError::InvalidRequest, + } + } +} + impl From for ErrorKind { fn from(error: RefindError) -> Self { match error { RefindError::EfiUnavailable => ErrorKind::NotInitialized, + RefindError::ToolNotFound => ErrorKind::NotFound, RefindError::PermissionDenied => ErrorKind::PermissionDenied, RefindError::InvalidRequest => ErrorKind::InvalidEntry, RefindError::Unexpected => ErrorKind::Unexpected, diff --git a/booters/refind/src/lib.rs b/booters/refind/src/lib.rs index 7bd4f6a7..0e216e61 100644 --- a/booters/refind/src/lib.rs +++ b/booters/refind/src/lib.rs @@ -9,7 +9,7 @@ use upac_abi::request::{ CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, }; -use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginSetOneShotRequest}; +use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginInstallRequest, BootPluginSetOneShotRequest}; use upac_types::traits::Booter; use self::backend::Refind; @@ -47,7 +47,7 @@ pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginSetOneShotReque } let result = BootPluginSetOneShotRequest::try_from(unsafe { &*request }) - .map_err(|_| RefindError::InvalidRequest) + .map_err(RefindError::from) .and_then(|request| Refind::new().and_then(|mut refind| refind.set_one_shot(&request.entry_name))); match result { @@ -75,7 +75,7 @@ pub unsafe extern "C" fn confirm_boot( } let result = BootPluginConfirmSuccsesBootRequest::try_from(unsafe { &*request }) - .map_err(|_| RefindError::InvalidRequest) + .map_err(RefindError::from) .and_then(|request| { Refind::new().and_then(|mut refind| refind.confirm_boot(&request.entry_name, &request.esp_mount_point)) }); @@ -92,9 +92,39 @@ pub unsafe extern "C" fn confirm_boot( } /// # Safety -/// Touches no pointers — rEFInd has nothing to install onto a pre-existing ESP, always succeeds -/// (its binary is copied from the source package tree via `esp_loader_source` instead). +/// `request`, if non-null, must point to a valid, initialized `CBootPluginInstallRequest` for the +/// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn install(_request: *const CBootPluginInstallRequest, _err_out: *mut ErrorKind) -> i32 { - 0 +pub unsafe extern "C" fn install(request: *const CBootPluginInstallRequest, err_out: *mut ErrorKind) -> i32 { + if request.is_null() { + write_error!(err_out, RefindError::InvalidRequest); + + return -1; + } + + let result = BootPluginInstallRequest::try_from(unsafe { &*request }) + .map_err(RefindError::from) + .and_then(|request| { + Refind::new().and_then(|mut refind| { + refind.install( + &request.esp_mount_point, + request.esp_partition_number, + request.esp_starting_lba, + request.esp_ending_lba, + request.esp_unique_partition_guid, + &request.to_slot, + &request.from_slot, + ) + }) + }); + + match result { + Ok(()) => 0, + + Err(error) => { + write_error!(err_out, error); + + -1 + } + } } diff --git a/booters/systemd-boot/build.rs b/booters/systemd-boot/build.rs index 62c06f36..8e41d59a 100644 --- a/booters/systemd-boot/build.rs +++ b/booters/systemd-boot/build.rs @@ -19,7 +19,17 @@ fn main() -> Result<(), Box> { let raw = read_to_string(&source)?; let config: Value = from_str(&raw)?; - let section = "boot"; + let mut generated = String::new(); + generated.push_str(&generate_section(&config, "boot")?); + generated.push_str(&generate_section(&config, "systemd_boot")?); + + let out = Path::new(&var("OUT_DIR")?).join("layout.rs"); + write(out, generated)?; + + Ok(()) +} + +fn generate_section(config: &Value, section: &str) -> Result> { let entries = config .get(section) .and_then(Value::as_table) @@ -38,8 +48,5 @@ fn main() -> Result<(), Box> { generated.push_str("}\n"); - let out = Path::new(&var("OUT_DIR")?).join("layout.rs"); - write(out, generated)?; - - Ok(()) + Ok(generated) } diff --git a/booters/systemd-boot/src/backend.rs b/booters/systemd-boot/src/backend.rs index 56ec0938..f93f9e20 100644 --- a/booters/systemd-boot/src/backend.rs +++ b/booters/systemd-boot/src/backend.rs @@ -4,9 +4,11 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use std::fs::OpenOptions; +use std::io::ErrorKind as IoErrorKind; use std::os::fd::AsRawFd; use std::os::raw::c_long; use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::process::Command; use std::str::FromStr; use efivar::VarManager; @@ -20,6 +22,7 @@ use upac_types::traits::Booter; use super::boot::{EFIVARFS_PATH, LOADER_ENTRY_DEFAULT_VAR, LOADER_ENTRY_ONE_SHOT_VAR, SD_BOOT_LOADER_GUID}; use super::error::SystemdBootError; +use super::systemd_boot::INSTALL_BIN; const FS_IMMUTABLE_FL: c_long = 0x0000_0010; @@ -62,7 +65,6 @@ impl Booter for SystemdBoot { esp_unique_partition_guid: [u8; 16], to_slot: &str, from_slot: &str, ) -> Result<(), SystemdBootError> { let _ = ( - esp_mount_point, esp_partition_number, esp_starting_lba, esp_ending_lba, @@ -71,7 +73,16 @@ impl Booter for SystemdBoot { from_slot, ); - Ok(()) + match Command::new(INSTALL_BIN) + .arg("install") + .arg(format!("--esp-path={esp_mount_point}")) + .status() + { + Ok(status) if status.success() => Ok(()), + Ok(_) => Err(SystemdBootError::Unexpected), + Err(error) if error.kind() == IoErrorKind::NotFound => Err(SystemdBootError::ToolNotFound), + Err(error) => Err(SystemdBootError::from(error)), + } } } diff --git a/booters/systemd-boot/src/error.rs b/booters/systemd-boot/src/error.rs index 72804775..5c0119ff 100644 --- a/booters/systemd-boot/src/error.rs +++ b/booters/systemd-boot/src/error.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use std::any::Any; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use efivar::Error as EfivarError; @@ -14,6 +15,7 @@ use upac_abi::error::ErrorKind; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SystemdBootError { EfiUnavailable, + ToolNotFound, PermissionDenied, InvalidRequest, Unexpected, @@ -28,6 +30,16 @@ impl From for SystemdBootError { } } +impl From for SystemdBootError { + fn from(error: IoError) -> Self { + match error.kind() { + IoErrorKind::NotFound => SystemdBootError::ToolNotFound, + IoErrorKind::PermissionDenied => SystemdBootError::PermissionDenied, + _ => SystemdBootError::Unexpected, + } + } +} + impl From for SystemdBootError { fn from(_: UuidError) -> Self { SystemdBootError::Unexpected @@ -40,10 +52,20 @@ impl From> for SystemdBootError { } } +impl From for SystemdBootError { + fn from(error: ErrorKind) -> Self { + match error { + ErrorKind::PermissionDenied => SystemdBootError::PermissionDenied, + _ => SystemdBootError::InvalidRequest, + } + } +} + impl From for ErrorKind { fn from(error: SystemdBootError) -> Self { match error { SystemdBootError::EfiUnavailable => ErrorKind::NotInitialized, + SystemdBootError::ToolNotFound => ErrorKind::NotFound, SystemdBootError::PermissionDenied => ErrorKind::PermissionDenied, SystemdBootError::InvalidRequest => ErrorKind::InvalidEntry, SystemdBootError::Unexpected => ErrorKind::Unexpected, diff --git a/booters/systemd-boot/src/lib.rs b/booters/systemd-boot/src/lib.rs index 09b3f616..955e9619 100644 --- a/booters/systemd-boot/src/lib.rs +++ b/booters/systemd-boot/src/lib.rs @@ -9,7 +9,7 @@ use upac_abi::request::{ CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, }; -use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginSetOneShotRequest}; +use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginInstallRequest, BootPluginSetOneShotRequest}; use upac_types::traits::Booter; use self::backend::SystemdBoot; @@ -46,7 +46,7 @@ pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginSetOneShotReque } let result = BootPluginSetOneShotRequest::try_from(unsafe { &*request }) - .map_err(|_| SystemdBootError::InvalidRequest) + .map_err(SystemdBootError::from) .and_then(|request| SystemdBoot::new().and_then(|mut systemd| systemd.set_one_shot(&request.entry_name))); match result { @@ -74,7 +74,7 @@ pub unsafe extern "C" fn confirm_boot( } let result = BootPluginConfirmSuccsesBootRequest::try_from(unsafe { &*request }) - .map_err(|_| SystemdBootError::InvalidRequest) + .map_err(SystemdBootError::from) .and_then(|request| { SystemdBoot::new() .and_then(|mut systemd| systemd.confirm_boot(&request.entry_name, &request.esp_mount_point)) @@ -92,9 +92,39 @@ pub unsafe extern "C" fn confirm_boot( } /// # Safety -/// Touches no pointers — systemd-boot has nothing to install onto a pre-existing ESP, always -/// succeeds (its binary is copied from the source package tree via `esp_loader_source` instead). +/// `request`, if non-null, must point to a valid, initialized `CBootPluginInstallRequest` for the +/// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn install(_request: *const CBootPluginInstallRequest, _err_out: *mut ErrorKind) -> i32 { - 0 +pub unsafe extern "C" fn install(request: *const CBootPluginInstallRequest, err_out: *mut ErrorKind) -> i32 { + if request.is_null() { + write_error!(err_out, SystemdBootError::InvalidRequest); + + return -1; + } + + let result = BootPluginInstallRequest::try_from(unsafe { &*request }) + .map_err(SystemdBootError::from) + .and_then(|request| { + SystemdBoot::new().and_then(|mut systemd| { + systemd.install( + &request.esp_mount_point, + request.esp_partition_number, + request.esp_starting_lba, + request.esp_ending_lba, + request.esp_unique_partition_guid, + &request.to_slot, + &request.from_slot, + ) + }) + }); + + match result { + Ok(()) => 0, + + Err(error) => { + write_error!(err_out, error); + + -1 + } + } } diff --git a/booters/uki/src/error.rs b/booters/uki/src/error.rs index 714a23da..d2602e0a 100644 --- a/booters/uki/src/error.rs +++ b/booters/uki/src/error.rs @@ -45,6 +45,15 @@ impl From> for UkiError { } } +impl From for UkiError { + fn from(error: ErrorKind) -> Self { + match error { + ErrorKind::PermissionDenied => UkiError::PermissionDenied, + _ => UkiError::InvalidRequest, + } + } +} + impl From for ErrorKind { fn from(error: UkiError) -> Self { match error { diff --git a/booters/uki/src/lib.rs b/booters/uki/src/lib.rs index 4dc80b16..2a5cd35d 100644 --- a/booters/uki/src/lib.rs +++ b/booters/uki/src/lib.rs @@ -9,7 +9,7 @@ use upac_abi::request::{ CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, }; -use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginSetOneShotRequest}; +use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginInstallRequest, BootPluginSetOneShotRequest}; use upac_types::traits::Booter; use self::backend::Uki; @@ -47,7 +47,7 @@ pub unsafe extern "C" fn set_one_shot(request: *const CBootPluginSetOneShotReque } let result = BootPluginSetOneShotRequest::try_from(unsafe { &*request }) - .map_err(|_| UkiError::InvalidRequest) + .map_err(UkiError::from) .and_then(|request| Uki::new().and_then(|mut uki| uki.set_one_shot(&request.entry_name))); match result { @@ -75,7 +75,7 @@ pub unsafe extern "C" fn confirm_boot( } let result = BootPluginConfirmSuccsesBootRequest::try_from(unsafe { &*request }) - .map_err(|_| UkiError::InvalidRequest) + .map_err(UkiError::from) .and_then(|request| { Uki::new().and_then(|mut uki| uki.confirm_boot(&request.entry_name, &request.esp_mount_point)) }); @@ -92,9 +92,39 @@ pub unsafe extern "C" fn confirm_boot( } /// # Safety -/// Touches no pointers — uki has nothing to install onto a pre-existing ESP, always succeeds -/// (its binary is copied from the source package tree via `esp_loader_source` instead). +/// `request`, if non-null, must point to a valid, initialized `CBootPluginInstallRequest` for the +/// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. #[cfg_attr(feature = "cdylib", unsafe(no_mangle))] -pub unsafe extern "C" fn install(_request: *const CBootPluginInstallRequest, _err_out: *mut ErrorKind) -> i32 { - 0 +pub unsafe extern "C" fn install(request: *const CBootPluginInstallRequest, err_out: *mut ErrorKind) -> i32 { + if request.is_null() { + write_error!(err_out, UkiError::InvalidRequest); + + return -1; + } + + let result = BootPluginInstallRequest::try_from(unsafe { &*request }) + .map_err(UkiError::from) + .and_then(|request| { + Uki::new().and_then(|mut uki| { + uki.install( + &request.esp_mount_point, + request.esp_partition_number, + request.esp_starting_lba, + request.esp_ending_lba, + request.esp_unique_partition_guid, + &request.to_slot, + &request.from_slot, + ) + }) + }); + + match result { + Ok(()) => 0, + + Err(error) => { + write_error!(err_out, error); + + -1 + } + } } From 50e2c2665f6e214f308d1af464f1cd9f1eca5de5 Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 01:29:50 +0400 Subject: [PATCH 124/148] feat: restructure genesis/ into stages/ and give upac-setup a real C-ABI export boundary (setup_existing/setup_whole_disk/setup_abi_version/cancel), matching upac-lib's own export/mod.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames lib/setup/src/genesis/ to stages/ (source.rs->prepare.rs, embed.rs->database.rs, entry.rs->boot.rs), extracts SourceArchive into its own archive.rs, and drops the old genesis/types.rs and data.rs in favor of SetupExistingData/SetupWholeDiskData built directly from the new CSetupExistingRequest/CSetupWholeDiskRequest C-ABI types. Real boot stage: resolves the boot plugin, calls install()/write_boot_entry()/set_one_shot(), UKI to/from-slot seed-copy. Manual-mode ESP partition geometry (partition number, starting/ending LBA, unique GUID) is now auto-detected from the existing GPT via a new partition::existing_esp_geometry() instead of being silently zeroed, so TargetSysroot no longer needs Option-wrapped geometry fields at all. Adds error.rs's missing From for ErrorKind and the crate's [lib] crate-type (cdylib+rlib), so the new extern "C" fns are actually exportable for up-sp to link against, either dynamically or statically. Kernel stage stays a no-op stub — tracked separately Co-Authored-By: Claude Sonnet 5 --- lib/setup/Cargo.toml | 4 + .../src/{genesis/source.rs => archive.rs} | 45 +-- lib/setup/src/data.rs | 132 ------- lib/setup/src/error.rs | 32 ++ lib/setup/src/format.rs | 52 +-- lib/setup/src/genesis/entry.rs | 123 ------- lib/setup/src/genesis/mod.rs | 168 --------- lib/setup/src/genesis/unpack.rs | 60 ---- lib/setup/src/lib.rs | 93 ++++- lib/setup/src/partition.rs | 54 ++- lib/setup/src/stages/boot.rs | 83 +++++ .../{genesis/embed.rs => stages/database.rs} | 32 +- lib/setup/src/{genesis => stages}/deploy.rs | 23 +- .../src/{genesis => stages}/enumerate.rs | 35 +- lib/setup/src/{genesis => stages}/import.rs | 61 ++-- lib/setup/src/stages/kernel.rs | 25 ++ lib/setup/src/stages/mod.rs | 324 ++++++++++++++++++ lib/setup/src/stages/prepare.rs | 47 +++ lib/setup/src/{genesis => stages}/system.rs | 56 ++- lib/setup/src/stages/unpack.rs | 59 ++++ lib/setup/src/target.rs | 51 +-- lib/setup/src/types.rs | 66 ---- lib/setup/tests/inline/deploy.rs | 18 +- lib/setup/tests/inline/embed.rs | 6 +- lib/setup/tests/inline/enumerate.rs | 11 +- lib/setup/tests/inline/format.rs | 6 +- lib/setup/tests/inline/source.rs | 117 ------- 27 files changed, 886 insertions(+), 897 deletions(-) rename lib/setup/src/{genesis/source.rs => archive.rs} (69%) delete mode 100644 lib/setup/src/data.rs delete mode 100644 lib/setup/src/genesis/entry.rs delete mode 100644 lib/setup/src/genesis/mod.rs delete mode 100644 lib/setup/src/genesis/unpack.rs create mode 100644 lib/setup/src/stages/boot.rs rename lib/setup/src/{genesis/embed.rs => stages/database.rs} (71%) rename lib/setup/src/{genesis => stages}/deploy.rs (69%) rename lib/setup/src/{genesis => stages}/enumerate.rs (68%) rename lib/setup/src/{genesis => stages}/import.rs (53%) create mode 100644 lib/setup/src/stages/kernel.rs create mode 100644 lib/setup/src/stages/mod.rs create mode 100644 lib/setup/src/stages/prepare.rs rename lib/setup/src/{genesis => stages}/system.rs (55%) create mode 100644 lib/setup/src/stages/unpack.rs delete mode 100644 lib/setup/src/types.rs delete mode 100644 lib/setup/tests/inline/source.rs diff --git a/lib/setup/Cargo.toml b/lib/setup/Cargo.toml index acb38ee4..7d13d704 100644 --- a/lib/setup/Cargo.toml +++ b/lib/setup/Cargo.toml @@ -23,6 +23,10 @@ categories.workspace = true [lints] workspace = true +[lib] +name = "upac_setup" +crate-type = ["cdylib", "rlib"] + [dependencies] upac-lib = { workspace = true, default-features = false, features = ["builtin-all-booters", "builtin-all-decoders"] } upac-types = { workspace = true } diff --git a/lib/setup/src/genesis/source.rs b/lib/setup/src/archive.rs similarity index 69% rename from lib/setup/src/genesis/source.rs rename to lib/setup/src/archive.rs index b2a50954..1d6e8b71 100644 --- a/lib/setup/src/genesis/source.rs +++ b/lib/setup/src/archive.rs @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::fs::{File, metadata}; +use std::fs::File; use std::io::{Cursor, Read}; use std::path::{Path, PathBuf}; @@ -11,23 +11,14 @@ use anyhow::Error as AnyhowError; use flate2::read::GzDecoder; use tar::Archive; -use tempfile::TempDir; use xz2::read::XzDecoder; use zip::ZipArchive; use zstd::stream::read::Decoder as ZstdDecoder; -use upac::orchestrator::Context; -use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use super::ctx_get; - use crate::error::SetupError; -use crate::types::{GenesisInput, ResolvedSourceDir}; #[cfg(test)] -#[path = "../../tests/inline/source.rs"] +#[path = "../tests/inline/archive.rs"] mod tests; const ZIP_MAGIC: [u8; 4] = [0x50, 0x4B, 0x03, 0x04]; @@ -36,14 +27,14 @@ const ZSTD_MAGIC: [u8; 4] = [0x28, 0xB5, 0x2F, 0xFD]; const XZ_MAGIC: [u8; 6] = [0xFD, b'7', b'z', b'X', b'Z', 0x00]; const GZIP_MAGIC: [u8; 2] = [0x1F, 0x8B]; -enum SourceArchive { +pub(crate) enum SourceArchive { Zip(File), SevenZip(PathBuf), Tar(Box), } impl SourceArchive { - fn sniff(path: &Path) -> Result { + pub(crate) fn sniff(path: &Path) -> Result { let mut file = File::open(path)?; let mut magic = [0u8; 6]; let bytes_read = file.read(&mut magic)?; @@ -78,7 +69,7 @@ impl SourceArchive { SourceArchive::Tar(Box::new(Cursor::new(sniffed.to_vec()).chain(file))) } - fn extract(self, destination: &Path) -> Result<(), SetupError> { + pub(crate) fn extract(self, destination: &Path) -> Result<(), SetupError> { match self { SourceArchive::Zip(file) => Self::extract_zip(file, destination), SourceArchive::SevenZip(path) => Self::extract_sevenzip(&path, destination), @@ -102,29 +93,3 @@ impl SourceArchive { Ok(()) } } - -pub struct PrepareSourceStage; - -impl Stage for PrepareSourceStage { - fn run( - &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, - ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { - let input = ctx_get!(context, GenesisInput); - - let source_path = Path::new(&input.source); - - if metadata(source_path)?.is_dir() { - context.put(ResolvedSourceDir(source_path.to_path_buf())); - return Ok((progress, StageResult::Advance, Box::new(NoRollback))); - } - - let archive = SourceArchive::sniff(source_path)?; - let scratch = TempDir::new()?; - archive.extract(scratch.path())?; - - context.put(ResolvedSourceDir(scratch.path().to_path_buf())); - context.put(scratch); - - Ok((progress, StageResult::Advance, Box::new(NoRollback))) - } -} diff --git a/lib/setup/src/data.rs b/lib/setup/src/data.rs deleted file mode 100644 index b2645e6d..00000000 --- a/lib/setup/src/data.rs +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::os::raw::c_void; - -use upac_abi::FsKind; -use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, HookMessageFn}; -use upac_abi::setup::{CSetupExistingRequest, CSetupWholeDiskRequest}; - -use upac_types::{BtrfsOptions, GptLayout, PartitionMount, PartitionSpec}; - -use crate::layout::mount::DEFAULT_MOUNT_POINT; - -pub struct SetupExistingData<'data> { - pub esp_device: &'data str, - pub deploy_device: &'data str, - pub deploy_fs: FsKind, - pub extra_mounts: Vec, - - pub mount_point: Option<&'data str>, - pub source: &'data str, - pub empty_config: bool, - pub pinned: bool, - pub boot_plugin: Option<&'data str>, - - pub hook_message: Option, - pub hook_message_context: *mut c_void, - - pub cancel_token: &'data CancelToken, -} - -impl SetupExistingData<'_> { - pub fn mount_point(&self) -> &str { - self.mount_point.unwrap_or(DEFAULT_MOUNT_POINT) - } -} - -impl<'data> TryFrom<&'data CSetupExistingRequest> for SetupExistingData<'data> { - type Error = ErrorKind; - - fn try_from(request: &'data CSetupExistingRequest) -> Result { - unsafe { request.validate()? }; - - let cancel_token = unsafe { request.base.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; - - Ok(SetupExistingData { - esp_device: (&request.esp_device).try_into()?, - deploy_device: (&request.deploy_device).try_into()?, - deploy_fs: request.deploy_fs, - extra_mounts: Vec::try_from(&request.extra_mounts)?, - - mount_point: (&request.base.mount_point).try_into()?, - source: (&request.base.source).try_into()?, - empty_config: request.base.empty_config, - pinned: request.base.pinned, - boot_plugin: (&request.base.boot_plugin).try_into()?, - - hook_message: request.base.base.on_hook, - hook_message_context: request.base.base.hook_ctx, - - cancel_token, - }) - } -} - -pub struct SetupWholeDiskData<'data> { - pub device_path: &'data str, - pub esp_size_mib: u64, - pub deploy_fs: FsKind, - pub deploy_size_mib: u64, - pub extra_partitions: Vec, - pub force_wipe: bool, - - pub node_size: u32, - pub sector_size: u32, - - pub mount_point: Option<&'data str>, - pub source: &'data str, - pub empty_config: bool, - pub pinned: bool, - pub boot_plugin: Option<&'data str>, - - pub hook_message: Option, - pub hook_message_context: *mut c_void, - - pub cancel_token: &'data CancelToken, -} - -impl SetupWholeDiskData<'_> { - pub fn mount_point(&self) -> &str { - self.mount_point.unwrap_or(DEFAULT_MOUNT_POINT) - } -} - -impl<'data> TryFrom<&'data CSetupWholeDiskRequest> for SetupWholeDiskData<'data> { - type Error = ErrorKind; - - fn try_from(request: &'data CSetupWholeDiskRequest) -> Result { - unsafe { request.validate()? }; - - let cancel_token = unsafe { request.base.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; - - let gpt = GptLayout::try_from(&request.gpt)?; - let btrfs = BtrfsOptions::try_from(&request.btrfs)?; - - Ok(SetupWholeDiskData { - device_path: (&request.device_path).try_into()?, - esp_size_mib: gpt.esp_size_mib, - deploy_fs: gpt.deploy_fs, - deploy_size_mib: gpt.deploy_size_mib, - extra_partitions: gpt.extra_partitions, - force_wipe: gpt.force_wipe, - - node_size: btrfs.node_size, - sector_size: btrfs.sector_size, - - mount_point: (&request.base.mount_point).try_into()?, - source: (&request.base.source).try_into()?, - empty_config: request.base.empty_config, - pinned: request.base.pinned, - boot_plugin: (&request.base.boot_plugin).try_into()?, - - hook_message: request.base.base.on_hook, - hook_message_context: request.base.base.hook_ctx, - - cancel_token, - }) - } -} diff --git a/lib/setup/src/error.rs b/lib/setup/src/error.rs index 2f9930c1..4e05e1f6 100644 --- a/lib/setup/src/error.rs +++ b/lib/setup/src/error.rs @@ -19,6 +19,8 @@ use upac::errors::CommonError; use upac::lock::LockError; use upac::plugin::boot::error::BootPluginError; +use upac_abi::error::ErrorKind; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum SetupError { Common(CommonError), @@ -122,3 +124,33 @@ impl From for SetupError { SetupError::Unexpected } } + +impl From for ErrorKind { + fn from(error: SetupError) -> Self { + match error { + SetupError::Common(common_error) => common_error.into(), + SetupError::Mount(_) => ErrorKind::Unexpected, + SetupError::Repo(repo_error) => repo_error.into(), + SetupError::Database(database_error) => database_error.into(), + SetupError::DeployRecord(deploy_record_error) => deploy_record_error.into(), + SetupError::Boot(boot_error) => boot_error.into(), + SetupError::BootPlugin(boot_plugin_error) => boot_plugin_error.into(), + SetupError::Io(kind) => match kind { + IoErrorKind::NotFound => ErrorKind::NotFound, + IoErrorKind::PermissionDenied => ErrorKind::PermissionDenied, + IoErrorKind::AlreadyExists => ErrorKind::AlreadyExists, + _ => ErrorKind::Unexpected, + }, + SetupError::NoSpaceLeft => ErrorKind::NoSpaceLeft, + SetupError::NotBlockDevice => ErrorKind::InvalidEntry, + SetupError::MkfsFailed => ErrorKind::WriteFailed, + SetupError::WipeFailed => ErrorKind::WriteFailed, + SetupError::PartitionNotReady => ErrorKind::NotInitialized, + SetupError::InvalidPartitionLayout => ErrorKind::InvalidEntry, + SetupError::InvalidFormatParams => ErrorKind::InvalidEntry, + SetupError::RereadFailed(_) => ErrorKind::ReadFailed, + SetupError::ComposefsSetupRootUnitNotFound => ErrorKind::NotFound, + SetupError::Unexpected => ErrorKind::Unexpected, + } + } +} diff --git a/lib/setup/src/format.rs b/lib/setup/src/format.rs index 9519f572..2ee2d4e9 100644 --- a/lib/setup/src/format.rs +++ b/lib/setup/src/format.rs @@ -18,13 +18,36 @@ use uuid::Uuid; use upac_abi::FsKind; -use crate::error::SetupError; -use crate::layout::mkfs::{EXT4_BIN, WIPEFS_BIN, XFS_BIN}; +use super::error::SetupError; +use super::layout::mkfs::{EXT4_BIN, WIPEFS_BIN, XFS_BIN}; #[cfg(test)] #[path = "../tests/inline/format.rs"] mod tests; +macro_rules! fat_label { + ($label:expr) => {{ + let mut bytes = [b' '; 11]; + for (slot, byte) in bytes.iter_mut().zip($label.as_bytes()) { + *slot = byte.to_ascii_uppercase(); + } + + bytes + }}; +} + +macro_rules! run_mkfs { + ($binary:expr, $args:expr) => {{ + let status = Command::new($binary).args($args).status()?; + + if !status.success() { + return Err(SetupError::MkfsFailed); + } + + Ok(()) + }}; +} + pub struct FormatTarget<'target> { pub device_path: &'target Path, pub label: Option<&'target str>, @@ -62,7 +85,7 @@ impl FormatTarget<'_> { let mut options = FormatVolumeOptions::new().fat_type(FatType::Fat32); if let Some(label) = self.label { - options = options.volume_label(fat_label(label)); + options = options.volume_label(fat_label!(label)); } format_volume(file, options)?; @@ -114,7 +137,7 @@ impl FormatTarget<'_> { } args.push(self.device_path.as_os_str()); - run_mkfs(EXT4_BIN, &args) + run_mkfs!(EXT4_BIN, &args) } pub fn format_xfs(&self) -> Result<(), SetupError> { @@ -125,25 +148,6 @@ impl FormatTarget<'_> { } args.push(self.device_path.as_os_str()); - run_mkfs(XFS_BIN, &args) - } -} - -fn fat_label(label: &str) -> [u8; 11] { - let mut bytes = [b' '; 11]; - for (slot, byte) in bytes.iter_mut().zip(label.as_bytes()) { - *slot = byte.to_ascii_uppercase(); - } - - bytes -} - -fn run_mkfs(binary: &str, args: &[&OsStr]) -> Result<(), SetupError> { - let status = Command::new(binary).args(args).status()?; - - if !status.success() { - return Err(SetupError::MkfsFailed); + run_mkfs!(XFS_BIN, &args) } - - Ok(()) } diff --git a/lib/setup/src/genesis/entry.rs b/lib/setup/src/genesis/entry.rs deleted file mode 100644 index 1e7c8eb0..00000000 --- a/lib/setup/src/genesis/entry.rs +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::{File, copy, create_dir_all, write}; -use std::io::Read; - -use composefs::erofs::reader::erofs_to_filesystem; -use composefs::fsverity::FsVerityHashValue; -use composefs::repository::Repository; -use composefs::tree::FileSystem; - -use upac::boot::write_boot_entry; -use upac::composefs::file::FileHandle; -use upac::composefs::repository::ObjectID; -use upac::layout::boot::{UPAC_UKI_FROM_SLOT, UPAC_UKI_TO_SLOT}; -use upac::layout::boot_plugins::{BOOT_PLUGINS_DIR, MANIFEST_EXTENSION}; -use upac::orchestrator::Context; -use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use upac::plugin::boot::resolve_boot_plugin; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use super::ctx_get; - -use crate::error::SetupError; -use crate::layout::genesis::{EFI_LINUX_DIR, ESP_FALLBACK_LOADER}; -use crate::target::TargetSysroot; -use crate::types::{GenesisInput, PrefixDigest}; - -pub struct StageBootStage; - -impl Stage for StageBootStage { - fn run( - &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, - ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { - let target = ctx_get!(context, TargetSysroot); - let input = ctx_get!(context, GenesisInput); - let prefix_digest = ctx_get!(context, PrefixDigest); - - let repository = target.repository(); - let prefix_digest_hex = prefix_digest.0.to_hex(); - - let prefix_tree = Self::reopen_tree(repository, &prefix_digest_hex)?; - - let plugin = resolve_boot_plugin(BOOT_PLUGINS_DIR, MANIFEST_EXTENSION, input.boot_plugin.as_deref())?; - - let esp_mount_point = target.esp_mount_point().to_string_lossy().into_owned(); - plugin.install(&esp_mount_point)?; - - if let Some(candidate) = plugin.esp_loader_source() { - let handle = FileHandle::new(candidate); - if handle.stat_in_tree(&prefix_tree).is_ok() { - let loader_bytes = handle.read_file(repository, &prefix_tree)?; - - let destination = target.esp_mount_point().join(ESP_FALLBACK_LOADER); - if let Some(parent) = destination.parent() { - create_dir_all(parent)?; - } - - write(&destination, &loader_bytes)?; - } - } - - let entry_name = write_boot_entry( - repository, - &prefix_tree, - prefix_digest.0.clone(), - &target.esp_mount_point(), - &prefix_digest_hex, - )?; - - // UKI-direct: genesis is the very first deploy, so `upac-from.efi` (the fallback slot) - // has nothing real to hold yet — seed it with the exact same image `write_boot_entry` just - // wrote to `upac-to.efi`. `up install`/`update` never do this: only genesis creates both - // files and both Boot#### entries; every later deploy only ever touches `upac-to.efi`. - if entry_name == UPAC_UKI_TO_SLOT { - let efi_linux = target.esp_mount_point().join(EFI_LINUX_DIR); - let to_path = efi_linux.join(format!("{UPAC_UKI_TO_SLOT}.efi")); - let from_path = efi_linux.join(format!("{UPAC_UKI_FROM_SLOT}.efi")); - copy(&to_path, &from_path)?; - - let geometry = ( - target.esp_partition_number(), - target.esp_starting_lba(), - target.esp_ending_lba(), - target.esp_unique_partition_guid(), - ); - - // Manual mode (pre-existing partitions) has nowhere to read GPT geometry back from — - // registering the two Boot#### entries is skipped there, same as the existing - // "pre-registered once, out of scope of this pipeline" assumption everywhere else. - if let (Some(partition_number), Some(starting_lba), Some(ending_lba), Some(unique_partition_guid)) = - geometry - { - plugin.register_boot_slots( - partition_number, - starting_lba, - ending_lba, - unique_partition_guid.to_bytes_le(), - UPAC_UKI_TO_SLOT, - UPAC_UKI_FROM_SLOT, - )?; - } - } - - plugin.set_one_shot(&entry_name)?; - - Ok((progress, StageResult::Advance, Box::new(NoRollback))) - } -} - -impl StageBootStage { - fn reopen_tree(repository: &Repository, digest: &str) -> Result, SetupError> { - let (image, _enable_verity) = repository.open_image(digest)?; - - let mut data = Vec::new(); - File::from(image).read_to_end(&mut data)?; - - Ok(erofs_to_filesystem(&data)?) - } -} diff --git a/lib/setup/src/genesis/mod.rs b/lib/setup/src/genesis/mod.rs deleted file mode 100644 index 991c3036..00000000 --- a/lib/setup/src/genesis/mod.rs +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::path::{Path, PathBuf}; - -use upac::errors::CommonError; -use upac::orchestrator::error::OrchestratorError; -use upac::orchestrator::{Context, Orchestrator, SequentialOrchestrator}; - -use upac_abi::hook::{Message, MessageHook}; - -use upac_macro::{FromStageIndex, StageKey}; - -use self::deploy::WriteDeployRecordStage; -use self::embed::EmbedDatabaseStage; -use self::entry::StageBootStage; -use self::enumerate::EnumeratePackagesStage; -use self::import::ImportPackageStage; -use self::source::PrepareSourceStage; -use self::system::ImportSystemStage; -use self::unpack::UnpackPackageStage; - -use crate::data::{SetupExistingData, SetupWholeDiskData}; -use crate::error::SetupError; -use crate::target::TargetSysroot; -use crate::types::GenesisInput; - -mod deploy; -mod embed; -mod entry; -mod enumerate; -mod import; -mod source; -mod system; -mod unpack; - -macro_rules! ctx_get { - ($context:expr, $ty:ty) => { - $context.get::<$ty>().ok_or(upac::errors::CommonError::MissingResult)? - }; -} -pub(crate) use ctx_get; - -macro_rules! ctx_take { - ($context:expr, $ty:ty) => { - $context.take::<$ty>().ok_or(upac::errors::CommonError::MissingResult)? - }; -} -pub(crate) use ctx_take; - -macro_rules! import_if_dir { - ($repository:expr, $tree:expr, $source:expr, $import_ctx:expr, $cancel:expr) => { - if $source.is_dir() { - upac::composefs::file::FileHandle::new(::std::path::PathBuf::new()).import_directory( - $repository, - $tree, - $source, - $import_ctx, - $cancel, - &mut |_| {}, - )? - } else { - Vec::new() - } - }; -} -pub(crate) use import_if_dir; - -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, FromStageIndex, StageKey)] -pub enum GenesisStage { - PrepareSource = 0, - EnumeratePackages = 1, - UnpackPackage = 2, - ImportPackage = 3, - ImportSystem = 4, - EmbedDatabase = 5, - WriteDeployRecord = 6, - StageBoot = 7, - Setup = 8, -} - -impl SetupExistingData<'_> { - pub fn run(&self) -> Result<(), (GenesisStage, SetupError)> { - let target = TargetSysroot::new( - Path::new(self.deploy_device), - self.deploy_fs, - Path::new(self.esp_device), - PathBuf::from(self.mount_point()), - &self.extra_mounts, - None, - None, - None, - None, - ) - .map_err(|error| (GenesisStage::Setup, error))?; - - let mut context = Context::new(); - context.put(Box::new(Message::new(self.hook_message, self.hook_message_context)) as Box); - context.put(target); - context.put(GenesisInput::from(self)); - - let orchestrator = SequentialOrchestrator::new(vec![ - Box::new(PrepareSourceStage), - Box::new(EnumeratePackagesStage), - Box::new(UnpackPackageStage), - Box::new(ImportPackageStage), - Box::new(ImportSystemStage), - Box::new(EmbedDatabaseStage), - Box::new(WriteDeployRecordStage), - Box::new(StageBootStage), - ]); - - let result = if orchestrator.validate(&context).is_err() { - Err((GenesisStage::Setup, SetupError::from(CommonError::PipelineInvalid))) - } else { - orchestrator - .run_exclusive(&mut context, self.cancel_token) - .map_err(|failure| match failure { - OrchestratorError::Setup(lock_error) => (GenesisStage::Setup, SetupError::from(lock_error)), - OrchestratorError::Stage(index, error) => (GenesisStage::from_stage_index(index), error), - }) - }; - - self.cancel_token.reset(); - - result - } -} - -impl SetupWholeDiskData<'_> { - pub fn run(&self) -> Result<(), (GenesisStage, SetupError)> { - let target = TargetSysroot::create_whole_disk(self).map_err(|error| (GenesisStage::Setup, error))?; - - let mut context = Context::new(); - context.put(Box::new(Message::new(self.hook_message, self.hook_message_context)) as Box); - context.put(target); - context.put(GenesisInput::from(self)); - - let orchestrator = SequentialOrchestrator::new(vec![ - Box::new(PrepareSourceStage), - Box::new(EnumeratePackagesStage), - Box::new(UnpackPackageStage), - Box::new(ImportPackageStage), - Box::new(ImportSystemStage), - Box::new(EmbedDatabaseStage), - Box::new(WriteDeployRecordStage), - Box::new(StageBootStage), - ]); - - let result = if orchestrator.validate(&context).is_err() { - Err((GenesisStage::Setup, SetupError::from(CommonError::PipelineInvalid))) - } else { - orchestrator - .run_exclusive(&mut context, self.cancel_token) - .map_err(|failure| match failure { - OrchestratorError::Setup(lock_error) => (GenesisStage::Setup, SetupError::from(lock_error)), - OrchestratorError::Stage(index, error) => (GenesisStage::from_stage_index(index), error), - }) - }; - - self.cancel_token.reset(); - - result - } -} diff --git a/lib/setup/src/genesis/unpack.rs b/lib/setup/src/genesis/unpack.rs deleted file mode 100644 index eda699b5..00000000 --- a/lib/setup/src/genesis/unpack.rs +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use upac::errors::CommonError; -use upac::orchestrator::Context; -use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; - -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; - -use upac_types::TmpPath; - -use super::{ctx_get, ctx_take}; - -use crate::error::SetupError; -use crate::types::{PendingPackagePaths, PendingPackages, TotalPackages, UnpackerState}; - -// No unit test: needs a real decoder + a real package archive to unpack, same untestable-in- -// isolation shape as `up install`'s own `PreparationStage` (lib/lib/src/mutated/installer). -pub struct UnpackPackageStage; - -impl Stage for UnpackPackageStage { - fn run( - &self, context: &mut Context, cancel: &CancelToken, mut progress: ProgressEventBuilder, - ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { - let mut pending_paths = ctx_take!(context, PendingPackagePaths); - let mut unpacker = ctx_take!(context, UnpackerState); - let mut pending_packages = ctx_take!(context, PendingPackages); - - let tmp_path = ctx_get!(context, TmpPath); - let total = ctx_get!(context, TotalPackages); - - let package_path = pending_paths.0.pop_front().ok_or(CommonError::MissingResult)?; - let index = pending_packages.0.len(); - - let (package, trigger) = unpacker - .0 - .unpack_one(&package_path, index, tmp_path.as_ref(), cancel) - .map_err(CommonError::Decoder)?; - - pending_packages.0.push_back((package, trigger)); - - let remaining = pending_paths.0.len() as u64; - let processed = total.0 - remaining; - progress = progress.subject(package_path).progress(processed, total.0); - - let result = if pending_paths.0.is_empty() { - StageResult::Advance - } else { - StageResult::Repeat - }; - - context.put(pending_paths); - context.put(unpacker); - context.put(pending_packages); - - Ok((progress, result, Box::new(NoRollback))) - } -} diff --git a/lib/setup/src/lib.rs b/lib/setup/src/lib.rs index 914201ed..cf82b2be 100644 --- a/lib/setup/src/lib.rs +++ b/lib/setup/src/lib.rs @@ -3,13 +3,90 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -pub mod data; -pub mod error; -pub mod format; -pub mod genesis; -pub mod layout { +use std::panic::{AssertUnwindSafe, catch_unwind}; + +use upac_abi::SETUP_ABI_VERSION; +use upac_abi::error::{CError, ErrorKind}; +use upac_abi::hook::CancelToken; +use upac_abi::request::{CSetupExistingRequest, CSetupWholeDiskRequest}; + +use upac_types::error::{try_convert_abi, write_error}; +use upac_types::states::SetupStateId; + +use self::stages::{SetupExistingData, SetupWholeDiskData, run_existing, run_whole_disk}; + +mod archive; +mod error; +mod format; +mod stages; +mod layout { include!(concat!(env!("OUT_DIR"), "/layout.rs")); } -pub mod partition; -pub mod target; -mod types; +mod partition; +mod target; + +/// # Safety +/// Touches no pointers — `unsafe extern "C"` only to match the ABI calling convention. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setup_abi_version() -> u32 { + SETUP_ABI_VERSION +} + +/// # Safety +/// `token`, if non-null, must point to a valid, initialized `CancelToken` for the duration of the +/// call. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cancel(token: *mut CancelToken) { + if token.is_null() { + return; + } + unsafe { (*token).cancel() }; +} + +/// # Safety +/// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the +/// call. `err_out`, if non-null, must point to writable `CError` storage. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setup_existing(request_c: CSetupExistingRequest, err_out: *mut CError) -> i32 { + let data = try_convert_abi!(SetupExistingData::try_from(&request_c), err_out, SetupStateId); + + let result = catch_unwind(AssertUnwindSafe(|| run_existing(data))); + + match result { + Ok(Ok(())) => 0, + + Ok(Err((state, error))) => { + unsafe { write_error(err_out, state, ErrorKind::from(error)) }; + -1 + } + + Err(_) => { + unsafe { write_error(err_out, SetupStateId::Setup, ErrorKind::Unexpected) }; + -1 + } + } +} + +/// # Safety +/// Any borrowed byte-slice fields inside `request_c` must remain valid for the duration of the +/// call. `err_out`, if non-null, must point to writable `CError` storage. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setup_whole_disk(request_c: CSetupWholeDiskRequest, err_out: *mut CError) -> i32 { + let data = try_convert_abi!(SetupWholeDiskData::try_from(&request_c), err_out, SetupStateId); + + let result = catch_unwind(AssertUnwindSafe(|| run_whole_disk(data))); + + match result { + Ok(Ok(())) => 0, + + Ok(Err((state, error))) => { + unsafe { write_error(err_out, state, ErrorKind::from(error)) }; + -1 + } + + Err(_) => { + unsafe { write_error(err_out, SetupStateId::Setup, ErrorKind::Unexpected) }; + -1 + } + } +} diff --git a/lib/setup/src/partition.rs b/lib/setup/src/partition.rs index bf406ec4..b0772a66 100644 --- a/lib/setup/src/partition.rs +++ b/lib/setup/src/partition.rs @@ -13,11 +13,11 @@ use gptman::{GPT, GPTPartitionEntry}; use uuid::{Uuid, uuid}; -use upac_types::PartitionSpec; +use upac_types::request::PartitionSpec; -use crate::error::SetupError; -use crate::format::FormatTarget; -use crate::layout::partition::{DEPLOY_LABEL, ESP_LABEL, SETTLE_ATTEMPTS, SETTLE_INTERVAL_MS}; +use super::error::SetupError; +use super::format::FormatTarget; +use super::layout::partition::{DEPLOY_LABEL, ESP_LABEL, SETTLE_ATTEMPTS, SETTLE_INTERVAL_MS}; #[cfg(test)] #[path = "../tests/inline/partition.rs"] @@ -133,6 +133,7 @@ impl DiskLayout { } GPT::write_protective_mbr_into(&mut device, sector_size)?; + gpt.write_into(&mut device)?; reread_partition_table(&mut device)?; @@ -217,3 +218,48 @@ impl DiskLayout { PathBuf::from(format!("{}{separator}{number}", self.device_path.display())) } } + +pub fn existing_esp_geometry(esp_device: &Path) -> Result<(u32, u64, u64, Uuid), SetupError> { + let (disk_path, esp_partition) = split_partition_device(esp_device)?; + + let mut device = File::open(disk_path)?; + let gpt = GPT::find_from(&mut device)?; + + let entry = gpt + .iter() + .find(|&(number, _)| number == esp_partition) + .map(|(_, entry)| entry) + .ok_or(SetupError::InvalidPartitionLayout)?; + + Ok(( + esp_partition, + entry.starting_lba, + entry.ending_lba, + Uuid::from_bytes_le(entry.unique_partition_guid), + )) +} + +fn split_partition_device(device: &Path) -> Result<(PathBuf, u32), SetupError> { + let name = device + .file_name() + .and_then(|name| name.to_str()) + .ok_or(SetupError::InvalidPartitionLayout)?; + + let digits_start = name.len() - name.chars().rev().take_while(char::is_ascii_digit).count(); + if digits_start == name.len() { + return Err(SetupError::InvalidPartitionLayout); + } + + let partition_number: u32 = name[digits_start..] + .parse() + .map_err(|_| SetupError::InvalidPartitionLayout)?; + + let mut disk_name = &name[..digits_start]; + if let Some(prefix) = disk_name.strip_suffix('p') { + if prefix.chars().next_back().is_some_and(|last| last.is_ascii_digit()) { + disk_name = prefix; + } + } + + Ok((device.with_file_name(disk_name), partition_number)) +} diff --git a/lib/setup/src/stages/boot.rs b/lib/setup/src/stages/boot.rs new file mode 100644 index 00000000..da8af654 --- /dev/null +++ b/lib/setup/src/stages/boot.rs @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::fs::{File, copy}; +use std::io::Read; + +use composefs::erofs::reader::erofs_to_filesystem; +use composefs::fsverity::FsVerityHashValue; + +use upac::boot::{WrittenBootEntry, write_boot_entry}; +use upac::layout::boot::{UPAC_UKI_FROM_SLOT, UPAC_UKI_TO_SLOT}; +use upac::orchestrator::context::{Context, ctx_get}; +use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; +use upac::plugin::boot::BootPlugins; + +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; +use upac_types::request::{BootPluginInstallRequest, BootPluginSetOneShotRequest}; + +use super::{DeployDigests, RequestedBootPlugin}; + +use crate::error::SetupError; +use crate::layout::genesis::EFI_LINUX_DIR; +use crate::target::TargetSysroot; + +pub struct StageBootStage; + +impl Stage for StageBootStage { + fn run( + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, + ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { + let target = ctx_get!(context, TargetSysroot); + let requested_boot_plugin = ctx_get!(context, RequestedBootPlugin); + let deploy_digests = ctx_get!(context, DeployDigests); + + let repository = target.repository(); + let prefix_digest_hex = deploy_digests.prefix.to_hex(); + let esp_mount_point = target.esp_mount_point(); + + let plugin = BootPlugins::new()?.load(&requested_boot_plugin)?; + + plugin.install(BootPluginInstallRequest { + esp_mount_point: esp_mount_point.to_string_lossy().into_owned(), + esp_partition_number: target.esp_partition_number(), + esp_starting_lba: target.esp_starting_lba(), + esp_ending_lba: target.esp_ending_lba(), + esp_unique_partition_guid: target.esp_unique_partition_guid().to_bytes_le(), + to_slot: UPAC_UKI_TO_SLOT.to_owned(), + from_slot: UPAC_UKI_FROM_SLOT.to_owned(), + })?; + + let (image, _enable_verity) = repository.open_image(prefix_digest_hex.as_str())?; + + let mut data = Vec::new(); + File::from(image).read_to_end(&mut data)?; + + let prefix_tree = erofs_to_filesystem(&data)?; + + let written = write_boot_entry( + repository, + &prefix_tree, + deploy_digests.prefix.clone(), + &esp_mount_point, + &prefix_digest_hex, + )?; + + if matches!(written, WrittenBootEntry::Uki(_)) { + let efi_linux = esp_mount_point.join(EFI_LINUX_DIR); + let to_path = efi_linux.join(format!("{UPAC_UKI_TO_SLOT}.efi")); + let from_path = efi_linux.join(format!("{UPAC_UKI_FROM_SLOT}.efi")); + copy(&to_path, &from_path)?; + } + + plugin.set_one_shot(BootPluginSetOneShotRequest { + entry_name: written.into_entry_name(), + })?; + + Ok((progress, StageResult::Advance, Box::new(NoRollback))) + } +} diff --git a/lib/setup/src/genesis/embed.rs b/lib/setup/src/stages/database.rs similarity index 71% rename from lib/setup/src/genesis/embed.rs rename to lib/setup/src/stages/database.rs index 7eaf3271..e2096fec 100644 --- a/lib/setup/src/genesis/embed.rs +++ b/lib/setup/src/stages/database.rs @@ -14,17 +14,18 @@ use upac::composefs::file::FileHandle; use upac::composefs::repository::commit_tree; use upac::database::InMemory; use upac::layout::database::DATABASE_PATH; -use upac::orchestrator::Context; +use upac::orchestrator::context::{Context, ctx_get, ctx_take}; use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use super::{ctx_get, ctx_take}; +use upac_types::hook::ProgressEventBuilder; + +use super::{ConfigState, DeployDigests, PrefixTree}; use crate::error::SetupError; use crate::layout::genesis::SCRATCH_FILENAME; use crate::target::TargetSysroot; -use crate::types::{ConfigDigest, ConfigTree, GenesisDatabase, PrefixDigest, PrefixTree}; #[cfg(test)] #[path = "../../tests/inline/embed.rs"] @@ -36,16 +37,15 @@ impl Stage for EmbedDatabaseStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { - let mut prefix_tree = ctx_take!(context, PrefixTree); - let config_tree = ctx_take!(context, ConfigTree); - let database = ctx_take!(context, GenesisDatabase); let mut import_ctx = ctx_take!(context, ImportContext); + let mut config_state = ctx_take!(context, ConfigState); + let mut prefix_tree = ctx_take!(context, PrefixTree); let target = ctx_get!(context, TargetSysroot); let repository = target.repository(); - let database_bytes = database.0.into_bytes()?; + let database_bytes = config_state.database.into_bytes()?; let database_scratch_path = temp_dir().join(SCRATCH_FILENAME); write(&database_scratch_path, &database_bytes)?; @@ -58,25 +58,27 @@ impl Stage for EmbedDatabaseStage { for ancestor in ancestors { let handle = FileHandle::new(ancestor); - if handle.stat_in_tree(&prefix_tree.0).is_err() { - handle.insert_in_tree(&mut prefix_tree.0, Stat::uninitialized())?; + if handle.stat_in_tree(&prefix_tree).is_err() { + handle.insert_in_tree(&mut prefix_tree, Stat::uninitialized())?; } } let database_handle = FileHandle::new(DATABASE_PATH); database_handle.insert_file( repository, - &mut prefix_tree.0, + &mut prefix_tree, &File::open(&database_scratch_path)?, Stat::uninitialized(), &mut import_ctx, )?; - let prefix_digest = commit_tree(repository, prefix_tree.0)?; - let config_digest = commit_tree(repository, config_tree.0)?; + let prefix_digest = commit_tree(repository, *prefix_tree)?; + let config_digest = commit_tree(repository, config_state.config_tree)?; - context.put(PrefixDigest(prefix_digest)); - context.put(ConfigDigest(config_digest)); + context.put(DeployDigests { + prefix: prefix_digest, + config: config_digest, + }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/setup/src/genesis/deploy.rs b/lib/setup/src/stages/deploy.rs similarity index 69% rename from lib/setup/src/genesis/deploy.rs rename to lib/setup/src/stages/deploy.rs index 9b9298a5..664942a5 100644 --- a/lib/setup/src/genesis/deploy.rs +++ b/lib/setup/src/stages/deploy.rs @@ -9,16 +9,17 @@ use composefs::fsverity::FsVerityHashValue; use upac::database::record::DeployRecord; use upac::fs::WrittenFile; -use upac::orchestrator::Context; +use upac::orchestrator::context::{Context, ctx_get}; use upac::orchestrator::stage::{RollbackGuard, Stage, StageResult}; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use super::ctx_get; +use upac_types::hook::ProgressEventBuilder; + +use super::{DeployDigests, Pinned}; use crate::error::SetupError; use crate::target::TargetSysroot; -use crate::types::{ConfigDigest, GenesisInput, PrefixDigest}; #[cfg(test)] #[path = "../../tests/inline/deploy.rs"] @@ -30,24 +31,22 @@ impl Stage for WriteDeployRecordStage { fn run( &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { + let deploy_digest = ctx_get!(context, DeployDigests); let target = ctx_get!(context, TargetSysroot); - let input = ctx_get!(context, GenesisInput); - let prefix_digest = ctx_get!(context, PrefixDigest); - let config_digest = ctx_get!(context, ConfigDigest); + let pinned = ctx_get!(context, Pinned); - let prefix_digest_hex = prefix_digest.0.to_hex(); - let deploy_dir = target.deploy_dir(&prefix_digest_hex); + let deploy_dir = target.deploy_dir(&deploy_digest.prefix.to_hex()); create_dir_all(&deploy_dir)?; let record = DeployRecord { - prefix_digest: prefix_digest_hex, + prefix_digest: deploy_digest.prefix.to_hex(), subject: "genesis".to_owned(), message: None, seq: DeployRecord::allocate_seq(&target.next_seq_path())?, timestamp: DeployRecord::now_secs(), config_history: Vec::new(), - working_config: config_digest.0.to_hex(), - pinned: input.pinned, + working_config: deploy_digest.config.to_hex(), + pinned: **pinned, }; let written_file = record.write(&deploy_dir)?; diff --git a/lib/setup/src/genesis/enumerate.rs b/lib/setup/src/stages/enumerate.rs similarity index 68% rename from lib/setup/src/genesis/enumerate.rs rename to lib/setup/src/stages/enumerate.rs index 16a2670b..03600f3a 100644 --- a/lib/setup/src/genesis/enumerate.rs +++ b/lib/setup/src/stages/enumerate.rs @@ -14,21 +14,18 @@ use tempfile::TempDir; use upac::database::{InMemory, MemoryDatabase}; use upac::errors::CommonError; -use upac::orchestrator::Context; +use upac::orchestrator::context::{Context, ctx_get}; use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; use upac::plugin::decoder::unpack::PackageUnpacker; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_types::TmpPath; +use upac_types::hook::ProgressEventBuilder; -use super::ctx_get; +use super::{ConfigState, PrefixTree, ResolvedSourceDir, SetupProgress, UnpackState}; use crate::error::SetupError; -use crate::types::{ - ConfigTree, GenesisDatabase, PendingPackagePaths, PendingPackages, PrefixTree, ResolvedSourceDir, TotalPackages, - UnpackerState, -}; #[cfg(test)] #[path = "../../tests/inline/enumerate.rs"] @@ -43,7 +40,7 @@ impl Stage for EnumeratePackagesStage { let resolved = ctx_get!(context, ResolvedSourceDir); let mut package_paths = Vec::new(); - for entry in read_dir(&resolved.0)? { + for entry in read_dir(&resolved)? { let entry = entry?; if entry.metadata()?.is_file() { @@ -53,21 +50,25 @@ impl Stage for EnumeratePackagesStage { let total = package_paths.len() as u64; - let unpacker = PackageUnpacker::new().map_err(CommonError::Decoder)?; let scratch = TempDir::new()?; let tmp_path = TmpPath(scratch.path().to_string_lossy().into_owned()); - let database = MemoryDatabase::new_in_memory()?; - context.put(PendingPackagePaths(VecDeque::from(package_paths))); - context.put(TotalPackages(total)); - context.put(UnpackerState(unpacker)); + context.put(SetupProgress { + pending: VecDeque::new(), + total, + }); + context.put(UnpackState { + unpacker: PackageUnpacker::new().map_err(CommonError::Decoder)?, + pending_paths: VecDeque::from(package_paths), + }); + context.put(ConfigState { + config_tree: FileSystem::new(Stat::uninitialized()), + database: MemoryDatabase::new_in_memory()?, + }); context.put(tmp_path); context.put(scratch); - context.put(PendingPackages(VecDeque::new())); - context.put(GenesisDatabase(database)); - context.put(PrefixTree(FileSystem::new(Stat::uninitialized()))); - context.put(ConfigTree(FileSystem::new(Stat::uninitialized()))); context.put(ImportContext::default()); + context.put(PrefixTree(FileSystem::new(Stat::uninitialized()))); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/setup/src/genesis/import.rs b/lib/setup/src/stages/import.rs similarity index 53% rename from lib/setup/src/genesis/import.rs rename to lib/setup/src/stages/import.rs index bafb3491..8b7cb962 100644 --- a/lib/setup/src/genesis/import.rs +++ b/lib/setup/src/stages/import.rs @@ -11,58 +11,60 @@ use upac::database::files::FileStoreMut; use upac::database::meta::MetaStoreMut; use upac::database::triggers::TriggerStoreMut; use upac::errors::CommonError; -use upac::orchestrator::Context; +use upac::orchestrator::context::{Context, ctx_get, ctx_take}; use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use upac_types::{FileEntry, FileEntryScope}; +use upac_types::entry::{FileEntry, FileEntryScope}; +use upac_types::hook::ProgressEventBuilder; -use super::{ctx_get, ctx_take, import_if_dir}; +use super::{ConfigState, EmptyConfig, PrefixTree, SetupProgress, import_if_dir}; use crate::error::SetupError; use crate::target::TargetSysroot; -use crate::types::{ConfigTree, GenesisDatabase, GenesisInput, PendingPackages, PrefixTree, TotalPackages}; -// No unit test: needs a real decoded package temp dir + a real composefs `Repository`, same -// untestable-in-isolation shape as `up install`'s own `ImportPackageStage` (lib/lib/src/mutated/installer). pub struct ImportPackageStage; impl Stage for ImportPackageStage { fn run( &self, context: &mut Context, cancel: &CancelToken, mut progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { - let mut pending_packages = ctx_take!(context, PendingPackages); + let mut setup_progress = ctx_take!(context, SetupProgress); + let mut imported_ctx = ctx_take!(context, ImportContext); let mut prefix_tree = ctx_take!(context, PrefixTree); - let mut config_tree = ctx_take!(context, ConfigTree); - let mut database = ctx_take!(context, GenesisDatabase); - let mut import_ctx = ctx_take!(context, ImportContext); + let mut config_state = ctx_take!(context, ConfigState); let target = ctx_get!(context, TargetSysroot); - let input = ctx_get!(context, GenesisInput); - let total = ctx_get!(context, TotalPackages); + let empty_config = ctx_get!(context, EmptyConfig); let repository = target.repository(); - let (package, trigger) = pending_packages.0.pop_front().ok_or(CommonError::MissingResult)?; + let (package, trigger) = setup_progress.pending.pop_front().ok_or(CommonError::MissingResult)?; let source_root = Path::new(&package.temp_package_path); let prefix_source = source_root.join("usr"); - let imported = import_if_dir!(repository, &mut prefix_tree.0, &prefix_source, &mut import_ctx, cancel); + let imported = import_if_dir!(repository, &mut prefix_tree, &prefix_source, &mut imported_ctx, cancel); let config_source = source_root.join("etc"); - let imported_config = if input.empty_config { + let imported_config = if **empty_config { Vec::new() } else { - import_if_dir!(repository, &mut config_tree.0, &config_source, &mut import_ctx, cancel) + import_if_dir!( + repository, + &mut config_state.config_tree, + &config_source, + &mut imported_ctx, + cancel + ) }; - let uuid = database.0.insert_package_meta(&package.meta)?; - database.0.set_declarative_triggers(uuid, &trigger)?; + let uuid = config_state.database.insert_package_meta(&package.meta)?; + config_state.database.set_declarative_triggers(uuid, &trigger)?; for path in imported { - database.0.insert_package_file( + config_state.database.insert_package_file( uuid, &FileEntry { path: path.to_string_lossy().into_owned(), @@ -73,7 +75,7 @@ impl Stage for ImportPackageStage { } for path in imported_config { - database.0.insert_package_file( + config_state.database.insert_package_file( uuid, &FileEntry { path: path.to_string_lossy().into_owned(), @@ -83,21 +85,22 @@ impl Stage for ImportPackageStage { )?; } - let remaining = pending_packages.0.len() as u64; - let processed = total.0 - remaining; - progress = progress.subject(package.meta.name.clone()).progress(processed, total.0); + let remaining = setup_progress.pending.len() as u64; + let processed = setup_progress.total - remaining; + progress = progress + .subject(package.meta.name.clone()) + .progress(processed, setup_progress.total); - let result = if pending_packages.0.is_empty() { + let result = if setup_progress.pending.is_empty() { StageResult::Advance } else { StageResult::Repeat }; - context.put(pending_packages); + context.put(setup_progress); + context.put(imported_ctx); + context.put(config_state); context.put(prefix_tree); - context.put(config_tree); - context.put(database); - context.put(import_ctx); Ok((progress, result, Box::new(NoRollback))) } diff --git a/lib/setup/src/stages/kernel.rs b/lib/setup/src/stages/kernel.rs new file mode 100644 index 00000000..df4a924b --- /dev/null +++ b/lib/setup/src/stages/kernel.rs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac::orchestrator::context::Context; +use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; + +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use crate::error::SetupError; + +pub struct KernelStage; + +impl Stage for KernelStage { + fn run( + &self, _context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, + ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { + todo!(); + + Ok((progress, StageResult::Advance, Box::new(NoRollback))) + } +} diff --git a/lib/setup/src/stages/mod.rs b/lib/setup/src/stages/mod.rs new file mode 100644 index 00000000..a660a7f3 --- /dev/null +++ b/lib/setup/src/stages/mod.rs @@ -0,0 +1,324 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::collections::VecDeque; +use std::os::raw::c_void; +use std::path::{Path, PathBuf}; + +use composefs::tree::FileSystem; + +use upac::composefs::repository::ObjectID; +use upac::database::MemoryDatabase; +use upac::errors::CommonError; +use upac::orchestrator::context::Context; +use upac::orchestrator::error::OrchestratorError; +use upac::orchestrator::{Orchestrator, SequentialOrchestrator}; +use upac::plugin::decoder::unpack::PackageUnpacker; + +use upac_abi::FsKind; +use upac_abi::HookMessageFn; +use upac_abi::error::ErrorKind; +use upac_abi::hook::CancelToken; +use upac_abi::request::{CSetupExistingRequest, CSetupWholeDiskRequest}; + +use upac_types::decoder::DeclarativeTrigger; +use upac_types::hook::Message; +use upac_types::package::PackageTemp; +use upac_types::request::{BtrfsOptions, GptLayout, PartitionMount, PartitionSpec}; +use upac_types::states::SetupStateId; +use upac_types::traits::MessageHook; + +use upac_macro::ContextValue; + +use self::boot::StageBootStage; +use self::database::EmbedDatabaseStage; +use self::deploy::WriteDeployRecordStage; +use self::enumerate::EnumeratePackagesStage; +use self::import::ImportPackageStage; +use self::kernel::KernelStage; +use self::prepare::PrepareSourceStage; +use self::system::ImportSystemStage; +use self::unpack::UnpackPackageStage; + +use super::error::SetupError; +use super::layout::mount::DEFAULT_MOUNT_POINT; +use super::partition::existing_esp_geometry; +use super::target::TargetSysroot; + +mod boot; +mod database; +mod deploy; +mod enumerate; +mod import; +mod kernel; +mod prepare; +mod system; +mod unpack; + +macro_rules! import_if_dir { + ($repository:expr, $tree:expr, $source:expr, $import_ctx:expr, $cancel:expr) => { + if $source.is_dir() { + upac::composefs::file::FileHandle::new(::std::path::PathBuf::new()).import_directory( + $repository, + $tree, + $source, + $import_ctx, + $cancel, + &mut |_| {}, + )? + } else { + Vec::new() + } + }; +} +pub(crate) use import_if_dir; + +pub struct SetupExistingData<'data> { + pub esp_device: &'data str, + pub deploy_device: &'data str, + pub deploy_fs: FsKind, + pub extra_mounts: Vec, + + pub mount_point: Option<&'data str>, + pub source: &'data str, + pub empty_config: bool, + pub pinned: bool, + pub boot_plugin: &'data str, + + pub hook_message: Option, + pub hook_message_context: *mut c_void, + + pub cancel_token: &'data CancelToken, +} + +impl<'data> TryFrom<&'data CSetupExistingRequest> for SetupExistingData<'data> { + type Error = ErrorKind; + + fn try_from(request: &'data CSetupExistingRequest) -> Result { + unsafe { request.validate()? }; + + let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + + Ok(SetupExistingData { + esp_device: (&request.esp_device).try_into()?, + deploy_device: (&request.deploy_device).try_into()?, + deploy_fs: request.deploy_fs, + extra_mounts: Vec::try_from(&request.extra_mounts)?, + + mount_point: (&request.mount_point).try_into()?, + source: (&request.source).try_into()?, + empty_config: request.empty_config, + pinned: request.pinned, + boot_plugin: (&request.boot_plugin).try_into()?, + + hook_message: request.base.on_hook, + hook_message_context: request.base.hook_ctx, + + cancel_token, + }) + } +} + +impl SetupExistingData<'_> { + pub fn mount_point(&self) -> &str { + self.mount_point.unwrap_or(DEFAULT_MOUNT_POINT) + } +} + +pub struct SetupWholeDiskData<'data> { + pub device_path: &'data str, + pub esp_size_mib: u64, + pub deploy_fs: FsKind, + pub deploy_size_mib: u64, + pub extra_partitions: Vec, + pub force_wipe: bool, + + pub node_size: u32, + pub sector_size: u32, + + pub mount_point: Option<&'data str>, + pub source: &'data str, + pub empty_config: bool, + pub pinned: bool, + pub boot_plugin: &'data str, + + pub hook_message: Option, + pub hook_message_context: *mut c_void, + + pub cancel_token: &'data CancelToken, +} + +impl SetupWholeDiskData<'_> { + pub fn mount_point(&self) -> &str { + self.mount_point.unwrap_or(DEFAULT_MOUNT_POINT) + } +} + +impl<'data> TryFrom<&'data CSetupWholeDiskRequest> for SetupWholeDiskData<'data> { + type Error = ErrorKind; + + fn try_from(request: &'data CSetupWholeDiskRequest) -> Result { + unsafe { request.validate()? }; + + let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; + + let gpt = GptLayout::try_from(&request.gpt)?; + let btrfs = BtrfsOptions::try_from(&request.btrfs)?; + + Ok(SetupWholeDiskData { + device_path: (&request.device_path).try_into()?, + esp_size_mib: gpt.esp_size_mib, + deploy_fs: gpt.deploy_fs, + deploy_size_mib: gpt.deploy_size_mib, + extra_partitions: gpt.extra_partitions, + force_wipe: gpt.force_wipe, + + node_size: btrfs.node_size, + sector_size: btrfs.sector_size, + + mount_point: (&request.mount_point).try_into()?, + source: (&request.source).try_into()?, + empty_config: request.empty_config, + pinned: request.pinned, + boot_plugin: (&request.boot_plugin).try_into()?, + + hook_message: request.base.on_hook, + hook_message_context: request.base.hook_ctx, + + cancel_token, + }) + } +} + +#[derive(ContextValue)] +pub(crate) struct RequestedSource(pub String); + +#[derive(ContextValue)] +pub(crate) struct EmptyConfig(pub bool); + +#[derive(ContextValue)] +pub(crate) struct Pinned(pub bool); + +#[derive(ContextValue)] +pub(crate) struct RequestedBootPlugin(pub String); + +#[derive(ContextValue)] +pub(crate) struct ResolvedSourceDir(pub PathBuf); + +#[derive(ContextValue)] +pub(crate) struct PrefixTree(pub FileSystem); + +pub(crate) struct UnpackState { + pub pending_paths: VecDeque, + pub unpacker: PackageUnpacker, +} + +pub(crate) struct SetupProgress { + pub pending: VecDeque<(PackageTemp, DeclarativeTrigger)>, + pub total: u64, +} + +pub(crate) struct ConfigState { + pub config_tree: FileSystem, + pub database: MemoryDatabase, +} + +pub(crate) struct DeployDigests { + pub prefix: ObjectID, + pub config: ObjectID, +} + +pub fn run_existing(data: SetupExistingData) -> Result<(), (SetupStateId, SetupError)> { + let esp_device = Path::new(data.esp_device); + let (esp_partition_number, esp_starting_lba, esp_ending_lba, esp_unique_partition_guid) = + existing_esp_geometry(esp_device).map_err(|error| (SetupStateId::Setup, error))?; + + let target = TargetSysroot::new( + Path::new(data.deploy_device), + data.deploy_fs, + esp_device, + PathBuf::from(data.mount_point()), + &data.extra_mounts, + esp_partition_number, + esp_starting_lba, + esp_ending_lba, + esp_unique_partition_guid, + ) + .map_err(|error| (SetupStateId::Setup, error))?; + + let mut context = Context::new(); + context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); + context.put(target); + context.put(RequestedSource(data.source.to_owned())); + context.put(EmptyConfig(data.empty_config)); + context.put(Pinned(data.pinned)); + context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); + + let orchestrator = SequentialOrchestrator::new(vec![ + Box::new(PrepareSourceStage), + Box::new(EnumeratePackagesStage), + Box::new(UnpackPackageStage), + Box::new(ImportPackageStage), + Box::new(ImportSystemStage), + Box::new(EmbedDatabaseStage), + Box::new(WriteDeployRecordStage), + Box::new(KernelStage), + Box::new(StageBootStage), + ]); + + let result = if orchestrator.validate(&context).is_err() { + Err((SetupStateId::Setup, SetupError::from(CommonError::PipelineInvalid))) + } else { + orchestrator + .run_exclusive(&mut context, data.cancel_token) + .map_err(|failure| match failure { + OrchestratorError::Setup(lock_error) => (SetupStateId::Setup, SetupError::from(lock_error)), + OrchestratorError::Stage(index, error) => (SetupStateId::from_stage_index(index), error), + }) + }; + + data.cancel_token.reset(); + + result +} + +pub fn run_whole_disk(data: SetupWholeDiskData) -> Result<(), (SetupStateId, SetupError)> { + let target = TargetSysroot::create_whole_disk(&data).map_err(|error| (SetupStateId::Setup, error))?; + + let mut context = Context::new(); + context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); + context.put(target); + context.put(RequestedSource(data.source.to_owned())); + context.put(EmptyConfig(data.empty_config)); + context.put(Pinned(data.pinned)); + context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); + + let orchestrator = SequentialOrchestrator::new(vec![ + Box::new(PrepareSourceStage), + Box::new(EnumeratePackagesStage), + Box::new(UnpackPackageStage), + Box::new(ImportPackageStage), + Box::new(ImportSystemStage), + Box::new(EmbedDatabaseStage), + Box::new(WriteDeployRecordStage), + Box::new(KernelStage), + Box::new(StageBootStage), + ]); + + let result = if orchestrator.validate(&context).is_err() { + Err((SetupStateId::Setup, SetupError::from(CommonError::PipelineInvalid))) + } else { + orchestrator + .run_exclusive(&mut context, data.cancel_token) + .map_err(|failure| match failure { + OrchestratorError::Setup(lock_error) => (SetupStateId::Setup, SetupError::from(lock_error)), + OrchestratorError::Stage(index, error) => (SetupStateId::from_stage_index(index), error), + }) + }; + + data.cancel_token.reset(); + + result +} diff --git a/lib/setup/src/stages/prepare.rs b/lib/setup/src/stages/prepare.rs new file mode 100644 index 00000000..5307b4a9 --- /dev/null +++ b/lib/setup/src/stages/prepare.rs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::fs::metadata; +use std::path::Path; + +use tempfile::TempDir; + +use upac::orchestrator::context::{Context, ctx_get}; +use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; + +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use super::{RequestedSource, ResolvedSourceDir}; + +use crate::archive::SourceArchive; +use crate::error::SetupError; + +pub struct PrepareSourceStage; + +impl Stage for PrepareSourceStage { + fn run( + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, + ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { + let source = ctx_get!(context, RequestedSource); + + let source_path = Path::new(source.as_str()); + + if metadata(source_path)?.is_dir() { + context.put(ResolvedSourceDir(source_path.to_path_buf())); + return Ok((progress, StageResult::Advance, Box::new(NoRollback))); + } + + let archive = SourceArchive::sniff(source_path)?; + let scratch = TempDir::new()?; + archive.extract(scratch.path())?; + + context.put(ResolvedSourceDir(scratch.path().to_path_buf())); + context.put(scratch); + + Ok((progress, StageResult::Advance, Box::new(NoRollback))) + } +} diff --git a/lib/setup/src/genesis/system.rs b/lib/setup/src/stages/system.rs similarity index 55% rename from lib/setup/src/genesis/system.rs rename to lib/setup/src/stages/system.rs index 1615e08e..24f0b233 100644 --- a/lib/setup/src/genesis/system.rs +++ b/lib/setup/src/stages/system.rs @@ -7,26 +7,23 @@ use std::path::Path; use composefs::generic_tree::Stat; use composefs::repository::ImportContext; -use composefs::tree::FileSystem; use upac::composefs::file::FileHandle; -use upac::composefs::repository::ObjectID; -use upac::orchestrator::Context; +use upac::orchestrator::context::{Context, ctx_get, ctx_take}; use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; -use super::{ctx_get, ctx_take, import_if_dir}; +use upac_types::hook::ProgressEventBuilder; + +use super::{PrefixTree, ResolvedSourceDir, import_if_dir}; use crate::error::SetupError; use crate::layout::genesis::{ COMPOSEFS_SETUP_ROOT_UNIT_PATH, COMPOSEFS_SETUP_ROOT_WANTS_PATH, COMPOSEFS_SETUP_ROOT_WANTS_TARGET, SYSTEM_DIR, }; use crate::target::TargetSysroot; -use crate::types::{PrefixTree, ResolvedSourceDir}; -// No unit test: needs a real filesystem tree to import + a real composefs `Repository`, same -// untestable-in-isolation shape as `ImportPackageStage`. pub struct ImportSystemStage; impl Stage for ImportSystemStage { @@ -34,49 +31,44 @@ impl Stage for ImportSystemStage { &self, context: &mut Context, cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { let mut prefix_tree = ctx_take!(context, PrefixTree); - let mut import_ctx = ctx_take!(context, ImportContext); + let mut imported_ctx = ctx_take!(context, ImportContext); let resolved = ctx_get!(context, ResolvedSourceDir); let target = ctx_get!(context, TargetSysroot); let repository = target.repository(); - let system_dir = resolved.0.join(SYSTEM_DIR); + let system_dir = resolved.join(SYSTEM_DIR); let unit_source = system_dir.join(COMPOSEFS_SETUP_ROOT_UNIT_PATH); if !unit_source.is_file() { return Err(SetupError::ComposefsSetupRootUnitNotFound); } - import_if_dir!(repository, &mut prefix_tree.0, &system_dir, &mut import_ctx, cancel); + import_if_dir!(repository, &mut prefix_tree, &system_dir, &mut imported_ctx, cancel); + + let mut ancestors: Vec<&Path> = Path::new(COMPOSEFS_SETUP_ROOT_WANTS_PATH) + .ancestors() + .skip(1) + .filter(|ancestor| !ancestor.as_os_str().is_empty()) + .collect(); + ancestors.reverse(); + + for ancestor in ancestors { + let handle = FileHandle::new(ancestor); + if handle.stat_in_tree(&mut prefix_tree).is_err() { + handle.insert_in_tree(&mut prefix_tree, Stat::uninitialized())?; + } + } - ensure_ancestor_dirs(COMPOSEFS_SETUP_ROOT_WANTS_PATH, &mut prefix_tree.0)?; FileHandle::new(COMPOSEFS_SETUP_ROOT_WANTS_PATH).symlink_in_tree( - &mut prefix_tree.0, + &mut prefix_tree, COMPOSEFS_SETUP_ROOT_WANTS_TARGET, Stat::uninitialized(), )?; + context.put(imported_ctx); context.put(prefix_tree); - context.put(import_ctx); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } } - -fn ensure_ancestor_dirs(path: &str, tree: &mut FileSystem) -> Result<(), SetupError> { - let mut ancestors: Vec<&Path> = Path::new(path) - .ancestors() - .skip(1) - .filter(|ancestor| !ancestor.as_os_str().is_empty()) - .collect(); - ancestors.reverse(); - - for ancestor in ancestors { - let handle = FileHandle::new(ancestor); - if handle.stat_in_tree(tree).is_err() { - handle.insert_in_tree(tree, Stat::uninitialized())?; - } - } - - Ok(()) -} diff --git a/lib/setup/src/stages/unpack.rs b/lib/setup/src/stages/unpack.rs new file mode 100644 index 00000000..6cee6f9a --- /dev/null +++ b/lib/setup/src/stages/unpack.rs @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use upac::errors::CommonError; +use upac::orchestrator::context::{Context, ctx_get, ctx_take}; +use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; + +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; + +use upac_types::TmpPath; + +use super::{SetupProgress, UnpackState}; + +use crate::error::SetupError; + +pub struct UnpackPackageStage; + +impl Stage for UnpackPackageStage { + fn run( + &self, context: &mut Context, cancel: &CancelToken, mut progress: ProgressEventBuilder, + ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { + let mut setup_progress = ctx_take!(context, SetupProgress); + let mut unpack_state = ctx_take!(context, UnpackState); + + let tmp_path = ctx_get!(context, TmpPath); + + let package_path = unpack_state + .pending_paths + .pop_front() + .ok_or(CommonError::MissingResult)?; + let index = setup_progress.pending.len(); + + let (package, trigger) = unpack_state + .unpacker + .unpack_one(&package_path, index, tmp_path.as_ref(), cancel) + .map_err(CommonError::Decoder)?; + + setup_progress.pending.push_back((package, trigger)); + + let remaining = setup_progress.pending.len() as u64; + let processed = setup_progress.total - remaining; + progress = progress.subject(package_path).progress(processed, setup_progress.total); + + let result = if unpack_state.pending_paths.is_empty() { + StageResult::Advance + } else { + StageResult::Repeat + }; + + context.put(setup_progress); + context.put(unpack_state); + + Ok((progress, result, Box::new(NoRollback))) + } +} diff --git a/lib/setup/src/target.rs b/lib/setup/src/target.rs index f10e526f..90ad7eca 100644 --- a/lib/setup/src/target.rs +++ b/lib/setup/src/target.rs @@ -19,23 +19,24 @@ use upac_abi::FsKind; use uuid::Uuid; -use upac_types::PartitionMount; +use upac_types::request::PartitionMount; -use crate::data::SetupWholeDiskData; -use crate::error::SetupError; -use crate::format::FormatTarget; -use crate::layout::partition::{DEPLOY_LABEL, ESP_LABEL}; -use crate::partition::DiskLayout; +use super::error::SetupError; +use super::format::FormatTarget; + +use super::layout::partition::{DEPLOY_LABEL, ESP_LABEL}; +use super::partition::DiskLayout; +use super::stages::SetupWholeDiskData; pub struct TargetSysroot { mount_point: PathBuf, deploy_dir: PathBuf, repository: ManuallyDrop>, mounted: Vec, - esp_partition_number: Option, - esp_starting_lba: Option, - esp_ending_lba: Option, - esp_unique_partition_guid: Option, + esp_partition_number: u32, + esp_starting_lba: u64, + esp_ending_lba: u64, + esp_unique_partition_guid: Uuid, } impl TargetSysroot { @@ -45,8 +46,8 @@ impl TargetSysroot { )] pub fn new( deploy_device: &Path, deploy_fs: FsKind, esp_device: &Path, mount_point: PathBuf, - extra_mounts: &[PartitionMount], esp_partition_number: Option, esp_starting_lba: Option, - esp_ending_lba: Option, esp_unique_partition_guid: Option, + extra_mounts: &[PartitionMount], esp_partition_number: u32, esp_starting_lba: u64, esp_ending_lba: u64, + esp_unique_partition_guid: Uuid, ) -> Result { create_dir_all(&mount_point)?; @@ -103,7 +104,7 @@ impl TargetSysroot { }) } - pub fn create_whole_disk(data: &SetupWholeDiskData) -> Result { + pub fn create_whole_disk(data: &SetupWholeDiskData<'_>) -> Result { let layout = DiskLayout::create( Path::new(data.device_path), data.esp_size_mib, @@ -150,10 +151,10 @@ impl TargetSysroot { &esp_path, PathBuf::from(data.mount_point()), &extra_mounts, - Some(layout.esp_partition_number()), - Some(layout.esp_starting_lba()), - Some(layout.esp_ending_lba()), - Some(layout.esp_unique_partition_guid()), + layout.esp_partition_number(), + layout.esp_starting_lba(), + layout.esp_ending_lba(), + layout.esp_unique_partition_guid(), ) } @@ -173,19 +174,19 @@ impl TargetSysroot { self.mount_point.join(ESP_MOUNT_PRIMARY.trim_start_matches('/')) } - pub fn esp_partition_number(&self) -> Option { + pub fn esp_partition_number(&self) -> u32 { self.esp_partition_number } - pub fn esp_starting_lba(&self) -> Option { + pub fn esp_starting_lba(&self) -> u64 { self.esp_starting_lba } - pub fn esp_ending_lba(&self) -> Option { + pub fn esp_ending_lba(&self) -> u64 { self.esp_ending_lba } - pub fn esp_unique_partition_guid(&self) -> Option { + pub fn esp_unique_partition_guid(&self) -> Uuid { self.esp_unique_partition_guid } } @@ -223,10 +224,10 @@ impl TargetSysroot { deploy_dir, repository: ManuallyDrop::new(repository), mounted: Vec::new(), - esp_partition_number: None, - esp_starting_lba: None, - esp_ending_lba: None, - esp_unique_partition_guid: None, + esp_partition_number: 0, + esp_starting_lba: 0, + esp_ending_lba: 0, + esp_unique_partition_guid: Uuid::nil(), }) } } diff --git a/lib/setup/src/types.rs b/lib/setup/src/types.rs deleted file mode 100644 index 60adac8a..00000000 --- a/lib/setup/src/types.rs +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::collections::VecDeque; -use std::path::PathBuf; - -use composefs::tree::FileSystem; - -use upac::composefs::repository::ObjectID; -use upac::database::MemoryDatabase; -use upac::plugin::decoder::unpack::PackageUnpacker; - -use upac_types::{DeclarativeTrigger, PackageTemp}; - -use crate::data::{SetupExistingData, SetupWholeDiskData}; - -pub(crate) struct GenesisInput { - pub source: String, - pub empty_config: bool, - pub pinned: bool, - pub boot_plugin: Option, -} - -pub(crate) struct ResolvedSourceDir(pub PathBuf); - -pub(crate) struct PendingPackagePaths(pub VecDeque); - -pub(crate) struct TotalPackages(pub u64); - -pub(crate) struct UnpackerState(pub PackageUnpacker); - -pub(crate) struct PendingPackages(pub VecDeque<(PackageTemp, DeclarativeTrigger)>); - -pub(crate) struct PrefixTree(pub FileSystem); - -pub(crate) struct ConfigTree(pub FileSystem); - -pub(crate) struct GenesisDatabase(pub MemoryDatabase); - -pub(crate) struct PrefixDigest(pub ObjectID); - -pub(crate) struct ConfigDigest(pub ObjectID); - -impl From<&SetupExistingData<'_>> for GenesisInput { - fn from(data: &SetupExistingData<'_>) -> Self { - GenesisInput { - source: data.source.to_owned(), - empty_config: data.empty_config, - pinned: data.pinned, - boot_plugin: data.boot_plugin.map(str::to_owned), - } - } -} - -impl From<&SetupWholeDiskData<'_>> for GenesisInput { - fn from(data: &SetupWholeDiskData<'_>) -> Self { - GenesisInput { - source: data.source.to_owned(), - empty_config: data.empty_config, - pinned: data.pinned, - boot_plugin: data.boot_plugin.map(str::to_owned), - } - } -} diff --git a/lib/setup/tests/inline/deploy.rs b/lib/setup/tests/inline/deploy.rs index 391e6d42..4b9c492c 100644 --- a/lib/setup/tests/inline/deploy.rs +++ b/lib/setup/tests/inline/deploy.rs @@ -9,25 +9,17 @@ use tempfile::TempDir; use upac::composefs::repository::ObjectID; use upac::database::record::DeployRecord; -use upac::orchestrator::Context; +use upac::orchestrator::context::Context; use upac::orchestrator::stage::{Stage, StageResult}; use upac_abi::hook::{CancelToken, ProgressEventBuilder}; use crate::target::TargetSysroot; -use crate::types::{ConfigDigest, GenesisInput, PrefixDigest}; +use super::super::Pinned; +use super::super::database::{ConfigDigest, PrefixDigest}; use super::WriteDeployRecordStage; -fn genesis_input(pinned: bool) -> GenesisInput { - GenesisInput { - source: String::new(), - empty_config: false, - pinned, - boot_plugin: None, - } -} - #[test] fn run_writes_a_deploy_record_readable_back_from_disk() { let scratch = TempDir::new().unwrap(); @@ -35,7 +27,7 @@ fn run_writes_a_deploy_record_readable_back_from_disk() { let mut context = Context::new(); context.put(target); - context.put(genesis_input(true)); + context.put(Pinned(true)); context.put(PrefixDigest(ObjectID::EMPTY)); context.put(ConfigDigest(ObjectID::EMPTY)); @@ -61,7 +53,7 @@ fn run_writes_a_deploy_record_readable_back_from_disk() { #[test] fn run_fails_when_target_missing_from_context() { let mut context = Context::new(); - context.put(genesis_input(false)); + context.put(Pinned(false)); context.put(PrefixDigest(ObjectID::EMPTY)); context.put(ConfigDigest(ObjectID::EMPTY)); diff --git a/lib/setup/tests/inline/embed.rs b/lib/setup/tests/inline/embed.rs index 64601588..46470eec 100644 --- a/lib/setup/tests/inline/embed.rs +++ b/lib/setup/tests/inline/embed.rs @@ -10,15 +10,15 @@ use composefs::tree::FileSystem; use tempfile::TempDir; use upac::database::{InMemory, MemoryDatabase}; -use upac::orchestrator::Context; +use upac::orchestrator::context::Context; use upac::orchestrator::stage::{Stage, StageResult}; use upac_abi::hook::{CancelToken, ProgressEventBuilder}; use crate::target::TargetSysroot; -use crate::types::{ConfigDigest, ConfigTree, GenesisDatabase, PrefixDigest, PrefixTree}; -use super::EmbedDatabaseStage; +use super::super::enumerate::{ConfigTree, GenesisDatabase, PrefixTree}; +use super::{ConfigDigest, EmbedDatabaseStage, PrefixDigest}; #[test] fn run_commits_both_trees_and_puts_digests() { diff --git a/lib/setup/tests/inline/enumerate.rs b/lib/setup/tests/inline/enumerate.rs index 779d039c..fa29aff8 100644 --- a/lib/setup/tests/inline/enumerate.rs +++ b/lib/setup/tests/inline/enumerate.rs @@ -7,20 +7,19 @@ use std::fs::{create_dir_all, write}; use tempfile::TempDir; -use upac::orchestrator::Context; +use upac::orchestrator::context::Context; use upac::orchestrator::stage::{Stage, StageResult}; use upac_abi::hook::{CancelToken, ProgressEventBuilder}; use upac_types::TmpPath; -use crate::types::{ - ConfigTree, GenesisDatabase, PendingPackagePaths, PendingPackages, PrefixTree, ResolvedSourceDir, TotalPackages, - UnpackerState, +use super::super::prepare::ResolvedSourceDir; +use super::{ + ConfigTree, EnumeratePackagesStage, GenesisDatabase, PendingPackagePaths, PendingPackages, PrefixTree, + TotalPackages, UnpackerState, }; -use super::EnumeratePackagesStage; - #[test] fn run_lists_only_files_and_initializes_pipeline_state() { let source = TempDir::new().unwrap(); diff --git a/lib/setup/tests/inline/format.rs b/lib/setup/tests/inline/format.rs index 6af4bca4..ea3382bb 100644 --- a/lib/setup/tests/inline/format.rs +++ b/lib/setup/tests/inline/format.rs @@ -7,7 +7,7 @@ use super::fat_label; #[test] fn fat_label_pads_short_labels_with_trailing_spaces() { - let label = fat_label("esp"); + let label = fat_label!("esp"); assert_eq!(&label[..3], b"ESP"); assert_eq!(&label[3..], [b' '; 8]); @@ -15,7 +15,7 @@ fn fat_label_pads_short_labels_with_trailing_spaces() { #[test] fn fat_label_uppercases_input() { - let label = fat_label("boot"); + let label = fat_label!("boot"); assert_eq!(&label[..4], b"BOOT"); assert_eq!(&label[4..], [b' '; 7]); @@ -23,7 +23,7 @@ fn fat_label_uppercases_input() { #[test] fn fat_label_truncates_labels_longer_than_eleven_bytes() { - let label = fat_label("a-very-long-label"); + let label = fat_label!("a-very-long-label"); assert_eq!(&label, b"A-VERY-LONG"); } diff --git a/lib/setup/tests/inline/source.rs b/lib/setup/tests/inline/source.rs deleted file mode 100644 index 39ed8c57..00000000 --- a/lib/setup/tests/inline/source.rs +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::{self, File}; -use std::io::Write; -use std::path::Path; - -use tar::{Builder as TarBuilder, Header as TarHeader}; -use tempfile::TempDir; -use xz2::write::XzEncoder; -use zip::ZipWriter; -use zip::write::SimpleFileOptions; - -use super::SourceArchive; - -fn write_plain_tar(path: &Path, entry_name: &str, content: &[u8]) { - let file = File::create(path).unwrap(); - let mut builder = TarBuilder::new(file); - - let mut header = TarHeader::new_gnu(); - header.set_size(content.len() as u64); - header.set_cksum(); - builder.append_data(&mut header, entry_name, content).unwrap(); - builder.finish().unwrap(); -} - -fn assert_round_trip(archive_path: &Path, entry_name: &str, content: &[u8]) { - let archive = SourceArchive::sniff(archive_path).unwrap(); - - let destination = TempDir::new().unwrap(); - archive.extract(destination.path()).unwrap(); - - let extracted = fs::read(destination.path().join(entry_name)).unwrap(); - assert_eq!(extracted, content); -} - -#[test] -fn sniff_and_extract_round_trip_plain_tar() { - let scratch = TempDir::new().unwrap(); - let archive_path = scratch.path().join("source.tar"); - write_plain_tar(&archive_path, "hello.txt", b"hello from plain tar"); - - assert_round_trip(&archive_path, "hello.txt", b"hello from plain tar"); -} - -#[test] -fn sniff_and_extract_round_trip_gzip_tar() { - let scratch = TempDir::new().unwrap(); - let tar_path = scratch.path().join("source.tar"); - write_plain_tar(&tar_path, "hello.txt", b"hello from gzip tar"); - let tar_bytes = fs::read(&tar_path).unwrap(); - - let archive_path = scratch.path().join("source.tar.gz"); - let out = File::create(&archive_path).unwrap(); - let mut encoder = flate2::write::GzEncoder::new(out, flate2::Compression::default()); - encoder.write_all(&tar_bytes).unwrap(); - encoder.finish().unwrap(); - - assert_round_trip(&archive_path, "hello.txt", b"hello from gzip tar"); -} - -#[test] -fn sniff_and_extract_round_trip_xz_tar() { - let scratch = TempDir::new().unwrap(); - let tar_path = scratch.path().join("source.tar"); - write_plain_tar(&tar_path, "hello.txt", b"hello from xz tar"); - let tar_bytes = fs::read(&tar_path).unwrap(); - - let archive_path = scratch.path().join("source.tar.xz"); - let out = File::create(&archive_path).unwrap(); - let mut encoder = XzEncoder::new(out, 6); - encoder.write_all(&tar_bytes).unwrap(); - encoder.finish().unwrap(); - - assert_round_trip(&archive_path, "hello.txt", b"hello from xz tar"); -} - -#[test] -fn sniff_and_extract_round_trip_zstd_tar() { - let scratch = TempDir::new().unwrap(); - let tar_path = scratch.path().join("source.tar"); - write_plain_tar(&tar_path, "hello.txt", b"hello from zstd tar"); - let tar_bytes = fs::read(&tar_path).unwrap(); - - let archive_path = scratch.path().join("source.tar.zst"); - let compressed = zstd::encode_all(&tar_bytes[..], 0).unwrap(); - fs::write(&archive_path, compressed).unwrap(); - - assert_round_trip(&archive_path, "hello.txt", b"hello from zstd tar"); -} - -#[test] -fn sniff_and_extract_round_trip_zip() { - let scratch = TempDir::new().unwrap(); - let archive_path = scratch.path().join("source.zip"); - - let file = File::create(&archive_path).unwrap(); - let mut writer = ZipWriter::new(file); - writer.start_file("hello.txt", SimpleFileOptions::default()).unwrap(); - writer.write_all(b"hello from zip").unwrap(); - writer.finish().unwrap(); - - assert_round_trip(&archive_path, "hello.txt", b"hello from zip"); -} - -#[test] -fn sniff_detects_sevenzip_magic_without_a_full_archive() { - let scratch = TempDir::new().unwrap(); - let archive_path = scratch.path().join("source.7z"); - fs::write(&archive_path, [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C, 0x00, 0x04]).unwrap(); - - let archive = SourceArchive::sniff(&archive_path).unwrap(); - - assert!(matches!(archive, SourceArchive::SevenZip(_))); -} From c68931a8e66a6d89be26abfa20d596d2534fc107 Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 01:30:06 +0400 Subject: [PATCH 125/148] fix: update files/uninstaller checkout.rs for write_boot_entry's WrittenBootEntry return type, matching installer/update/rollback's already-fixed call sites Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/mutated/files/checkout.rs | 7 +++++-- lib/lib/src/mutated/uninstaller/checkout.rs | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/lib/src/mutated/files/checkout.rs b/lib/lib/src/mutated/files/checkout.rs index 49bb854e..3fdac6e1 100644 --- a/lib/lib/src/mutated/files/checkout.rs +++ b/lib/lib/src/mutated/files/checkout.rs @@ -31,11 +31,14 @@ impl Stage for CheckoutStage { let digest = object_id_from_hex(new_prefix)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &deploy_tree, digest, &esp_mount, new_prefix)?; + let written = write_boot_entry(&repository, &deploy_tree, digest, &esp_mount, new_prefix)?; let plugin = BootPlugins::new()?.load(requested_boot_plugins)?; - context.put(ResolvedBootEntry { plugin, entry_name }); + context.put(ResolvedBootEntry { + plugin, + entry_name: written.into_entry_name(), + }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } diff --git a/lib/lib/src/mutated/uninstaller/checkout.rs b/lib/lib/src/mutated/uninstaller/checkout.rs index 7251d3fb..c4cc0906 100644 --- a/lib/lib/src/mutated/uninstaller/checkout.rs +++ b/lib/lib/src/mutated/uninstaller/checkout.rs @@ -31,11 +31,14 @@ impl Stage for CheckoutStage { let digest = object_id_from_hex(&new_state.prefix_digest)?; let esp_mount = find_esp_mount()?; - let entry_name = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; + let written = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; let plugin = BootPlugins::new()?.load(requested_boot_plugin)?; - context.put(ResolvedBootEntry { plugin, entry_name }); + context.put(ResolvedBootEntry { + plugin, + entry_name: written.into_entry_name(), + }); Ok((progress, StageResult::Advance, Box::new(NoRollback))) } From b01d6362888b7441d7b0ad5b7fb09f9d06a5d7d0 Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 01:30:24 +0400 Subject: [PATCH 126/148] fix: add setup abi vertion const Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/abi/src/lib.rs b/lib/abi/src/lib.rs index e78a3a13..69267adf 100644 --- a/lib/abi/src/lib.rs +++ b/lib/abi/src/lib.rs @@ -23,6 +23,7 @@ pub mod types; pub const LIB_ABI_VERSION: u32 = 2; pub const BOOT_ABI_VERSION: u32 = 2; pub const DECODER_ABI_VERSION: u32 = 2; +pub const SETUP_ABI_VERSION: u32 = 2; pub const CONSTRAINT_LESS: u8 = 0b001; pub const CONSTRAINT_EQUAL: u8 = 0b010; From 4a7244921084e82bd35757849dc1a89f20543b2b Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 01:58:56 +0400 Subject: [PATCH 127/148] fix: fixed Clippy suggestions; exported function `cancel` is now `setup_cancel` Co-Authored-By: Claude Sonnet 5 --- lib/setup/lib.toml | 5 ----- lib/setup/src/lib.rs | 2 +- lib/setup/src/partition.rs | 8 ++++---- lib/setup/src/stages/boot.rs | 2 +- lib/setup/src/stages/database.rs | 4 ++-- lib/setup/src/stages/enumerate.rs | 2 +- lib/setup/src/stages/kernel.rs | 2 -- lib/setup/src/stages/system.rs | 2 +- 8 files changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/setup/lib.toml b/lib/setup/lib.toml index 6d454eb2..2d4322f4 100644 --- a/lib/setup/lib.toml +++ b/lib/setup/lib.toml @@ -38,10 +38,6 @@ settle_interval_ms = 100 # written (under the OS temp dir) before being embedded into the new /usr # tree — purely transient, read back once right after the write. # -# esp_fallback_loader is the fixed "removable media" path UEFI firmware always tries when NVRAM -# has no Boot#### entries at all, which is exactly genesis's situation on a brand-new disk. The -# resolved boot plugin's own `esp_loader_source()` (see `booters/booter.toml`) says which source -# path — if any — a fresh ESP needs it copied from; genesis only owns the destination. # efi_linux_dir is a real, mounted-filesystem-relative path (forward slashes, joined via # `Path::join`) to where `composefs-boot`'s own UKI writer always lands `upac-to.efi`/ # `upac-from.efi` (`EFI/Linux/`, hardcoded upstream) — used only to seed `upac-from.efi` with the @@ -69,7 +65,6 @@ settle_interval_ms = 100 # not guessed). [genesis] scratch_filename = "genesis-packages.redb" -esp_fallback_loader = "EFI/BOOT/BOOTX64.EFI" efi_linux_dir = "EFI/Linux" system_dir = "system" composefs_setup_root_unit_path = "lib/systemd/system/composefs-setup-root.service" diff --git a/lib/setup/src/lib.rs b/lib/setup/src/lib.rs index cf82b2be..84d23b2a 100644 --- a/lib/setup/src/lib.rs +++ b/lib/setup/src/lib.rs @@ -36,7 +36,7 @@ pub unsafe extern "C" fn setup_abi_version() -> u32 { /// `token`, if non-null, must point to a valid, initialized `CancelToken` for the duration of the /// call. #[unsafe(no_mangle)] -pub unsafe extern "C" fn cancel(token: *mut CancelToken) { +pub unsafe extern "C" fn setup_cancel(token: *mut CancelToken) { if token.is_null() { return; } diff --git a/lib/setup/src/partition.rs b/lib/setup/src/partition.rs index b0772a66..6806a004 100644 --- a/lib/setup/src/partition.rs +++ b/lib/setup/src/partition.rs @@ -255,10 +255,10 @@ fn split_partition_device(device: &Path) -> Result<(PathBuf, u32), SetupError> { .map_err(|_| SetupError::InvalidPartitionLayout)?; let mut disk_name = &name[..digits_start]; - if let Some(prefix) = disk_name.strip_suffix('p') { - if prefix.chars().next_back().is_some_and(|last| last.is_ascii_digit()) { - disk_name = prefix; - } + if let Some(prefix) = disk_name.strip_suffix('p') + && prefix.chars().next_back().is_some_and(|last| last.is_ascii_digit()) + { + disk_name = prefix; } Ok((device.with_file_name(disk_name), partition_number)) diff --git a/lib/setup/src/stages/boot.rs b/lib/setup/src/stages/boot.rs index da8af654..b76c98b7 100644 --- a/lib/setup/src/stages/boot.rs +++ b/lib/setup/src/stages/boot.rs @@ -40,7 +40,7 @@ impl Stage for StageBootStage { let prefix_digest_hex = deploy_digests.prefix.to_hex(); let esp_mount_point = target.esp_mount_point(); - let plugin = BootPlugins::new()?.load(&requested_boot_plugin)?; + let plugin = BootPlugins::new()?.load(requested_boot_plugin)?; plugin.install(BootPluginInstallRequest { esp_mount_point: esp_mount_point.to_string_lossy().into_owned(), diff --git a/lib/setup/src/stages/database.rs b/lib/setup/src/stages/database.rs index e2096fec..4f42dade 100644 --- a/lib/setup/src/stages/database.rs +++ b/lib/setup/src/stages/database.rs @@ -38,8 +38,8 @@ impl Stage for EmbedDatabaseStage { &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { let mut import_ctx = ctx_take!(context, ImportContext); - let mut config_state = ctx_take!(context, ConfigState); let mut prefix_tree = ctx_take!(context, PrefixTree); + let config_state = ctx_take!(context, ConfigState); let target = ctx_get!(context, TargetSysroot); @@ -72,7 +72,7 @@ impl Stage for EmbedDatabaseStage { &mut import_ctx, )?; - let prefix_digest = commit_tree(repository, *prefix_tree)?; + let prefix_digest = commit_tree(repository, prefix_tree.0)?; let config_digest = commit_tree(repository, config_state.config_tree)?; context.put(DeployDigests { diff --git a/lib/setup/src/stages/enumerate.rs b/lib/setup/src/stages/enumerate.rs index 03600f3a..b6f85498 100644 --- a/lib/setup/src/stages/enumerate.rs +++ b/lib/setup/src/stages/enumerate.rs @@ -40,7 +40,7 @@ impl Stage for EnumeratePackagesStage { let resolved = ctx_get!(context, ResolvedSourceDir); let mut package_paths = Vec::new(); - for entry in read_dir(&resolved)? { + for entry in read_dir(&**resolved)? { let entry = entry?; if entry.metadata()?.is_file() { diff --git a/lib/setup/src/stages/kernel.rs b/lib/setup/src/stages/kernel.rs index df4a924b..3e84d649 100644 --- a/lib/setup/src/stages/kernel.rs +++ b/lib/setup/src/stages/kernel.rs @@ -18,8 +18,6 @@ impl Stage for KernelStage { fn run( &self, _context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { - todo!(); - Ok((progress, StageResult::Advance, Box::new(NoRollback))) } } diff --git a/lib/setup/src/stages/system.rs b/lib/setup/src/stages/system.rs index 24f0b233..ba11f6a5 100644 --- a/lib/setup/src/stages/system.rs +++ b/lib/setup/src/stages/system.rs @@ -55,7 +55,7 @@ impl Stage for ImportSystemStage { for ancestor in ancestors { let handle = FileHandle::new(ancestor); - if handle.stat_in_tree(&mut prefix_tree).is_err() { + if handle.stat_in_tree(&prefix_tree).is_err() { handle.insert_in_tree(&mut prefix_tree, Stat::uninitialized())?; } } From 2896560209148cb8b1108ebbd5f7fb87a0ee3173 Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 01:59:13 +0400 Subject: [PATCH 128/148] fix: cancel now names lib_cancel Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/export/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/lib/src/export/mod.rs b/lib/lib/src/export/mod.rs index f7c06c72..1ed87ce0 100644 --- a/lib/lib/src/export/mod.rs +++ b/lib/lib/src/export/mod.rs @@ -20,7 +20,7 @@ pub unsafe extern "C" fn lib_abi_version() -> u32 { /// `token`, if non-null, must point to a valid, initialized `CancelToken` for the duration of the /// call. #[unsafe(no_mangle)] -pub unsafe extern "C" fn cancel(token: *mut CancelToken) { +pub unsafe extern "C" fn lib_cancel(token: *mut CancelToken) { if token.is_null() { return; } From fe8914190af566d8c49396792d1cdf95a6c6bf65 Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 02:00:34 +0400 Subject: [PATCH 129/148] feat: rewrite up-sp to talk to upac-setup exclusively through its new C-ABI export functions, mirroring up's own libcore.rs/types::errors pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganizes src/ into upac-cli's types/{mod,errors,progress}.rs shape, adds libcore.rs (Lib::load() resolving setup_existing/setup_whole_disk/setup_cancel/setup_abi_version as static fn pointers — always static-link, genesis has nowhere to dlopen a .so from on a blank disk) plus a shared invoke() helper. commands/{auto,manual}.rs (whole_disk.rs renamed to auto.rs) now build SetupExistingRequest/SetupWholeDiskRequest and cross the C-ABI boundary directly instead of calling upac_setup::stages::run_existing/run_whole_disk as plain Rust. types::errors::LibError/error_kind_message replace the old rich SetupError Display impl, formatting straight off CError's domain-agnostic ErrorKind + SetupStateId::from_stage_index — same shape up already uses, just scoped to one domain. i18n keys swapped to the generic ErrorKind set to match. Renames upac-setup's own cancel export to setup_cancel — it collided with upac-lib's identically-named #[no_mangle] cancel, which also ends up statically linked into up-sp since upac-setup depends on upac-lib for its own package-install machinery Co-Authored-By: Claude Sonnet 5 --- user/setup-cli/Cargo.toml | 6 +- user/setup-cli/i18n/en/upac-setup-cli.ftl | 34 ++-- user/setup-cli/i18n/ru/upac-setup-cli.ftl | 34 ++-- .../src/commands/{whole_disk.rs => auto.rs} | 70 ++++---- user/setup-cli/src/commands/manual.rs | 49 +++--- user/setup-cli/src/errors.rs | 71 -------- user/setup-cli/src/libcore.rs | 71 ++++++++ user/setup-cli/src/main.rs | 31 ++-- user/setup-cli/src/types/errors.rs | 91 ++++++++++ user/setup-cli/src/{types.rs => types/mod.rs} | 8 +- user/setup-cli/src/{ => types}/progress.rs | 14 +- .../tests/inline/{whole_disk.rs => auto.rs} | 33 +++- user/setup-cli/tests/inline/errors.rs | 164 ++++-------------- user/setup-cli/tests/inline/progress.rs | 18 +- 14 files changed, 363 insertions(+), 331 deletions(-) rename user/setup-cli/src/commands/{whole_disk.rs => auto.rs} (59%) delete mode 100644 user/setup-cli/src/errors.rs create mode 100644 user/setup-cli/src/libcore.rs create mode 100644 user/setup-cli/src/types/errors.rs rename user/setup-cli/src/{types.rs => types/mod.rs} (96%) rename user/setup-cli/src/{ => types}/progress.rs (88%) rename user/setup-cli/tests/inline/{whole_disk.rs => auto.rs} (72%) diff --git a/user/setup-cli/Cargo.toml b/user/setup-cli/Cargo.toml index 8f199d08..65a0d675 100644 --- a/user/setup-cli/Cargo.toml +++ b/user/setup-cli/Cargo.toml @@ -40,11 +40,9 @@ indicatif = { workspace = true } i18n-embed = { workspace = true } i18n-embed-fl = { workspace = true } +nix = { version = "0.31.3", features = ["user"] } + rust-embed = "8.12.0" [build-dependencies] toml = { workspace = true } - -[dev-dependencies] -upac-lib = { workspace = true } -nix = { workspace = true } diff --git a/user/setup-cli/i18n/en/upac-setup-cli.ftl b/user/setup-cli/i18n/en/upac-setup-cli.ftl index fc924160..e91e34c5 100644 --- a/user/setup-cli/i18n/en/upac-setup-cli.ftl +++ b/user/setup-cli/i18n/en/upac-setup-cli.ftl @@ -1,27 +1,26 @@ error = Error -err-common = Common subsystem failure -err-mount = Mount failed -err-repo = Repository operation failed -err-database = Database operation failed -err-deploy-record = Deploy record operation failed -err-boot = Boot entry staging failed -err-boot-plugin = Boot plugin operation failed -err-io = I/O error -err-no-space-left = No space left on device -err-not-block-device = Not a block device -err-mkfs-failed = Filesystem creation failed -err-wipe-failed = Failed to wipe the target partition's existing filesystem signature -err-partition-not-ready = Partition device did not appear in time after partitioning +abi-version-mismatch = ABI version mismatch + err-unexpected = Unexpected error +err-oom = Out of memory +err-not-found = File not found +err-already-exists = Already exists +err-permission-denied = Permission denied +err-requires-root = This command requires root +err-invalid-path = Invalid path +err-no-space = No space left +err-cancelled = Cancelled +err-read = Read failed +err-write = Write failed +err-not-initialized = Not initialized +err-abi-mismatch = ABI mismatch +err-invalid-entry = Invalid entry err-missing-device = Missing required argument: --device err-missing-deploy-size = Missing required argument: --deploy-size err-missing-source = Missing required argument: --source -err-invalid-partition-layout = Requested partition sizes don't fit on the disk -err-invalid-format-params = Invalid filesystem formatting parameters -err-reread-failed = Failed to reread the partition table (device busy?) -err-composefs-setup-root-unit-not-found = composefs-setup-root.service not found under source's system/ directory +err-missing-boot-plugin = Missing required argument: --boot-plugin stage-prepare-source = Preparing source stage-enumerate-packages = Enumerating packages @@ -30,5 +29,6 @@ stage-import-package = Importing package stage-import-system = Importing system files stage-embed-database = Embedding package database stage-write-deploy-record = Writing deploy record +stage-kernel = Generating kernel stage-stage-boot = Staging boot entry stage-setup = Setup diff --git a/user/setup-cli/i18n/ru/upac-setup-cli.ftl b/user/setup-cli/i18n/ru/upac-setup-cli.ftl index cd6ca91d..18849428 100644 --- a/user/setup-cli/i18n/ru/upac-setup-cli.ftl +++ b/user/setup-cli/i18n/ru/upac-setup-cli.ftl @@ -1,27 +1,26 @@ error = Ошибка -err-common = Сбой общей подсистемы -err-mount = Ошибка монтирования -err-repo = Ошибка операции с репозиторием -err-database = Ошибка операции с базой данных -err-deploy-record = Ошибка операции с записью деплоя -err-boot = Ошибка подготовки загрузочной записи -err-boot-plugin = Ошибка загрузочного плагина -err-io = Ошибка ввода-вывода -err-no-space-left = Не осталось места на устройстве -err-not-block-device = Не является блочным устройством -err-mkfs-failed = Ошибка создания файловой системы -err-wipe-failed = Не удалось стереть существующую сигнатуру файловой системы на разделе -err-partition-not-ready = Устройство раздела не появилось за отведённое время после разметки +abi-version-mismatch = Несовпадение версии ABI + err-unexpected = Непредвиденная ошибка +err-oom = Недостаточно памяти +err-not-found = Файл не найден +err-already-exists = Уже существует +err-permission-denied = Доступ запрещён +err-requires-root = Для этой команды нужны права root +err-invalid-path = Неверный путь +err-no-space = Нет свободного места +err-cancelled = Отменено +err-read = Ошибка чтения +err-write = Ошибка записи +err-not-initialized = Не инициализировано +err-abi-mismatch = Несовпадение ABI +err-invalid-entry = Некорректная запись err-missing-device = Отсутствует обязательный аргумент: --device err-missing-deploy-size = Отсутствует обязательный аргумент: --deploy-size err-missing-source = Отсутствует обязательный аргумент: --source -err-invalid-partition-layout = Запрошенные размеры разделов не помещаются на диск -err-invalid-format-params = Некорректные параметры форматирования файловой системы -err-reread-failed = Не удалось перечитать таблицу разделов (устройство занято?) -err-composefs-setup-root-unit-not-found = composefs-setup-root.service не найден в директории system/ источника +err-missing-boot-plugin = Отсутствует обязательный аргумент: --boot-plugin stage-prepare-source = Подготовка источника stage-enumerate-packages = Перечисление пакетов @@ -30,5 +29,6 @@ stage-import-package = Импорт пакета stage-import-system = Импорт системных файлов stage-embed-database = Встраивание базы данных пакета stage-write-deploy-record = Запись записи деплоя +stage-kernel = Генерация ядра stage-stage-boot = Подготовка загрузочной записи stage-setup = Настройка diff --git a/user/setup-cli/src/commands/whole_disk.rs b/user/setup-cli/src/commands/auto.rs similarity index 59% rename from user/setup-cli/src/commands/whole_disk.rs rename to user/setup-cli/src/commands/auto.rs index 73b834ca..7c566348 100644 --- a/user/setup-cli/src/commands/whole_disk.rs +++ b/user/setup-cli/src/commands/auto.rs @@ -10,20 +10,18 @@ use clap::{Args as ClapArgs, ValueEnum}; use i18n_embed_fl::fl; use upac_abi::FsKind as FsKindAbi; -use upac_abi::hook::CancelToken; -use upac_setup::data::SetupWholeDiskData; +use upac_types::request::{BtrfsOptions, GptLayout, PartitionSpec, RequestBase, SetupWholeDiskRequest}; -use upac_types::PartitionSpec; - -use crate::errors::LocalizedSetupError; +use crate::cancel_token_ptr; use crate::layout::disk_defaults; +use crate::libcore::{Lib, invoke}; use crate::locale::LOADER; -use crate::progress::{ProgressState, on_progress}; +use crate::types::progress::{ProgressState, on_progress}; use crate::types::{FsKind, parse_extra_partition, parse_size_mib}; #[cfg(test)] -#[path = "../../tests/inline/whole_disk.rs"] +#[path = "../../tests/inline/auto.rs"] mod tests; #[derive(ClapArgs)] @@ -57,46 +55,54 @@ pub struct Args { pub boot_plugin: Option, } -pub fn run(args: Args, cancel_token: &CancelToken) -> Result<()> { - let Some(device) = args.device.as_deref() else { +pub fn run(args: Args, lib: &Lib) -> Result<()> { + let Some(device) = args.device else { bail!(fl!(LOADER, "err-missing-device")); }; let Some(deploy_size_mib) = args.deploy_size_mib else { bail!(fl!(LOADER, "err-missing-deploy-size")); }; - let Some(source) = args.source.as_deref() else { + let Some(source) = args.source else { bail!(fl!(LOADER, "err-missing-source")); }; + let Some(boot_plugin) = args.boot_plugin else { + bail!(fl!(LOADER, "err-missing-boot-plugin")); + }; - let mut progress = ProgressState::new(); + lib.require_root()?; - let data = SetupWholeDiskData { - device_path: device, - esp_size_mib: args.esp_size_mib, - deploy_fs: args.deploy_fs.into(), - deploy_size_mib, - extra_partitions: args.extra_partitions, - force_wipe: args.force_wipe, + let mut progress = ProgressState::new(); - node_size: args.node_size, - sector_size: args.sector_size, + let request = SetupWholeDiskRequest { + base: RequestBase { + on_hook: Some(on_progress), + hook_ctx: progress.ctx_ptr(), + cancel_token: cancel_token_ptr(), + }, - mount_point: args.mount_point.as_deref(), + device_path: device, + gpt: GptLayout { + esp_size_mib: args.esp_size_mib, + deploy_fs: args.deploy_fs.into(), + deploy_size_mib, + extra_partitions: args.extra_partitions, + force_wipe: args.force_wipe, + }, + btrfs: BtrfsOptions { + node_size: args.node_size, + sector_size: args.sector_size, + }, + + mount_point: args.mount_point, source, empty_config: args.empty_config, pinned: args.pinned, - boot_plugin: args.boot_plugin.as_deref(), - - hook_message: Some(on_progress), - hook_message_context: progress.ctx_ptr(), + boot_plugin, + } + .into(); - cancel_token, - }; - - let result = data.run(); + let result = invoke(|error| unsafe { (lib.setup_whole_disk)(request, error) }); progress.finish(); - result.map_err(LocalizedSetupError)?; - - Ok(()) + result } diff --git a/user/setup-cli/src/commands/manual.rs b/user/setup-cli/src/commands/manual.rs index 6adb8529..df415e46 100644 --- a/user/setup-cli/src/commands/manual.rs +++ b/user/setup-cli/src/commands/manual.rs @@ -7,14 +7,11 @@ use anyhow::Result; use clap::Args as ClapArgs; -use upac_abi::hook::CancelToken; +use upac_types::request::{PartitionMount, RequestBase, SetupExistingRequest}; -use upac_setup::data::SetupExistingData; - -use upac_types::PartitionMount; - -use crate::errors::LocalizedSetupError; -use crate::progress::{ProgressState, on_progress}; +use crate::cancel_token_ptr; +use crate::libcore::{Lib, invoke}; +use crate::types::progress::{ProgressState, on_progress}; use crate::types::{FsKind, parse_extra_mount}; #[derive(ClapArgs)] @@ -37,34 +34,36 @@ pub struct Args { #[arg(long)] pub pinned: bool, #[arg(long)] - pub boot_plugin: Option, + pub boot_plugin: String, } -pub fn run(args: Args, cancel_token: &CancelToken) -> Result<()> { +pub fn run(args: Args, lib: &Lib) -> Result<()> { + lib.require_root()?; + let mut progress = ProgressState::new(); - let data = SetupExistingData { - esp_device: &args.esp_device, - deploy_device: &args.deploy_device, + let request = SetupExistingRequest { + base: RequestBase { + on_hook: Some(on_progress), + hook_ctx: progress.ctx_ptr(), + cancel_token: cancel_token_ptr(), + }, + + esp_device: args.esp_device, + deploy_device: args.deploy_device, deploy_fs: args.deploy_fs.into(), extra_mounts: args.extra_mounts, - mount_point: args.mount_point.as_deref(), - source: &args.source, + mount_point: args.mount_point, + source: args.source, empty_config: args.empty_config, pinned: args.pinned, - boot_plugin: args.boot_plugin.as_deref(), + boot_plugin: args.boot_plugin, + } + .into(); - hook_message: Some(on_progress), - hook_message_context: progress.ctx_ptr(), - - cancel_token, - }; - - let result = data.run(); + let result = invoke(|error| unsafe { (lib.setup_existing)(request, error) }); progress.finish(); - result.map_err(LocalizedSetupError)?; - - Ok(()) + result } diff --git a/user/setup-cli/src/errors.rs b/user/setup-cli/src/errors.rs deleted file mode 100644 index 90be2507..00000000 --- a/user/setup-cli/src/errors.rs +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: GPL-3.0-only - -use std::fmt::{Display, Formatter, Result as FmtResult}; - -use i18n_embed_fl::fl; - -use upac_setup::error::SetupError; -use upac_setup::genesis::GenesisStage; - -use crate::locale::LOADER; - -#[cfg(test)] -#[path = "../tests/inline/errors.rs"] -mod tests; - -#[derive(Debug)] -pub struct LocalizedSetupError(pub (GenesisStage, SetupError)); - -impl Display for LocalizedSetupError { - fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult { - let (stage, error) = &self.0; - - write!(formatter, "{}: ", LOADER.get(stage.stage_key()))?; - - match error { - SetupError::Common(error) => { - write!(formatter, "{} ({error:?})", fl!(LOADER, "err-common")) - } - SetupError::Mount(errno) => { - write!(formatter, "{} ({errno})", fl!(LOADER, "err-mount")) - } - SetupError::Repo(error) => { - write!(formatter, "{} ({error:?})", fl!(LOADER, "err-repo")) - } - SetupError::Database(error) => { - write!(formatter, "{} ({error:?})", fl!(LOADER, "err-database")) - } - SetupError::DeployRecord(error) => { - write!(formatter, "{} ({error:?})", fl!(LOADER, "err-deploy-record")) - } - SetupError::Boot(error) => { - write!(formatter, "{} ({error:?})", fl!(LOADER, "err-boot")) - } - SetupError::BootPlugin(error) => { - write!(formatter, "{} ({error:?})", fl!(LOADER, "err-boot-plugin")) - } - SetupError::Io(kind) => { - write!(formatter, "{} ({kind:?})", fl!(LOADER, "err-io")) - } - SetupError::NoSpaceLeft => formatter.write_str(&fl!(LOADER, "err-no-space-left")), - SetupError::NotBlockDevice => formatter.write_str(&fl!(LOADER, "err-not-block-device")), - SetupError::MkfsFailed => formatter.write_str(&fl!(LOADER, "err-mkfs-failed")), - SetupError::WipeFailed => formatter.write_str(&fl!(LOADER, "err-wipe-failed")), - SetupError::PartitionNotReady => formatter.write_str(&fl!(LOADER, "err-partition-not-ready")), - SetupError::InvalidPartitionLayout => formatter.write_str(&fl!(LOADER, "err-invalid-partition-layout")), - SetupError::InvalidFormatParams => formatter.write_str(&fl!(LOADER, "err-invalid-format-params")), - SetupError::RereadFailed(errno) => { - write!(formatter, "{} ({errno})", fl!(LOADER, "err-reread-failed")) - } - SetupError::ComposefsSetupRootUnitNotFound => { - formatter.write_str(&fl!(LOADER, "err-composefs-setup-root-unit-not-found")) - } - SetupError::Unexpected => formatter.write_str(&fl!(LOADER, "err-unexpected")), - } - } -} - -impl std::error::Error for LocalizedSetupError {} diff --git a/user/setup-cli/src/libcore.rs b/user/setup-cli/src/libcore.rs new file mode 100644 index 00000000..ec2afc81 --- /dev/null +++ b/user/setup-cli/src/libcore.rs @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: GPL-3.0-only + +use std::mem::MaybeUninit; + +use anyhow::Result; + +use i18n_embed_fl::fl; + +use nix::unistd::Uid; + +use upac_abi::SETUP_ABI_VERSION; +use upac_abi::error::CError; +use upac_abi::hook::CancelToken; +use upac_abi::request::{CSetupExistingRequest, CSetupWholeDiskRequest}; + +use upac_setup::{setup_abi_version, setup_cancel, setup_existing, setup_whole_disk}; + +use crate::locale::LOADER; +use crate::types::errors::{AbiMismatch, LibError}; + +pub struct Lib { + pub setup_existing: unsafe extern "C" fn(CSetupExistingRequest, *mut CError) -> i32, + pub setup_whole_disk: unsafe extern "C" fn(CSetupWholeDiskRequest, *mut CError) -> i32, + + pub cancel: unsafe extern "C" fn(*mut CancelToken), + pub version_abi: unsafe extern "C" fn() -> u32, +} + +impl Lib { + pub fn load() -> Result { + let lib = Self { + setup_existing, + setup_whole_disk, + cancel: setup_cancel, + version_abi: setup_abi_version, + }; + + let abi_version = unsafe { (lib.version_abi)() }; + if abi_version != SETUP_ABI_VERSION { + let err = AbiMismatch { + got: abi_version, + expected: SETUP_ABI_VERSION, + }; + + return Err(err.into()); + } + + Ok(lib) + } + + pub fn require_root(&self) -> Result<()> { + if !Uid::effective().is_root() { + anyhow::bail!(fl!(LOADER, "err-requires-root")); + } + + Ok(()) + } +} + +pub fn invoke(call: impl FnOnce(*mut CError) -> i32) -> Result<()> { + let mut error = MaybeUninit::uninit(); + + let code = call(error.as_mut_ptr()); + + unsafe { LibError::check(code, error.as_ptr())? }; + + Ok(()) +} diff --git a/user/setup-cli/src/main.rs b/user/setup-cli/src/main.rs index 430395cf..19c38520 100644 --- a/user/setup-cli/src/main.rs +++ b/user/setup-cli/src/main.rs @@ -3,8 +3,9 @@ // // SPDX-License-Identifier: GPL-3.0-only -// ── Imports ───────────────────────────────────────────────────────────────── use std::process::ExitCode; +use std::ptr::addr_of_mut; +use std::sync::Arc; use anyhow::Result; @@ -16,22 +17,26 @@ use i18n_embed_fl::fl; use upac_abi::hook::CancelToken; +use self::libcore::Lib; + mod commands { + pub mod auto; pub mod manual; - pub mod whole_disk; } -mod errors; +mod libcore; mod locale; mod layout { include!(concat!(env!("OUT_DIR"), "/layout.rs")); } -mod progress; mod types; -static CANCEL_TOKEN: CancelToken = CancelToken::new(); +static mut CANCEL_TOKEN: CancelToken = CancelToken::new(); + +pub(crate) fn cancel_token_ptr() -> *mut CancelToken { + addr_of_mut!(CANCEL_TOKEN) +} -// ── CLI arguments ───────────────────────────────────────────────────────────── #[derive(Parser)] #[command(name = "up-sp", author, version, about)] struct Cli { @@ -39,7 +44,7 @@ struct Cli { command: Option, #[command(flatten)] - whole_disk: commands::whole_disk::Args, + whole_disk: commands::auto::Args, } #[derive(Subcommand)] @@ -47,7 +52,6 @@ enum Command { Manual(commands::manual::Args), } -// ── Entry points ─────────────────────────────────────────────────────────────── fn main() -> ExitCode { locale::init(); @@ -61,13 +65,18 @@ fn main() -> ExitCode { } fn run() -> Result<()> { - ctrlc::set_handler(|| CANCEL_TOKEN.cancel())?; + let lib = Arc::new(Lib::load()?); + + let lib_cancel = Arc::clone(&lib); + ctrlc::set_handler(move || { + unsafe { (lib_cancel.cancel)(cancel_token_ptr()) }; + })?; let cli = Cli::parse(); match cli.command { - Some(Command::Manual(args)) => commands::manual::run(args, &CANCEL_TOKEN)?, - None => commands::whole_disk::run(cli.whole_disk, &CANCEL_TOKEN)?, + Some(Command::Manual(args)) => commands::manual::run(args, &lib)?, + None => commands::auto::run(cli.whole_disk, &lib)?, } Ok(()) diff --git a/user/setup-cli/src/types/errors.rs b/user/setup-cli/src/types/errors.rs new file mode 100644 index 00000000..3c18c78b --- /dev/null +++ b/user/setup-cli/src/types/errors.rs @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: GPL-3.0-only + +use std::error::Error; +use std::fmt::{Display, Formatter, Result as FmtResult}; + +use i18n_embed_fl::fl; + +use upac_abi::error::{CError, ErrorKind}; + +use upac_types::states::SetupStateId; + +use crate::locale::LOADER; + +#[cfg(test)] +#[path = "../../tests/inline/errors.rs"] +mod tests; + +#[derive(Debug)] +pub struct AbiMismatch { + pub got: u32, + pub expected: u32, +} + +impl Display for AbiMismatch { + fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult { + write!( + formatter, + "{} ({} → {})", + fl!(LOADER, "abi-version-mismatch"), + self.got, + self.expected + ) + } +} + +impl Error for AbiMismatch {} + +fn error_kind_message(kind: ErrorKind) -> String { + match kind { + ErrorKind::Unexpected => fl!(LOADER, "err-unexpected"), + ErrorKind::OutOfMemory => fl!(LOADER, "err-oom"), + ErrorKind::NotFound => fl!(LOADER, "err-not-found"), + ErrorKind::AlreadyExists => fl!(LOADER, "err-already-exists"), + ErrorKind::PermissionDenied => fl!(LOADER, "err-permission-denied"), + ErrorKind::InvalidPath => fl!(LOADER, "err-invalid-path"), + ErrorKind::NoSpaceLeft => fl!(LOADER, "err-no-space"), + ErrorKind::Cancelled => fl!(LOADER, "err-cancelled"), + ErrorKind::ReadFailed => fl!(LOADER, "err-read"), + ErrorKind::WriteFailed => fl!(LOADER, "err-write"), + ErrorKind::NotInitialized => fl!(LOADER, "err-not-initialized"), + ErrorKind::AbiMismatch => fl!(LOADER, "err-abi-mismatch"), + ErrorKind::InvalidEntry => fl!(LOADER, "err-invalid-entry"), + } +} + +#[derive(Debug)] +pub struct LibError { + pub error: CError, +} + +impl LibError { + /// # Safety + /// `error` must point to a valid, initialized `CError` whenever `code != 0` — the ABI only writes + /// to it on the failure path, leaving it uninitialized on success. + pub unsafe fn check(code: i32, error: *const CError) -> Result<(), Self> { + if code == 0 { + return Ok(()); + } + Err(Self { + error: unsafe { *error }, + }) + } +} + +impl Display for LibError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult { + let stage = SetupStateId::from_stage_index(self.error.state as usize).stage_key(); + + write!( + formatter, + "{}: {}", + LOADER.get(stage), + error_kind_message(self.error.error) + ) + } +} + +impl Error for LibError {} diff --git a/user/setup-cli/src/types.rs b/user/setup-cli/src/types/mod.rs similarity index 96% rename from user/setup-cli/src/types.rs rename to user/setup-cli/src/types/mod.rs index e8542f0d..bece86c1 100644 --- a/user/setup-cli/src/types.rs +++ b/user/setup-cli/src/types/mod.rs @@ -7,10 +7,14 @@ use clap::ValueEnum; use clap::builder::PossibleValue; use upac_abi::FsKind as FsKindAbi; -use upac_types::{PartitionMount, PartitionSpec}; + +use upac_types::request::{PartitionMount, PartitionSpec}; + +pub mod errors; +pub mod progress; #[cfg(test)] -#[path = "../tests/inline/types.rs"] +#[path = "../../tests/inline/types.rs"] mod tests; #[repr(transparent)] diff --git a/user/setup-cli/src/progress.rs b/user/setup-cli/src/types/progress.rs similarity index 88% rename from user/setup-cli/src/progress.rs rename to user/setup-cli/src/types/progress.rs index c800b83c..e9fd9f6b 100644 --- a/user/setup-cli/src/progress.rs +++ b/user/setup-cli/src/types/progress.rs @@ -4,19 +4,20 @@ // SPDX-License-Identifier: GPL-3.0-only use std::os::raw::c_void; +use std::ptr::from_mut; use std::time::Duration; use indicatif::{ProgressBar, ProgressStyle}; use upac_abi::hook::{CProgressEvent, HookAck}; -use upac_setup::genesis::GenesisStage; +use upac_types::states::SetupStateId; use crate::layout::progress; use crate::locale::LOADER; #[cfg(test)] -#[path = "../tests/inline/progress.rs"] +#[path = "../../tests/inline/progress.rs"] mod tests; /// # Safety @@ -39,6 +40,7 @@ pub struct ProgressState { impl ProgressState { pub fn new() -> Self { let bar = ProgressBar::new_spinner(); + bar.set_style(Self::spinner_style()); bar.enable_steady_tick(Duration::from_millis(u64::from(progress::TICK_INTERVAL_MS))); @@ -46,7 +48,7 @@ impl ProgressState { } pub fn ctx_ptr(&mut self) -> *mut c_void { - std::ptr::from_mut(self).cast() + from_mut(self).cast() } pub fn finish(&self) { @@ -54,7 +56,7 @@ impl ProgressState { } fn apply(&mut self, event: &CProgressEvent) { - let stage = Self::stage_name(event.stage); + let stage = LOADER.get(SetupStateId::from_stage_index(event.stage as usize).stage_key()); let subject = <&str>::try_from(&event.subject).unwrap_or_default(); if event.total > 0 { @@ -76,10 +78,6 @@ impl ProgressState { } impl ProgressState { - fn stage_name(index: u32) -> String { - LOADER.get(GenesisStage::from_stage_index(index as usize).stage_key()) - } - fn spinner_style() -> ProgressStyle { ProgressStyle::with_template(progress::SPINNER_TEMPLATE) .unwrap_or_else(|_| ProgressStyle::default_spinner()) diff --git a/user/setup-cli/tests/inline/whole_disk.rs b/user/setup-cli/tests/inline/auto.rs similarity index 72% rename from user/setup-cli/tests/inline/whole_disk.rs rename to user/setup-cli/tests/inline/auto.rs index 5a6a275f..adfb77d3 100644 --- a/user/setup-cli/tests/inline/whole_disk.rs +++ b/user/setup-cli/tests/inline/auto.rs @@ -4,8 +4,8 @@ // SPDX-License-Identifier: GPL-3.0-only use upac_abi::FsKind as FsKindAbi; -use upac_abi::hook::CancelToken; +use crate::libcore::Lib; use crate::locale; use crate::types::FsKind; @@ -26,21 +26,21 @@ fn valid_args() -> Args { source: Some("/mnt/source".to_owned()), empty_config: false, pinned: false, - boot_plugin: None, + boot_plugin: Some("systemd-boot".to_owned()), } } #[test] fn missing_device_bails_before_touching_the_disk() { locale::init_for_test(); - let cancel_token = CancelToken::new(); + let lib = Lib::load().unwrap(); let error = run( Args { device: None, ..valid_args() }, - &cancel_token, + &lib, ) .unwrap_err(); @@ -50,14 +50,14 @@ fn missing_device_bails_before_touching_the_disk() { #[test] fn missing_deploy_size_bails_before_touching_the_disk() { locale::init_for_test(); - let cancel_token = CancelToken::new(); + let lib = Lib::load().unwrap(); let error = run( Args { deploy_size_mib: None, ..valid_args() }, - &cancel_token, + &lib, ) .unwrap_err(); @@ -67,16 +67,33 @@ fn missing_deploy_size_bails_before_touching_the_disk() { #[test] fn missing_source_bails_before_touching_the_disk() { locale::init_for_test(); - let cancel_token = CancelToken::new(); + let lib = Lib::load().unwrap(); let error = run( Args { source: None, ..valid_args() }, - &cancel_token, + &lib, ) .unwrap_err(); assert_eq!(error.to_string(), "Missing required argument: --source"); } + +#[test] +fn missing_boot_plugin_bails_before_touching_the_disk() { + locale::init_for_test(); + let lib = Lib::load().unwrap(); + + let error = run( + Args { + boot_plugin: None, + ..valid_args() + }, + &lib, + ) + .unwrap_err(); + + assert_eq!(error.to_string(), "Missing required argument: --boot-plugin"); +} diff --git a/user/setup-cli/tests/inline/errors.rs b/user/setup-cli/tests/inline/errors.rs index f3dede54..095e04ea 100644 --- a/user/setup-cli/tests/inline/errors.rs +++ b/user/setup-cli/tests/inline/errors.rs @@ -3,153 +3,63 @@ // // SPDX-License-Identifier: GPL-3.0-only -use std::io::ErrorKind as IoErrorKind; +use upac_abi::error::{CError, ErrorDomain, ErrorKind}; -use nix::errno::Errno; - -use upac::boot::error::BootError; -use upac::composefs::error::RepoError; -use upac::database::error::{DatabaseError, DeployRecordError}; -use upac::errors::CommonError; -use upac::plugin::boot::error::BootPluginError; - -use upac_setup::error::SetupError; -use upac_setup::genesis::GenesisStage; +use upac_types::states::SetupStateId; use crate::locale; -use super::LocalizedSetupError; +use super::{AbiMismatch, LibError}; -fn localized(stage: GenesisStage, error: SetupError) -> String { +fn localized(state: SetupStateId, error: ErrorKind) -> String { locale::init_for_test(); - LocalizedSetupError((stage, error)).to_string() -} - -#[test] -fn prefixes_the_message_with_the_localized_failing_stage_name() { - let message = localized(GenesisStage::ImportPackage, SetupError::Unexpected); - - assert_eq!(message, "Importing package: Unexpected error"); -} - -#[test] -fn common_variant_embeds_debug_detail() { - let message = localized(GenesisStage::Setup, SetupError::Common(CommonError::PipelineInvalid)); - - assert_eq!(message, "Setup: Common subsystem failure (PipelineInvalid)"); -} - -#[test] -fn mount_variant_embeds_the_errno() { - let errno = Errno::EIO; - let message = localized(GenesisStage::Setup, SetupError::Mount(errno)); - - assert_eq!(message, format!("Setup: Mount failed ({errno})")); -} - -#[test] -fn repo_variant_embeds_debug_detail() { - let message = localized(GenesisStage::ImportPackage, SetupError::Repo(RepoError::NotFound)); - - assert_eq!(message, "Importing package: Repository operation failed (NotFound)"); -} - -#[test] -fn database_variant_embeds_debug_detail() { - let message = localized( - GenesisStage::EmbedDatabase, - SetupError::Database(DatabaseError::WriteError), - ); - - assert_eq!( - message, - "Embedding package database: Database operation failed (WriteError)" - ); -} -#[test] -fn deploy_record_variant_embeds_debug_detail() { - let message = localized( - GenesisStage::WriteDeployRecord, - SetupError::DeployRecord(DeployRecordError::WriteFailed), - ); + let error = CError { + domain: ErrorDomain::Setup, + state: state as u32, + error, + }; - assert_eq!( - message, - "Writing deploy record: Deploy record operation failed (WriteFailed)" - ); + LibError { error }.to_string() } #[test] -fn boot_variant_embeds_debug_detail() { - let message = localized(GenesisStage::StageBoot, SetupError::Boot(BootError::NoBootResource)); - - assert_eq!( - message, - "Staging boot entry: Boot entry staging failed (NoBootResource)" - ); -} - -#[test] -fn boot_plugin_variant_embeds_debug_detail() { - let message = localized( - GenesisStage::StageBoot, - SetupError::BootPlugin(BootPluginError::NoClaimant), - ); +fn prefixes_the_message_with_the_localized_failing_stage_name() { + let message = localized(SetupStateId::ImportPackage, ErrorKind::Unexpected); - assert_eq!(message, "Staging boot entry: Boot plugin operation failed (NoClaimant)"); + assert_eq!(message, "Importing package: Unexpected error"); } #[test] -fn io_variant_embeds_the_error_kind() { - let message = localized(GenesisStage::UnpackPackage, SetupError::Io(IoErrorKind::NotFound)); - - assert_eq!(message, "Unpacking package: I/O error (NotFound)"); -} +fn every_error_kind_has_its_own_localized_message() { + let cases = [ + (ErrorKind::Unexpected, "Unexpected error"), + (ErrorKind::OutOfMemory, "Out of memory"), + (ErrorKind::NotFound, "File not found"), + (ErrorKind::AlreadyExists, "Already exists"), + (ErrorKind::PermissionDenied, "Permission denied"), + (ErrorKind::InvalidPath, "Invalid path"), + (ErrorKind::NoSpaceLeft, "No space left"), + (ErrorKind::Cancelled, "Cancelled"), + (ErrorKind::ReadFailed, "Read failed"), + (ErrorKind::WriteFailed, "Write failed"), + (ErrorKind::NotInitialized, "Not initialized"), + (ErrorKind::AbiMismatch, "ABI mismatch"), + (ErrorKind::InvalidEntry, "Invalid entry"), + ]; -#[test] -fn reread_failed_variant_embeds_the_errno() { - let errno = Errno::ENOSPC; - let message = localized(GenesisStage::Setup, SetupError::RereadFailed(errno)); + for (kind, expected) in cases { + let message = localized(SetupStateId::Setup, kind); - assert_eq!( - message, - format!("Setup: Failed to reread the partition table (device busy?) ({errno})") - ); + assert_eq!(message, format!("Setup: {expected}")); + } } #[test] -fn no_payload_variants_use_their_fixed_localized_message() { - let cases = [ - (SetupError::NoSpaceLeft, "No space left on device"), - (SetupError::NotBlockDevice, "Not a block device"), - (SetupError::MkfsFailed, "Filesystem creation failed"), - ( - SetupError::WipeFailed, - "Failed to wipe the target partition's existing filesystem signature", - ), - ( - SetupError::PartitionNotReady, - "Partition device did not appear in time after partitioning", - ), - ( - SetupError::InvalidPartitionLayout, - "Requested partition sizes don't fit on the disk", - ), - ( - SetupError::InvalidFormatParams, - "Invalid filesystem formatting parameters", - ), - ( - SetupError::ComposefsSetupRootUnitNotFound, - "composefs-setup-root.service not found under source's system/ directory", - ), - (SetupError::Unexpected, "Unexpected error"), - ]; +fn abi_mismatch_embeds_both_versions() { + locale::init_for_test(); - for (error, expected) in cases { - let message = localized(GenesisStage::Setup, error); + let message = AbiMismatch { got: 1, expected: 2 }.to_string(); - assert_eq!(message, format!("Setup: {expected}")); - } + assert_eq!(message, "ABI version mismatch (1 → 2)"); } diff --git a/user/setup-cli/tests/inline/progress.rs b/user/setup-cli/tests/inline/progress.rs index 6c3bda52..2e15fe5d 100644 --- a/user/setup-cli/tests/inline/progress.rs +++ b/user/setup-cli/tests/inline/progress.rs @@ -9,7 +9,7 @@ use std::mem::size_of; use upac_abi::hook::CProgressEvent; use upac_abi::types::CSlice; -use upac_setup::genesis::GenesisStage; +use upac_types::states::SetupStateId; use crate::locale; @@ -45,7 +45,7 @@ fn apply_with_zero_total_stays_on_spinner() { locale::init_for_test(); let mut state = ProgressState::new(); - state.apply(&event(GenesisStage::EnumeratePackages as u32, 0, 0, empty_slice())); + state.apply(&event(SetupStateId::EnumeratePackages as u32, 0, 0, empty_slice())); assert!(!state.is_bar); assert_eq!(state.bar.message(), "Enumerating packages"); @@ -56,7 +56,7 @@ fn apply_with_nonzero_total_switches_to_bar_and_sets_position() { locale::init_for_test(); let mut state = ProgressState::new(); - state.apply(&event(GenesisStage::ImportPackage as u32, 3, 10, empty_slice())); + state.apply(&event(SetupStateId::ImportPackage as u32, 3, 10, empty_slice())); assert!(state.is_bar); assert_eq!(state.bar.length(), Some(10)); @@ -70,7 +70,7 @@ fn apply_includes_subject_in_message_when_present() { let subject = CString::new("foo.txt").unwrap(); state.apply(&event( - GenesisStage::EnumeratePackages as u32, + SetupStateId::EnumeratePackages as u32, 0, 0, slice_from_cstr(&subject), @@ -80,11 +80,11 @@ fn apply_includes_subject_in_message_when_present() { } #[test] -fn stage_name_resolves_the_localized_stage_key() { +fn apply_resolves_the_localized_stage_key() { locale::init_for_test(); + let mut state = ProgressState::new(); + + state.apply(&event(SetupStateId::StageBoot as u32, 0, 0, empty_slice())); - assert_eq!( - ProgressState::stage_name(GenesisStage::StageBoot as u32), - "Staging boot entry" - ); + assert_eq!(state.bar.message(), "Staging boot entry"); } From 1f83f1c6cc4a63a8e8dfbf38e6f288b4502a4a9f Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 02:17:40 +0400 Subject: [PATCH 130/148] feat: make write_boot_entry pick the boot resource matching the selected plugin instead of whichever single one happens to exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds BootResourceKind (Bls/Uki, same axis WrittenBootEntry already uses) as a new required Booter trait method plus C-ABI query (boot_resource_kind, BOOT_ABI_VERSION 2->3), implemented by all four booter plugins (uki -> Uki, grub/systemd-boot/refind -> Bls) and threaded through BootPlugin/static_link.rs/dynamic_link.rs. write_boot_entry now takes the wanted kind and filters get_boot_resources() against it — errors UnsupportedBootResource if the wanted kind is absent even when a different kind is present, AmbiguousBootResource only among matches of the wanted kind, NoBootResource only when the tree has nothing at all. Reorders all 5 ordinary checkout.rs stages (installer/update/rollback/files/uninstaller) to resolve the boot plugin before calling write_boot_entry, since the call now needs plugin.boot_resource_kind() as an argument — genesis's own stages/boot.rs already had the right order. Closes the two TODO.md items about write_boot_entry's resource-selection blindness and the checkout.rs call ordering Co-Authored-By: Claude Sonnet 5 --- booters/grub/src/backend.rs | 6 ++ booters/grub/src/lib.rs | 8 ++ booters/refind/src/backend.rs | 6 ++ booters/refind/src/lib.rs | 8 ++ booters/systemd-boot/src/backend.rs | 6 ++ booters/systemd-boot/src/lib.rs | 8 ++ booters/uki/src/backend.rs | 6 ++ booters/uki/src/lib.rs | 8 ++ lib/abi/src/lib.rs | 21 ++++- lib/lib/src/boot/mod.rs | 28 ++++++- lib/lib/src/mutated/files/checkout.rs | 13 ++- lib/lib/src/mutated/installer/checkout.rs | 13 ++- lib/lib/src/mutated/rollback/checkout.rs | 13 ++- lib/lib/src/mutated/uninstaller/checkout.rs | 13 ++- lib/lib/src/mutated/update/checkout.rs | 13 ++- lib/lib/src/plugin/boot/dynamic_link.rs | 4 +- lib/lib/src/plugin/boot/mod.rs | 7 +- lib/lib/src/plugin/boot/static_link.rs | 29 +++++-- lib/lib/tests/boot.rs | 19 ++++- lib/setup/src/error.rs | 4 + lib/setup/src/stages/boot.rs | 1 + lib/setup/src/stages/mod.rs | 4 + lib/setup/tests/data.rs | 90 --------------------- lib/setup/tests/error.rs | 57 ------------- lib/setup/tests/format.rs | 34 -------- lib/setup/tests/inline/embed.rs | 18 +++-- lib/setup/tests/inline/enumerate.rs | 32 ++++---- lib/setup/tests/inline/format.rs | 30 ++++++- lib/types/src/traits.rs | 2 + 29 files changed, 262 insertions(+), 239 deletions(-) delete mode 100644 lib/setup/tests/data.rs delete mode 100644 lib/setup/tests/error.rs delete mode 100644 lib/setup/tests/format.rs diff --git a/booters/grub/src/backend.rs b/booters/grub/src/backend.rs index 68625726..80b8ad8d 100644 --- a/booters/grub/src/backend.rs +++ b/booters/grub/src/backend.rs @@ -8,6 +8,8 @@ use std::io::ErrorKind as IoErrorKind; use std::path::Path; use std::process::Command; +use upac_abi::BootResourceKind; + use upac_types::traits::Booter; use super::error::GrubError; @@ -27,6 +29,10 @@ impl Booter for Grub { Ok(Grub) } + fn boot_resource_kind() -> BootResourceKind { + BootResourceKind::Bls + } + fn set_one_shot(&mut self, entry_name: &str) -> Result<(), GrubError> { self.run_first_available([REBOOT_BIN_PRIMARY, REBOOT_BIN_FALLBACK], &[entry_name]) } diff --git a/booters/grub/src/lib.rs b/booters/grub/src/lib.rs index 62e03b9e..59e7c788 100644 --- a/booters/grub/src/lib.rs +++ b/booters/grub/src/lib.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use upac_abi::BOOT_ABI_VERSION; +use upac_abi::BootResourceKind; use upac_abi::error::ErrorKind; use upac_abi::request::{ CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, @@ -35,6 +36,13 @@ pub unsafe extern "C" fn boot_abi_version() -> u32 { BOOT_ABI_VERSION } +/// # Safety +/// Touches no pointers — `unsafe extern "C"` only to match the ABI calling convention. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn boot_resource_kind() -> BootResourceKind { + Grub::boot_resource_kind() +} + /// # Safety /// `request`, if non-null, must point to a valid, initialized `CBootPluginSetOneShotRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. diff --git a/booters/refind/src/backend.rs b/booters/refind/src/backend.rs index a29b174a..83580b8e 100644 --- a/booters/refind/src/backend.rs +++ b/booters/refind/src/backend.rs @@ -18,6 +18,8 @@ use uuid::Uuid; use nix::{ioctl_read, ioctl_write_ptr}; +use upac_abi::BootResourceKind; + use upac_types::traits::Booter; use super::boot::EFIVARFS_PATH; @@ -50,6 +52,10 @@ impl Booter for Refind { }) } + fn boot_resource_kind() -> BootResourceKind { + BootResourceKind::Bls + } + fn set_one_shot(&mut self, entry_name: &str) -> Result<(), RefindError> { self.write_previous_boot(entry_name) } diff --git a/booters/refind/src/lib.rs b/booters/refind/src/lib.rs index 0e216e61..b53b18e8 100644 --- a/booters/refind/src/lib.rs +++ b/booters/refind/src/lib.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use upac_abi::BOOT_ABI_VERSION; +use upac_abi::BootResourceKind; use upac_abi::error::ErrorKind; use upac_abi::request::{ CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, @@ -35,6 +36,13 @@ pub unsafe extern "C" fn boot_abi_version() -> u32 { BOOT_ABI_VERSION } +/// # Safety +/// Touches no pointers — `unsafe extern "C"` only to match the ABI calling convention. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn boot_resource_kind() -> BootResourceKind { + Refind::boot_resource_kind() +} + /// # Safety /// `request`, if non-null, must point to a valid, initialized `CBootPluginSetOneShotRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. diff --git a/booters/systemd-boot/src/backend.rs b/booters/systemd-boot/src/backend.rs index f93f9e20..5cbba2a7 100644 --- a/booters/systemd-boot/src/backend.rs +++ b/booters/systemd-boot/src/backend.rs @@ -18,6 +18,8 @@ use nix::{ioctl_read, ioctl_write_ptr}; use uuid::Uuid; +use upac_abi::BootResourceKind; + use upac_types::traits::Booter; use super::boot::{EFIVARFS_PATH, LOADER_ENTRY_DEFAULT_VAR, LOADER_ENTRY_ONE_SHOT_VAR, SD_BOOT_LOADER_GUID}; @@ -50,6 +52,10 @@ impl Booter for SystemdBoot { }) } + fn boot_resource_kind() -> BootResourceKind { + BootResourceKind::Bls + } + fn set_one_shot(&mut self, entry_name: &str) -> Result<(), SystemdBootError> { self.write_loader_variable(LOADER_ENTRY_ONE_SHOT_VAR, entry_name) } diff --git a/booters/systemd-boot/src/lib.rs b/booters/systemd-boot/src/lib.rs index 955e9619..14468cb0 100644 --- a/booters/systemd-boot/src/lib.rs +++ b/booters/systemd-boot/src/lib.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use upac_abi::BOOT_ABI_VERSION; +use upac_abi::BootResourceKind; use upac_abi::error::ErrorKind; use upac_abi::request::{ CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, @@ -35,6 +36,13 @@ pub unsafe extern "C" fn boot_abi_version() -> u32 { BOOT_ABI_VERSION } +/// # Safety +/// Touches no pointers — `unsafe extern "C"` only to match the ABI calling convention. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn boot_resource_kind() -> BootResourceKind { + SystemdBoot::boot_resource_kind() +} + /// # Safety /// `request`, if non-null, must point to a valid, initialized `CBootPluginSetOneShotRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. diff --git a/booters/uki/src/backend.rs b/booters/uki/src/backend.rs index b32b2f17..6917502d 100644 --- a/booters/uki/src/backend.rs +++ b/booters/uki/src/backend.rs @@ -19,6 +19,8 @@ use nix::{ioctl_read, ioctl_write_ptr}; use uuid::Uuid; +use upac_abi::BootResourceKind; + use upac_types::traits::Booter; use super::boot::{BOOT_NEXT_VAR, BOOT_ORDER_VAR, EFIVARFS_PATH}; @@ -43,6 +45,10 @@ impl Booter for Uki { }) } + fn boot_resource_kind() -> BootResourceKind { + BootResourceKind::Uki + } + fn set_one_shot(&mut self, entry_name: &str) -> Result<(), UkiError> { let id = self.find_boot_id(entry_name)?; diff --git a/booters/uki/src/lib.rs b/booters/uki/src/lib.rs index 2a5cd35d..1eab8f5d 100644 --- a/booters/uki/src/lib.rs +++ b/booters/uki/src/lib.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use upac_abi::BOOT_ABI_VERSION; +use upac_abi::BootResourceKind; use upac_abi::error::ErrorKind; use upac_abi::request::{ CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, @@ -35,6 +36,13 @@ pub unsafe extern "C" fn boot_abi_version() -> u32 { BOOT_ABI_VERSION } +/// # Safety +/// Touches no pointers — `unsafe extern "C"` only to match the ABI calling convention. +#[cfg_attr(feature = "cdylib", unsafe(no_mangle))] +pub unsafe extern "C" fn boot_resource_kind() -> BootResourceKind { + Uki::boot_resource_kind() +} + /// # Safety /// `request`, if non-null, must point to a valid, initialized `CBootPluginSetOneShotRequest` for the /// duration of the call. `err_out`, if non-null, must point to writable `ErrorKind` storage. diff --git a/lib/abi/src/lib.rs b/lib/abi/src/lib.rs index 69267adf..320dda42 100644 --- a/lib/abi/src/lib.rs +++ b/lib/abi/src/lib.rs @@ -21,7 +21,7 @@ pub mod response; pub mod types; pub const LIB_ABI_VERSION: u32 = 2; -pub const BOOT_ABI_VERSION: u32 = 2; +pub const BOOT_ABI_VERSION: u32 = 3; pub const DECODER_ABI_VERSION: u32 = 2; pub const SETUP_ABI_VERSION: u32 = 2; @@ -44,10 +44,29 @@ pub type ConfirmBootFn = pub type InstallFn = unsafe extern "C" fn(request: *const CBootPluginInstallRequest, err_out: *mut ErrorKind) -> i32; +pub type BootResourceKindFn = unsafe extern "C" fn() -> BootResourceKind; + pub type DecodeFn = unsafe extern "C" fn(request: *const CDecodeRequest, response_out: *mut CDecodeResponse) -> i32; pub type FreeDecodeResponseFn = unsafe extern "C" fn(response: *mut CDecodeResponse); +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BootResourceKind { + Bls = 0, + Uki = 1, +} + +impl BootResourceKind { + pub fn from_u8(version: u8) -> Result { + match version { + 0 => Ok(BootResourceKind::Bls), + 1 => Ok(BootResourceKind::Uki), + _ => Err(ErrorKind::InvalidEntry), + } + } +} + #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FileDiffKind { diff --git a/lib/lib/src/boot/mod.rs b/lib/lib/src/boot/mod.rs index 8c92f5c7..c470a923 100644 --- a/lib/lib/src/boot/mod.rs +++ b/lib/lib/src/boot/mod.rs @@ -14,6 +14,8 @@ use composefs_boot::bootloader::{BootEntry, get_boot_resources}; use composefs_boot::cmdline::ComposefsCmdline; use composefs_boot::write_boot::write_boot_simple; +use upac_abi::BootResourceKind; + use self::error::BootError; use crate::composefs::repository::ObjectID; @@ -42,15 +44,33 @@ impl WrittenBootEntry { pub fn write_boot_entry( repository: &Repository, tree: &FileSystem, digest: ObjectID, boot_partition: &Path, - prefix_digest: &str, + prefix_digest: &str, wanted: BootResourceKind, ) -> Result { let rooted_tree = wrap_under_usr(tree); - let mut entries = get_boot_resources(&rooted_tree, repository)?; + let entries = get_boot_resources(&rooted_tree, repository)?; + + if entries.is_empty() { + return Err(BootError::NoBootResource); + } - if entries.len() > 1 { + let mut matching: Vec<_> = entries + .into_iter() + .filter(|entry| { + matches!( + (wanted, entry), + (BootResourceKind::Uki, BootEntry::Type2(_)) + | ( + BootResourceKind::Bls, + BootEntry::Type1(_) | BootEntry::UsrLibModulesVmLinuz(_) + ) + ) + }) + .collect(); + + if matching.len() > 1 { return Err(BootError::AmbiguousBootResource); } - let entry = entries.pop().ok_or(BootError::NoBootResource)?; + let entry = matching.pop().ok_or(BootError::UnsupportedBootResource)?; let written = match &entry { BootEntry::Type1(_) | BootEntry::UsrLibModulesVmLinuz(_) => WrittenBootEntry::Bls(prefix_digest.to_owned()), diff --git a/lib/lib/src/mutated/files/checkout.rs b/lib/lib/src/mutated/files/checkout.rs index 3fdac6e1..4c620993 100644 --- a/lib/lib/src/mutated/files/checkout.rs +++ b/lib/lib/src/mutated/files/checkout.rs @@ -30,11 +30,18 @@ impl Stage for CheckoutStage { let deploy_tree = deploy.open_tree(new_prefix)?; let digest = object_id_from_hex(new_prefix)?; - let esp_mount = find_esp_mount()?; - let written = write_boot_entry(&repository, &deploy_tree, digest, &esp_mount, new_prefix)?; - let plugin = BootPlugins::new()?.load(requested_boot_plugins)?; + let esp_mount = find_esp_mount()?; + let written = write_boot_entry( + &repository, + &deploy_tree, + digest, + &esp_mount, + new_prefix, + plugin.boot_resource_kind(), + )?; + context.put(ResolvedBootEntry { plugin, entry_name: written.into_entry_name(), diff --git a/lib/lib/src/mutated/installer/checkout.rs b/lib/lib/src/mutated/installer/checkout.rs index b22b7285..237deb5d 100644 --- a/lib/lib/src/mutated/installer/checkout.rs +++ b/lib/lib/src/mutated/installer/checkout.rs @@ -31,11 +31,18 @@ impl Stage for CheckoutStage { let tree = deploy.open_tree(&new_state.prefix_digest)?; let digest = object_id_from_hex(&new_state.prefix_digest)?; - let esp_mount = find_esp_mount()?; - let written = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; - let plugin = BootPlugins::new()?.load(requested_boot_plugins)?; + let esp_mount = find_esp_mount()?; + let written = write_boot_entry( + &repository, + &tree, + digest, + &esp_mount, + &new_state.prefix_digest, + plugin.boot_resource_kind(), + )?; + context.put(ResolvedBootEntry { plugin, entry_name: written.into_entry_name(), diff --git a/lib/lib/src/mutated/rollback/checkout.rs b/lib/lib/src/mutated/rollback/checkout.rs index 880188e6..6108853c 100644 --- a/lib/lib/src/mutated/rollback/checkout.rs +++ b/lib/lib/src/mutated/rollback/checkout.rs @@ -30,11 +30,18 @@ impl Stage for CheckoutStage { let tree = deploy.open_tree(target)?; let digest = object_id_from_hex(target)?; - let esp_mount = find_esp_mount()?; - let written = write_boot_entry(&repository, &tree, digest, &esp_mount, target)?; - let plugin = BootPlugins::new()?.load(requested_boot_plugins)?; + let esp_mount = find_esp_mount()?; + let written = write_boot_entry( + &repository, + &tree, + digest, + &esp_mount, + target, + plugin.boot_resource_kind(), + )?; + context.put(ResolvedBootEntry { plugin, entry_name: written.into_entry_name(), diff --git a/lib/lib/src/mutated/uninstaller/checkout.rs b/lib/lib/src/mutated/uninstaller/checkout.rs index c4cc0906..a9f42824 100644 --- a/lib/lib/src/mutated/uninstaller/checkout.rs +++ b/lib/lib/src/mutated/uninstaller/checkout.rs @@ -30,11 +30,18 @@ impl Stage for CheckoutStage { let tree = deploy.open_tree(&new_state.prefix_digest)?; let digest = object_id_from_hex(&new_state.prefix_digest)?; - let esp_mount = find_esp_mount()?; - let written = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; - let plugin = BootPlugins::new()?.load(requested_boot_plugin)?; + let esp_mount = find_esp_mount()?; + let written = write_boot_entry( + &repository, + &tree, + digest, + &esp_mount, + &new_state.prefix_digest, + plugin.boot_resource_kind(), + )?; + context.put(ResolvedBootEntry { plugin, entry_name: written.into_entry_name(), diff --git a/lib/lib/src/mutated/update/checkout.rs b/lib/lib/src/mutated/update/checkout.rs index b5a41926..abc6adf8 100644 --- a/lib/lib/src/mutated/update/checkout.rs +++ b/lib/lib/src/mutated/update/checkout.rs @@ -30,11 +30,18 @@ impl Stage for CheckoutStage { let tree = deploy.open_tree(&new_state.prefix_digest)?; let digest = object_id_from_hex(&new_state.prefix_digest)?; - let esp_mount = find_esp_mount()?; - let written = write_boot_entry(&repository, &tree, digest, &esp_mount, &new_state.prefix_digest)?; - let plugin = BootPlugins::new()?.load(requested_boot_plugin)?; + let esp_mount = find_esp_mount()?; + let written = write_boot_entry( + &repository, + &tree, + digest, + &esp_mount, + &new_state.prefix_digest, + plugin.boot_resource_kind(), + )?; + context.put(ResolvedBootEntry { plugin, entry_name: written.into_entry_name(), diff --git a/lib/lib/src/plugin/boot/dynamic_link.rs b/lib/lib/src/plugin/boot/dynamic_link.rs index 2723c790..24a1e6bd 100644 --- a/lib/lib/src/plugin/boot/dynamic_link.rs +++ b/lib/lib/src/plugin/boot/dynamic_link.rs @@ -5,7 +5,7 @@ use libloading::Library; -use upac_abi::{BOOT_ABI_VERSION, BootPluginAbiVersionFn, ConfirmBootFn, InstallFn, SetOneShotFn}; +use upac_abi::{BOOT_ABI_VERSION, BootPluginAbiVersionFn, BootResourceKindFn, ConfirmBootFn, InstallFn, SetOneShotFn}; use super::BootPlugin; use super::error::BootPluginError; @@ -31,6 +31,7 @@ impl BootPlugin { let set_one_shot: SetOneShotFn = load_symbol!(library, "set_one_shot"); let confirm_boot: ConfirmBootFn = load_symbol!(library, "confirm_boot"); let install: InstallFn = load_symbol!(library, "install"); + let boot_resource_kind: BootResourceKindFn = load_symbol!(library, "boot_resource_kind"); let got_booter_abi_version = unsafe { booter_abi_version() }; if got_booter_abi_version != BOOT_ABI_VERSION { @@ -45,6 +46,7 @@ impl BootPlugin { confirm_boot, install, + boot_resource_kind, _library: Some(library), }) } diff --git a/lib/lib/src/plugin/boot/mod.rs b/lib/lib/src/plugin/boot/mod.rs index ce49c46a..65710936 100644 --- a/lib/lib/src/plugin/boot/mod.rs +++ b/lib/lib/src/plugin/boot/mod.rs @@ -9,7 +9,7 @@ use upac_abi::error::ErrorKind; use upac_abi::request::{ CBootPluginConfirmSuccsesBootRequest, CBootPluginInstallRequest, CBootPluginSetOneShotRequest, }; -use upac_abi::{ConfirmBootFn, InstallFn, SetOneShotFn}; +use upac_abi::{BootResourceKind, BootResourceKindFn, ConfirmBootFn, InstallFn, SetOneShotFn}; use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginInstallRequest, BootPluginSetOneShotRequest}; @@ -67,12 +67,17 @@ pub struct BootPlugin { set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, install: InstallFn, + boot_resource_kind: BootResourceKindFn, #[cfg(feature = "dynamic-plugins")] _library: Option, } impl BootPlugin { + pub fn boot_resource_kind(&self) -> BootResourceKind { + unsafe { (self.boot_resource_kind)() } + } + pub fn set_one_shot(&self, request: BootPluginSetOneShotRequest) -> Result<(), BootPluginError> { let request: CBootPluginSetOneShotRequest = request.into(); diff --git a/lib/lib/src/plugin/boot/static_link.rs b/lib/lib/src/plugin/boot/static_link.rs index 7dea4df6..9f918849 100644 --- a/lib/lib/src/plugin/boot/static_link.rs +++ b/lib/lib/src/plugin/boot/static_link.rs @@ -3,34 +3,45 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac_abi::{ConfirmBootFn, InstallFn, SetOneShotFn}; +use upac_abi::{BootResourceKindFn, ConfirmBootFn, InstallFn, SetOneShotFn}; use super::BootPlugin; use super::error::BootPluginError; #[cfg(feature = "builtin-grub")] -use upac_boot_grub::{confirm_boot as grub_confirm_boot, install as grub_install, set_one_shot as grub_set_one_shot}; +use upac_boot_grub::{ + boot_resource_kind as grub_boot_resource_kind, confirm_boot as grub_confirm_boot, install as grub_install, + set_one_shot as grub_set_one_shot, +}; #[cfg(feature = "builtin-systemd-boot")] use upac_boot_systemd_boot::{ - confirm_boot as systemd_boot_confirm_boot, install as systemd_boot_install, - set_one_shot as systemd_boot_set_one_shot, + boot_resource_kind as systemd_boot_boot_resource_kind, confirm_boot as systemd_boot_confirm_boot, + install as systemd_boot_install, set_one_shot as systemd_boot_set_one_shot, }; #[cfg(feature = "builtin-uki")] -use upac_boot_uki::{confirm_boot as uki_confirm_boot, install as uki_install, set_one_shot as uki_set_one_shot}; +use upac_boot_uki::{ + boot_resource_kind as uki_boot_resource_kind, confirm_boot as uki_confirm_boot, install as uki_install, + set_one_shot as uki_set_one_shot, +}; #[cfg(feature = "builtin-refind")] use upac_boot_refind::{ - confirm_boot as refind_confirm_boot, install as refind_install, set_one_shot as refind_set_one_shot, + boot_resource_kind as refind_boot_resource_kind, confirm_boot as refind_confirm_boot, install as refind_install, + set_one_shot as refind_set_one_shot, }; impl BootPlugin { - fn load_plugin_from_static(set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, install: InstallFn) -> Self { + fn load_plugin_from_static( + set_one_shot: SetOneShotFn, confirm_boot: ConfirmBootFn, install: InstallFn, + boot_resource_kind: BootResourceKindFn, + ) -> Self { BootPlugin { set_one_shot, confirm_boot, install, + boot_resource_kind, #[cfg(feature = "dynamic-plugins")] _library: None, @@ -45,6 +56,7 @@ pub(super) fn load_boot_plugin_static(name: &str) -> Result Result Result Result TempDir { Builder::new().prefix(name).tempdir().unwrap() @@ -77,7 +78,14 @@ fn write_boot_entry_fails_when_the_tree_has_no_boot_resource() { let tree = empty_tree(); let esp = scratch_dir("boot-none-esp"); - let result = write_boot_entry(&repository, &tree, ObjectID::EMPTY, esp.path(), "deadbeef"); + let result = write_boot_entry( + &repository, + &tree, + ObjectID::EMPTY, + esp.path(), + "deadbeef", + BootResourceKind::Bls, + ); assert_eq!(result.unwrap_err(), BootError::NoBootResource); } @@ -91,7 +99,14 @@ fn write_boot_entry_fails_when_the_tree_has_more_than_one_boot_resource() { insert_kernel(&repository, &mut tree, &mut ctx, "6.7.0"); let esp = scratch_dir("boot-ambiguous-esp"); - let result = write_boot_entry(&repository, &tree, ObjectID::EMPTY, esp.path(), "deadbeef"); + let result = write_boot_entry( + &repository, + &tree, + ObjectID::EMPTY, + esp.path(), + "deadbeef", + BootResourceKind::Bls, + ); assert_eq!(result.unwrap_err(), BootError::AmbiguousBootResource); } diff --git a/lib/setup/src/error.rs b/lib/setup/src/error.rs index 4e05e1f6..3680a99d 100644 --- a/lib/setup/src/error.rs +++ b/lib/setup/src/error.rs @@ -21,6 +21,10 @@ use upac::plugin::boot::error::BootPluginError; use upac_abi::error::ErrorKind; +#[cfg(test)] +#[path = "../tests/inline/error.rs"] +mod tests; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum SetupError { Common(CommonError), diff --git a/lib/setup/src/stages/boot.rs b/lib/setup/src/stages/boot.rs index b76c98b7..28d9a0e2 100644 --- a/lib/setup/src/stages/boot.rs +++ b/lib/setup/src/stages/boot.rs @@ -65,6 +65,7 @@ impl Stage for StageBootStage { deploy_digests.prefix.clone(), &esp_mount_point, &prefix_digest_hex, + plugin.boot_resource_kind(), )?; if matches!(written, WrittenBootEntry::Uki(_)) { diff --git a/lib/setup/src/stages/mod.rs b/lib/setup/src/stages/mod.rs index a660a7f3..d4cb774c 100644 --- a/lib/setup/src/stages/mod.rs +++ b/lib/setup/src/stages/mod.rs @@ -57,6 +57,10 @@ mod prepare; mod system; mod unpack; +#[cfg(test)] +#[path = "../../tests/inline/stages.rs"] +mod tests; + macro_rules! import_if_dir { ($repository:expr, $tree:expr, $source:expr, $import_ctx:expr, $cancel:expr) => { if $source.is_dir() { diff --git a/lib/setup/tests/data.rs b/lib/setup/tests/data.rs deleted file mode 100644 index 7b55ea88..00000000 --- a/lib/setup/tests/data.rs +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::ptr::null_mut; - -use upac_abi::FsKind; -use upac_abi::hook::CancelToken; - -use upac_setup::data::{SetupExistingData, SetupWholeDiskData}; - -fn existing_data<'data>(cancel_token: &'data CancelToken, mount_point: Option<&'data str>) -> SetupExistingData<'data> { - SetupExistingData { - esp_device: "/dev/sda1", - deploy_device: "/dev/sda2", - deploy_fs: FsKind::Ext4, - extra_mounts: Vec::new(), - - mount_point, - source: "/mnt/source", - empty_config: false, - pinned: false, - boot_plugin: None, - - hook_message: None, - hook_message_context: null_mut(), - - cancel_token, - } -} - -fn whole_disk_data<'data>( - cancel_token: &'data CancelToken, mount_point: Option<&'data str>, -) -> SetupWholeDiskData<'data> { - SetupWholeDiskData { - device_path: "/dev/sda", - esp_size_mib: 512, - deploy_fs: FsKind::Ext4, - deploy_size_mib: 8192, - extra_partitions: Vec::new(), - force_wipe: false, - - node_size: 0, - sector_size: 0, - - mount_point, - source: "/mnt/source", - empty_config: false, - pinned: false, - boot_plugin: None, - - hook_message: None, - hook_message_context: null_mut(), - - cancel_token, - } -} - -#[test] -fn setup_existing_data_mount_point_returns_explicit_value_when_given() { - let cancel_token = CancelToken::new(); - let data = existing_data(&cancel_token, Some("/custom/mount")); - - assert_eq!(data.mount_point(), "/custom/mount"); -} - -#[test] -fn setup_existing_data_mount_point_falls_back_to_the_configured_default() { - let cancel_token = CancelToken::new(); - let data = existing_data(&cancel_token, None); - - assert_eq!(data.mount_point(), "/mnt/upac-setup"); -} - -#[test] -fn setup_whole_disk_data_mount_point_returns_explicit_value_when_given() { - let cancel_token = CancelToken::new(); - let data = whole_disk_data(&cancel_token, Some("/custom/mount")); - - assert_eq!(data.mount_point(), "/custom/mount"); -} - -#[test] -fn setup_whole_disk_data_mount_point_falls_back_to_the_configured_default() { - let cancel_token = CancelToken::new(); - let data = whole_disk_data(&cancel_token, None); - - assert_eq!(data.mount_point(), "/mnt/upac-setup"); -} diff --git a/lib/setup/tests/error.rs b/lib/setup/tests/error.rs deleted file mode 100644 index 1bb1f367..00000000 --- a/lib/setup/tests/error.rs +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::io::{Error as IoError, ErrorKind as IoErrorKind}; - -use gptman::Error as GptError; -use gptman::linux::BlockError as GptBlockError; - -use nix::errno::Errno; - -use upac_setup::error::SetupError; - -#[test] -fn gpt_error_io_maps_to_setup_error_io_with_the_same_kind() { - let error = GptError::Io(IoError::new(IoErrorKind::PermissionDenied, "denied")); - - assert_eq!(SetupError::from(error), SetupError::Io(IoErrorKind::PermissionDenied)); -} - -#[test] -fn gpt_error_no_space_left_maps_directly() { - assert_eq!(SetupError::from(GptError::NoSpaceLeft), SetupError::NoSpaceLeft); -} - -#[test] -fn gpt_error_invalid_partition_boundaries_maps_to_invalid_partition_layout() { - assert_eq!( - SetupError::from(GptError::InvalidPartitionBoundaries), - SetupError::InvalidPartitionLayout - ); -} - -#[test] -fn gpt_error_falls_back_to_unexpected_for_other_variants() { - assert_eq!(SetupError::from(GptError::InvalidSignature), SetupError::Unexpected); -} - -#[test] -fn gpt_block_error_metadata_maps_to_setup_error_io_with_the_same_kind() { - let error = GptBlockError::Metadata(IoError::new(IoErrorKind::NotFound, "missing")); - - assert_eq!(SetupError::from(error), SetupError::Io(IoErrorKind::NotFound)); -} - -#[test] -fn gpt_block_error_not_block_maps_directly() { - assert_eq!(SetupError::from(GptBlockError::NotBlock), SetupError::NotBlockDevice); -} - -#[test] -fn gpt_block_error_reread_table_maps_to_reread_failed_with_the_same_errno() { - let error = GptBlockError::RereadTable(Errno::ENOSPC); - - assert_eq!(SetupError::from(error), SetupError::RereadFailed(Errno::ENOSPC)); -} diff --git a/lib/setup/tests/format.rs b/lib/setup/tests/format.rs deleted file mode 100644 index 794e47d4..00000000 --- a/lib/setup/tests/format.rs +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::{File, OpenOptions}; -use std::io::Read; - -use tempfile::TempDir; - -use upac_setup::format::FormatTarget; - -#[test] -fn format_esp_writes_a_valid_fat32_boot_sector() { - let scratch = TempDir::new().unwrap(); - let device_path = scratch.path().join("esp.img"); - - { - let file = File::create(&device_path).unwrap(); - file.set_len(64 * 1024 * 1024).unwrap(); - } - - let target = FormatTarget { - device_path: &device_path, - label: Some("ESP"), - }; - target.format_esp().unwrap(); - - let mut file = OpenOptions::new().read(true).open(&device_path).unwrap(); - let mut boot_sector = [0u8; 512]; - file.read_exact(&mut boot_sector).unwrap(); - - assert_eq!(&boot_sector[510..512], &[0x55, 0xAA]); -} diff --git a/lib/setup/tests/inline/embed.rs b/lib/setup/tests/inline/embed.rs index 46470eec..a86103ee 100644 --- a/lib/setup/tests/inline/embed.rs +++ b/lib/setup/tests/inline/embed.rs @@ -13,12 +13,14 @@ use upac::database::{InMemory, MemoryDatabase}; use upac::orchestrator::context::Context; use upac::orchestrator::stage::{Stage, StageResult}; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; use crate::target::TargetSysroot; -use super::super::enumerate::{ConfigTree, GenesisDatabase, PrefixTree}; -use super::{ConfigDigest, EmbedDatabaseStage, PrefixDigest}; +use super::super::{ConfigState, DeployDigests, PrefixTree}; +use super::EmbedDatabaseStage; #[test] fn run_commits_both_trees_and_puts_digests() { @@ -28,8 +30,10 @@ fn run_commits_both_trees_and_puts_digests() { let mut context = Context::new(); context.put(target); context.put(PrefixTree(FileSystem::new(Stat::uninitialized()))); - context.put(ConfigTree(FileSystem::new(Stat::uninitialized()))); - context.put(GenesisDatabase(MemoryDatabase::new_in_memory().unwrap())); + context.put(ConfigState { + config_tree: FileSystem::new(Stat::uninitialized()), + database: MemoryDatabase::new_in_memory().unwrap(), + }); context.put(ImportContext::default()); let cancel = CancelToken::new(); @@ -38,8 +42,7 @@ fn run_commits_both_trees_and_puts_digests() { let (_, result, _guard) = EmbedDatabaseStage.run(&mut context, &cancel, progress).unwrap(); assert!(matches!(result, StageResult::Advance)); - assert!(context.get::().is_some()); - assert!(context.get::().is_some()); + assert!(context.get::().is_some()); } #[test] @@ -50,7 +53,6 @@ fn run_fails_when_database_missing_from_context() { let mut context = Context::new(); context.put(target); context.put(PrefixTree(FileSystem::new(Stat::uninitialized()))); - context.put(ConfigTree(FileSystem::new(Stat::uninitialized()))); context.put(ImportContext::default()); let cancel = CancelToken::new(); diff --git a/lib/setup/tests/inline/enumerate.rs b/lib/setup/tests/inline/enumerate.rs index fa29aff8..2804b650 100644 --- a/lib/setup/tests/inline/enumerate.rs +++ b/lib/setup/tests/inline/enumerate.rs @@ -10,15 +10,13 @@ use tempfile::TempDir; use upac::orchestrator::context::Context; use upac::orchestrator::stage::{Stage, StageResult}; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; use upac_types::TmpPath; +use upac_types::hook::ProgressEventBuilder; -use super::super::prepare::ResolvedSourceDir; -use super::{ - ConfigTree, EnumeratePackagesStage, GenesisDatabase, PendingPackagePaths, PendingPackages, PrefixTree, - TotalPackages, UnpackerState, -}; +use super::super::{ConfigState, PrefixTree, ResolvedSourceDir, SetupProgress, UnpackState}; +use super::EnumeratePackagesStage; #[test] fn run_lists_only_files_and_initializes_pipeline_state() { @@ -37,18 +35,16 @@ fn run_lists_only_files_and_initializes_pipeline_state() { assert!(matches!(result, StageResult::Advance)); - let total = context.get::().unwrap(); - assert_eq!(total.0, 2); + let setup_progress = context.get::().unwrap(); + assert_eq!(setup_progress.total, 2); + assert!(setup_progress.pending.is_empty()); - let pending = context.get::().unwrap(); - assert_eq!(pending.0.len(), 2); + let unpack_state = context.get::().unwrap(); + assert_eq!(unpack_state.pending_paths.len(), 2); - assert!(context.get::().is_some()); assert!(context.get::().is_some()); - assert!(context.get::().unwrap().0.is_empty()); - assert!(context.get::().is_some()); + assert!(context.get::().is_some()); assert!(context.get::().is_some()); - assert!(context.get::().is_some()); } #[test] @@ -63,11 +59,11 @@ fn run_with_empty_directory_sets_total_to_zero() { EnumeratePackagesStage.run(&mut context, &cancel, progress).unwrap(); - let total = context.get::().unwrap(); - assert_eq!(total.0, 0); + let setup_progress = context.get::().unwrap(); + assert_eq!(setup_progress.total, 0); - let pending = context.get::().unwrap(); - assert!(pending.0.is_empty()); + let unpack_state = context.get::().unwrap(); + assert!(unpack_state.pending_paths.is_empty()); } #[test] diff --git a/lib/setup/tests/inline/format.rs b/lib/setup/tests/inline/format.rs index ea3382bb..08b6b9ce 100644 --- a/lib/setup/tests/inline/format.rs +++ b/lib/setup/tests/inline/format.rs @@ -3,7 +3,35 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use super::fat_label; +use std::fs::{File, OpenOptions}; +use std::io::Read; + +use tempfile::TempDir; + +use super::{FormatTarget, fat_label}; + +#[test] +fn format_esp_writes_a_valid_fat32_boot_sector() { + let scratch = TempDir::new().unwrap(); + let device_path = scratch.path().join("esp.img"); + + { + let file = File::create(&device_path).unwrap(); + file.set_len(64 * 1024 * 1024).unwrap(); + } + + let target = FormatTarget { + device_path: &device_path, + label: Some("ESP"), + }; + target.format_esp().unwrap(); + + let mut file = OpenOptions::new().read(true).open(&device_path).unwrap(); + let mut boot_sector = [0u8; 512]; + file.read_exact(&mut boot_sector).unwrap(); + + assert_eq!(&boot_sector[510..512], &[0x55, 0xAA]); +} #[test] fn fat_label_pads_short_labels_with_trailing_spaces() { diff --git a/lib/types/src/traits.rs b/lib/types/src/traits.rs index c01a1c85..733a4997 100644 --- a/lib/types/src/traits.rs +++ b/lib/types/src/traits.rs @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception +use upac_abi::BootResourceKind; use upac_abi::error::ErrorDomain; use upac_abi::hook::{CProgressEvent, HookAck}; @@ -20,6 +21,7 @@ pub trait Booter: Sized { type Error; fn new() -> Result; + fn boot_resource_kind() -> BootResourceKind; fn set_one_shot(&mut self, entry_name: &str) -> Result<(), Self::Error>; fn confirm_boot(&mut self, entry_name: &str, esp_mount_point: &str) -> Result<(), Self::Error>; From 7a194d86c84f35950addf1046cee3df562686367 Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 07:10:04 +0400 Subject: [PATCH 131/148] fix: repair up's static-link symbol names in libcore.rs (cancel/version_abi never got renamed to lib_cancel/lib_abi_version) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The static-link path referenced upac::export::{cancel, version_abi} and upac_abi::ABI_VERSION, none of which exist anymore — upac-lib's own export/mod.rs already uses lib_cancel/lib_abi_version/LIB_ABI_VERSION, but the dynamic-plugins dlopen path (which resolves symbols by string name) had the same stale "cancel"/"version_abi" names too. Both paths now agree with what upac-lib actually exports; `cargo clippy -p upac-cli -p upac-lib --no-default-features --features upac-cli/builtin-all,upac-lib/builtin-all` (the CI "fully static" build) is clean again Co-Authored-By: Claude Sonnet 5 --- user/upac-cli/src/libcore.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/user/upac-cli/src/libcore.rs b/user/upac-cli/src/libcore.rs index a0fa14dc..eacd7a42 100644 --- a/user/upac-cli/src/libcore.rs +++ b/user/upac-cli/src/libcore.rs @@ -48,7 +48,7 @@ use upac::export::unmutated::{ search_meta::search_meta, }; #[cfg(feature = "static-link")] -use upac::export::{cancel, version_abi}; +use upac::export::{lib_abi_version, lib_cancel}; #[cfg(feature = "static-link")] impl RoSymbols { @@ -93,15 +93,15 @@ impl Lib { let lib = Self { ro: RoSymbols::from_static(), rw: RwSymbols::from_static(), - cancel, - version_abi, + cancel: lib_cancel, + version_abi: lib_abi_version, }; let abi_version = unsafe { (lib.version_abi)() }; - if abi_version != upac_abi::ABI_VERSION { + if abi_version != LIB_ABI_VERSION { let err = AbiMismatch { got: abi_version, - expected: upac_abi::ABI_VERSION, + expected: LIB_ABI_VERSION, }; return Err(err.into()); @@ -162,8 +162,8 @@ impl Lib { ro: RoSymbols::load(&loaded_library)?, rw: RwSymbols::load(&loaded_library)?, - cancel: unsafe { Lib::load_symbol(&loaded_library, "cancel")? }, - version_abi: unsafe { Lib::load_symbol(&loaded_library, "version_abi")? }, + cancel: unsafe { Lib::load_symbol(&loaded_library, "lib_cancel")? }, + version_abi: unsafe { Lib::load_symbol(&loaded_library, "lib_abi_version")? }, _lib: loaded_library, }; From a24c910f13de2babcef05403cbc0b9988ed02b09 Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 07:10:18 +0400 Subject: [PATCH 132/148] fix: repair six stale integration tests in lib/lib/tests/ left behind by earlier refactors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit database.rs/orchestrator.rs/scripts_hook.rs imported types from their pre-refactor locations (upac_types::{DeclarativeTrigger,FileEntry,FileEntryScope,PackageMeta} at crate root instead of their actual submodules; ProgressEventBuilder from upac_abi::hook instead of upac_types::hook; scripts::load::load_hooks instead of scripts::load_hooks). plugin_boot_error.rs asserted a BootPluginError::AmbiguousClaim variant that no longer exists. plugin_boot_manifest.rs and half of plugin_decoder.rs tested load_boot_plugin_manifests/load_decoder_manifests as directory+extension-parameterized free functions — both are now BootPluginManifests::new()/DecoderManifests::new(), which read unconditionally from a hardcoded lib.toml-configured path with no way to inject a test directory, so that style of test isn't reconstructible; plugin_boot_manifest.rs is deleted and plugin_decoder.rs keeps only its still-valid build_trigger_table coverage. `cargo test -p upac-lib --all-targets` is green Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/boot/mod.rs | 1 + lib/lib/tests/database.rs | 4 +- lib/lib/tests/orchestrator.rs | 4 +- lib/lib/tests/plugin_boot_error.rs | 1 - lib/lib/tests/plugin_boot_manifest.rs | 89 -------------------------- lib/lib/tests/plugin_decoder.rs | 82 ------------------------ lib/lib/tests/scripts_hook.rs | 4 +- lib/setup/src/format.rs | 8 +-- lib/setup/tests/inline/deploy.rs | 19 +++--- lib/setup/tests/inline/error.rs | 57 +++++++++++++++++ lib/setup/tests/inline/format.rs | 2 +- lib/setup/tests/inline/stages.rs | 90 +++++++++++++++++++++++++++ 12 files changed, 173 insertions(+), 188 deletions(-) delete mode 100644 lib/lib/tests/plugin_boot_manifest.rs create mode 100644 lib/setup/tests/inline/error.rs create mode 100644 lib/setup/tests/inline/stages.rs diff --git a/lib/lib/src/boot/mod.rs b/lib/lib/src/boot/mod.rs index c470a923..e5a6f441 100644 --- a/lib/lib/src/boot/mod.rs +++ b/lib/lib/src/boot/mod.rs @@ -23,6 +23,7 @@ use crate::layout::boot::UPAC_UKI_TO_SLOT; pub mod error; +#[derive(Debug)] pub enum WrittenBootEntry { Bls(String), Uki(String), diff --git a/lib/lib/tests/database.rs b/lib/lib/tests/database.rs index 711b4785..aa762e0a 100644 --- a/lib/lib/tests/database.rs +++ b/lib/lib/tests/database.rs @@ -10,7 +10,9 @@ use upac::database::meta::{MetaStore, MetaStoreMut}; use upac::database::triggers::{TriggerStore, TriggerStoreMut}; use upac::database::{InMemory, MemoryDatabase}; -use upac_types::{DeclarativeTrigger, FileEntry, FileEntryScope, PackageMeta}; +use upac_types::decoder::DeclarativeTrigger; +use upac_types::entry::{FileEntry, FileEntryScope}; +use upac_types::package::PackageMeta; fn sample_meta(name: &str) -> PackageMeta { PackageMeta { diff --git a/lib/lib/tests/orchestrator.rs b/lib/lib/tests/orchestrator.rs index 3b2db695..712bc43d 100644 --- a/lib/lib/tests/orchestrator.rs +++ b/lib/lib/tests/orchestrator.rs @@ -14,7 +14,9 @@ use upac::orchestrator::error::OrchestratorError; use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; use upac::orchestrator::{Orchestrator, SequentialOrchestrator}; use upac_abi::error::ErrorKind; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; #[derive(Debug, Clone, PartialEq, Eq)] enum TestError { diff --git a/lib/lib/tests/plugin_boot_error.rs b/lib/lib/tests/plugin_boot_error.rs index 6a931ac4..530e68ab 100644 --- a/lib/lib/tests/plugin_boot_error.rs +++ b/lib/lib/tests/plugin_boot_error.rs @@ -47,7 +47,6 @@ fn every_variant_maps_to_the_documented_error_kind() { ), (BootPluginError::UnknownName("uki".to_owned()), ErrorKind::NotFound), (BootPluginError::NoClaimant, ErrorKind::NotFound), - (BootPluginError::AmbiguousClaim, ErrorKind::InvalidEntry), ]; for (error, expected) in cases { diff --git a/lib/lib/tests/plugin_boot_manifest.rs b/lib/lib/tests/plugin_boot_manifest.rs deleted file mode 100644 index e661da30..00000000 --- a/lib/lib/tests/plugin_boot_manifest.rs +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception - -use std::fs::write; - -use tempfile::{Builder, TempDir}; - -use upac::plugin::boot::error::BootPluginError; -use upac::plugin::boot::manifest::load_boot_plugin_manifests; - -fn scratch_dir(name: &str) -> TempDir { - Builder::new().prefix(name).tempdir().unwrap() -} - -#[test] -fn load_boot_plugin_manifests_collects_distinct_names() { - let dir = scratch_dir("distinct-names"); - write( - dir.path().join("uki.boot"), - "name = \"uki\"\nlibrary = \"libupac-uki.so\"\n", - ) - .unwrap(); - write( - dir.path().join("grub.boot"), - "name = \"grub\"\nlibrary = \"libupac-grub.so\"\n", - ) - .unwrap(); - - let manifests = load_boot_plugin_manifests(dir.path().to_str().unwrap(), "boot").unwrap(); - - assert_eq!(manifests.len(), 2); - assert_eq!(manifests["uki"].library, "libupac-uki.so"); - assert_eq!(manifests["grub"].library, "libupac-grub.so"); -} - -#[test] -fn load_boot_plugin_manifests_ignores_non_matching_extension() { - let dir = scratch_dir("ignore-extension"); - write( - dir.path().join("uki.boot"), - "name = \"uki\"\nlibrary = \"libupac-uki.so\"\n", - ) - .unwrap(); - write(dir.path().join("README.md"), b"not a manifest").unwrap(); - - let manifests = load_boot_plugin_manifests(dir.path().to_str().unwrap(), "boot").unwrap(); - - assert_eq!(manifests.len(), 1); -} - -#[test] -fn load_boot_plugin_manifests_fails_on_duplicate_name() { - let dir = scratch_dir("duplicate-name"); - write( - dir.path().join("a.boot"), - "name = \"uki\"\nlibrary = \"libupac-uki-a.so\"\n", - ) - .unwrap(); - write( - dir.path().join("b.boot"), - "name = \"uki\"\nlibrary = \"libupac-uki-b.so\"\n", - ) - .unwrap(); - - let result = load_boot_plugin_manifests(dir.path().to_str().unwrap(), "boot"); - - assert_eq!(result.unwrap_err(), BootPluginError::DuplicateName("uki".to_owned())); -} - -#[test] -fn load_boot_plugin_manifests_fails_on_malformed_toml() { - let dir = scratch_dir("malformed-toml"); - write(dir.path().join("broken.boot"), "not valid toml [[[").unwrap(); - - let result = load_boot_plugin_manifests(dir.path().to_str().unwrap(), "boot"); - - assert_eq!(result.unwrap_err(), BootPluginError::Manifest); -} - -#[test] -fn load_boot_plugin_manifests_treats_a_missing_directory_as_no_manifests() { - let dir = scratch_dir("missing-dir").path().join("does-not-exist"); - - let manifests = load_boot_plugin_manifests(dir.to_str().unwrap(), "boot").unwrap(); - - assert!(manifests.is_empty()); -} diff --git a/lib/lib/tests/plugin_decoder.rs b/lib/lib/tests/plugin_decoder.rs index 77ddd3e9..5054ff88 100644 --- a/lib/lib/tests/plugin_decoder.rs +++ b/lib/lib/tests/plugin_decoder.rs @@ -4,19 +4,11 @@ // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception use std::collections::HashMap; -use std::fs::write; -use tempfile::{Builder, TempDir}; -use upac::plugin::decoder::error::DecoderError; -use upac::plugin::decoder::manifest::load_decoder_manifests; use upac::plugin::decoder::triggers::build_trigger_table; use upac::scripts::error::HookError; use upac::scripts::file::HookFile; -fn scratch_dir(name: &str) -> TempDir { - Builder::new().prefix(name).tempdir().unwrap() -} - fn hook_file(priority: i32, triggers: &[(&str, &[&str])]) -> HookFile { let mut triggers_map = HashMap::new(); for (format, names) in triggers { @@ -88,77 +80,3 @@ fn build_trigger_table_keeps_distinct_names_independent() { assert_eq!(names, vec!["postinst", "postrm"]); } - -#[test] -fn load_decoder_manifests_collects_distinct_formats() { - let dir = scratch_dir("distinct-formats"); - write( - dir.path().join("deb.decoder"), - "format = \"deb\"\nextensions = [\"deb\"]\nlibrary = \"libupac-deb.so\"\nmime = \"application/vnd.debian.binary-package\"\n", - ) - .unwrap(); - write( - dir.path().join("rpm.decoder"), - "format = \"rpm\"\nextensions = [\"rpm\"]\nlibrary = \"libupac-rpm.so\"\nmime = \"application/x-rpm\"\n", - ) - .unwrap(); - - let manifests = load_decoder_manifests(dir.path().to_str().unwrap(), "decoder").unwrap(); - - assert_eq!(manifests.len(), 2); - assert_eq!(manifests["deb"].library, "libupac-deb.so"); - assert_eq!(manifests["rpm"].extensions, vec!["rpm".to_owned()]); -} - -#[test] -fn load_decoder_manifests_ignores_non_matching_extension() { - let dir = scratch_dir("ignore-extension"); - write( - dir.path().join("deb.decoder"), - "format = \"deb\"\nextensions = [\"deb\"]\nlibrary = \"libupac-deb.so\"\nmime = \"application/vnd.debian.binary-package\"\n", - ) - .unwrap(); - write(dir.path().join("README.md"), b"not a manifest").unwrap(); - - let manifests = load_decoder_manifests(dir.path().to_str().unwrap(), "decoder").unwrap(); - - assert_eq!(manifests.len(), 1); -} - -#[test] -fn load_decoder_manifests_fails_on_duplicate_format() { - let dir = scratch_dir("duplicate-format"); - write( - dir.path().join("a.decoder"), - "format = \"deb\"\nextensions = [\"deb\"]\nlibrary = \"libupac-deb-a.so\"\nmime = \"application/vnd.debian.binary-package\"\n", - ) - .unwrap(); - write( - dir.path().join("b.decoder"), - "format = \"deb\"\nextensions = [\"deb\"]\nlibrary = \"libupac-deb-b.so\"\nmime = \"application/vnd.debian.binary-package\"\n", - ) - .unwrap(); - - let result = load_decoder_manifests(dir.path().to_str().unwrap(), "decoder"); - - assert_eq!(result.unwrap_err(), DecoderError::DuplicateFormat("deb".to_owned())); -} - -#[test] -fn load_decoder_manifests_treats_a_missing_directory_as_no_manifests() { - let dir = scratch_dir("missing-dir").path().join("does-not-exist"); - - let manifests = load_decoder_manifests(dir.to_str().unwrap(), "decoder").unwrap(); - - assert!(manifests.is_empty()); -} - -#[test] -fn load_decoder_manifests_fails_on_malformed_toml() { - let dir = scratch_dir("malformed-toml"); - write(dir.path().join("broken.decoder"), "not valid toml [[[").unwrap(); - - let result = load_decoder_manifests(dir.path().to_str().unwrap(), "decoder"); - - assert_eq!(result.unwrap_err(), DecoderError::Manifest); -} diff --git a/lib/lib/tests/scripts_hook.rs b/lib/lib/tests/scripts_hook.rs index 28968b0c..47b897aa 100644 --- a/lib/lib/tests/scripts_hook.rs +++ b/lib/lib/tests/scripts_hook.rs @@ -11,12 +11,12 @@ use upac::errors::CommonError; use upac::orchestrator::stage::{ConcurrentStage, StageResult}; use upac::scripts::error::HookError; use upac::scripts::file::HookFile; -use upac::scripts::load::load_hooks; +use upac::scripts::load_hooks; use upac::scripts::pipeline::{Operation, PipelineTrigger, Timing}; use upac::scripts::primitive::Step; -use upac_abi::hook::ProgressEventBuilder; use upac_pki::generate::{Identity, SigningIdentity, generate_root, generate_signing_cert}; use upac_pki::signature::HookSignature; +use upac_types::hook::ProgressEventBuilder; fn scratch_dir(name: &str) -> TempDir { Builder::new().prefix(name).tempdir().unwrap() diff --git a/lib/setup/src/format.rs b/lib/setup/src/format.rs index 2ee2d4e9..0ed9cf9e 100644 --- a/lib/setup/src/format.rs +++ b/lib/setup/src/format.rs @@ -21,10 +21,6 @@ use upac_abi::FsKind; use super::error::SetupError; use super::layout::mkfs::{EXT4_BIN, WIPEFS_BIN, XFS_BIN}; -#[cfg(test)] -#[path = "../tests/inline/format.rs"] -mod tests; - macro_rules! fat_label { ($label:expr) => {{ let mut bytes = [b' '; 11]; @@ -36,6 +32,10 @@ macro_rules! fat_label { }}; } +#[cfg(test)] +#[path = "../tests/inline/format.rs"] +mod tests; + macro_rules! run_mkfs { ($binary:expr, $args:expr) => {{ let status = Command::new($binary).args($args).status()?; diff --git a/lib/setup/tests/inline/deploy.rs b/lib/setup/tests/inline/deploy.rs index 4b9c492c..011a9cc0 100644 --- a/lib/setup/tests/inline/deploy.rs +++ b/lib/setup/tests/inline/deploy.rs @@ -12,12 +12,13 @@ use upac::database::record::DeployRecord; use upac::orchestrator::context::Context; use upac::orchestrator::stage::{Stage, StageResult}; -use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::hook::CancelToken; + +use upac_types::hook::ProgressEventBuilder; use crate::target::TargetSysroot; -use super::super::Pinned; -use super::super::database::{ConfigDigest, PrefixDigest}; +use super::super::{DeployDigests, Pinned}; use super::WriteDeployRecordStage; #[test] @@ -28,8 +29,10 @@ fn run_writes_a_deploy_record_readable_back_from_disk() { let mut context = Context::new(); context.put(target); context.put(Pinned(true)); - context.put(PrefixDigest(ObjectID::EMPTY)); - context.put(ConfigDigest(ObjectID::EMPTY)); + context.put(DeployDigests { + prefix: ObjectID::EMPTY, + config: ObjectID::EMPTY, + }); let cancel = CancelToken::new(); let progress = ProgressEventBuilder::new(0); @@ -54,8 +57,10 @@ fn run_writes_a_deploy_record_readable_back_from_disk() { fn run_fails_when_target_missing_from_context() { let mut context = Context::new(); context.put(Pinned(false)); - context.put(PrefixDigest(ObjectID::EMPTY)); - context.put(ConfigDigest(ObjectID::EMPTY)); + context.put(DeployDigests { + prefix: ObjectID::EMPTY, + config: ObjectID::EMPTY, + }); let cancel = CancelToken::new(); let progress = ProgressEventBuilder::new(0); diff --git a/lib/setup/tests/inline/error.rs b/lib/setup/tests/inline/error.rs new file mode 100644 index 00000000..7bbcfac3 --- /dev/null +++ b/lib/setup/tests/inline/error.rs @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; + +use gptman::Error as GptError; +use gptman::linux::BlockError as GptBlockError; + +use nix::errno::Errno; + +use super::SetupError; + +#[test] +fn gpt_error_io_maps_to_setup_error_io_with_the_same_kind() { + let error = GptError::Io(IoError::new(IoErrorKind::PermissionDenied, "denied")); + + assert_eq!(SetupError::from(error), SetupError::Io(IoErrorKind::PermissionDenied)); +} + +#[test] +fn gpt_error_no_space_left_maps_directly() { + assert_eq!(SetupError::from(GptError::NoSpaceLeft), SetupError::NoSpaceLeft); +} + +#[test] +fn gpt_error_invalid_partition_boundaries_maps_to_invalid_partition_layout() { + assert_eq!( + SetupError::from(GptError::InvalidPartitionBoundaries), + SetupError::InvalidPartitionLayout + ); +} + +#[test] +fn gpt_error_falls_back_to_unexpected_for_other_variants() { + assert_eq!(SetupError::from(GptError::InvalidSignature), SetupError::Unexpected); +} + +#[test] +fn gpt_block_error_metadata_maps_to_setup_error_io_with_the_same_kind() { + let error = GptBlockError::Metadata(IoError::new(IoErrorKind::NotFound, "missing")); + + assert_eq!(SetupError::from(error), SetupError::Io(IoErrorKind::NotFound)); +} + +#[test] +fn gpt_block_error_not_block_maps_directly() { + assert_eq!(SetupError::from(GptBlockError::NotBlock), SetupError::NotBlockDevice); +} + +#[test] +fn gpt_block_error_reread_table_maps_to_reread_failed_with_the_same_errno() { + let error = GptBlockError::RereadTable(Errno::ENOSPC); + + assert_eq!(SetupError::from(error), SetupError::RereadFailed(Errno::ENOSPC)); +} diff --git a/lib/setup/tests/inline/format.rs b/lib/setup/tests/inline/format.rs index 08b6b9ce..b4e93f6a 100644 --- a/lib/setup/tests/inline/format.rs +++ b/lib/setup/tests/inline/format.rs @@ -8,7 +8,7 @@ use std::io::Read; use tempfile::TempDir; -use super::{FormatTarget, fat_label}; +use super::FormatTarget; #[test] fn format_esp_writes_a_valid_fat32_boot_sector() { diff --git a/lib/setup/tests/inline/stages.rs b/lib/setup/tests/inline/stages.rs new file mode 100644 index 00000000..22205741 --- /dev/null +++ b/lib/setup/tests/inline/stages.rs @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception + +use std::ptr::null_mut; + +use upac_abi::FsKind; +use upac_abi::hook::CancelToken; + +use super::{SetupExistingData, SetupWholeDiskData}; + +fn existing_data<'data>(cancel_token: &'data CancelToken, mount_point: Option<&'data str>) -> SetupExistingData<'data> { + SetupExistingData { + esp_device: "/dev/sda1", + deploy_device: "/dev/sda2", + deploy_fs: FsKind::Ext4, + extra_mounts: Vec::new(), + + mount_point, + source: "/mnt/source", + empty_config: false, + pinned: false, + boot_plugin: "systemd-boot", + + hook_message: None, + hook_message_context: null_mut(), + + cancel_token, + } +} + +fn whole_disk_data<'data>( + cancel_token: &'data CancelToken, mount_point: Option<&'data str>, +) -> SetupWholeDiskData<'data> { + SetupWholeDiskData { + device_path: "/dev/sda", + esp_size_mib: 512, + deploy_fs: FsKind::Ext4, + deploy_size_mib: 8192, + extra_partitions: Vec::new(), + force_wipe: false, + + node_size: 0, + sector_size: 0, + + mount_point, + source: "/mnt/source", + empty_config: false, + pinned: false, + boot_plugin: "systemd-boot", + + hook_message: None, + hook_message_context: null_mut(), + + cancel_token, + } +} + +#[test] +fn setup_existing_data_mount_point_returns_explicit_value_when_given() { + let cancel_token = CancelToken::new(); + let data = existing_data(&cancel_token, Some("/custom/mount")); + + assert_eq!(data.mount_point(), "/custom/mount"); +} + +#[test] +fn setup_existing_data_mount_point_falls_back_to_the_configured_default() { + let cancel_token = CancelToken::new(); + let data = existing_data(&cancel_token, None); + + assert_eq!(data.mount_point(), "/mnt/upac-setup"); +} + +#[test] +fn setup_whole_disk_data_mount_point_returns_explicit_value_when_given() { + let cancel_token = CancelToken::new(); + let data = whole_disk_data(&cancel_token, Some("/custom/mount")); + + assert_eq!(data.mount_point(), "/custom/mount"); +} + +#[test] +fn setup_whole_disk_data_mount_point_falls_back_to_the_configured_default() { + let cancel_token = CancelToken::new(); + let data = whole_disk_data(&cancel_token, None); + + assert_eq!(data.mount_point(), "/mnt/upac-setup"); +} From d19d4ae82371acccdbd0e83c2506ab797629050d Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 07:12:07 +0400 Subject: [PATCH 133/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/TODO.md b/TODO.md index e8c2b4df..5eb0afbd 100644 --- a/TODO.md +++ b/TODO.md @@ -36,27 +36,16 @@ match upac's on-disk layout exactly (repo at `composefs/`, per-deploy state at ` Still unresolved: whether upac ships/packages the `composefs-setup-root` binary itself or expects it to already exist on the source distro (same open question as the systemd-boot/rEFInd binaries). -**Booter ABI redesign — decided this session, execution in progress file-by-file under direct -supervision (no batch edits).** Four canonical plugin responsibilities: - -1. Plugin sets itself for one-time boot (`set_one_shot`) — done. -2. Plugin sets itself for persistent boot (`confirm_boot`) — done, including UKI's `to.efi`↔ - `from.efi` file swap (needed a new `esp_mount_point` parameter on `confirm_boot`, added this - session; the swap only fires when the confirmed `entry_name` is the `to` slot specifically). -3. Plugin installs itself onto the ESP (`install`) — done for grub (real `grub-install - --removable --no-nvram` + a minimal `blscfg` `grub.cfg`), no-op for the other 3. -4. Plugin declares where its own pre-built loader binary lives in the source package tree - (`esp_loader_source`) — done, stays a separate passive query (only genesis can reach the - composefs tree to copy the bytes out, plugins can't do this step themselves). - -**`write_boot_entry` must search only for the resource type the selected plugin needs, not -autonomously scan everything and guess.** Right now (`lib/lib/src/boot/mod.rs`) it calls -`get_boot_resources` unconditionally, takes whichever single boot resource exists in the tree -(Type1/Type2/`UsrLibModulesVmLinuz`), and only errors if more than one is found total — completely -independent of which plugin was actually selected. This is the case in all 6 call sites: -`lib/lib/src/boot/mod.rs` itself, `mutated/{files,installer,uninstaller,update,rollback}/checkout.rs`, -and `lib/setup/src/genesis/entry.rs`. Needs to take the (now always-explicit) plugin name and require -specifically: `uki` → `Type2` only; `grub`/`systemd-boot`/`rEFInd` → `Type1`/`UsrLibModulesVmLinuz` -only — hard error if that type isn't present, even if a different type is. This also means -`resolve_boot_plugin` must run before `write_boot_entry` everywhere — today the 5 ordinary -`checkout.rs` stages call it after (only genesis already has the order right). +## upac-setup + +`KernelStage` (`lib/setup/src/stages/kernel.rs`) is still a no-op stub — needs to actually run +mkinitcpio/dracut against a scratch directory and import the resulting kernel/initramfs into the +prefix tree. + +**Composefs initramfs hook does not exist anywhere in the project** — a dracut module or +mkinitcpio hook that runs inside the generated initramfs on the target machine at boot: resolve +`composefs.digest=` from the kernel cmdline, mount the erofs image with fs-verity, overlay +`state/deploy//etc/` on top. Without it, a genesis'd disk cannot boot into the installed +system even once `KernelStage` above produces a real initramfs — confirmed via a live QEMU/VM boot +test, not just reading the code (see `ROADMAP.md` §5). Tracked here as a new, unstarted, +non-trivial subsystem. From 71d021745dc051a26c738c432205af5c4300ab9c Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 08:59:15 +0400 Subject: [PATCH 134/148] fix: update TODOA Co-Authored-By: Claude Sonnet 5 --- TODO.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/TODO.md b/TODO.md index 5eb0afbd..b131e300 100644 --- a/TODO.md +++ b/TODO.md @@ -33,19 +33,18 @@ assembles `--source` has to place it under `system/` too, same assumption alread systemd-boot/rEFInd binaries. Confirmed `composefs-setup-root`'s own hardcoded expectations already match upac's on-disk layout exactly (repo at `composefs/`, per-deploy state at `state/deploy//`, `composefs=` cmdline karg) — no restructuring was needed, only the unit + the `system/` plumbing. -Still unresolved: whether upac ships/packages the `composefs-setup-root` binary itself or expects it -to already exist on the source distro (same open question as the systemd-boot/rEFInd binaries). +**Decided: upac packages/vendors `composefs-setup-root` itself** (same call for the systemd-boot/ +rEFInd binaries) rather than assuming the source distro already provides it — genesis-time import +should also check whether one already exists under `system/` rather than blindly trusting our own +copy is the only source. Not yet implemented. ## upac-setup `KernelStage` (`lib/setup/src/stages/kernel.rs`) is still a no-op stub — needs to actually run mkinitcpio/dracut against a scratch directory and import the resulting kernel/initramfs into the -prefix tree. - -**Composefs initramfs hook does not exist anywhere in the project** — a dracut module or -mkinitcpio hook that runs inside the generated initramfs on the target machine at boot: resolve -`composefs.digest=` from the kernel cmdline, mount the erofs image with fs-verity, overlay -`state/deploy//etc/` on top. Without it, a genesis'd disk cannot boot into the installed -system even once `KernelStage` above produces a real initramfs — confirmed via a live QEMU/VM boot -test, not just reading the code (see `ROADMAP.md` §5). Tracked here as a new, unstarted, -non-trivial subsystem. +prefix tree. `composefs-setup-root` (see the `upac-lib` section above) is the actual initramfs-side +mechanism that resolves `composefs.digest=`/mounts the erofs image/overlays `/etc` — it isn't a +separate subsystem to author from scratch, but `KernelStage` still needs to confirm dracut/ +mkinitcpio actually pick the unit up into the generated initrd (not just leave it sitting in +`/usr` unused) — confirmed via a live QEMU/VM boot test that a genesis'd disk doesn't yet boot into +the installed system (see `ROADMAP.md` §5). From 1b44069884825b5f0616b09ae65711da7adaa01e Mon Sep 17 00:00:00 2001 From: JustPav Date: Wed, 16 Sep 2026 09:43:53 +0400 Subject: [PATCH 135/148] fix: removed the `lib` directory and the library build configuration; converted tests to use inline definitions within the module, following convention fix: removed the unnecessary CLI structure and replaced it with an enum fix: renamed folders to use single-word names new: now can build composefs-setup-root bin as command Co-Authored-By: Claude Sonnet 5 --- xtask/Cargo.toml | 4 -- xtask/src/error.rs | 2 +- xtask/src/hook/mod.rs | 47 +++++++++++++++++++ xtask/src/lib.rs | 8 ---- .../cargo_toml_dependency_order.rs | 25 ++++++++-- .../cargo_toml_package_order.rs | 19 ++++++-- .../extern_fn_position.rs | 2 +- .../macro_visibility_adjacency.rs | 5 +- xtask/src/{lint_style => lint}/mod.rs | 10 +++- .../no_pub_use_reexport.rs | 2 +- .../toml_config_field_order.rs | 8 +++- xtask/src/{lint_style => lint}/violation.rs | 0 xtask/src/{lint_style => lint}/walk.rs | 0 xtask/src/main.rs | 33 ++++++------- xtask/src/{gen_tree => tree}/mod.rs | 4 +- .../src/{gen_tree/tree.rs => tree/render.rs} | 0 xtask/src/{gen_tree => tree}/splice.rs | 0 xtask/src/{gen_tree => tree}/walk.rs | 0 xtask/tests/{lint_style.rs => inline/lint.rs} | 17 +++++-- 19 files changed, 134 insertions(+), 52 deletions(-) create mode 100644 xtask/src/hook/mod.rs delete mode 100644 xtask/src/lib.rs rename xtask/src/{lint_style => lint}/cargo_toml_dependency_order.rs (80%) rename xtask/src/{lint_style => lint}/cargo_toml_package_order.rs (85%) rename xtask/src/{lint_style => lint}/extern_fn_position.rs (97%) rename xtask/src/{lint_style => lint}/macro_visibility_adjacency.rs (97%) rename xtask/src/{lint_style => lint}/mod.rs (92%) rename xtask/src/{lint_style => lint}/no_pub_use_reexport.rs (97%) rename xtask/src/{lint_style => lint}/toml_config_field_order.rs (89%) rename xtask/src/{lint_style => lint}/violation.rs (100%) rename xtask/src/{lint_style => lint}/walk.rs (100%) rename xtask/src/{gen_tree => tree}/mod.rs (98%) rename xtask/src/{gen_tree/tree.rs => tree/render.rs} (100%) rename xtask/src/{gen_tree => tree}/splice.rs (100%) rename xtask/src/{gen_tree => tree}/walk.rs (100%) rename xtask/tests/{lint_style.rs => inline/lint.rs} (92%) diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 9d62e333..386485e2 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -11,10 +11,6 @@ version = "0.1.0" edition = "2024" publish = false -[lib] -name = "upac_xtask" -path = "src/lib.rs" - [[bin]] name = "xtask" path = "src/main.rs" diff --git a/xtask/src/error.rs b/xtask/src/error.rs index 5e5529d2..e208f4fa 100644 --- a/xtask/src/error.rs +++ b/xtask/src/error.rs @@ -8,7 +8,7 @@ use std::fmt::{Display, Formatter}; use std::io::Error as IoError; use std::path::PathBuf; -use crate::gen_tree::splice::{MARKER_END, MARKER_START}; +use crate::tree::splice::{MARKER_END, MARKER_START}; #[derive(Debug)] pub enum XtaskError { diff --git a/xtask/src/hook/mod.rs b/xtask/src/hook/mod.rs new file mode 100644 index 00000000..d3de987f --- /dev/null +++ b/xtask/src/hook/mod.rs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 SmoothTeam +// +// SPDX-License-Identifier: GPL-3.0-only + +//! `cargo xtask vendor-setup-root` +//! +//! Builds `composefs-setup-root` (crates.io, bin-only — no `[lib]` target, so it can't be pulled +//! in as a normal `[dependencies]` entry) via `cargo install --root`, dropping the resulting +//! binary under `target/composefs-setup-root/bin/`, alongside the rest of the workspace's own +//! build output. + +use std::path::{Path, PathBuf}; +use std::process::{Command, ExitCode}; + +use crate::error::XtaskError; + +const CRATE_NAME: &str = "composefs-setup-root"; + +pub fn run() -> Result { + let install_root = repo_root()?.join("target").join(CRATE_NAME); + + let status = Command::new("cargo") + .args(["install", CRATE_NAME, "--root"]) + .arg(&install_root) + .status()?; + + if !status.success() { + return Ok(ExitCode::FAILURE); + } + + println!( + "{CRATE_NAME} installed at {}", + install_root.join("bin").join(CRATE_NAME).display() + ); + + Ok(ExitCode::SUCCESS) +} + +fn repo_root() -> Result { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + manifest_dir + .parent() + .map(Path::to_path_buf) + .ok_or(XtaskError::RepoRootNotFound) +} diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs deleted file mode 100644 index 5a265606..00000000 --- a/xtask/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2026 JustPav -// SPDX-FileCopyrightText: 2026 SmoothTeam -// -// SPDX-License-Identifier: GPL-3.0-only - -pub mod error; -pub mod gen_tree; -pub mod lint_style; diff --git a/xtask/src/lint_style/cargo_toml_dependency_order.rs b/xtask/src/lint/cargo_toml_dependency_order.rs similarity index 80% rename from xtask/src/lint_style/cargo_toml_dependency_order.rs rename to xtask/src/lint/cargo_toml_dependency_order.rs index 29ffa694..2303c0d7 100644 --- a/xtask/src/lint_style/cargo_toml_dependency_order.rs +++ b/xtask/src/lint/cargo_toml_dependency_order.rs @@ -5,7 +5,7 @@ use std::path::Path; -use crate::lint_style::violation::Violation; +use super::violation::Violation; const RULE: &str = "cargo-toml-dependency-order"; @@ -33,11 +33,17 @@ pub fn check(path: &Path, contents: &str) -> Vec { for entry in &entries { if entry.tier < highest_tier { - violations.push(violation(path, entry.line, &format!("`{}` is out of its expected group", entry.name))); + violations.push(violation( + path, + entry.line, + &format!("`{}` is out of its expected group", entry.name), + )); continue; } - if entry.tier == Tier::Bracketed && highest_tier == Tier::Bracketed && entry.key_count > last_bracketed_key_count + if entry.tier == Tier::Bracketed + && highest_tier == Tier::Bracketed + && entry.key_count > last_bracketed_key_count { violations.push(violation( path, @@ -61,7 +67,11 @@ pub fn check(path: &Path, contents: &str) -> Vec { fn dependency_entries(contents: &str) -> Vec { let lines: Vec<&str> = contents.lines().collect(); - let Some(start) = lines.iter().position(|line| line.trim() == "[dependencies]").map(|index| index + 1) else { + let Some(start) = lines + .iter() + .position(|line| line.trim() == "[dependencies]") + .map(|index| index + 1) + else { return Vec::new(); }; @@ -100,7 +110,12 @@ fn dependency_entries(contents: &str) -> Vec { 0 }; - entries.push(Entry { line: start + offset, name, tier, key_count }); + entries.push(Entry { + line: start + offset, + name, + tier, + key_count, + }); } entries diff --git a/xtask/src/lint_style/cargo_toml_package_order.rs b/xtask/src/lint/cargo_toml_package_order.rs similarity index 85% rename from xtask/src/lint_style/cargo_toml_package_order.rs rename to xtask/src/lint/cargo_toml_package_order.rs index 6b522c06..7d6ac34f 100644 --- a/xtask/src/lint_style/cargo_toml_package_order.rs +++ b/xtask/src/lint/cargo_toml_package_order.rs @@ -5,7 +5,7 @@ use std::path::Path; -use crate::lint_style::violation::Violation; +use super::violation::Violation; const RULE: &str = "cargo-toml-package-order"; @@ -25,7 +25,11 @@ pub fn check(path: &Path, contents: &str) -> Vec { if let Some(first) = fields.first() && first.name != "name" { - violations.push(violation(path, first.line, "`name` must be the first field in [package]")); + violations.push(violation( + path, + first.line, + "`name` must be the first field in [package]", + )); } if let Some(description) = fields.iter().find(|field| field.name == "description") @@ -39,7 +43,10 @@ pub fn check(path: &Path, contents: &str) -> Vec { } let mut seen_non_workspace = false; - for field in fields.iter().filter(|field| field.name != "name" && field.name != "description") { + for field in fields + .iter() + .filter(|field| field.name != "name" && field.name != "description") + { if field.is_workspace { if seen_non_workspace { violations.push(violation( @@ -85,7 +92,11 @@ fn package_fields(contents: &str) -> Option> { None => (key.to_owned(), false), }; - fields.push(Field { line: start + offset, name, is_workspace }); + fields.push(Field { + line: start + offset, + name, + is_workspace, + }); } Some(fields) diff --git a/xtask/src/lint_style/extern_fn_position.rs b/xtask/src/lint/extern_fn_position.rs similarity index 97% rename from xtask/src/lint_style/extern_fn_position.rs rename to xtask/src/lint/extern_fn_position.rs index 1e402d2f..859faaf1 100644 --- a/xtask/src/lint_style/extern_fn_position.rs +++ b/xtask/src/lint/extern_fn_position.rs @@ -5,7 +5,7 @@ use std::path::Path; -use crate::lint_style::violation::Violation; +use super::violation::Violation; const RULE: &str = "extern-fn-position"; diff --git a/xtask/src/lint_style/macro_visibility_adjacency.rs b/xtask/src/lint/macro_visibility_adjacency.rs similarity index 97% rename from xtask/src/lint_style/macro_visibility_adjacency.rs rename to xtask/src/lint/macro_visibility_adjacency.rs index cf9b772e..440ef42e 100644 --- a/xtask/src/lint_style/macro_visibility_adjacency.rs +++ b/xtask/src/lint/macro_visibility_adjacency.rs @@ -5,7 +5,7 @@ use std::path::Path; -use crate::lint_style::violation::Violation; +use super::violation::Violation; const RULE: &str = "macro-visibility-adjacency"; @@ -74,7 +74,8 @@ fn brace_delta(line: &str) -> i32 { fn macro_name(line: &str) -> Option<&str> { let rest = line.trim_start().strip_prefix("macro_rules! ")?; - rest.split(|character: char| character == '{' || character.is_whitespace()).next() + rest.split(|character: char| character == '{' || character.is_whitespace()) + .next() } fn visibility_use_name(line: &str) -> Option<&str> { diff --git a/xtask/src/lint_style/mod.rs b/xtask/src/lint/mod.rs similarity index 92% rename from xtask/src/lint_style/mod.rs rename to xtask/src/lint/mod.rs index 84ff39b9..7e4066ff 100644 --- a/xtask/src/lint_style/mod.rs +++ b/xtask/src/lint/mod.rs @@ -22,8 +22,13 @@ pub mod macro_visibility_adjacency; pub mod no_pub_use_reexport; pub mod toml_config_field_order; pub mod violation; + mod walk; +#[cfg(test)] +#[path = "../../tests/inline/lint.rs"] +mod tests; + pub fn run() -> Result { let repo_root = repo_root()?; let rust_files = walk::find_rust_files(&repo_root)?; @@ -63,5 +68,8 @@ pub fn run() -> Result { fn repo_root() -> Result { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - manifest_dir.parent().map(Path::to_path_buf).ok_or(XtaskError::RepoRootNotFound) + manifest_dir + .parent() + .map(Path::to_path_buf) + .ok_or(XtaskError::RepoRootNotFound) } diff --git a/xtask/src/lint_style/no_pub_use_reexport.rs b/xtask/src/lint/no_pub_use_reexport.rs similarity index 97% rename from xtask/src/lint_style/no_pub_use_reexport.rs rename to xtask/src/lint/no_pub_use_reexport.rs index 79aabb3f..ab141e74 100644 --- a/xtask/src/lint_style/no_pub_use_reexport.rs +++ b/xtask/src/lint/no_pub_use_reexport.rs @@ -6,7 +6,7 @@ use std::collections::HashSet; use std::path::Path; -use crate::lint_style::violation::Violation; +use super::violation::Violation; const RULE: &str = "no-pub-use-reexport"; diff --git a/xtask/src/lint_style/toml_config_field_order.rs b/xtask/src/lint/toml_config_field_order.rs similarity index 89% rename from xtask/src/lint_style/toml_config_field_order.rs rename to xtask/src/lint/toml_config_field_order.rs index 2a0bbee8..5d890cf0 100644 --- a/xtask/src/lint_style/toml_config_field_order.rs +++ b/xtask/src/lint/toml_config_field_order.rs @@ -5,7 +5,7 @@ use std::path::Path; -use crate::lint_style::violation::Violation; +use super::violation::Violation; const RULE: &str = "toml-config-field-order"; @@ -58,7 +58,11 @@ fn check_block(path: &Path, block: &[&str], block_start: usize) -> Vec ExitCode { - let cli = Cli::parse(); + let command = Command::parse(); - match dispatch(cli.command) { + match dispatch(command) { Ok(code) => code, Err(error) => { eprintln!("{error}"); @@ -39,7 +39,8 @@ fn main() -> ExitCode { fn dispatch(command: Command) -> Result { match command { - Command::GenTree(args) => gen_tree::run(args), - Command::LintStyle => lint_style::run(), + Command::Tree(args) => tree::run(args), + Command::Lint => lint::run(), + Command::Hook => hook::run(), } } diff --git a/xtask/src/gen_tree/mod.rs b/xtask/src/tree/mod.rs similarity index 98% rename from xtask/src/gen_tree/mod.rs rename to xtask/src/tree/mod.rs index 72c4c493..14d95018 100644 --- a/xtask/src/gen_tree/mod.rs +++ b/xtask/src/tree/mod.rs @@ -9,13 +9,13 @@ use std::process::ExitCode; use clap::Args as ClapArgs; +use self::render::TreeRenderer; use self::splice::{find_marked_files, splice}; -use self::tree::TreeRenderer; use crate::error::XtaskError; +mod render; pub(crate) mod splice; -mod tree; mod walk; #[derive(ClapArgs)] diff --git a/xtask/src/gen_tree/tree.rs b/xtask/src/tree/render.rs similarity index 100% rename from xtask/src/gen_tree/tree.rs rename to xtask/src/tree/render.rs diff --git a/xtask/src/gen_tree/splice.rs b/xtask/src/tree/splice.rs similarity index 100% rename from xtask/src/gen_tree/splice.rs rename to xtask/src/tree/splice.rs diff --git a/xtask/src/gen_tree/walk.rs b/xtask/src/tree/walk.rs similarity index 100% rename from xtask/src/gen_tree/walk.rs rename to xtask/src/tree/walk.rs diff --git a/xtask/tests/lint_style.rs b/xtask/tests/inline/lint.rs similarity index 92% rename from xtask/tests/lint_style.rs rename to xtask/tests/inline/lint.rs index dc707e8e..3024feb2 100644 --- a/xtask/tests/lint_style.rs +++ b/xtask/tests/inline/lint.rs @@ -5,7 +5,7 @@ use std::path::Path; -use upac_xtask::lint_style::{ +use super::{ cargo_toml_dependency_order, cargo_toml_package_order, extern_fn_position, macro_visibility_adjacency, no_pub_use_reexport, toml_config_field_order, }; @@ -124,8 +124,7 @@ mod cargo_toml_dependency_order_rule { #[test] fn flags_ascending_key_count_among_bracketed_dependencies() { - let contents = - "[dependencies]\nbar = { version = \"1\" }\nbaz = { version = \"1\", features = [\"x\"] }\n"; + let contents = "[dependencies]\nbar = { version = \"1\" }\nbaz = { version = \"1\", features = [\"x\"] }\n"; let violations = cargo_toml_dependency_order::check(Path::new("Cargo.toml"), contents); @@ -153,7 +152,11 @@ mod cargo_toml_package_order_rule { let violations = cargo_toml_package_order::check(Path::new("Cargo.toml"), contents); - assert!(violations.iter().any(|violation| violation.message.contains("must be the first field"))); + assert!( + violations + .iter() + .any(|violation| violation.message.contains("must be the first field")) + ); } #[test] @@ -162,7 +165,11 @@ mod cargo_toml_package_order_rule { let violations = cargo_toml_package_order::check(Path::new("Cargo.toml"), contents); - assert!(violations.iter().any(|violation| violation.message.contains("workspace fields go first"))); + assert!( + violations + .iter() + .any(|violation| violation.message.contains("workspace fields go first")) + ); } } From 77042999d61727e597b62262c4e39504f8b3fbd9 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:44:23 +0400 Subject: [PATCH 136/148] Register InitramfsGenerator as a shared C-ABI type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RustToC/CTryToRust assume any field type not listed in SHARED_TYPES has a separate CXxx composite counterpart to convert through. InitramfsGenerator is a plain Copy enum used directly on both sides, like FsKind already is — add it to SHARED_TYPES so the derives stop looking for a nonexistent CInitramfsGenerator Co-Authored-By: Claude Sonnet 5 --- lib/macro/src/common.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/macro/src/common.rs b/lib/macro/src/common.rs index 53bd89a8..347adb88 100644 --- a/lib/macro/src/common.rs +++ b/lib/macro/src/common.rs @@ -12,7 +12,13 @@ pub(crate) const PRIMITIVES: &[&str] = &[ "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize", "bool", "f32", "f64", ]; -pub(crate) const SHARED_TYPES: &[&str] = &["FileDiffKind", "PackageDiffKind", "DiffFileSource", "FsKind"]; +pub(crate) const SHARED_TYPES: &[&str] = &[ + "FileDiffKind", + "PackageDiffKind", + "DiffFileSource", + "FsKind", + "InitramfsGenerator", +]; pub(crate) const VALIDATABLE_COMPOSITES: &[&str] = &[ "CVersion", From 9a5c4e46726a9f7b12a9b0f1f8a21cc7544fc541 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:44:32 +0400 Subject: [PATCH 137/148] Add InitramfsGenerator to the C-ABI Setup requests hardcoded dracut as the only initramfs generator with no way to ask for mkinitcpio instead. Add a plain repr(u8) InitramfsGenerator enum (Dracut/Mkinitcpio) and thread it through CSetupExistingRequest/ CSetupWholeDiskRequest Co-Authored-By: Claude Sonnet 5 --- lib/abi/src/lib.rs | 26 ++++++++++++++++++++++++++ lib/abi/src/request.rs | 4 +++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/abi/src/lib.rs b/lib/abi/src/lib.rs index 320dda42..9d73ce21 100644 --- a/lib/abi/src/lib.rs +++ b/lib/abi/src/lib.rs @@ -152,3 +152,29 @@ impl AsRef for FsKind { } } } + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InitramfsGenerator { + Dracut = 0, + Mkinitcpio = 1, +} + +impl InitramfsGenerator { + pub fn from_u8(version: u8) -> Result { + match version { + 0 => Ok(InitramfsGenerator::Dracut), + 1 => Ok(InitramfsGenerator::Mkinitcpio), + _ => Err(ErrorKind::InvalidEntry), + } + } +} + +impl AsRef for InitramfsGenerator { + fn as_ref(&self) -> &str { + match self { + InitramfsGenerator::Dracut => "dracut", + InitramfsGenerator::Mkinitcpio => "mkinitcpio", + } + } +} diff --git a/lib/abi/src/request.rs b/lib/abi/src/request.rs index 0a502531..2c30d069 100644 --- a/lib/abi/src/request.rs +++ b/lib/abi/src/request.rs @@ -12,7 +12,7 @@ use crate::error::ErrorKind; use crate::hook::CancelToken; use crate::package::CPackageInfo; use crate::types::{CSlice, CVec, check_size}; -use crate::{DiffFileSource, FileDiffKind, FsKind}; +use crate::{DiffFileSource, FileDiffKind, FsKind, InitramfsGenerator}; #[repr(C)] #[derive(CNew, CValidate)] @@ -345,6 +345,7 @@ pub struct CSetupExistingRequest { pub empty_config: bool, pub pinned: bool, pub boot_plugin: CSlice, + pub initramfs_generator: InitramfsGenerator, } #[repr(C)] @@ -394,4 +395,5 @@ pub struct CSetupWholeDiskRequest { pub empty_config: bool, pub pinned: bool, pub boot_plugin: CSlice, + pub initramfs_generator: InitramfsGenerator, } From 5e5d885d2ba539e64bf74622fc758af26c588b8e Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:44:47 +0400 Subject: [PATCH 138/148] Add --initramfs-generator flag to up-sp Both auto and manual mode were passing a hardcoded Dracut regardless of user intent. Add a clap ValueEnum wrapper (InitramfsGeneratorClapArg, needed for the same orphan-rule reason FsKind already has one) and a --initramfs-generator flag whose default comes from cli.toml's new [initramfs] section, mirroring how --deploy-fs already defaults from disk_defaults Co-Authored-By: Claude Sonnet 5 --- user/cli.toml | 7 +++++++ user/setup-cli/build.rs | 2 +- user/setup-cli/src/commands/auto.rs | 8 ++++++-- user/setup-cli/src/commands/manual.rs | 10 ++++++++-- user/setup-cli/src/types/mod.rs | 27 +++++++++++++++++++++++++++ user/setup-cli/tests/inline/auto.rs | 4 +++- user/setup-cli/tests/inline/types.rs | 14 +++++++++++++- 7 files changed, 65 insertions(+), 7 deletions(-) diff --git a/user/cli.toml b/user/cli.toml index 0757e464..52f1f107 100644 --- a/user/cli.toml +++ b/user/cli.toml @@ -22,6 +22,13 @@ esp_size_mib = 256 node_size = 16384 sector_size = 4096 +# initramfs is up-sp's own compiled-in fallback for the --initramfs-generator flag, offered by +# both whole-disk and manual mode. Same CLI-only-policy stance as disk_defaults: upac-setup's own +# lib.toml has no opinion on which generator to prefer, only on the filenames it writes once one +# has run. +[initramfs] +generator = "dracut" + # up-sp's own compiled-in progress-bar styling. Deliberately NOT read from # upac_types::settings::RuntimeSettings (that reads /etc/upac.d/upac.toml at runtime, which # doesn't exist yet during bootstrap) — tick_chars is plain ASCII on purpose, since up-sp runs on diff --git a/user/setup-cli/build.rs b/user/setup-cli/build.rs index 50b0f3e3..493dfd8d 100644 --- a/user/setup-cli/build.rs +++ b/user/setup-cli/build.rs @@ -10,7 +10,7 @@ use std::path::Path; use toml::{Value, from_str}; -const SECTIONS: &[&str] = &["disk_defaults", "progress"]; +const SECTIONS: &[&str] = &["disk_defaults", "progress", "initramfs"]; fn main() -> Result<(), Box> { let manifest = var("CARGO_MANIFEST_DIR")?; diff --git a/user/setup-cli/src/commands/auto.rs b/user/setup-cli/src/commands/auto.rs index 7c566348..3bc54ec5 100644 --- a/user/setup-cli/src/commands/auto.rs +++ b/user/setup-cli/src/commands/auto.rs @@ -10,15 +10,16 @@ use clap::{Args as ClapArgs, ValueEnum}; use i18n_embed_fl::fl; use upac_abi::FsKind as FsKindAbi; +use upac_abi::InitramfsGenerator; use upac_types::request::{BtrfsOptions, GptLayout, PartitionSpec, RequestBase, SetupWholeDiskRequest}; use crate::cancel_token_ptr; -use crate::layout::disk_defaults; +use crate::layout::{disk_defaults, initramfs}; use crate::libcore::{Lib, invoke}; use crate::locale::LOADER; use crate::types::progress::{ProgressState, on_progress}; -use crate::types::{FsKind, parse_extra_partition, parse_size_mib}; +use crate::types::{FsKind, InitramfsGeneratorClapArg, parse_extra_partition, parse_size_mib}; #[cfg(test)] #[path = "../../tests/inline/auto.rs"] @@ -53,6 +54,8 @@ pub struct Args { pub pinned: bool, #[arg(long)] pub boot_plugin: Option, + #[arg(long, value_enum, default_value_t = InitramfsGeneratorClapArg::from_str(initramfs::GENERATOR, false).unwrap_or(InitramfsGeneratorClapArg(InitramfsGenerator::Dracut)))] + pub initramfs_generator: InitramfsGeneratorClapArg, } pub fn run(args: Args, lib: &Lib) -> Result<()> { @@ -98,6 +101,7 @@ pub fn run(args: Args, lib: &Lib) -> Result<()> { empty_config: args.empty_config, pinned: args.pinned, boot_plugin, + initramfs_generator: args.initramfs_generator.into(), } .into(); diff --git a/user/setup-cli/src/commands/manual.rs b/user/setup-cli/src/commands/manual.rs index df415e46..f89c9996 100644 --- a/user/setup-cli/src/commands/manual.rs +++ b/user/setup-cli/src/commands/manual.rs @@ -5,14 +5,17 @@ use anyhow::Result; -use clap::Args as ClapArgs; +use clap::{Args as ClapArgs, ValueEnum}; + +use upac_abi::InitramfsGenerator; use upac_types::request::{PartitionMount, RequestBase, SetupExistingRequest}; use crate::cancel_token_ptr; +use crate::layout::initramfs; use crate::libcore::{Lib, invoke}; use crate::types::progress::{ProgressState, on_progress}; -use crate::types::{FsKind, parse_extra_mount}; +use crate::types::{FsKind, InitramfsGeneratorClapArg, parse_extra_mount}; #[derive(ClapArgs)] pub struct Args { @@ -35,6 +38,8 @@ pub struct Args { pub pinned: bool, #[arg(long)] pub boot_plugin: String, + #[arg(long, value_enum, default_value_t = InitramfsGeneratorClapArg::from_str(initramfs::GENERATOR, false).unwrap_or(InitramfsGeneratorClapArg(InitramfsGenerator::Dracut)))] + pub initramfs_generator: InitramfsGeneratorClapArg, } pub fn run(args: Args, lib: &Lib) -> Result<()> { @@ -59,6 +64,7 @@ pub fn run(args: Args, lib: &Lib) -> Result<()> { empty_config: args.empty_config, pinned: args.pinned, boot_plugin: args.boot_plugin, + initramfs_generator: args.initramfs_generator.into(), } .into(); diff --git a/user/setup-cli/src/types/mod.rs b/user/setup-cli/src/types/mod.rs index bece86c1..f156d459 100644 --- a/user/setup-cli/src/types/mod.rs +++ b/user/setup-cli/src/types/mod.rs @@ -7,6 +7,7 @@ use clap::ValueEnum; use clap::builder::PossibleValue; use upac_abi::FsKind as FsKindAbi; +use upac_abi::InitramfsGenerator; use upac_types::request::{PartitionMount, PartitionSpec}; @@ -45,6 +46,32 @@ impl ValueEnum for FsKind { } } +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct InitramfsGeneratorClapArg(pub InitramfsGenerator); + +impl From for InitramfsGenerator { + fn from(value: InitramfsGeneratorClapArg) -> Self { + value.0 + } +} + +impl ValueEnum for InitramfsGeneratorClapArg { + fn value_variants<'a>() -> &'a [Self] { + &[ + InitramfsGeneratorClapArg(InitramfsGenerator::Dracut), + InitramfsGeneratorClapArg(InitramfsGenerator::Mkinitcpio), + ] + } + + fn to_possible_value(&self) -> Option { + Some(match self.0 { + InitramfsGenerator::Dracut => PossibleValue::new("dracut"), + InitramfsGenerator::Mkinitcpio => PossibleValue::new("mkinitcpio"), + }) + } +} + pub fn parse_extra_mount(raw: &str) -> Result { let mut parts = raw.splitn(3, ':'); diff --git a/user/setup-cli/tests/inline/auto.rs b/user/setup-cli/tests/inline/auto.rs index adfb77d3..3609a58b 100644 --- a/user/setup-cli/tests/inline/auto.rs +++ b/user/setup-cli/tests/inline/auto.rs @@ -4,10 +4,11 @@ // SPDX-License-Identifier: GPL-3.0-only use upac_abi::FsKind as FsKindAbi; +use upac_abi::InitramfsGenerator; use crate::libcore::Lib; use crate::locale; -use crate::types::FsKind; +use crate::types::{FsKind, InitramfsGeneratorClapArg}; use super::{Args, run}; @@ -27,6 +28,7 @@ fn valid_args() -> Args { empty_config: false, pinned: false, boot_plugin: Some("systemd-boot".to_owned()), + initramfs_generator: InitramfsGeneratorClapArg(InitramfsGenerator::Dracut), } } diff --git a/user/setup-cli/tests/inline/types.rs b/user/setup-cli/tests/inline/types.rs index b7363c94..dbb20dad 100644 --- a/user/setup-cli/tests/inline/types.rs +++ b/user/setup-cli/tests/inline/types.rs @@ -7,7 +7,7 @@ use clap::ValueEnum; use upac_abi::FsKind as FsKindAbi; -use super::{FsKind, parse_extra_mount, parse_extra_partition, parse_size_mib}; +use super::{FsKind, InitramfsGeneratorClapArg, parse_extra_mount, parse_extra_partition, parse_size_mib}; #[test] fn parse_extra_mount_accepts_a_well_formed_triple() { @@ -85,3 +85,15 @@ fn fs_kind_from_str_matches_lowercase_names() { assert!(FsKind::from_str("xfs", false).is_ok()); assert!(FsKind::from_str("EXT4", false).is_err()); } + +#[test] +fn initramfs_generator_has_exactly_the_two_supported_variants() { + assert_eq!(InitramfsGeneratorClapArg::value_variants().len(), 2); +} + +#[test] +fn initramfs_generator_from_str_matches_lowercase_names() { + assert!(InitramfsGeneratorClapArg::from_str("dracut", false).is_ok()); + assert!(InitramfsGeneratorClapArg::from_str("mkinitcpio", false).is_ok()); + assert!(InitramfsGeneratorClapArg::from_str("DRACUT", false).is_err()); +} From 17ae630de0116647714f55487676f3a78492fc57 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:45:03 +0400 Subject: [PATCH 139/148] Wire initramfs_generator through the request types, fix stage ordering Add initramfs_generator to SetupExistingRequest/SetupWholeDiskRequest so the new ABI field actually reaches the domain layer. Also fix SetupStateId's variant order, which had drifted from the real stage pipeline (Kernel now runs before EmbedDatabase, not after) and was silently misreporting which stage failed Co-Authored-By: Claude Sonnet 5 --- lib/types/src/request.rs | 6 +++++- lib/types/src/states.rs | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/types/src/request.rs b/lib/types/src/request.rs index d2d91f55..653a5fa6 100644 --- a/lib/types/src/request.rs +++ b/lib/types/src/request.rs @@ -19,7 +19,7 @@ use upac_abi::request::{ CSearchMetaRequest, CSetupExistingRequest, CSetupWholeDiskRequest, CUninstallRequest, CUpdateRequest, }; use upac_abi::types::{COwned, CSlice, CVec}; -use upac_abi::{DiffFileSource, FileDiffKind, FsKind}; +use upac_abi::{DiffFileSource, FileDiffKind, FsKind, InitramfsGenerator}; use upac_macro::{CTryToRust, RustToC}; @@ -285,7 +285,9 @@ pub struct SetupExistingRequest { pub source: String, pub empty_config: bool, pub pinned: bool, + pub boot_plugin: String, + pub initramfs_generator: InitramfsGenerator, } #[derive(Debug, Clone, RustToC)] @@ -300,5 +302,7 @@ pub struct SetupWholeDiskRequest { pub source: String, pub empty_config: bool, pub pinned: bool, + pub boot_plugin: String, + pub initramfs_generator: InitramfsGenerator, } diff --git a/lib/types/src/states.rs b/lib/types/src/states.rs index 16733636..7955a17f 100644 --- a/lib/types/src/states.rs +++ b/lib/types/src/states.rs @@ -286,9 +286,9 @@ pub enum SetupStateId { UnpackPackage = 2, ImportPackage = 3, ImportSystem = 4, - EmbedDatabase = 5, - WriteDeployRecord = 6, - Kernel = 7, + Kernel = 5, + EmbedDatabase = 6, + WriteDeployRecord = 7, StageBoot = 8, Setup = 9, } From 8ea73e55845290ad51d45c5ffd6c86e4225eb15a Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:45:12 +0400 Subject: [PATCH 140/148] Add FileHandle::export_directory, the reverse of import_directory KernelStage needs a real directory on disk to hand to dracut/mkinitcpio, but only the tree-to-disk direction (import_directory) existed. Add export_directory plus small per-node-kind helpers (export_leaf/ export_symlink/export_regular_file, and a shared regular_file_content also reused by read_file) so the recursive walk stays a thin dispatcher instead of inlining the content-resolution match Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/composefs/file.rs | 84 ++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/lib/lib/src/composefs/file.rs b/lib/lib/src/composefs/file.rs index f1cb6ead..9fffe783 100644 --- a/lib/lib/src/composefs/file.rs +++ b/lib/lib/src/composefs/file.rs @@ -5,15 +5,15 @@ use std::collections::BTreeMap; use std::ffi::OsStr; -use std::fs::{File, Metadata, read_dir, read_link}; +use std::fs::{File, Metadata, Permissions, create_dir_all, read_dir, read_link, set_permissions, write}; use std::io::Read; -use std::os::unix::fs::MetadataExt; +use std::os::unix::fs::{MetadataExt, PermissionsExt, symlink}; use std::path::{Path, PathBuf}; use composefs::MAX_INLINE_CONTENT; use composefs::generic_tree::Stat; use composefs::repository::{ImportContext, Repository}; -use composefs::tree::{Directory, FileSystem, Inode, LeafContent, RegularFile}; +use composefs::tree::{Directory, FileSystem, Inode, Leaf, LeafContent, RegularFile}; use upac_abi::hook::CancelToken; @@ -131,6 +131,51 @@ impl FileHandle { } } +impl FileHandle { + fn regular_file_content( + repository: &Repository, regular: &RegularFile, + ) -> Result, RepoError> { + match regular { + RegularFile::Inline(content) => Ok(content.to_vec()), + RegularFile::External(object_id, _) | RegularFile::ExternalNoVerity(object_id, _) => { + Ok(repository.read_object(object_id)?) + } + RegularFile::Sparse(size) => Ok(vec![0u8; *size as usize]), + } + } + + fn export_leaf( + repository: &Repository, leaf: &Leaf, dest_path: &Path, + ) -> Result<(), RepoError> { + match &leaf.content { + LeafContent::Symlink(target) => Self::export_symlink(target, dest_path), + LeafContent::Regular(regular) => { + Self::export_regular_file(repository, regular, leaf.stat.st_mode, dest_path) + } + LeafContent::BlockDevice(_) | LeafContent::CharacterDevice(_) | LeafContent::Fifo | LeafContent::Socket => { + Ok(()) + } + } + } + + fn export_symlink(target: &OsStr, dest_path: &Path) -> Result<(), RepoError> { + symlink(target, dest_path)?; + + Ok(()) + } + + fn export_regular_file( + repository: &Repository, regular: &RegularFile, mode: u32, dest_path: &Path, + ) -> Result<(), RepoError> { + let content = Self::regular_file_content(repository, regular)?; + + write(dest_path, content)?; + set_permissions(dest_path, Permissions::from_mode(mode))?; + + Ok(()) + } +} + impl FileHandle { pub fn stat_in_tree<'t>(&self, tree: &'t FileSystem) -> Result<&'t Stat, RepoError> { let (parent, filename) = tree.root.split(self.path.as_os_str())?; @@ -200,13 +245,7 @@ impl FileHandle { let (parent, filename) = tree.root.split(self.path.as_os_str())?; let regular = parent.get_file(filename, &tree.leaves)?; - match regular { - RegularFile::Inline(content) => Ok(content.to_vec()), - RegularFile::External(object_id, _size) | RegularFile::ExternalNoVerity(object_id, _size) => { - Ok(repository.read_object(object_id)?) - } - RegularFile::Sparse(size) => Ok(vec![0u8; *size as usize]), - } + Self::regular_file_content(repository, regular) } pub fn import_directory( @@ -244,6 +283,31 @@ impl FileHandle { Ok(imported) } + + pub fn export_directory( + &self, repository: &Repository, tree: &FileSystem, dest_dir: &Path, cancel: &CancelToken, + ) -> Result<(), RepoError> { + create_dir_all(dest_dir)?; + + for (name, inode) in self.list_in_tree(tree)? { + if cancel.is_cancelled() { + return Err(RepoError::Cancelled); + } + + let dest_path = dest_dir.join(name); + let child = FileHandle::new(self.path.join(name)); + + match inode { + Inode::Directory(directory) => { + child.export_directory(repository, tree, &dest_path, cancel)?; + set_permissions(&dest_path, Permissions::from_mode(directory.stat.st_mode))?; + } + Inode::Leaf(leaf_id, _) => Self::export_leaf(repository, tree.leaf(*leaf_id), &dest_path)?, + } + } + + Ok(()) + } } pub(crate) fn stat_from_metadata(metadata: &Metadata) -> Stat { From 50005312e5039cc85fed2da3de78fd68cd66103c Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:45:58 +0400 Subject: [PATCH 141/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index b131e300..af9c130c 100644 --- a/TODO.md +++ b/TODO.md @@ -40,11 +40,13 @@ copy is the only source. Not yet implemented. ## upac-setup -`KernelStage` (`lib/setup/src/stages/kernel.rs`) is still a no-op stub — needs to actually run -mkinitcpio/dracut against a scratch directory and import the resulting kernel/initramfs into the -prefix tree. `composefs-setup-root` (see the `upac-lib` section above) is the actual initramfs-side -mechanism that resolves `composefs.digest=`/mounts the erofs image/overlays `/etc` — it isn't a -separate subsystem to author from scratch, but `KernelStage` still needs to confirm dracut/ -mkinitcpio actually pick the unit up into the generated initrd (not just leave it sitting in -`/usr` unused) — confirmed via a live QEMU/VM boot test that a genesis'd disk doesn't yet boot into -the installed system (see `ROADMAP.md` §5). +`KernelStage`'s mkinitcpio path (`lib/setup/src/stages/kernel.rs`) needs rechecking — it only +redirects `/lib/modules` via `-r /lib/modules`, and there's no confirmed mkinitcpio +equivalent of dracut's full `--sysroot` (which redirects everything: hooks, config, binaries). +Unlike dracut, mkinitcpio may still fall through to the *real* host's `/etc/mkinitcpio.conf`/`/usr` +instead of the scratch tree genesis built. Needs verifying against a real mkinitcpio run before +trusting the generated initramfs for the `mkinitcpio` generator choice. + +`RequestedInitramfsGenerator` (`lib/setup/src/stages/mod.rs`) is hardcoded to `"dracut"` in both +`run_existing`/`run_whole_disk` — it isn't a real request field yet, `CSetupExistingRequest`/ +`CSetupWholeDiskRequest` don't carry it. Needs the same ABI treatment `boot_plugin` already got. From 23b0e02b8934e524754ad9e4fa70a1a137dc8969 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:49:34 +0400 Subject: [PATCH 142/148] Implement KernelStage Detect the kernel version from the imported tree, export the whole prefix tree to a scratch directory, and run dracut or mkinitcpio (non-hostonly/generic flags, since genesis targets an arbitrary disk, not the build host) to produce initramfs.img/uki.efi, then import the result back under lib/modules//. Output filenames move to lib.toml's [genesis] section rather than being hardcoded. Requires KernelStage to run before EmbedDatabaseStage, since committing the tree removes PrefixTree from context Co-Authored-By: Claude Sonnet 5 --- lib/setup/lib.toml | 8 +++ lib/setup/src/error.rs | 8 +++ lib/setup/src/stages/kernel.rs | 108 ++++++++++++++++++++++++++++++- lib/setup/src/stages/mod.rs | 20 ++++-- lib/setup/tests/inline/stages.rs | 3 + 5 files changed, 141 insertions(+), 6 deletions(-) diff --git a/lib/setup/lib.toml b/lib/setup/lib.toml index 2d4322f4..522db713 100644 --- a/lib/setup/lib.toml +++ b/lib/setup/lib.toml @@ -63,6 +63,12 @@ settle_interval_ms = 100 # reason to burden `--source` with it) — see TODO.md for why this specific set of paths matches # `composefs-setup-root`'s own hardcoded expectations (confirmed by reading its upstream source, # not guessed). +# +# initramfs_filename is fixed by composefs-boot itself, not our choice — `UsrLibModulesVmlinuz:: +# load_all` only recognizes a vmlinuz's companion initramfs under this exact name. uki_filename is +# our own convention (composefs-boot only requires a `.efi` extension in `lib/modules//` for +# `Type2Entry::load_all` to pick it up, the stem is free) — kept alongside initramfs_filename since +# `KernelStage` writes both under the same `lib/modules//` directory. [genesis] scratch_filename = "genesis-packages.redb" efi_linux_dir = "EFI/Linux" @@ -70,3 +76,5 @@ system_dir = "system" composefs_setup_root_unit_path = "lib/systemd/system/composefs-setup-root.service" composefs_setup_root_wants_path = "lib/systemd/system/initrd-root-fs.target.wants/composefs-setup-root.service" composefs_setup_root_wants_target = "../composefs-setup-root.service" +initramfs_filename = "initramfs.img" +uki_filename = "uki.efi" diff --git a/lib/setup/src/error.rs b/lib/setup/src/error.rs index 3680a99d..a0e76cbe 100644 --- a/lib/setup/src/error.rs +++ b/lib/setup/src/error.rs @@ -44,6 +44,10 @@ pub enum SetupError { InvalidFormatParams, RereadFailed(Errno), ComposefsSetupRootUnitNotFound, + NoKernelFound, + AmbiguousKernelVersion, + UnknownInitramfsGenerator, + InitramfsGeneratorFailed, Unexpected, } @@ -154,6 +158,10 @@ impl From for ErrorKind { SetupError::InvalidFormatParams => ErrorKind::InvalidEntry, SetupError::RereadFailed(_) => ErrorKind::ReadFailed, SetupError::ComposefsSetupRootUnitNotFound => ErrorKind::NotFound, + SetupError::NoKernelFound => ErrorKind::NotFound, + SetupError::AmbiguousKernelVersion => ErrorKind::InvalidEntry, + SetupError::UnknownInitramfsGenerator => ErrorKind::InvalidEntry, + SetupError::InitramfsGeneratorFailed => ErrorKind::Unexpected, SetupError::Unexpected => ErrorKind::Unexpected, } } diff --git a/lib/setup/src/stages/kernel.rs b/lib/setup/src/stages/kernel.rs index 3e84d649..411ded65 100644 --- a/lib/setup/src/stages/kernel.rs +++ b/lib/setup/src/stages/kernel.rs @@ -3,21 +3,125 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use upac::orchestrator::context::Context; +use std::fs::File; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use composefs::generic_tree::Stat; +use composefs::repository::ImportContext; +use composefs::tree::FileSystem; + +use tempfile::TempDir; + +use upac::composefs::file::FileHandle; +use upac::composefs::repository::ObjectID; +use upac::orchestrator::context::{Context, ctx_get, ctx_take}; use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; +use upac::plugin::boot::BootPlugins; use upac_abi::hook::CancelToken; +use upac_abi::{BootResourceKind, InitramfsGenerator}; use upac_types::hook::ProgressEventBuilder; +use super::{PrefixTree, RequestedBootPlugin, RequestedInitramfsGenerator}; + use crate::error::SetupError; +use crate::layout::genesis::{INITRAMFS_FILENAME, UKI_FILENAME}; +use crate::target::TargetSysroot; pub struct KernelStage; impl Stage for KernelStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, + &self, context: &mut Context, cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, StageResult, Box), SetupError> { + let mut prefix_tree = ctx_take!(context, PrefixTree); + let mut import_ctx = ctx_take!(context, ImportContext); + + let target = ctx_get!(context, TargetSysroot); + let requested_boot_plugin = ctx_get!(context, RequestedBootPlugin); + let requested_generator = ctx_get!(context, RequestedInitramfsGenerator); + + let repository = target.repository(); + + let kver = detect_kernel_version(&prefix_tree)?; + let plugin = BootPlugins::new()?.load(requested_boot_plugin)?; + let is_uki = plugin.boot_resource_kind() == BootResourceKind::Uki; + + let scratch = TempDir::new()?; + FileHandle::new(PathBuf::new()).export_directory(repository, &prefix_tree, scratch.path(), cancel)?; + + let output_name = if is_uki { UKI_FILENAME } else { INITRAMFS_FILENAME }; + let output_path = scratch.path().join("lib/modules").join(&kver).join(output_name); + + match **requested_generator { + InitramfsGenerator::Dracut => run_dracut(scratch.path(), &kver, is_uki, &output_path)?, + InitramfsGenerator::Mkinitcpio => run_mkinitcpio(scratch.path(), &kver, is_uki, &output_path)?, + } + + FileHandle::new(format!("lib/modules/{kver}/{output_name}")).insert_file( + repository, + &mut prefix_tree, + &File::open(&output_path)?, + Stat::uninitialized(), + &mut import_ctx, + )?; + + context.put(import_ctx); + context.put(prefix_tree); + Ok((progress, StageResult::Advance, Box::new(NoRollback))) } } + +fn detect_kernel_version(prefix_tree: &FileSystem) -> Result { + let mut versions: Vec = FileHandle::new("lib/modules") + .list_in_tree(prefix_tree)? + .map(|(name, _)| name.to_string_lossy().into_owned()) + .collect(); + + match versions.len() { + 0 => Err(SetupError::NoKernelFound), + 1 => Ok(versions.remove(0)), + _ => Err(SetupError::AmbiguousKernelVersion), + } +} + +fn run_dracut(scratch: &Path, kver: &str, is_uki: bool, output_path: &Path) -> Result<(), SetupError> { + let mut command = Command::new("dracut"); + command + .arg("--sysroot") + .arg(scratch) + .args(["--no-hostonly", "--no-hostonly-cmdline", "--force", "--kver", kver]); + + if is_uki { + command.arg("--uefi"); + } + + command.arg(output_path); + + let status = command.status()?; + if !status.success() { + return Err(SetupError::InitramfsGeneratorFailed); + } + + Ok(()) +} + +fn run_mkinitcpio(scratch: &Path, kver: &str, is_uki: bool, output_path: &Path) -> Result<(), SetupError> { + let mut command = Command::new("mkinitcpio"); + command + .args(["-k", kver, "-r"]) + .arg(scratch.join("lib/modules")) + .args(["-S", "autodetect"]) + .arg(if is_uki { "-U" } else { "-g" }) + .arg(output_path); + + let status = command.status()?; + if !status.success() { + return Err(SetupError::InitramfsGeneratorFailed); + } + + Ok(()) +} diff --git a/lib/setup/src/stages/mod.rs b/lib/setup/src/stages/mod.rs index d4cb774c..145dedec 100644 --- a/lib/setup/src/stages/mod.rs +++ b/lib/setup/src/stages/mod.rs @@ -17,11 +17,10 @@ use upac::orchestrator::error::OrchestratorError; use upac::orchestrator::{Orchestrator, SequentialOrchestrator}; use upac::plugin::decoder::unpack::PackageUnpacker; -use upac_abi::FsKind; -use upac_abi::HookMessageFn; use upac_abi::error::ErrorKind; use upac_abi::hook::CancelToken; use upac_abi::request::{CSetupExistingRequest, CSetupWholeDiskRequest}; +use upac_abi::{FsKind, HookMessageFn, InitramfsGenerator}; use upac_types::decoder::DeclarativeTrigger; use upac_types::hook::Message; @@ -89,7 +88,9 @@ pub struct SetupExistingData<'data> { pub source: &'data str, pub empty_config: bool, pub pinned: bool, + pub boot_plugin: &'data str, + pub initramfs_generator: InitramfsGenerator, pub hook_message: Option, pub hook_message_context: *mut c_void, @@ -115,7 +116,9 @@ impl<'data> TryFrom<&'data CSetupExistingRequest> for SetupExistingData<'data> { source: (&request.source).try_into()?, empty_config: request.empty_config, pinned: request.pinned, + boot_plugin: (&request.boot_plugin).try_into()?, + initramfs_generator: request.initramfs_generator, hook_message: request.base.on_hook, hook_message_context: request.base.hook_ctx, @@ -146,7 +149,9 @@ pub struct SetupWholeDiskData<'data> { pub source: &'data str, pub empty_config: bool, pub pinned: bool, + pub boot_plugin: &'data str, + pub initramfs_generator: InitramfsGenerator, pub hook_message: Option, pub hook_message_context: *mut c_void, @@ -186,7 +191,9 @@ impl<'data> TryFrom<&'data CSetupWholeDiskRequest> for SetupWholeDiskData<'data> source: (&request.source).try_into()?, empty_config: request.empty_config, pinned: request.pinned, + boot_plugin: (&request.boot_plugin).try_into()?, + initramfs_generator: request.initramfs_generator, hook_message: request.base.on_hook, hook_message_context: request.base.hook_ctx, @@ -208,6 +215,9 @@ pub(crate) struct Pinned(pub bool); #[derive(ContextValue)] pub(crate) struct RequestedBootPlugin(pub String); +#[derive(ContextValue)] +pub(crate) struct RequestedInitramfsGenerator(pub InitramfsGenerator); + #[derive(ContextValue)] pub(crate) struct ResolvedSourceDir(pub PathBuf); @@ -259,6 +269,7 @@ pub fn run_existing(data: SetupExistingData) -> Result<(), (SetupStateId, SetupE context.put(EmptyConfig(data.empty_config)); context.put(Pinned(data.pinned)); context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); + context.put(RequestedInitramfsGenerator(data.initramfs_generator)); let orchestrator = SequentialOrchestrator::new(vec![ Box::new(PrepareSourceStage), @@ -266,9 +277,9 @@ pub fn run_existing(data: SetupExistingData) -> Result<(), (SetupStateId, SetupE Box::new(UnpackPackageStage), Box::new(ImportPackageStage), Box::new(ImportSystemStage), + Box::new(KernelStage), Box::new(EmbedDatabaseStage), Box::new(WriteDeployRecordStage), - Box::new(KernelStage), Box::new(StageBootStage), ]); @@ -298,6 +309,7 @@ pub fn run_whole_disk(data: SetupWholeDiskData) -> Result<(), (SetupStateId, Set context.put(EmptyConfig(data.empty_config)); context.put(Pinned(data.pinned)); context.put(RequestedBootPlugin(data.boot_plugin.to_owned())); + context.put(RequestedInitramfsGenerator(data.initramfs_generator)); let orchestrator = SequentialOrchestrator::new(vec![ Box::new(PrepareSourceStage), @@ -305,9 +317,9 @@ pub fn run_whole_disk(data: SetupWholeDiskData) -> Result<(), (SetupStateId, Set Box::new(UnpackPackageStage), Box::new(ImportPackageStage), Box::new(ImportSystemStage), + Box::new(KernelStage), Box::new(EmbedDatabaseStage), Box::new(WriteDeployRecordStage), - Box::new(KernelStage), Box::new(StageBootStage), ]); diff --git a/lib/setup/tests/inline/stages.rs b/lib/setup/tests/inline/stages.rs index 22205741..1e380107 100644 --- a/lib/setup/tests/inline/stages.rs +++ b/lib/setup/tests/inline/stages.rs @@ -6,6 +6,7 @@ use std::ptr::null_mut; use upac_abi::FsKind; +use upac_abi::InitramfsGenerator; use upac_abi::hook::CancelToken; use super::{SetupExistingData, SetupWholeDiskData}; @@ -22,6 +23,7 @@ fn existing_data<'data>(cancel_token: &'data CancelToken, mount_point: Option<&' empty_config: false, pinned: false, boot_plugin: "systemd-boot", + initramfs_generator: InitramfsGenerator::Dracut, hook_message: None, hook_message_context: null_mut(), @@ -49,6 +51,7 @@ fn whole_disk_data<'data>( empty_config: false, pinned: false, boot_plugin: "systemd-boot", + initramfs_generator: InitramfsGenerator::Dracut, hook_message: None, hook_message_context: null_mut(), From 7491319632999209f2217712ca0577b44f18adf7 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:49:50 +0400 Subject: [PATCH 143/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index af9c130c..7bc6d132 100644 --- a/TODO.md +++ b/TODO.md @@ -47,6 +47,8 @@ Unlike dracut, mkinitcpio may still fall through to the *real* host's `/etc/mkin instead of the scratch tree genesis built. Needs verifying against a real mkinitcpio run before trusting the generated initramfs for the `mkinitcpio` generator choice. -`RequestedInitramfsGenerator` (`lib/setup/src/stages/mod.rs`) is hardcoded to `"dracut"` in both -`run_existing`/`run_whole_disk` — it isn't a real request field yet, `CSetupExistingRequest`/ -`CSetupWholeDiskRequest` don't carry it. Needs the same ABI treatment `boot_plugin` already got. +`KernelStage`'s `run_dracut`/`run_mkinitcpio` (`lib/setup/src/stages/kernel.rs`) currently take a +plain `is_uki: bool` and branch internally (`--uefi`/`-U` vs the plain-initramfs flags). Once UKI +signing or a separate UKI-specific generation path is added, this needs splitting into distinct +`run_`/`run__with_uki` functions instead of a bool flag, so the two concerns (plain +initramfs vs UKI build+sign) don't stay tangled inside one function. From 07ec42ea1591c862c985875ccd0d79f7dbcf4232 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:50:55 +0400 Subject: [PATCH 144/148] Add the composefs-setup-root dracut module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit genesis's system/ import mechanism expects lib/systemd/system/ composefs-setup-root.service in the source tree, but that alone isn't enough — the initrd needs its own module-setup.sh to pull the binary and unit into the generated initramfs and wire the initrd-root-fs.target enablement (systemd running inside the initrd never sees anything add-wants'd into the real root's own unit tree after switch-root). Sourced from containers/composefs-rs's own dracut module example (MIT OR Apache-2.0) rather than hand-rolled, since dracut module ordering/conditions are easy to get subtly wrong Co-Authored-By: Claude Sonnet 5 --- .../37composefs/composefs-setup-root.service | 38 +++++++++++++++++++ hooks/dracut/37composefs/module-setup.sh | 22 +++++++++++ 2 files changed, 60 insertions(+) create mode 100644 hooks/dracut/37composefs/composefs-setup-root.service create mode 100644 hooks/dracut/37composefs/module-setup.sh diff --git a/hooks/dracut/37composefs/composefs-setup-root.service b/hooks/dracut/37composefs/composefs-setup-root.service new file mode 100644 index 00000000..d14b781b --- /dev/null +++ b/hooks/dracut/37composefs/composefs-setup-root.service @@ -0,0 +1,38 @@ +# Copyright (C) 2013 Colin Walters +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . +# +# Sourced verbatim from containers/composefs-rs's +# examples/*/extra/usr/lib/dracut/modules.d/37composefs/ (MIT OR Apache-2.0). + +[Unit] +DefaultDependencies=no +ConditionKernelCommandLine=|composefs +ConditionKernelCommandLine=|composefs.digest +ConditionPathExists=/etc/initrd-release +After=sysroot.mount +Requires=sysroot.mount +Before=initrd-root-fs.target +Before=initrd-switch-root.target + +OnFailure=emergency.target +OnFailureJobMode=isolate + +[Service] +Type=oneshot +ExecStart=/usr/bin/composefs-setup-root +StandardInput=null +StandardOutput=journal +StandardError=journal+console +RemainAfterExit=yes diff --git a/hooks/dracut/37composefs/module-setup.sh b/hooks/dracut/37composefs/module-setup.sh new file mode 100644 index 00000000..a87f6f43 --- /dev/null +++ b/hooks/dracut/37composefs/module-setup.sh @@ -0,0 +1,22 @@ +#!/usr/bin/bash +# Adapted from containers/composefs-rs's examples/*/extra/usr/lib/dracut/modules.d/37composefs/ +# (MIT OR Apache-2.0). Only change from upstream: dropped the examples' debug-only strace inst. + +check() { + return 0 +} + +depends() { + return 0 +} + +install() { + inst \ + "${moddir}/composefs-setup-root" /bin/composefs-setup-root + inst \ + "${moddir}/composefs-setup-root.service" \ + "${systemdsystemunitdir}/composefs-setup-root.service" + + $SYSTEMCTL -q --root "${initdir}" add-wants \ + 'initrd-root-fs.target' 'composefs-setup-root.service' +} From 89113df1cee9ac0aa9c07655ee00a5a120e014d2 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:54:30 +0400 Subject: [PATCH 145/148] fix: update TODO Co-Authored-By: Claude Sonnet 5 --- TODO.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/TODO.md b/TODO.md index 7bc6d132..0db7dc0d 100644 --- a/TODO.md +++ b/TODO.md @@ -26,13 +26,16 @@ extractable. literal 1:1 mirror of the target's real `/usr`, sibling to the package archives — `EnumeratePackagesStage` already skips it, it only looks at files) to contain `lib/systemd/system/composefs-setup-root.service` (hard error, `SetupError:: -ComposefsSetupRootUnitNotFound`, if missing), imports the whole tree into `PrefixTree`, and creates -the unit's `*.target.wants/` enablement symlink itself. This is also how a built `up`/`upac-lib`/ -booters gets onto a genesis'd disk at all — genesis never installs itself automatically, whoever -assembles `--source` has to place it under `system/` too, same assumption already made for the -systemd-boot/rEFInd binaries. Confirmed `composefs-setup-root`'s own hardcoded expectations already -match upac's on-disk layout exactly (repo at `composefs/`, per-deploy state at `state/deploy//`, -`composefs=` cmdline karg) — no restructuring was needed, only the unit + the `system/` plumbing. +ComposefsSetupRootUnitNotFound`, if missing) and imports the whole tree into `PrefixTree`. This is +also how a built `up`/`upac-lib`/booters gets onto a genesis'd disk at all — genesis never installs +itself automatically, whoever assembles `--source` has to place it under `system/` too, same +assumption already made for the systemd-boot/rEFInd binaries. Confirmed `composefs-setup-root`'s own +hardcoded expectations already match upac's on-disk layout exactly (repo at `composefs/`, per-deploy +state at `state/deploy//`, `composefs=` cmdline karg) — no restructuring was needed, only +the unit + the `system/` plumbing. The unit's `*.target.wants/` enablement is deliberately NOT +created by this stage (a symlink to `initrd-root-fs.target.wants/` in the real root tree is a no-op +— that target only exists inside the initrd's own systemd instance) — it's created instead by the +dracut module at `hooks/dracut/37composefs/` at initrd-build time. **Decided: upac packages/vendors `composefs-setup-root` itself** (same call for the systemd-boot/ rEFInd binaries) rather than assuming the source distro already provides it — genesis-time import should also check whether one already exists under `system/` rather than blindly trusting our own From c4ef1eff1f68f6671d9b118c9ad09f0c6be9f451 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 07:57:42 +0400 Subject: [PATCH 146/148] fix: remove ImportSystemStage's dead wants-symlink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit initrd-root-fs.target only exists inside the initrd's own systemd instance, so a *.target.wants/ symlink written into the real root tree is a no-op after switch-root — the real root's systemd never processes it. The dracut module (hooks/dracut/37composefs/) already creates the actual enablement at initrd-build time via `$SYSTEMCTL --root "${initdir}" add-wants`, which is the only place it can take effect Co-Authored-By: Claude Sonnet 5 --- lib/setup/lib.toml | 12 +++++------- lib/setup/src/stages/system.rs | 28 +--------------------------- 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/lib/setup/lib.toml b/lib/setup/lib.toml index 522db713..0cf7b212 100644 --- a/lib/setup/lib.toml +++ b/lib/setup/lib.toml @@ -58,11 +58,11 @@ settle_interval_ms = 100 # composefs_setup_root_unit_path (relative to `system_dir`, i.e. also relative to real `/usr`) is # where `ImportSystemStage` requires to find a `composefs-setup-root.service` unit — hard error # (`SetupError::ComposefsSetupRootUnitNotFound`) if missing, since a genesis'd disk cannot boot at -# all without it. composefs_setup_root_wants_path/composefs_setup_root_wants_target: the stage -# also creates the unit's `*.target.wants/` enablement symlink itself (pure systemd mechanics, no -# reason to burden `--source` with it) — see TODO.md for why this specific set of paths matches -# `composefs-setup-root`'s own hardcoded expectations (confirmed by reading its upstream source, -# not guessed). +# all without it. `ImportSystemStage` deliberately does NOT create the unit's `*.target.wants/` +# enablement symlink itself — `initrd-root-fs.target` only exists inside the initrd's own systemd +# instance, so wanting it from the real root's unit tree is a no-op after switch-root. The dracut +# module under `hooks/dracut/37composefs/` creates that enablement at initrd-build time instead +# (`$SYSTEMCTL --root "${initdir}" add-wants`), which is the only place it can actually take effect. # # initramfs_filename is fixed by composefs-boot itself, not our choice — `UsrLibModulesVmlinuz:: # load_all` only recognizes a vmlinuz's companion initramfs under this exact name. uki_filename is @@ -74,7 +74,5 @@ scratch_filename = "genesis-packages.redb" efi_linux_dir = "EFI/Linux" system_dir = "system" composefs_setup_root_unit_path = "lib/systemd/system/composefs-setup-root.service" -composefs_setup_root_wants_path = "lib/systemd/system/initrd-root-fs.target.wants/composefs-setup-root.service" -composefs_setup_root_wants_target = "../composefs-setup-root.service" initramfs_filename = "initramfs.img" uki_filename = "uki.efi" diff --git a/lib/setup/src/stages/system.rs b/lib/setup/src/stages/system.rs index ba11f6a5..a7687fdc 100644 --- a/lib/setup/src/stages/system.rs +++ b/lib/setup/src/stages/system.rs @@ -3,12 +3,8 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception -use std::path::Path; - -use composefs::generic_tree::Stat; use composefs::repository::ImportContext; -use upac::composefs::file::FileHandle; use upac::orchestrator::context::{Context, ctx_get, ctx_take}; use upac::orchestrator::stage::{NoRollback, RollbackGuard, Stage, StageResult}; @@ -19,9 +15,7 @@ use upac_types::hook::ProgressEventBuilder; use super::{PrefixTree, ResolvedSourceDir, import_if_dir}; use crate::error::SetupError; -use crate::layout::genesis::{ - COMPOSEFS_SETUP_ROOT_UNIT_PATH, COMPOSEFS_SETUP_ROOT_WANTS_PATH, COMPOSEFS_SETUP_ROOT_WANTS_TARGET, SYSTEM_DIR, -}; +use crate::layout::genesis::{COMPOSEFS_SETUP_ROOT_UNIT_PATH, SYSTEM_DIR}; use crate::target::TargetSysroot; pub struct ImportSystemStage; @@ -46,26 +40,6 @@ impl Stage for ImportSystemStage { import_if_dir!(repository, &mut prefix_tree, &system_dir, &mut imported_ctx, cancel); - let mut ancestors: Vec<&Path> = Path::new(COMPOSEFS_SETUP_ROOT_WANTS_PATH) - .ancestors() - .skip(1) - .filter(|ancestor| !ancestor.as_os_str().is_empty()) - .collect(); - ancestors.reverse(); - - for ancestor in ancestors { - let handle = FileHandle::new(ancestor); - if handle.stat_in_tree(&prefix_tree).is_err() { - handle.insert_in_tree(&mut prefix_tree, Stat::uninitialized())?; - } - } - - FileHandle::new(COMPOSEFS_SETUP_ROOT_WANTS_PATH).symlink_in_tree( - &mut prefix_tree, - COMPOSEFS_SETUP_ROOT_WANTS_TARGET, - Stat::uninitialized(), - )?; - context.put(imported_ctx); context.put(prefix_tree); From fdf38bb15d879aae057bbe45e93fb8c2ec66ac18 Mon Sep 17 00:00:00 2001 From: JustPav Date: Sun, 20 Sep 2026 08:01:17 +0400 Subject: [PATCH 147/148] fix: remove compile err Co-Authored-By: Claude Sonnet 5 --- lib/lib/src/plugin/boot/mod.rs | 3 --- lib/lib/src/plugin/decoder/mod.rs | 3 --- 2 files changed, 6 deletions(-) diff --git a/lib/lib/src/plugin/boot/mod.rs b/lib/lib/src/plugin/boot/mod.rs index 65710936..367bd8c1 100644 --- a/lib/lib/src/plugin/boot/mod.rs +++ b/lib/lib/src/plugin/boot/mod.rs @@ -15,9 +15,6 @@ use upac_types::request::{BootPluginConfirmSuccsesBootRequest, BootPluginInstall use self::error::BootPluginError; -#[cfg(all(feature = "dynamic-plugins", feature = "builtin-booters"))] -compile_error!("dynamic-plugins and builtin-booters are mutually exclusive"); - #[cfg(feature = "dynamic-plugins")] use libloading::Library; diff --git a/lib/lib/src/plugin/decoder/mod.rs b/lib/lib/src/plugin/decoder/mod.rs index 6ad75850..308f4abe 100644 --- a/lib/lib/src/plugin/decoder/mod.rs +++ b/lib/lib/src/plugin/decoder/mod.rs @@ -18,9 +18,6 @@ use libloading::Library; use crate::plugin::decoder::error::DecoderError; -#[cfg(all(feature = "dynamic-plugins", feature = "builtin-decoders"))] -compile_error!("dynamic-plugins and builtin-decoders are mutually exclusive"); - #[cfg(feature = "dynamic-plugins")] pub mod dynamic_link; pub mod error; From c3f16ecd7dafd56bd60e48edd6732fe5a256356a Mon Sep 17 00:00:00 2001 From: JustPav Date: Mon, 21 Sep 2026 18:34:28 +0400 Subject: [PATCH 148/148] Add a non-systemd mkinitcpio hook for composefs-setup-root, fix REUSE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unlike dracut, mkinitcpio doesn't force systemd inside the initramfs — confirmed by reading its actual /init (plain busybox ash: run_hook → mount_handler → run_latehook → switch_root) and composefs-setup-root's own source (pure rustix syscalls, no systemd dependency at all beyond a best-effort /run/systemd/volatile-root workaround that no-ops cleanly without it). Add install/composefs + hooks/composefs mirroring mkinitcpio's own two-file hook convention (install/ copies the binary and runscript at build time, hooks/ runs run_latehook() after the real root device is mounted, before switch_root) — no flags needed, since composefs-setup-root's --sysroot default already matches mkinitcpio's own /sysroot convention. Also fixes REUSE compliance for the two vendored dracut files: they carry their own upstream license text as prose, not machine-readable SPDX tags, so reuse lint flagged them as unlicensed. Annotate them externally in REUSE.toml instead of injecting tags into third-party code, and vendor the newly-referenced license texts (MIT, Apache-2.0, LGPL-2.1-or-later) via `reuse download --all` Co-Authored-By: Claude Sonnet 5 --- LICENSES/Apache-2.0.txt | 73 ++++++++++++ LICENSES/LGPL-2.1-or-later.txt | 176 +++++++++++++++++++++++++++++ LICENSES/MIT.txt | 18 +++ REUSE.toml | 10 ++ hooks/mkinitcpio/hooks/composefs | 15 +++ hooks/mkinitcpio/install/composefs | 22 ++++ 6 files changed, 314 insertions(+) create mode 100644 LICENSES/Apache-2.0.txt create mode 100644 LICENSES/LGPL-2.1-or-later.txt create mode 100644 LICENSES/MIT.txt create mode 100755 hooks/mkinitcpio/hooks/composefs create mode 100755 hooks/mkinitcpio/install/composefs diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 00000000..137069b8 --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSES/LGPL-2.1-or-later.txt b/LICENSES/LGPL-2.1-or-later.txt new file mode 100644 index 00000000..c6487f4f --- /dev/null +++ b/LICENSES/LGPL-2.1-or-later.txt @@ -0,0 +1,176 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. + +GNU LESSER GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. + + e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the library's name and an idea of what it does. + Copyright (C) year name of author + + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob' (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice +That's all there is to it! diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 00000000..d817195d --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/REUSE.toml b/REUSE.toml index 6f8cfb08..84946ee4 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -34,3 +34,13 @@ SPDX-License-Identifier = "CC-BY-SA-4.0" path = "**/i18n/**/*.ftl" SPDX-FileCopyrightText = ["JustPav", "SmoothTeam"] SPDX-License-Identifier = "GPL-3.0-only" + +[[annotations]] +path = "hooks/dracut/37composefs/composefs-setup-root.service" +SPDX-FileCopyrightText = "2013 Colin Walters " +SPDX-License-Identifier = "LGPL-2.1-or-later" + +[[annotations]] +path = "hooks/dracut/37composefs/module-setup.sh" +SPDX-FileCopyrightText = "The composefs-rs contributors" +SPDX-License-Identifier = "MIT OR Apache-2.0" diff --git a/hooks/mkinitcpio/hooks/composefs b/hooks/mkinitcpio/hooks/composefs new file mode 100755 index 00000000..c702bf61 --- /dev/null +++ b/hooks/mkinitcpio/hooks/composefs @@ -0,0 +1,15 @@ +#!/usr/bin/ash +# SPDX-FileCopyrightText: 2026 JustPav +# SPDX-FileCopyrightText: 2026 SmoothTeam +# +# SPDX-License-Identifier: GPL-3.0-only + +run_latehook() { + msg ":: setting up composefs root" + + if ! composefs-setup-root; then + err "composefs-setup-root failed to prepare /sysroot" + fi +} + +# vim: set ft=sh ts=4 sw=4 et: diff --git a/hooks/mkinitcpio/install/composefs b/hooks/mkinitcpio/install/composefs new file mode 100755 index 00000000..c2ba3ab8 --- /dev/null +++ b/hooks/mkinitcpio/install/composefs @@ -0,0 +1,22 @@ +#!/usr/bin/bash +# SPDX-FileCopyrightText: 2026 JustPav +# SPDX-FileCopyrightText: 2026 SmoothTeam +# +# SPDX-License-Identifier: GPL-3.0-only + +build() { + add_binary composefs-setup-root + add_runscript +} + +help() { + cat <