From 7ff3855d17c42dddfa3c0e528a88e70d6ed9ae3d Mon Sep 17 00:00:00 2001 From: JustPav Date: Sat, 5 Sep 2026 11:09:19 +0400 Subject: [PATCH 01/85] 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 a24a3b9..dc80a01 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 693536d..acb63d2 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 02/85] 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 9a4a7e3..2ae36f5 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 b69189d..d3394c5 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 03/85] 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 8f6b7a9..3a1f177 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 38117c7..2c34abd 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 04/85] 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 00e013c..6adb852 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 3b783c4..73b834c 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 5963f9a..611d672 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 da68b73..f9d040f 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 f9be7db..6c3bda5 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 f1f7068..5a6a275 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 05/85] 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 c6a54b6..6b5f70c 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 46b4302..9151dda 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 8168fa4..b2645e6 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 3c3f5da..a98edc8 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 98c7b69..0000000 --- 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 ebeb342..ed5b5d0 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 0000000..16a2670 --- /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 aac8d95..0000000 --- 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 0000000..bafb349 --- /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 9c8c81a..0000000 --- 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 8691f41..969f419 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 7da3190..0000000 --- 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 0000000..eda699b --- /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 ad3926b..914201e 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 7d850bf..0000000 --- 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 6a04cde..60adac8 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 d8fd820..7b55ea8 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 0793178..0000000 --- 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 3c473a8..391e6d4 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 0000000..779d039 --- /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 cc8b91a..0000000 --- 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 1ac2341..0000000 --- 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 fa66a80..0000000 --- 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 61ed10d..0000000 --- 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 06/85] 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 3eed822..f6f466c 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 34e7e48..87d0994 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 875a803..c5a124c 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 5bfcfdf..6957b32 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 dfa033f..579b027 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 ca1510b..07b05a1 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 07/85] 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 816c635..7bea9bc 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 08/85] 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 1483c5b..0c496b8 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 3a89c2c..9e3673a 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 09/85] 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 2a0385c..d69540e 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 7be841b..6c6c29e 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 6cd8b29..10deca5 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 5f38537..e501fcc 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 10/85] 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 a091b15..bc11b76 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 11/85] 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 c28aa52..6966193 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 1add65a..e77620c 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 12/85] 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 af32a19..edf9842 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 8dfcd1e..079b67e 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 6874553..6d3c13e 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 26a62fe..623a633 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 39b9fab..8b4a09b 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 b11082c..385375b 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 1140ec7..e0bbe7e 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 eaee682..ca3b251 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 13/85] 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 9151dda..c1ee9ba 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 ed5b5d0..b1a317e 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 14/85] 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 7bea9bc..2fa32a5 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 15/85] 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 8b1262e..dc84d83 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 16/85] 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 92de957..f41c4b9 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 17/85] 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 0c496b8..5fdf6cf 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 3820039..38252fc 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 18/85] 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 29fa700..925a3fc 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 19/85] 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 5fdf6cf..d132040 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 20/85] 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 edf9842..366bd02 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 dc84d83..a10b53f 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 ea030a0..75dd7ac 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 e0bbe7e..7d0ea2c 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 21/85] 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 dc1ce59..9e29b36 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 079b67e..0bb7930 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 6d3c13e..3b8dd74 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 8b4a09b..1b4750f 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 22/85] 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 0bb7930..079b67e 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 23/85] 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 ca3b251..22fd43f 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 24/85] 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 079b67e..0bb7930 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 623a633..fcf0f5c 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 385375b..77f219d 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 25/85] 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 fcf0f5c..3022c9e 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 77f219d..fe0b4a8 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 26/85] 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 c1ee9ba..1edb62d 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 b1a317e..c726405 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 969f419..9e63183 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 38252fc..bf406ec 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 07b05a1..799a764 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 27/85] 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 2fa32a5..ca66357 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 28/85] 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 799a764..f10e526 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 29/85] 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 d4df458..0ce9683 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 0000000..47c2f51 --- /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 0000000..9c0134b --- /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 0000000..6a931ac --- /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 0000000..e661da3 --- /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 6ffae21..77ddd3e 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 0000000..a4fd413 --- /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 0000000..b39b7ec --- /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 2b431bb..28968b0 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 30/85] 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 ca66357..2c541a4 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 31/85] 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 49bf891..0f5a6c6 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 a410613..b297164 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 1b5aa05..ccd778b 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 d84f523..e61c5a4 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 e7db861..fe02356 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 b0af659..e6dfb3c 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 639655b..71b1b22 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 327879a..6fefb3c 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 cd58832..a928244 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 9a5fe78..58dfe10 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 b0ae8d8..931e207 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 0000000..7714333 --- /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 0000000..b90f8b1 --- /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 0000000..ffbdd29 --- /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 0000000..e3ba423 --- /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 0000000..fb4b526 --- /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 0000000..8c97738 --- /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 0000000..ebce8c7 --- /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 0000000..2a5f35e --- /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 0000000..8ea2698 --- /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 0000000..4bc6f73 --- /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 0000000..3987011 --- /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 0000000..0199a10 --- /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 0000000..097ba98 --- /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 0000000..e891224 --- /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 0000000..591bcc4 --- /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 32/85] 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 2c541a4..6f9c481 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 33/85] 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 d132040..53676c2 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 34/85] 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 366bd02..88c055f 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 9e29b36..507622a 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 0bb7930..a685aa4 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 3b8dd74..8fdb78d 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 3022c9e..616630f 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 1b4750f..994b0ae 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 fe0b4a8..96529a9 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 a10b53f..3e494c9 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 7d0ea2c..b888bd8 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 35/85] 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 22fd43f..90ce285 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 36/85] 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 c726405..1e7c8eb 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 37/85] 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 6f9c481..72b55b8 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 38/85] 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 177dfc9..0000000 --- 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 906fcaf..0976a69 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 baf84bc..4a14086 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 76c1efd..3a2791b 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 c8383bb..1769b88 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 39/85] 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 72b55b8..40dab58 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 40/85] 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 0000000..081428e --- /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 90ce285..50fd5a7 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 0000000..433ec20 --- /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 1edb62d..79203e4 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 41/85] 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 40dab58..fa7323b 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 42/85] 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 79203e4..6d454eb 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 a98edc8..2f9930c 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 9e63183..991c303 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 0000000..1615e08 --- /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 43/85] 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 3a1f177..fc92416 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 2c34abd..cd6ca91 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 611d672..90be250 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 f9d040f..f3dede5 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 44/85] 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 fa7323b..d92834d 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 45/85] 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 53676c2..0000000 --- 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 5ea575d..0000000 --- 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 53ec0d7..80f9e94 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 0fda259..9fc48a1 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 9e3673a..bf44bf8 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 2c594f7..c798599 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 6c2518d..aaa726f 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 762b4fb..4b2ba36 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 dc80a01..0000000 --- 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 3f8ae6f..a0536ad 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 acb63d2..efeddc3 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 46/85] 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 9bf290c..7feb428 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 47/85] 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 7f7da1c..64b74d6 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 0000000..6c34f6f --- /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 0000000..758348a --- /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 0000000..64bad63 --- /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 4e9682f..211a5a9 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 0000000..b5611f8 --- /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 ed167e8..bea4cf3 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 31f0982..0000000 --- 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 0000000..15684c4 --- /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 7718d89..3d56a12 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 48/85] 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 bf44bf8..e78a3a1 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 c798599..f5562bd 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 4b2ba36..6175716 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 49/85] 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 0000000..d4d7444 --- /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 64b74d6..36b7181 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 211a5a9..7e7c427 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 50/85] 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 f064cac..9cfe143 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 8be8934..1d0b4f0 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 f6f466c..727a863 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 020dbe7..5516d4b 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 87d0994..b32e718 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 c5a124c..36abfb0 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 6957b32..63246eb 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 579b027..544e59f 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 e75816e..4aa915d 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 5caae6c..0000000 --- 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 0ce9683..c0b10df 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 ff4add2..5c02d7f 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 6966193..632f963 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 89b32c0..0c2bc6e 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 10eba37..144c29b 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 f7c3bbd..5904e5f 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 c8c7d1f..1e4bc5a 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 88e5a31..9ac0847 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 60a0511..f4897fc 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 0e169a2..8a2de9f 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 4f26c6f..163670a 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 1df78d4..53e8ca9 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 fd2be85..0c3173f 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 f407c33..ae51230 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 811caea..4274da7 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 5049721..6ab08a8 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 b0d379f..f8bf310 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 3711861..511d7cf 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 98603be..95d0ed2 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 a50a3de..0358455 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 0976a69..5dab1ca 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 ca5ec8c..d34d381 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 a3d090d..e6baef6 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 6a0b4dc..8ee89cf 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 d7d53e9..8610fe5 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 c7bbf63..9b63e0c 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 7b31fa6..9cd5277 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 d42e464..120b2a9 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 9ce90c1..4783aa5 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 77675f8..23cde07 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 059aeeb..693d1b0 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 bcaef91..124ee3b 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 ce0d41f..238dd00 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 dc911db..ecae6db 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 e55104d..4e71240 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 05fe546..a46291a 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 905767b..42cef5f 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 4a14086..022bd54 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 5ac2546..4455340 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 e069c44..5a75905 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 d3432c7..06cc3d8 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 c7ce000..fe8593f 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 e48ee0a..cc5b421 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 ffee7e4..2ae11d5 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 60d1a4f..6393c44 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 77b98cd..a7653ad 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 510f14b..ec1639b 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 3a2791b..2c912d0 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 4acb0b3..881b65a 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 d4a82b4..ba9f4bc 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 47385be..7cea052 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 9d179e7..e2cd04c 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 0000000..5655a58 --- /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 9714602..2afcc76 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 eb5da1b..e96096b 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 9c7fdcf..134306a 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 50fd5a7..46c3b62 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 e77620c..7a2c799 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 d3394c5..c3b6ae1 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 98943b3..0000000 --- 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 13468a3..1a97a6a 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 aaa8fdf..ff03cdd 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 611a7d1..b5db152 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 faef783..40ad92d 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 b6ccca0..87839cf 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 f956a7f..0d0abd7 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 04f20f1..79a4aa1 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 aecf754..a8ec896 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 fc80c1f..83693a0 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 04ffb02..1b085f0 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 06db41c..184ebee 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 9c97b14..64d94b1 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 d307554..e385cdd 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 daee05d..156f137 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 84a5dd7..7a2b536 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 e206bf3..1fe8391 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 f9bac4b..d9b2b0a 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 bae1dc0..41b371a 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 bd33bbc..6340866 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 9cefb2e..a18a943 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 a784440..07f4102 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 9fbd871..bb6e301 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 94a24ca..5ea5ff4 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 acb5bfc..85685e4 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 03ece76..7d38498 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 4869b7b..a7d816c 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 971c716..a165c4f 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 1c14122..1395bbd 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 8952ff4..b63442c 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 ac0b455..3b2db69 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 51/85] 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 8858e25..07ae3a2 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 0076080..691f994 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 cc13f0d..bc4646f 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 52/85] 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 9c70a17..a652bea 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 42862ca..dc4ef9d 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 0de299a..21ac249 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 53/85] 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 d4d7444..0000000 --- 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 36b7181..97af950 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 7e7c427..beefaf3 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 b5611f8..eb8ad1b 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 0000000..d3729c5 --- /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 0000000..cf0cc92 --- /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 54/85] 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 cd590ea..e49fe1c 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 9593e1e..0c07621 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 25713a9..5843e4c 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 6844f40..25fc782 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 9a26bcc..5db3fa6 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 9e07965..dd96ed2 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 d557072..614c43a 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 ce2b5a1..f7f6627 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 3a76719..23d90aa 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 e51f0b3..db6554e 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 5d33e01..f6cdaaf 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 3544a92..f9a8c92 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 904d786..ca4003d 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 c0cd50f..056c2f9 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 ea8a0a6..ed3f0fd 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 442aa11..dbf0dfc 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 5a2adbc..2e519f2 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 6e4ff0e..7c29dd8 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 20ee402..a0396b7 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 1c2e1aa..0dedaa3 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 e8b211c..8c14051 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 b5db152..e3d801d 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 0d0abd7..89f68af 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 83693a0..e302bec 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 64d94b1..8dd5a44 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 7a2b536..754736a 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 d9b2b0a..264f320 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 6340866..2201ed2 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 07f4102..aae03e5 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 bb6e301..2791043 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 85685e4..cb622df 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 a7d816c..aedecf1 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 1395bbd..d32c747 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 55/85] 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 07ae3a2..155a558 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 56/85] 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 124b61d..778b2a3 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 57/85] 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 691f994..3bce2a2 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 58/85] 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 2f8403d..20998cf 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 fd763e7..2575e5f 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 1e34612..233bc0d 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 84e67d2..7ab04fb 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 2d7ccfd..d8230fe 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 76e3236..573eec8 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 5f6614a..f4c06a9 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 d86621c..d76b3a7 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 1f8f211..b63c6e1 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 0e61509..fe4db66 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 b769f40..5c65d83 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 ebf73ee..9bb53d5 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 027dbe5..f2ecd68 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 058d087..5ad8d18 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 f6de569..d2920ab 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 842fed5..5b7d60f 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 823e932..aab1f68 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 cdb9912..80b4663 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 371ae36..af55801 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 e813b91..2a57719 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 0516797..8660030 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 3a28345..4f421bc 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 eb3f591..3fc6025 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 68b54ff..b0eec81 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 f2d8ae2..92e7a09 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 c0f80e7..b78a82b 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 bc11b76..a0fa14d 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 67254c6..ec4f1e3 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 b64a30d..b13beba 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 3ff422d..3bab109 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 87730c7..3c43f1f 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 07563e9..1fc5377 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 b641046..6774d4f 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 2a6d7f4..840bae7 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 59/85] 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 a652bea..d05a5fe 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 dc4ef9d..1867165 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 60/85] 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 0df1547..490a4d4 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 c6b4b10..e0bd589 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 a0da1cc..6db1920 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 454d75a..e832177 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 195d972..f141dc2 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 bb322b2..a31dc86 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 61/85] 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 081428e..6763c96 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 ca7434f..7cc8f7c 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 fc801b7..ce088a7 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 46c3b62..ce49c46 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 433ec20..7dea4df 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 62/85] 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 aaa726f..e25f57d 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 63/85] 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 d3729c5..6fedfe3 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 64/85] 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 1e4bc5a..d48b00c 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 9ac0847..294b488 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 8a2de9f..0cdab51 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 163670a..abfc57f 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 53e8ca9..fa25e6e 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 f8bf310..674353e 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 d34d381..06085a9 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 8610fe5..e4f03ff 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 124ee3b..9f943e2 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 ecae6db..fb8f7c5 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 4e71240..0c08469 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 a46291a..327bafd 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 4455340..1dd9ecf 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 cc5b421..e4f6f89 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 2ae11d5..edeb920 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 881b65a..5d46476 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 e2cd04c..396d4a6 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 65/85] 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 ad2ab2a..1b7b62a 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 1828014..110b585 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 f86983a..94b7515 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 5c65d83..45e0261 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 5ad8d18..ec8d4ee 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 2a57719..e5c7b0a 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 3fc6025..053807b 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 92e7a09..f499d0c 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 b78a82b..5f74899 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 66/85] 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 978833d..73e8fbd 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 67/85] 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 6fedfe3..e863fa1 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 68/85] 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 88c055f..30d5be4 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 9cf8f60..365c7f8 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 23be7f0..1e56aec 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 507622a..8eb5621 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 a685aa4..e7ea733 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 69/85] 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 77204ac..571cc6b 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 23be7f0..e0d9eec 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 8fdb78d..d1abd87 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 616630f..7bd4f6a 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 70/85] 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 8eb5621..9a6867b 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 e7ea733..9ead2b2 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 71/85] 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 30d5be4..2028e0e 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 d2e1514..1dd6f9e 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 23be7f0..62c06f3 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 994b0ae..56ec093 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 ace8c74..7280477 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 96529a9..09b3f61 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 72/85] 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 dfbe34a..1fd695d 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 23be7f0..b65b8a7 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 3e494c9..b32b2f1 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 75dd7ac..714a23d 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 b888bd8..4dc80b1 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 73/85] 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 2028e0e..9336646 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 74/85] 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 15684c4..2115ed5 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 75/85] 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 0000000..b1175b4 --- /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 2ae36f5..7c34e00 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 7a2c799..958f086 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 0000000..9af9ff1 --- /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 c3b6ae1..08ca5df 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 76/85] 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 9cd5277..0ebe131 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 77/85] 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 6763c96..2723c79 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 78/85] 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 0000000..9498889 --- /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 9db7cd0..2215faa 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 79/85] 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 ca4003d..57bda4d 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 144c29b..3f13905 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 5904e5f..230c452 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 d48b00c..122adf4 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 294b488..5acff55 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 f4897fc..227f9da 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 0cdab51..4bde5b8 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 abfc57f..ece80f0 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 0c3173f..5f96ab3 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 ae51230..aa6c847 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 4274da7..be15a48 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 6ab08a8..d1f8911 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 674353e..c2677cf 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 511d7cf..571ef59 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 0358455..c0cbe76 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 5dab1ca..2cf91ae 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 06085a9..de660ce 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 e6baef6..ab781e0 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 8ee89cf..694cfa2 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 9b63e0c..f461045 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 120b2a9..03a2a4c 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 4783aa5..40b7bd9 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 23cde07..680168d 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 693d1b0..e4933ff 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 9f943e2..f935cbb 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 238dd00..3761b4a 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 fb8f7c5..ea993bc 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 327bafd..7357895 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 42cef5f..0cfef3b 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 022bd54..1498361 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 1dd9ecf..0153a3d 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 5a75905..ddeb84c 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 06cc3d8..2ff9ffd 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 fe8593f..5fed6c5 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 edeb920..4e5e2aa 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 6393c44..42ec74d 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 ec1639b..3fc816f 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 2c912d0..bcd3427 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 5d46476..0020542 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 ba9f4bc..1635781 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 7cea052..f721e19 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 958f086..6ad7585 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 9af9ff1..6e3013c 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 50dd8ce..5e7a265 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 1a97a6a..e06364a 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 e3d801d..6aba3d8 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 80/85] 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 d92834d..e8c2b4d 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 81/85] 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 38eac82..8b34b0c 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 97af950..a2573e4 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 82/85] 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 9498889..d3e0618 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 83/85] 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 a2573e4..5945aa6 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 84/85] 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 fcd01bc..38cdab9 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 e755930..6c93797 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 d69540e..27bff89 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 5128ccb..f79bbf9 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 82e4a9c..abaf131 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 aae6636..0000000 --- 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 85/85] 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 bbc0dae..0d5a54f 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];