diff --git a/Cargo.toml b/Cargo.toml index 912f9e24..5557d81b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = ["user/upac-cli", "user/sign-cli", "lib/macro", "lib/abi", "lib/lib" , version = "0.1.5" edition = "2024" license = "GPL-3.0-only" -repository = "https://github.com/justpav05/upac" +repository = "https://github.com/SmoothTeam/upac" authors = ["justpav05 "] [workspace.dependencies] diff --git a/README.md b/README.md index 01cd298a..b7618c3e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # 📦 Upac -[![GitHub](https://img.shields.io/badge/GitHub-justpav05%2Fupac-181717?logo=github)](https://github.com/justpav05/upac) -[![Version](https://img.shields.io/badge/version-0.1.5-green)](https://github.com/justpav05/upac/releases) -[![REUSE status](https://api.reuse.software/badge/github.com/justpav05/upac)](https://api.reuse.software/info/github.com/justpav05/upac) -[![lib: LGPL-3.0-or-later](https://img.shields.io/badge/lib-LGPL--3.0--or--later-blue.svg)](LICENSES/LGPL-3.0-or-later.txt) -[![cli: GPL-3.0-only](https://img.shields.io/badge/cli-GPL--3.0--only-blue.svg)](LICENSES/GPL-3.0-only.txt) +Links for repositories: [![GitHub](https://img.shields.io/badge/GitHub-SmoothTeam%2Fupac-181717?logo=github)](https://github.com/SmoothTeam/upac) [![Codeberg](https://img.shields.io/badge/Codeberg-justpav05%2Fupac-2185D0?logo=codeberg)](https://codeberg.org/justpav05/upac) + +General information: [![Version](https://img.shields.io/badge/version-0.1.5-green)](https://github.com/SmoothTeam/upac/releases) [![REUSE status](https://api.reuse.software/badge/github.com/SmoothTeam/upac)](https://api.reuse.software/info/github.com/SmoothTeam/upac) + +Licensing: [![lib: LGPL-3.0-or-later](https://img.shields.io/badge/lib-LGPL--3.0--or--later-blue.svg)](LICENSES/LGPL-3.0-or-later.txt) [![cli: GPL-3.0-only](https://img.shields.io/badge/cli-GPL--3.0--only-blue.svg)](LICENSES/GPL-3.0-only.txt) > **⚠️ Branch in progress.** This branch (`lib-rs`) is a from-scratch rewrite of upac's core library in Rust, built around [composefs](https://github.com/containers/composefs) instead of OSTree. The FFI/orchestration engine is done; the actual command bodies, the hook system, and packaging are still being implemented — expect gaps and `todo!()`s. diff --git a/SECURITY.md b/SECURITY.md index 7d814301..3450d473 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,7 +13,7 @@ only the latest commit on `main` is supported. Security fixes are not backported Please use one of the following instead of opening a public issue: -- GitHub's [private vulnerability reporting](https://github.com/justpav05/upac/security/advisories/new) +- GitHub's [private vulnerability reporting](https://github.com/SmoothTeam/upac/security/advisories/new) (Security tab → "Report a vulnerability") - Email: aksenovpaveldmitrievich@gmail.com diff --git a/lib/abi/src/lib.rs b/lib/abi/src/lib.rs index e2564999..46f80e30 100644 --- a/lib/abi/src/lib.rs +++ b/lib/abi/src/lib.rs @@ -18,18 +18,46 @@ pub const ABI_VERSION: u32 = 2; #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DiffKind { +pub enum FileDiffKind { Added = 0, Removed = 1, Modified = 2, } -impl DiffKind { - pub fn from_u8(version: u8) -> Result { +impl FileDiffKind { + pub fn from_u8(version: u8) -> Result { match version { - 0 => Ok(DiffKind::Added), - 1 => Ok(DiffKind::Removed), - 2 => Ok(DiffKind::Modified), + 0 => Ok(FileDiffKind::Added), + 1 => Ok(FileDiffKind::Removed), + 2 => Ok(FileDiffKind::Modified), + _ => Err(ErrorKind::InvalidEntry), + } + } +} + +// 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 { + Added = 0, + Removed = 1, + Modified = 2, + FilesChanged = 3, +} + +impl PackageDiffKind { + pub fn from_u8(version: u8) -> Result { + match version { + 0 => Ok(PackageDiffKind::Added), + 1 => Ok(PackageDiffKind::Removed), + 2 => Ok(PackageDiffKind::Modified), + 3 => Ok(PackageDiffKind::FilesChanged), _ => Err(ErrorKind::InvalidEntry), } } diff --git a/lib/abi/src/request.rs b/lib/abi/src/request.rs index 1eacf4c8..cdc7568d 100644 --- a/lib/abi/src/request.rs +++ b/lib/abi/src/request.rs @@ -7,7 +7,7 @@ use std::os::raw::c_void; use upac_macro::CValidate; -use crate::DiffKind; +use crate::FileDiffKind; use crate::error::ErrorKind; use crate::hook::{CancelToken, HookMessageFn}; use crate::package::{CPackageInfo, CUnpackedPackage}; @@ -74,7 +74,7 @@ pub struct CRollbackRequest { pub base: CRequestBase, pub tmp_path: CSlice, - pub commit_hash: CSlice, + pub config_digest: CSlice, } #[repr(C)] @@ -100,7 +100,7 @@ pub struct CFilesRequest { #[optional] pub message: CSlice, pub files: CVec, - pub file_kind: DiffKind, + pub file_kind: FileDiffKind, pub file_package: *const CPackageInfo, } @@ -154,9 +154,9 @@ pub struct CDiffFilesConfigRequest { pub base: CRequestBase, #[optional] - pub from_commit_hash: CSlice, + pub from_config_digest: CSlice, #[optional] - pub to_commit_hash: CSlice, + pub to_config_digest: CSlice, } #[repr(C)] @@ -178,9 +178,9 @@ pub struct CDiffRequest { pub base: CRequestBase, #[optional] - pub from_commit_hash: CSlice, + pub from_prefix_digest: CSlice, #[optional] - pub to_commit_hash: CSlice, + pub to_prefix_digest: CSlice, } #[repr(C)] diff --git a/lib/abi/src/response.rs b/lib/abi/src/response.rs index 93108078..d203298c 100644 --- a/lib/abi/src/response.rs +++ b/lib/abi/src/response.rs @@ -5,18 +5,18 @@ use upac_macro::{CFree, CValidate}; -use crate::DiffKind; use crate::error::ErrorKind; use crate::memory::{free_cslice, free_cvec_owning}; use crate::package::{CPackageMeta, CVersion}; use crate::types::{CSlice, CVec, check_size}; +use crate::{FileDiffKind, PackageDiffKind}; #[repr(C)] #[derive(CFree)] pub struct CDiffPackageEntry { pub struct_size: usize, pub name: CSlice, - pub kind: DiffKind, + pub kind: PackageDiffKind, pub version: CVersion, pub files: CVec, } @@ -27,7 +27,7 @@ pub struct CDiffPrefixFileEntry { pub struct_size: usize, pub path: CSlice, - pub kind: DiffKind, + pub kind: FileDiffKind, pub package_name: CSlice, pub is_user: bool, } @@ -38,11 +38,20 @@ pub struct CDiffConfigFileEntry { pub struct_size: usize, pub path: CSlice, - pub kind: DiffKind, + pub kind: FileDiffKind, #[optional] pub package_name: CSlice, } +#[repr(C)] +#[derive(CFree, CValidate)] +pub struct CDiffUntrackedFileEntry { + pub struct_size: usize, + + pub path: CSlice, + pub kind: FileDiffKind, +} + #[repr(C)] #[derive(CFree, CValidate)] pub struct CCommitEntry { @@ -231,8 +240,8 @@ impl CDiffPackagesResponse { #[repr(C)] pub struct CDiffResponse { pub struct_size: usize, - pub unattached_files: CVec, pub diff_packages: CVec, + pub unattached_files: CVec, } impl CDiffResponse { diff --git a/lib/lib/src/composefs/diff.rs b/lib/lib/src/composefs/diff.rs new file mode 100644 index 00000000..246f5685 --- /dev/null +++ b/lib/lib/src/composefs/diff.rs @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 JustPav +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +use std::cmp::Ordering; +use std::path::{Path, PathBuf}; + +use composefs::tree::{Directory, FileSystem, Inode, Leaf, LeafContent, RegularFile}; +use upac_abi::FileDiffKind; + +use crate::composefs::repository::ObjectID; + +macro_rules! advance_subtree { + ($differ:expr, $prefix:expr, $entries:expr, $next:expr, $name:expr, $inode:expr, $side:expr) => {{ + $differ.mark_subtree(&$prefix.join($name), $inode, $side); + $next = $entries.next(); + }}; +} + +#[derive(Clone, Copy)] +enum Side { + From, + To, +} + +impl Side { + fn kind(self) -> FileDiffKind { + match self { + Side::From => FileDiffKind::Removed, + Side::To => FileDiffKind::Added, + } + } +} + +pub struct TreeDiff<'a> { + from_leaves: &'a [Leaf], + to_leaves: &'a [Leaf], + changes: Vec<(String, FileDiffKind)>, +} + +impl<'a> TreeDiff<'a> { + pub fn run(from: &'a FileSystem, to: &'a FileSystem) -> Vec<(String, FileDiffKind)> { + let mut differ = Self { + from_leaves: &from.leaves, + to_leaves: &to.leaves, + changes: Vec::new(), + }; + + differ.compare_directories(&PathBuf::new(), &from.root, &to.root); + + differ.changes + } + + fn compare_directories(&mut self, prefix: &Path, from_dir: &Directory, to_dir: &Directory) { + let mut from_entries = from_dir.sorted_entries(); + let mut to_entries = to_dir.sorted_entries(); + + let mut from_next = from_entries.next(); + let mut to_next = to_entries.next(); + + loop { + match (from_next, to_next) { + (Some((from_name, from_inode)), Some((to_name, to_inode))) => match from_name.cmp(to_name) { + Ordering::Less => { + advance_subtree!(self, prefix, from_entries, from_next, from_name, from_inode, Side::From) + } + Ordering::Greater => { + advance_subtree!(self, prefix, to_entries, to_next, to_name, to_inode, Side::To) + } + Ordering::Equal => { + self.compare_matched_entry(&prefix.join(from_name), from_inode, to_inode); + from_next = from_entries.next(); + to_next = to_entries.next(); + } + }, + (Some((from_name, from_inode)), None) => { + advance_subtree!(self, prefix, from_entries, from_next, from_name, from_inode, Side::From) + } + (None, Some((to_name, to_inode))) => { + advance_subtree!(self, prefix, to_entries, to_next, to_name, to_inode, Side::To) + } + (None, None) => break, + } + } + } + + fn compare_matched_entry(&mut self, path: &Path, from_inode: &Inode, to_inode: &Inode) { + match (from_inode, to_inode) { + (Inode::Directory(from_sub), Inode::Directory(to_sub)) => { + self.compare_directories(path, from_sub, to_sub); + } + (Inode::Leaf(from_id, _), Inode::Leaf(to_id, _)) => { + let from_leaf = &self.from_leaves[from_id.0]; + let to_leaf = &self.to_leaves[to_id.0]; + + if Self::is_regular_or_symlink(from_leaf) + && Self::is_regular_or_symlink(to_leaf) + && !Self::content_matches(from_leaf, to_leaf) + { + self.changes.push((Self::path_to_string(path), FileDiffKind::Modified)); + } + } + (from_inode, to_inode) => { + self.mark_subtree(path, from_inode, Side::From); + self.mark_subtree(path, to_inode, Side::To); + } + } + } + + fn mark_subtree(&mut self, path: &Path, inode: &Inode, side: Side) { + match inode { + Inode::Leaf(id, _) => { + let leaf = &self.leaves(side)[id.0]; + + if Self::is_regular_or_symlink(leaf) { + self.changes.push((Self::path_to_string(path), side.kind())); + } + } + Inode::Directory(dir) => { + for (name, child) in dir.sorted_entries() { + self.mark_subtree(&path.join(name), child, side); + } + } + } + } + + fn leaves(&self, side: Side) -> &'a [Leaf] { + match side { + Side::From => self.from_leaves, + Side::To => self.to_leaves, + } + } + + fn is_regular_or_symlink(leaf: &Leaf) -> bool { + matches!(leaf.content, LeafContent::Regular(_) | LeafContent::Symlink(_)) + } + + fn content_matches(from: &Leaf, to: &Leaf) -> bool { + match (&from.content, &to.content) { + ( + LeafContent::Regular(RegularFile::External(from_id, from_size)), + LeafContent::Regular(RegularFile::External(to_id, to_size)), + ) => from_id == to_id && from_size == to_size, + ( + LeafContent::Regular(RegularFile::Inline(from_bytes)), + LeafContent::Regular(RegularFile::Inline(to_bytes)), + ) => from_bytes == to_bytes, + (LeafContent::Symlink(from_target), LeafContent::Symlink(to_target)) => from_target == to_target, + _ => false, + } + } + + fn path_to_string(path: &Path) -> String { + path.to_string_lossy().into_owned() + } +} diff --git a/lib/lib/src/composefs/mod.rs b/lib/lib/src/composefs/mod.rs index 9ac8f2f2..dfcfd088 100644 --- a/lib/lib/src/composefs/mod.rs +++ b/lib/lib/src/composefs/mod.rs @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later +pub mod diff; pub mod error; pub mod file; pub mod repository; diff --git a/lib/lib/src/database/attribution.rs b/lib/lib/src/database/attribution.rs new file mode 100644 index 00000000..1421e2f7 --- /dev/null +++ b/lib/lib/src/database/attribution.rs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 JustPav +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +use super::error::DatabaseError; +use super::files::FileStore; +use super::meta::MetaStore; + +use crate::types::{FileEntry, PackageMeta}; + +pub struct FileAttribution { + pub package_meta: PackageMeta, + pub file_entry: FileEntry, +} + +pub trait FileAttribute: FileStore + MetaStore { + fn attribute_file(&self, path: &str) -> Result, DatabaseError> { + let Some(uuid) = self.find_file_owner(path)? else { + return Ok(None); + }; + let Some(package_meta) = self.get_package_meta(uuid)? else { + return Ok(None); + }; + let Some(file_entry) = self.list_files(uuid)?.into_iter().find(|entry| entry.path == path) else { + return Ok(None); + }; + + Ok(Some(FileAttribution { + package_meta, + file_entry, + })) + } +} + +impl FileAttribute for T {} diff --git a/lib/lib/src/database/mod.rs b/lib/lib/src/database/mod.rs index 1e1ddac2..c014f007 100644 --- a/lib/lib/src/database/mod.rs +++ b/lib/lib/src/database/mod.rs @@ -21,6 +21,7 @@ use crate::types::database::{ use self::error::DatabaseError; +pub mod attribution; pub mod error; pub mod files; pub mod meta; diff --git a/lib/lib/src/database/record.rs b/lib/lib/src/database/record.rs index b75b8133..5c98c539 100644 --- a/lib/lib/src/database/record.rs +++ b/lib/lib/src/database/record.rs @@ -14,7 +14,7 @@ use crate::types::deployment::RECORD_FILENAME; #[derive(Debug, Clone, PartialEq, Eq, JsonCodec)] pub struct EtcHistoryEntry { - pub etc_digest: String, + pub config_digest: String, pub subject: String, pub message: Option, } @@ -26,7 +26,7 @@ pub struct DeployRecord { pub message: Option, pub seq: u64, pub timestamp: u64, - pub etc_history: Vec, + pub config_history: Vec, pub working_etc: String, } diff --git a/lib/lib/src/export/unmutated/diff.rs b/lib/lib/src/export/unmutated/diff.rs index fc53b058..16053476 100644 --- a/lib/lib/src/export/unmutated/diff.rs +++ b/lib/lib/src/export/unmutated/diff.rs @@ -8,7 +8,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use upac_abi::error::{CError, ErrorKind}; use upac_abi::request::CDiffRequest; -use upac_abi::response::{CDiffPackageEntry, CDiffPrefixFileEntry, CDiffResponse}; +use upac_abi::response::{CDiffPackageEntry, CDiffResponse, CDiffUntrackedFileEntry}; use upac_abi::types::{COwned, CVec}; use crate::export::{try_convert_abi, write_error}; @@ -27,12 +27,15 @@ pub unsafe extern "C" fn diff(request_c: CDiffRequest, response_out: *mut CDiffR unsafe { *response_out = CDiffResponse { struct_size: size_of::(), - unattached_files: CVec::from_owned( - unattached_files.into_iter().map(CDiffPrefixFileEntry::from).collect(), - ), diff_packages: CVec::from_owned( diff_packages.into_iter().map(CDiffPackageEntry::from).collect(), ), + unattached_files: CVec::from_owned( + unattached_files + .into_iter() + .map(CDiffUntrackedFileEntry::from) + .collect(), + ), }; } } diff --git a/lib/lib/src/mutated/commit/mod.rs b/lib/lib/src/mutated/commit/mod.rs index 5ce6271b..419d5430 100644 --- a/lib/lib/src/mutated/commit/mod.rs +++ b/lib/lib/src/mutated/commit/mod.rs @@ -56,18 +56,6 @@ impl<'a> TryFrom<&'a CCommitRequest> for CommitData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![ - Box::new(HookStage { - trigger: NativeTrigger::pre(Operation::Commit), - }), - Box::new(TransactionStage), - Box::new(HookStage { - trigger: NativeTrigger::post(Operation::Commit), - }), - ]) -} - pub fn run(data: CommitData) -> Result<(), (CommitStateId, CommitError)> { let mut context = Context::new(); context.put(TmpPath(data.tmp_path.to_owned())); @@ -81,3 +69,15 @@ pub fn run(data: CommitData) -> Result<(), (CommitStateId, CommitError)> { result } + +fn assemble() -> SequentialOrchestrator { + SequentialOrchestrator::new(vec![ + Box::new(HookStage { + trigger: NativeTrigger::pre(Operation::Commit), + }), + Box::new(TransactionStage), + Box::new(HookStage { + trigger: NativeTrigger::post(Operation::Commit), + }), + ]) +} diff --git a/lib/lib/src/mutated/files/mod.rs b/lib/lib/src/mutated/files/mod.rs index 5df534b1..fd3b4b95 100644 --- a/lib/lib/src/mutated/files/mod.rs +++ b/lib/lib/src/mutated/files/mod.rs @@ -5,7 +5,7 @@ use std::os::raw::c_void; -use upac_abi::DiffKind; +use upac_abi::FileDiffKind; use upac_abi::error::ErrorKind; use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; use upac_abi::package::CPackageInfo; @@ -30,7 +30,7 @@ mod transaction; pub struct FilesData<'a> { pub files: Vec<&'a str>, - pub file_kind: DiffKind, + pub file_kind: FileDiffKind, pub file_package: &'a CPackageInfo, pub tmp_path: &'a str, @@ -71,20 +71,6 @@ impl<'a> TryFrom<&'a CFilesRequest> for FilesData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![ - Box::new(HookStage { - trigger: NativeTrigger::pre(Operation::Files), - }), - Box::new(TransactionStage), - Box::new(CheckoutStage), - Box::new(SwapStage), - Box::new(HookStage { - trigger: NativeTrigger::post(Operation::Files), - }), - ]) -} - pub fn run(data: FilesData) -> Result<(), (FilesStateId, FilesError)> { let mut context = Context::new(); context.put(TmpPath(data.tmp_path.to_owned())); @@ -98,3 +84,17 @@ pub fn run(data: FilesData) -> Result<(), (FilesStateId, FilesError)> { result } + +fn assemble() -> SequentialOrchestrator { + SequentialOrchestrator::new(vec![ + Box::new(HookStage { + trigger: NativeTrigger::pre(Operation::Files), + }), + Box::new(TransactionStage), + Box::new(CheckoutStage), + Box::new(SwapStage), + Box::new(HookStage { + trigger: NativeTrigger::post(Operation::Files), + }), + ]) +} diff --git a/lib/lib/src/mutated/installer/mod.rs b/lib/lib/src/mutated/installer/mod.rs index c07f4c5f..9b9c43c2 100644 --- a/lib/lib/src/mutated/installer/mod.rs +++ b/lib/lib/src/mutated/installer/mod.rs @@ -68,6 +68,21 @@ impl<'a> TryFrom<&'a CInstallRequest> for InstallData<'a> { } } +pub fn run(data: InstallData) -> Result<(), (InstallStateId, InstallError)> { + let mut context = Context::new(); + context.put(data.packages); + context.put(TmpPath(data.tmp_path.to_owned())); + context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); + + let orchestrator = assemble(); + + let result = run_mutating!(orchestrator, context, data.cancel_token, InstallStateId, InstallError); + + data.cancel_token.reset(); + + result +} + fn assemble() -> SequentialOrchestrator { SequentialOrchestrator::new(vec![ Box::new(HookStage { @@ -83,18 +98,3 @@ fn assemble() -> SequentialOrchestrator { }), ]) } - -pub fn run(data: InstallData) -> Result<(), (InstallStateId, InstallError)> { - let mut context = Context::new(); - context.put(data.packages); - context.put(TmpPath(data.tmp_path.to_owned())); - context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); - - let orchestrator = assemble(); - - let result = run_mutating!(orchestrator, context, data.cancel_token, InstallStateId, InstallError); - - data.cancel_token.reset(); - - result -} diff --git a/lib/lib/src/mutated/rollback/mod.rs b/lib/lib/src/mutated/rollback/mod.rs index 34244d63..7ca6672d 100644 --- a/lib/lib/src/mutated/rollback/mod.rs +++ b/lib/lib/src/mutated/rollback/mod.rs @@ -27,7 +27,7 @@ mod merge; mod swap; pub struct RollbackData<'a> { - pub commit_hash: &'a str, + pub config_digest: &'a str, pub tmp_path: &'a str, @@ -46,7 +46,7 @@ impl<'a> TryFrom<&'a CRollbackRequest> for RollbackData<'a> { let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; Ok(RollbackData { - commit_hash: (&request.commit_hash).try_into()?, + config_digest: (&request.config_digest).try_into()?, tmp_path: (&request.tmp_path).try_into()?, @@ -58,20 +58,6 @@ impl<'a> TryFrom<&'a CRollbackRequest> for RollbackData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![ - Box::new(HookStage { - trigger: NativeTrigger::pre(Operation::Rollback), - }), - Box::new(MergeStage), - Box::new(CheckoutStage), - Box::new(SwapStage), - Box::new(HookStage { - trigger: NativeTrigger::post(Operation::Rollback), - }), - ]) -} - pub fn run(data: RollbackData) -> Result<(), (RollbackStateId, RollbackError)> { let mut context = Context::new(); context.put(TmpPath(data.tmp_path.to_owned())); @@ -85,3 +71,17 @@ pub fn run(data: RollbackData) -> Result<(), (RollbackStateId, RollbackError)> { result } + +fn assemble() -> SequentialOrchestrator { + SequentialOrchestrator::new(vec![ + Box::new(HookStage { + trigger: NativeTrigger::pre(Operation::Rollback), + }), + Box::new(MergeStage), + Box::new(CheckoutStage), + Box::new(SwapStage), + Box::new(HookStage { + trigger: NativeTrigger::post(Operation::Rollback), + }), + ]) +} diff --git a/lib/lib/src/mutated/uninstaller/mod.rs b/lib/lib/src/mutated/uninstaller/mod.rs index 9cd81445..be4f5f90 100644 --- a/lib/lib/src/mutated/uninstaller/mod.rs +++ b/lib/lib/src/mutated/uninstaller/mod.rs @@ -92,23 +92,6 @@ impl<'a> TryFrom<&'a CUninstallRequest> for UninstallData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![ - Box::new(HookStage { - trigger: NativeTrigger::pre(Operation::Uninstall), - }), - Box::new(PreparationStage), - Box::new(BuildStage), - Box::new(CommitStage), - Box::new(ConfigMergeStage), - Box::new(PrepareBootStage), - Box::new(BootOptionStage), - Box::new(HookStage { - trigger: NativeTrigger::post(Operation::Uninstall), - }), - ]) -} - pub fn run(data: UninstallData) -> Result<(), (UninstallStateId, UninstallError)> { let deploy = Deploy::new(DeployMode::ReadWrite).map_err(|error| (UninstallStateId::Setup, UninstallError::from(error)))?; @@ -144,3 +127,20 @@ pub fn run(data: UninstallData) -> Result<(), (UninstallStateId, UninstallError) result } + +fn assemble() -> SequentialOrchestrator { + SequentialOrchestrator::new(vec![ + Box::new(HookStage { + trigger: NativeTrigger::pre(Operation::Uninstall), + }), + Box::new(PreparationStage), + Box::new(BuildStage), + Box::new(CommitStage), + Box::new(ConfigMergeStage), + Box::new(PrepareBootStage), + Box::new(BootOptionStage), + Box::new(HookStage { + trigger: NativeTrigger::post(Operation::Uninstall), + }), + ]) +} diff --git a/lib/lib/src/mutated/update/mod.rs b/lib/lib/src/mutated/update/mod.rs index 7ad956e2..2f258acc 100644 --- a/lib/lib/src/mutated/update/mod.rs +++ b/lib/lib/src/mutated/update/mod.rs @@ -68,6 +68,21 @@ impl<'a> TryFrom<&'a CUpdateRequest> for UpdateData<'a> { } } +pub fn run(data: UpdateData) -> Result<(), (UpdateStateId, UpdateError)> { + let mut context = Context::new(); + context.put(data.packages); + context.put(TmpPath(data.tmp_path.to_owned())); + context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); + + let orchestrator = assemble(); + + let result = run_mutating!(orchestrator, context, data.cancel_token, UpdateStateId, UpdateError); + + data.cancel_token.reset(); + + result +} + fn assemble() -> SequentialOrchestrator { SequentialOrchestrator::new(vec![ Box::new(HookStage { @@ -83,18 +98,3 @@ fn assemble() -> SequentialOrchestrator { }), ]) } - -pub fn run(data: UpdateData) -> Result<(), (UpdateStateId, UpdateError)> { - let mut context = Context::new(); - context.put(data.packages); - context.put(TmpPath(data.tmp_path.to_owned())); - context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); - - let orchestrator = assemble(); - - let result = run_mutating!(orchestrator, context, data.cancel_token, UpdateStateId, UpdateError); - - data.cancel_token.reset(); - - result -} diff --git a/lib/lib/src/types/mod.rs b/lib/lib/src/types/mod.rs index 1fa06d19..4a643884 100644 --- a/lib/lib/src/types/mod.rs +++ b/lib/lib/src/types/mod.rs @@ -5,21 +5,24 @@ use std::mem::size_of; -use upac_abi::DiffKind; use upac_abi::decoder::CDependency; use upac_abi::error::ErrorKind; use upac_abi::package::{CPackageMeta, CUnpackedPackage, CVersion}; use upac_abi::response::{ - CCommitEntry, CDiffConfigFileEntry, CDiffPackageEntry, CDiffPrefixFileEntry, CHistoryEntry, CPrefixEntry, - CSearchFileEntry, + CCommitEntry, CDiffConfigFileEntry, CDiffPackageEntry, CDiffPrefixFileEntry, CDiffUntrackedFileEntry, + CHistoryEntry, CPrefixEntry, CSearchFileEntry, }; use upac_abi::types::{CBorrowed, COwned, CSlice, CVec}; +use upac_abi::{FileDiffKind, PackageDiffKind}; use upac_macro::{CTryToRust, RedbCodec, RustToC}; include!(concat!(env!("OUT_DIR"), "/layout.rs")); pub mod states; +#[cfg(test)] +mod tests; + macro_rules! as_str_method { ($name:ty) => { impl AsRef for $name { @@ -156,7 +159,7 @@ pub struct HistoryEntry { #[derive(Debug, Clone, RustToC)] pub struct DiffPrefixFileEntry { pub path: String, - pub kind: DiffKind, + pub kind: FileDiffKind, pub package_name: String, pub is_user: bool, } @@ -165,7 +168,7 @@ pub struct DiffPrefixFileEntry { #[derive(Debug, Clone, RustToC)] pub struct DiffConfigFileEntry { pub path: String, - pub kind: DiffKind, + pub kind: FileDiffKind, pub package_name: Option, } @@ -173,7 +176,7 @@ pub struct DiffConfigFileEntry { #[derive(Debug, Clone, RustToC)] pub struct DiffPackageEntry { pub name: String, - pub kind: DiffKind, + pub kind: PackageDiffKind, pub version: Version, // Only this package's own files. A changed file with no package to @@ -182,6 +185,17 @@ pub struct DiffPackageEntry { 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 path: String, + pub kind: FileDiffKind, +} + pub struct Targets(pub Vec); impl Targets { @@ -198,48 +212,19 @@ pub struct Search(pub String); as_str_method!(Search); -#[cfg(test)] -mod tests { - use super::*; - - fn sample_version() -> Version { - Version { - epoch: 1, - parts: vec![2, 5, 0], - pre: Some("rc1".to_owned()), - release: 3, - } - } - - #[test] - fn version_redb_round_trip_preserves_value() { - let original = sample_version(); - - let mut buf = Vec::new(); - Version::encode_into(&mut buf, &original); - - let mut offset = 0; - let restored = Version::decode_from(&buf, &mut offset); - - assert_eq!(restored, original); - assert_eq!(offset, buf.len()); - } - - #[test] - fn file_entry_redb_round_trip_preserves_value() { - let original = FileEntry { - path: "/usr/bin/up".to_owned(), - is_user: false, - }; +pub struct RequestedPrefixDigest(pub Option); - let mut buf = Vec::new(); - FileEntry::encode_into(&mut buf, &original); +pub struct RequestedPrefixDigestRange { + pub from: Option, + pub to: Option, +} - let mut offset = 0; - let restored = FileEntry::decode_from(&buf, &mut offset); +pub struct RequestedConfigDigestRange { + pub from: Option, + pub to: Option, +} - assert_eq!(restored.path, original.path); - assert_eq!(restored.is_user, original.is_user); - assert_eq!(offset, buf.len()); - } +pub struct DiffPackagesSnapshot { + pub from: Vec, + pub to: Vec, } diff --git a/lib/lib/src/types/tests.rs b/lib/lib/src/types/tests.rs new file mode 100644 index 00000000..9f8f9c32 --- /dev/null +++ b/lib/lib/src/types/tests.rs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2026 JustPav +// SPDX-FileCopyrightText: 2026 JustPav +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +use super::*; + +fn sample_version() -> Version { + Version { + epoch: 1, + parts: vec![2, 5, 0], + pre: Some("rc1".to_owned()), + release: 3, + } +} + +#[test] +fn version_redb_round_trip_preserves_value() { + let original = sample_version(); + + let mut buf = Vec::new(); + Version::encode_into(&mut buf, &original); + + let mut offset = 0; + let restored = Version::decode_from(&buf, &mut offset); + + assert_eq!(restored, original); + assert_eq!(offset, buf.len()); +} + +#[test] +fn file_entry_redb_round_trip_preserves_value() { + let original = FileEntry { + path: "/usr/bin/up".to_owned(), + is_user: false, + }; + + let mut buf = Vec::new(); + FileEntry::encode_into(&mut buf, &original); + + let mut offset = 0; + let restored = FileEntry::decode_from(&buf, &mut offset); + + assert_eq!(restored.path, original.path); + assert_eq!(restored.is_user, original.is_user); + assert_eq!(offset, buf.len()); +} diff --git a/lib/lib/src/unmutated/diff/comparing.rs b/lib/lib/src/unmutated/diff/comparing.rs index 1515e1c8..5d47741f 100644 --- a/lib/lib/src/unmutated/diff/comparing.rs +++ b/lib/lib/src/unmutated/diff/comparing.rs @@ -3,18 +3,126 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later +use std::collections::HashMap; + use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use upac_abi::{FileDiffKind, PackageDiffKind}; +use crate::database::attribution::FileAttribute; +use crate::errors::CommonError; use crate::orchestrator::Context; -use crate::orchestrator::stage::{RollbackGuard, Stage}; -use crate::unmutated::diff::DiffError; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage}; +use crate::types::{DiffPackageEntry, DiffPrefixFileEntry, DiffUntrackedFileEntry, PackageMeta, Version}; +use crate::unmutated::diff::{DiffError, DiffSnapshot}; + +type PackageIdentity = (String, String, Option); pub struct ComparingStage; impl Stage for ComparingStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, _progress: ProgressEventBuilder, + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, Box), DiffError> { - todo!() + let snapshot = context.take::().ok_or(CommonError::MissingResult)?; + + let mut packages = Self::diff_packages(snapshot.from_packages, snapshot.to_packages); + let mut unattached_files = Vec::new(); + + for (path, kind) in snapshot.changed_files { + let database = match kind { + FileDiffKind::Removed => &snapshot.from_database, + FileDiffKind::Added | FileDiffKind::Modified => &snapshot.to_database, + }; + + match database.attribute_file(&path)? { + Some(attribution) => { + let identity = Self::identity(&attribution.package_meta); + + let entry = packages.entry(identity).or_insert_with(|| DiffPackageEntry { + name: attribution.package_meta.name.clone(), + kind: PackageDiffKind::FilesChanged, + version: attribution.package_meta.version.clone(), + files: Vec::new(), + }); + + entry.files.push(DiffPrefixFileEntry { + path, + kind, + package_name: attribution.package_meta.name, + is_user: attribution.file_entry.is_user, + }); + } + None => unattached_files.push(DiffUntrackedFileEntry { path, kind }), + } + } + + context.put(packages.into_values().collect::>()); + context.put(unattached_files); + + Ok((progress, Box::new(NoRollback))) + } +} + +impl ComparingStage { + fn identity(meta: &PackageMeta) -> PackageIdentity { + (meta.name.clone(), meta.arch.clone(), meta.arch_sub.clone()) + } + + fn diff_packages(from: Vec, to: Vec) -> HashMap { + let from: HashMap<_, _> = from.into_iter().map(|meta| (Self::identity(&meta), meta)).collect(); + let mut to: HashMap<_, _> = to.into_iter().map(|meta| (Self::identity(&meta), meta)).collect(); + + let mut packages = HashMap::new(); + + for (identity, from_meta) in from { + match to.remove(&identity) { + Some(to_meta) if to_meta.sha256 != from_meta.sha256 => { + Self::insert( + &mut packages, + identity, + to_meta.name, + PackageDiffKind::Modified, + to_meta.version, + ); + } + Some(_) => {} + None => { + Self::insert( + &mut packages, + identity, + from_meta.name, + PackageDiffKind::Removed, + from_meta.version, + ); + } + } + } + + for (identity, to_meta) in to { + Self::insert( + &mut packages, + identity, + to_meta.name, + PackageDiffKind::Added, + to_meta.version, + ); + } + + packages + } + + fn insert( + packages: &mut HashMap, identity: PackageIdentity, name: String, + kind: PackageDiffKind, version: Version, + ) { + packages.insert( + identity, + DiffPackageEntry { + name, + kind, + version, + files: Vec::new(), + }, + ); } } diff --git a/lib/lib/src/unmutated/diff/error.rs b/lib/lib/src/unmutated/diff/error.rs index e8ac8567..bd598091 100644 --- a/lib/lib/src/unmutated/diff/error.rs +++ b/lib/lib/src/unmutated/diff/error.rs @@ -5,9 +5,12 @@ use upac_abi::error::ErrorKind; +use crate::composefs::error::RepoError; use crate::database::error::DatabaseError; use crate::deploy::error::SysrootError; -use crate::errors::{CommonError, common_error_from, database_error_from, lock_error_from, sysroot_error_from}; +use crate::errors::{ + CommonError, common_error_from, database_error_from, lock_error_from, repo_error_from, sysroot_error_from, +}; use crate::lock::LockError; #[derive(Debug, Clone, PartialEq, Eq)] @@ -19,6 +22,8 @@ common_error_from!(DiffError); database_error_from!(DiffError); +repo_error_from!(DiffError); + sysroot_error_from!(DiffError); lock_error_from!(DiffError); diff --git a/lib/lib/src/unmutated/diff/mod.rs b/lib/lib/src/unmutated/diff/mod.rs index d4d603ad..6b7a065f 100644 --- a/lib/lib/src/unmutated/diff/mod.rs +++ b/lib/lib/src/unmutated/diff/mod.rs @@ -5,6 +5,7 @@ use std::os::raw::c_void; +use upac_abi::FileDiffKind; use upac_abi::error::ErrorKind; use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; use upac_abi::request::CDiffRequest; @@ -14,17 +15,26 @@ pub use self::error::DiffError; use self::comparing::ComparingStage; use self::preparing::PreparingStage; +use crate::database::MemoryDatabase; use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; use crate::types::states::DiffStateId; -use crate::types::{DiffPackageEntry, DiffPrefixFileEntry}; +use crate::types::{DiffPackageEntry, DiffUntrackedFileEntry, PackageMeta, RequestedPrefixDigestRange}; mod comparing; mod error; mod preparing; +struct DiffSnapshot { + from_packages: Vec, + to_packages: Vec, + changed_files: Vec<(String, FileDiffKind)>, + from_database: MemoryDatabase, + to_database: MemoryDatabase, +} + pub struct DiffData<'a> { - pub from_commit_hash: Option<&'a str>, - pub to_commit_hash: Option<&'a str>, + pub from_prefix_digest: Option<&'a str>, + pub to_prefix_digest: Option<&'a str>, pub hook_message: Option, pub hook_message_context: *mut c_void, @@ -41,8 +51,8 @@ impl<'a> TryFrom<&'a CDiffRequest> for DiffData<'a> { let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; Ok(DiffData { - from_commit_hash: (&request.from_commit_hash).try_into()?, - to_commit_hash: (&request.to_commit_hash).try_into()?, + from_prefix_digest: (&request.from_prefix_digest).try_into()?, + to_prefix_digest: (&request.to_prefix_digest).try_into()?, hook_message: request.base.on_hook, hook_message_context: request.base.hook_ctx, @@ -52,15 +62,15 @@ impl<'a> TryFrom<&'a CDiffRequest> for DiffData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]) -} - -pub fn run(data: DiffData) -> Result<(Vec, Vec), (DiffStateId, DiffError)> { +pub fn run(data: DiffData) -> Result<(Vec, Vec), (DiffStateId, DiffError)> { 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(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); - let orchestrator = assemble(); + let orchestrator = SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]); run_unmutated!( orchestrator, @@ -69,6 +79,6 @@ pub fn run(data: DiffData) -> Result<(Vec, Vec, - Vec + Vec ) } diff --git a/lib/lib/src/unmutated/diff/preparing.rs b/lib/lib/src/unmutated/diff/preparing.rs index 9729b8eb..1851f519 100644 --- a/lib/lib/src/unmutated/diff/preparing.rs +++ b/lib/lib/src/unmutated/diff/preparing.rs @@ -5,16 +5,62 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use crate::composefs::diff::TreeDiff; +use crate::composefs::file::FileHandle; +use crate::database::meta::MetaStore; +use crate::database::{InMemory, MemoryDatabase}; +use crate::deploy::digest::current_prefix_digest; +use crate::deploy::{Deploy, DeployMode}; +use crate::errors::CommonError; use crate::orchestrator::Context; -use crate::orchestrator::stage::{RollbackGuard, Stage}; -use crate::unmutated::diff::DiffError; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage}; +use crate::types::RequestedPrefixDigestRange; +use crate::types::database::DATABASE_PATH; +use crate::unmutated::diff::{DiffError, DiffSnapshot}; pub struct PreparingStage; impl Stage for PreparingStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, _progress: ProgressEventBuilder, + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, Box), DiffError> { - todo!() + let requested = context + .get::() + .ok_or(CommonError::MissingResult)?; + + let from_prefix_digest = match &requested.from { + Some(prefix_digest) => prefix_digest.clone(), + None => current_prefix_digest()?, + }; + let to_prefix_digest = match &requested.to { + Some(prefix_digest) => prefix_digest.clone(), + None => current_prefix_digest()?, + }; + + let deploy = Deploy::new(DeployMode::ReadOnly)?; + let repository = deploy.open_repository()?; + + let from_tree = deploy.open_tree(&from_prefix_digest)?; + let to_tree = deploy.open_tree(&to_prefix_digest)?; + + let changed_files = TreeDiff::run(&from_tree, &to_tree); + + let from_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &from_tree)?; + let from_database = MemoryDatabase::open_in_memory(from_bytes)?; + let from_packages = from_database.list_packages_metas()?; + + let to_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &to_tree)?; + let to_database = MemoryDatabase::open_in_memory(to_bytes)?; + let to_packages = to_database.list_packages_metas()?; + + context.put(DiffSnapshot { + from_packages, + to_packages, + changed_files, + from_database, + to_database, + }); + + Ok((progress, Box::new(NoRollback))) } } diff --git a/lib/lib/src/unmutated/diff_files_config/comparing.rs b/lib/lib/src/unmutated/diff_files_config/comparing.rs index 8a321e66..10788ef7 100644 --- a/lib/lib/src/unmutated/diff_files_config/comparing.rs +++ b/lib/lib/src/unmutated/diff_files_config/comparing.rs @@ -3,18 +3,47 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later +use upac_abi::FileDiffKind; use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use crate::database::attribution::FileAttribute; +use crate::errors::CommonError; use crate::orchestrator::Context; -use crate::orchestrator::stage::{RollbackGuard, Stage}; -use crate::unmutated::diff_files_config::DiffFilesConfigError; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage}; +use crate::types::DiffConfigFileEntry; +use crate::unmutated::diff_files_config::{DiffFilesConfigError, DiffFilesConfigSnapshot}; pub struct ComparingStage; impl Stage for ComparingStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, _progress: ProgressEventBuilder, + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, Box), DiffFilesConfigError> { - todo!() + let snapshot = context + .take::() + .ok_or(CommonError::MissingResult)?; + + let mut entries = Vec::new(); + + for (path, kind) in snapshot.changed { + let database = match kind { + FileDiffKind::Removed => &snapshot.from_database, + FileDiffKind::Added | FileDiffKind::Modified => &snapshot.to_database, + }; + + let package_name = database + .attribute_file(&path)? + .map(|attribution| attribution.package_meta.name); + + entries.push(DiffConfigFileEntry { + path, + kind, + package_name, + }); + } + + context.put(entries); + + Ok((progress, Box::new(NoRollback))) } } diff --git a/lib/lib/src/unmutated/diff_files_config/error.rs b/lib/lib/src/unmutated/diff_files_config/error.rs index 274dd1af..5a738eb8 100644 --- a/lib/lib/src/unmutated/diff_files_config/error.rs +++ b/lib/lib/src/unmutated/diff_files_config/error.rs @@ -5,20 +5,29 @@ use upac_abi::error::ErrorKind; -use crate::database::error::DatabaseError; +use crate::composefs::error::RepoError; +use crate::database::error::{DatabaseError, DeployRecordError}; use crate::deploy::error::SysrootError; -use crate::errors::{CommonError, common_error_from, database_error_from, lock_error_from, sysroot_error_from}; +use crate::errors::{ + CommonError, common_error_from, database_error_from, deploy_record_error_from, lock_error_from, repo_error_from, + sysroot_error_from, +}; use crate::lock::LockError; #[derive(Debug, Clone, PartialEq, Eq)] pub enum DiffFilesConfigError { Common(CommonError), + ConfigDigestNotFound(String), } common_error_from!(DiffFilesConfigError); database_error_from!(DiffFilesConfigError); +deploy_record_error_from!(DiffFilesConfigError); + +repo_error_from!(DiffFilesConfigError); + sysroot_error_from!(DiffFilesConfigError); lock_error_from!(DiffFilesConfigError); @@ -27,6 +36,7 @@ impl From for ErrorKind { fn from(error: DiffFilesConfigError) -> Self { match error { DiffFilesConfigError::Common(common_error) => common_error.into(), + DiffFilesConfigError::ConfigDigestNotFound(_) => ErrorKind::NotFound, } } } diff --git a/lib/lib/src/unmutated/diff_files_config/mod.rs b/lib/lib/src/unmutated/diff_files_config/mod.rs index ac68afd6..34ff6fb0 100644 --- a/lib/lib/src/unmutated/diff_files_config/mod.rs +++ b/lib/lib/src/unmutated/diff_files_config/mod.rs @@ -5,6 +5,7 @@ use std::os::raw::c_void; +use upac_abi::FileDiffKind; use upac_abi::error::ErrorKind; use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; use upac_abi::request::CDiffFilesConfigRequest; @@ -14,17 +15,24 @@ pub use self::error::DiffFilesConfigError; use self::comparing::ComparingStage; use self::preparing::PreparingStage; +use crate::database::MemoryDatabase; use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use crate::types::DiffConfigFileEntry; use crate::types::states::DiffFilesConfigStateId; +use crate::types::{DiffConfigFileEntry, RequestedConfigDigestRange}; mod comparing; mod error; mod preparing; +struct DiffFilesConfigSnapshot { + changed: Vec<(String, FileDiffKind)>, + from_database: MemoryDatabase, + to_database: MemoryDatabase, +} + pub struct DiffFilesConfigData<'a> { - pub from_commit_hash: Option<&'a str>, - pub to_commit_hash: Option<&'a str>, + pub from_config_digest: Option<&'a str>, + pub to_config_digest: Option<&'a str>, pub hook_message: Option, pub hook_message_context: *mut c_void, @@ -41,8 +49,8 @@ impl<'a> TryFrom<&'a CDiffFilesConfigRequest> for DiffFilesConfigData<'a> { let cancel_token = unsafe { request.base.cancel_token.as_ref() }.ok_or(ErrorKind::InvalidEntry)?; Ok(DiffFilesConfigData { - from_commit_hash: (&request.from_commit_hash).try_into()?, - to_commit_hash: (&request.to_commit_hash).try_into()?, + from_config_digest: (&request.from_config_digest).try_into()?, + to_config_digest: (&request.to_config_digest).try_into()?, hook_message: request.base.on_hook, hook_message_context: request.base.hook_ctx, @@ -52,17 +60,17 @@ impl<'a> TryFrom<&'a CDiffFilesConfigRequest> for DiffFilesConfigData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]) -} - pub fn run( data: DiffFilesConfigData, ) -> Result<(Vec,), (DiffFilesConfigStateId, DiffFilesConfigError)> { let mut context = Context::new(); + 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 = assemble(); + let orchestrator = SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]); run_unmutated!( orchestrator, diff --git a/lib/lib/src/unmutated/diff_files_config/preparing.rs b/lib/lib/src/unmutated/diff_files_config/preparing.rs index 9309b665..feca8559 100644 --- a/lib/lib/src/unmutated/diff_files_config/preparing.rs +++ b/lib/lib/src/unmutated/diff_files_config/preparing.rs @@ -5,16 +5,94 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use crate::composefs::diff::TreeDiff; +use crate::composefs::file::FileHandle; +use crate::database::error::DeployRecordError; +use crate::database::record::DeployRecord; +use crate::database::{InMemory, MemoryDatabase}; +use crate::deploy::digest::current_prefix_digest; +use crate::deploy::{Deploy, DeployMode}; +use crate::errors::CommonError; use crate::orchestrator::Context; -use crate::orchestrator::stage::{RollbackGuard, Stage}; -use crate::unmutated::diff_files_config::DiffFilesConfigError; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage}; +use crate::types::RequestedConfigDigestRange; +use crate::types::database::DATABASE_PATH; +use crate::unmutated::diff_files_config::{DiffFilesConfigError, DiffFilesConfigSnapshot}; pub struct PreparingStage; impl Stage for PreparingStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, _progress: ProgressEventBuilder, + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, Box), DiffFilesConfigError> { - todo!() + let requested = context + .get::() + .ok_or(CommonError::MissingResult)?; + + let deploy = Deploy::new(DeployMode::ReadOnly)?; + + let (from_config_digest, from_prefix_digest) = Self::resolve(&deploy, requested.from.as_ref())?; + let (to_config_digest, to_prefix_digest) = Self::resolve(&deploy, requested.to.as_ref())?; + + let repository = deploy.open_repository()?; + + let from_config_tree = deploy.open_tree(&from_config_digest)?; + let to_config_tree = deploy.open_tree(&to_config_digest)?; + + let changed = TreeDiff::run(&from_config_tree, &to_config_tree); + + let from_prefix_tree = deploy.open_tree(&from_prefix_digest)?; + let from_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &from_prefix_tree)?; + let from_database = MemoryDatabase::open_in_memory(from_bytes)?; + + let to_prefix_tree = deploy.open_tree(&to_prefix_digest)?; + let to_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &to_prefix_tree)?; + let to_database = MemoryDatabase::open_in_memory(to_bytes)?; + + context.put(DiffFilesConfigSnapshot { + changed, + from_database, + to_database, + }); + + Ok((progress, Box::new(NoRollback))) + } +} + +impl PreparingStage { + // Resolves a requested (possibly absent) config_digest to the config_digest + // itself plus the prefix_digest of the deploy it belongs to — needed because + // the /usr package database used for file attribution lives under the prefix, + // not under the standalone /etc image the config_digest names. + fn resolve(deploy: &Deploy, requested: Option<&String>) -> Result<(String, String), DiffFilesConfigError> { + match requested { + Some(config_digest) => { + for prefix_digest in deploy.deploys()? { + let record = match DeployRecord::read(&deploy.deploy(&prefix_digest)) { + Ok(record) => record, + Err(DeployRecordError::NotFound) => continue, + Err(error) => return Err(error.into()), + }; + + let owns_config_digest = record.working_etc == *config_digest + || record + .config_history + .iter() + .any(|entry| entry.config_digest == *config_digest); + + if owns_config_digest { + return Ok((config_digest.clone(), prefix_digest)); + } + } + + Err(DiffFilesConfigError::ConfigDigestNotFound(config_digest.clone())) + } + None => { + let prefix_digest = current_prefix_digest()?; + let record = DeployRecord::read(&deploy.deploy(&prefix_digest))?; + + Ok((record.working_etc, prefix_digest)) + } + } } } diff --git a/lib/lib/src/unmutated/diff_files_prefix/comparing.rs b/lib/lib/src/unmutated/diff_files_prefix/comparing.rs index 00792211..880523a6 100644 --- a/lib/lib/src/unmutated/diff_files_prefix/comparing.rs +++ b/lib/lib/src/unmutated/diff_files_prefix/comparing.rs @@ -3,18 +3,46 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later +use upac_abi::FileDiffKind; use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use crate::database::attribution::FileAttribute; +use crate::errors::CommonError; use crate::orchestrator::Context; -use crate::orchestrator::stage::{RollbackGuard, Stage}; -use crate::unmutated::diff_files_prefix::DiffFilesPrefixError; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage}; +use crate::types::DiffPrefixFileEntry; +use crate::unmutated::diff_files_prefix::{DiffFilesPrefixError, DiffFilesPrefixSnapshot}; pub struct ComparingStage; impl Stage for ComparingStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, _progress: ProgressEventBuilder, + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, Box), DiffFilesPrefixError> { - todo!() + let snapshot = context + .take::() + .ok_or(CommonError::MissingResult)?; + + let mut entries = Vec::new(); + + for (path, kind) in snapshot.changed { + let database = match kind { + FileDiffKind::Removed => &snapshot.from_database, + FileDiffKind::Added | FileDiffKind::Modified => &snapshot.to_database, + }; + + if let Some(attribution) = database.attribute_file(&path)? { + entries.push(DiffPrefixFileEntry { + path, + kind, + package_name: attribution.package_meta.name, + is_user: attribution.file_entry.is_user, + }); + } + } + + context.put(entries); + + Ok((progress, Box::new(NoRollback))) } } diff --git a/lib/lib/src/unmutated/diff_files_prefix/error.rs b/lib/lib/src/unmutated/diff_files_prefix/error.rs index 9f645d10..2fb834ae 100644 --- a/lib/lib/src/unmutated/diff_files_prefix/error.rs +++ b/lib/lib/src/unmutated/diff_files_prefix/error.rs @@ -5,9 +5,12 @@ use upac_abi::error::ErrorKind; +use crate::composefs::error::RepoError; use crate::database::error::DatabaseError; use crate::deploy::error::SysrootError; -use crate::errors::{CommonError, common_error_from, database_error_from, lock_error_from, sysroot_error_from}; +use crate::errors::{ + CommonError, common_error_from, database_error_from, lock_error_from, repo_error_from, sysroot_error_from, +}; use crate::lock::LockError; #[derive(Debug, Clone, PartialEq, Eq)] @@ -19,6 +22,8 @@ common_error_from!(DiffFilesPrefixError); database_error_from!(DiffFilesPrefixError); +repo_error_from!(DiffFilesPrefixError); + sysroot_error_from!(DiffFilesPrefixError); lock_error_from!(DiffFilesPrefixError); diff --git a/lib/lib/src/unmutated/diff_files_prefix/mod.rs b/lib/lib/src/unmutated/diff_files_prefix/mod.rs index 9ea2a284..ede5fc12 100644 --- a/lib/lib/src/unmutated/diff_files_prefix/mod.rs +++ b/lib/lib/src/unmutated/diff_files_prefix/mod.rs @@ -5,6 +5,7 @@ use std::os::raw::c_void; +use upac_abi::FileDiffKind; use upac_abi::error::ErrorKind; use upac_abi::hook::{CancelToken, HookMessageFn, Message, MessageHook}; use upac_abi::request::CDiffFilesPrefixRequest; @@ -14,14 +15,21 @@ pub use self::error::DiffFilesPrefixError; use self::comparing::ComparingStage; use self::preparing::PreparingStage; +use crate::database::MemoryDatabase; use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use crate::types::DiffPrefixFileEntry; use crate::types::states::DiffFilesPrefixStateId; +use crate::types::{DiffPrefixFileEntry, RequestedPrefixDigestRange}; mod comparing; mod error; mod preparing; +struct DiffFilesPrefixSnapshot { + changed: Vec<(String, FileDiffKind)>, + from_database: MemoryDatabase, + to_database: MemoryDatabase, +} + pub struct DiffFilesPrefixData<'a> { pub from_prefix_digest: Option<&'a str>, pub to_prefix_digest: Option<&'a str>, @@ -52,17 +60,17 @@ impl<'a> TryFrom<&'a CDiffFilesPrefixRequest> for DiffFilesPrefixData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]) -} - pub fn run( data: DiffFilesPrefixData, ) -> Result<(Vec,), (DiffFilesPrefixStateId, DiffFilesPrefixError)> { 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(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); - let orchestrator = assemble(); + let orchestrator = SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]); run_unmutated!( orchestrator, diff --git a/lib/lib/src/unmutated/diff_files_prefix/preparing.rs b/lib/lib/src/unmutated/diff_files_prefix/preparing.rs index 39c31d63..922636ff 100644 --- a/lib/lib/src/unmutated/diff_files_prefix/preparing.rs +++ b/lib/lib/src/unmutated/diff_files_prefix/preparing.rs @@ -5,16 +5,58 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use crate::composefs::diff::TreeDiff; +use crate::composefs::file::FileHandle; +use crate::database::InMemory; +use crate::database::MemoryDatabase; +use crate::deploy::digest::current_prefix_digest; +use crate::deploy::{Deploy, DeployMode}; +use crate::errors::CommonError; use crate::orchestrator::Context; -use crate::orchestrator::stage::{RollbackGuard, Stage}; -use crate::unmutated::diff_files_prefix::DiffFilesPrefixError; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage}; +use crate::types::RequestedPrefixDigestRange; +use crate::types::database::DATABASE_PATH; +use crate::unmutated::diff_files_prefix::{DiffFilesPrefixError, DiffFilesPrefixSnapshot}; pub struct PreparingStage; impl Stage for PreparingStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, _progress: ProgressEventBuilder, + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, Box), DiffFilesPrefixError> { - todo!() + let requested = context + .get::() + .ok_or(CommonError::MissingResult)?; + + let from_prefix_digest = match &requested.from { + Some(prefix_digest) => prefix_digest.clone(), + None => current_prefix_digest()?, + }; + let to_prefix_digest = match &requested.to { + Some(prefix_digest) => prefix_digest.clone(), + None => current_prefix_digest()?, + }; + + let deploy = Deploy::new(DeployMode::ReadOnly)?; + let repository = deploy.open_repository()?; + + let from_tree = deploy.open_tree(&from_prefix_digest)?; + let to_tree = deploy.open_tree(&to_prefix_digest)?; + + let changed = TreeDiff::run(&from_tree, &to_tree); + + let from_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &from_tree)?; + let from_database = MemoryDatabase::open_in_memory(from_bytes)?; + + let to_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &to_tree)?; + let to_database = MemoryDatabase::open_in_memory(to_bytes)?; + + context.put(DiffFilesPrefixSnapshot { + changed, + from_database, + to_database, + }); + + Ok((progress, Box::new(NoRollback))) } } diff --git a/lib/lib/src/unmutated/diff_packages/comparing.rs b/lib/lib/src/unmutated/diff_packages/comparing.rs index c2e30781..afd521d4 100644 --- a/lib/lib/src/unmutated/diff_packages/comparing.rs +++ b/lib/lib/src/unmutated/diff_packages/comparing.rs @@ -3,18 +3,69 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later +use std::collections::HashMap; + +use upac_abi::PackageDiffKind; use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use crate::errors::CommonError; use crate::orchestrator::Context; -use crate::orchestrator::stage::{RollbackGuard, Stage}; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage}; +use crate::types::{DiffPackageEntry, DiffPackagesSnapshot}; use crate::unmutated::diff_packages::DiffPackagesError; pub struct ComparingStage; impl Stage for ComparingStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, _progress: ProgressEventBuilder, + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, Box), DiffPackagesError> { - todo!() + let snapshot = context + .take::() + .ok_or(CommonError::MissingResult)?; + + let from: HashMap<_, _> = snapshot + .from + .into_iter() + .map(|meta| ((meta.name.clone(), meta.arch.clone(), meta.arch_sub.clone()), meta)) + .collect(); + let mut to: HashMap<_, _> = snapshot + .to + .into_iter() + .map(|meta| ((meta.name.clone(), meta.arch.clone(), meta.arch_sub.clone()), meta)) + .collect(); + + let mut entries = Vec::new(); + + for (identity, from_meta) in from { + match to.remove(&identity) { + Some(to_meta) if to_meta.sha256 != from_meta.sha256 => entries.push(DiffPackageEntry { + name: to_meta.name, + kind: PackageDiffKind::Modified, + version: to_meta.version, + files: Vec::new(), + }), + Some(_) => {} + None => entries.push(DiffPackageEntry { + name: from_meta.name, + kind: PackageDiffKind::Removed, + version: from_meta.version, + files: Vec::new(), + }), + } + } + + for (_identity, to_meta) in to { + entries.push(DiffPackageEntry { + name: to_meta.name, + kind: PackageDiffKind::Added, + version: to_meta.version, + files: Vec::new(), + }); + } + + context.put(entries); + + Ok((progress, Box::new(NoRollback))) } } diff --git a/lib/lib/src/unmutated/diff_packages/error.rs b/lib/lib/src/unmutated/diff_packages/error.rs index 8fe6b9a5..2b8782a6 100644 --- a/lib/lib/src/unmutated/diff_packages/error.rs +++ b/lib/lib/src/unmutated/diff_packages/error.rs @@ -5,9 +5,12 @@ use upac_abi::error::ErrorKind; +use crate::composefs::error::RepoError; use crate::database::error::DatabaseError; use crate::deploy::error::SysrootError; -use crate::errors::{CommonError, common_error_from, database_error_from, lock_error_from, sysroot_error_from}; +use crate::errors::{ + CommonError, common_error_from, database_error_from, lock_error_from, repo_error_from, sysroot_error_from, +}; use crate::lock::LockError; #[derive(Debug, Clone, PartialEq, Eq)] @@ -19,6 +22,8 @@ common_error_from!(DiffPackagesError); database_error_from!(DiffPackagesError); +repo_error_from!(DiffPackagesError); + sysroot_error_from!(DiffPackagesError); lock_error_from!(DiffPackagesError); diff --git a/lib/lib/src/unmutated/diff_packages/mod.rs b/lib/lib/src/unmutated/diff_packages/mod.rs index bd3f93c8..6471d263 100644 --- a/lib/lib/src/unmutated/diff_packages/mod.rs +++ b/lib/lib/src/unmutated/diff_packages/mod.rs @@ -15,8 +15,8 @@ use self::comparing::ComparingStage; use self::preparing::PreparingStage; use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use crate::types::DiffPackageEntry; use crate::types::states::DiffPackagesStateId; +use crate::types::{DiffPackageEntry, RequestedPrefixDigestRange}; mod comparing; mod error; @@ -52,15 +52,15 @@ impl<'a> TryFrom<&'a CDiffPackagesRequest> for DiffPackagesData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]) -} - pub fn run(data: DiffPackagesData) -> Result<(Vec,), (DiffPackagesStateId, DiffPackagesError)> { 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(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); - let orchestrator = assemble(); + let orchestrator = SequentialOrchestrator::new(vec![Box::new(PreparingStage), Box::new(ComparingStage)]); run_unmutated!( orchestrator, diff --git a/lib/lib/src/unmutated/diff_packages/preparing.rs b/lib/lib/src/unmutated/diff_packages/preparing.rs index 5d108d54..b9b5eb66 100644 --- a/lib/lib/src/unmutated/diff_packages/preparing.rs +++ b/lib/lib/src/unmutated/diff_packages/preparing.rs @@ -5,16 +5,50 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use crate::composefs::file::FileHandle; +use crate::database::meta::MetaStore; +use crate::database::{InMemory, MemoryDatabase}; +use crate::deploy::digest::current_prefix_digest; +use crate::deploy::{Deploy, DeployMode}; +use crate::errors::CommonError; use crate::orchestrator::Context; -use crate::orchestrator::stage::{RollbackGuard, Stage}; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage}; +use crate::types::database::DATABASE_PATH; +use crate::types::{DiffPackagesSnapshot, RequestedPrefixDigestRange}; use crate::unmutated::diff_packages::DiffPackagesError; pub struct PreparingStage; impl Stage for PreparingStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, _progress: ProgressEventBuilder, + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, Box), DiffPackagesError> { - todo!() + let requested = context + .get::() + .ok_or(CommonError::MissingResult)?; + + let from_prefix_digest = match &requested.from { + Some(prefix_digest) => prefix_digest.clone(), + None => current_prefix_digest()?, + }; + let to_prefix_digest = match &requested.to { + Some(prefix_digest) => prefix_digest.clone(), + None => current_prefix_digest()?, + }; + + let deploy = Deploy::new(DeployMode::ReadOnly)?; + let repository = deploy.open_repository()?; + + let from_tree = deploy.open_tree(&from_prefix_digest)?; + let from_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &from_tree)?; + let from = MemoryDatabase::open_in_memory(from_bytes)?.list_packages_metas()?; + + let to_tree = deploy.open_tree(&to_prefix_digest)?; + let to_bytes = FileHandle::new(DATABASE_PATH).read_file(&repository, &to_tree)?; + let to = MemoryDatabase::open_in_memory(to_bytes)?.list_packages_metas()?; + + context.put(DiffPackagesSnapshot { from, to }); + + Ok((progress, Box::new(NoRollback))) } } diff --git a/lib/lib/src/unmutated/list_commit/error.rs b/lib/lib/src/unmutated/list_commit/error.rs index 5b8687f6..667de2f6 100644 --- a/lib/lib/src/unmutated/list_commit/error.rs +++ b/lib/lib/src/unmutated/list_commit/error.rs @@ -5,9 +5,11 @@ use upac_abi::error::ErrorKind; -use crate::database::error::DatabaseError; +use crate::database::error::{DatabaseError, DeployRecordError}; use crate::deploy::error::SysrootError; -use crate::errors::{CommonError, common_error_from, database_error_from, lock_error_from, sysroot_error_from}; +use crate::errors::{ + CommonError, common_error_from, database_error_from, deploy_record_error_from, lock_error_from, sysroot_error_from, +}; use crate::lock::LockError; #[derive(Debug, Clone, PartialEq, Eq)] @@ -19,6 +21,8 @@ common_error_from!(ListCommitError); database_error_from!(ListCommitError); +deploy_record_error_from!(ListCommitError); + sysroot_error_from!(ListCommitError); lock_error_from!(ListCommitError); diff --git a/lib/lib/src/unmutated/list_commit/fetching.rs b/lib/lib/src/unmutated/list_commit/fetching.rs index 167a4e3d..0e1448b7 100644 --- a/lib/lib/src/unmutated/list_commit/fetching.rs +++ b/lib/lib/src/unmutated/list_commit/fetching.rs @@ -5,16 +5,45 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use crate::database::record::DeployRecord; +use crate::deploy::digest::current_prefix_digest; +use crate::deploy::{Deploy, DeployMode}; +use crate::errors::CommonError; use crate::orchestrator::Context; -use crate::orchestrator::stage::{RollbackGuard, Stage}; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage}; +use crate::types::{CommitEntry, RequestedPrefixDigest}; use crate::unmutated::list_commit::ListCommitError; pub struct FetchingStage; impl Stage for FetchingStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, _progress: ProgressEventBuilder, + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, Box), ListCommitError> { - todo!() + let requested = context + .get::() + .ok_or(CommonError::MissingResult)?; + + let prefix_digest = match &requested.0 { + Some(prefix_digest) => prefix_digest.clone(), + None => current_prefix_digest()?, + }; + + let deploy = Deploy::new(DeployMode::ReadOnly)?; + let record = DeployRecord::read(&deploy.deploy(&prefix_digest))?; + + let entries: Vec = record + .config_history + .into_iter() + .map(|entry| CommitEntry { + config_digest: entry.config_digest, + subject: entry.subject, + message: entry.message, + }) + .collect(); + + context.put(entries); + + Ok((progress, Box::new(NoRollback))) } } diff --git a/lib/lib/src/unmutated/list_commit/mod.rs b/lib/lib/src/unmutated/list_commit/mod.rs index c0ac128d..66e37af4 100644 --- a/lib/lib/src/unmutated/list_commit/mod.rs +++ b/lib/lib/src/unmutated/list_commit/mod.rs @@ -14,8 +14,8 @@ pub use self::error::ListCommitError; use self::fetching::FetchingStage; use crate::orchestrator::{Context, Orchestrator, SequentialOrchestrator, run_unmutated}; -use crate::types::CommitEntry; use crate::types::states::ListCommitStateId; +use crate::types::{CommitEntry, RequestedPrefixDigest}; mod error; mod fetching; @@ -48,15 +48,12 @@ impl<'a> TryFrom<&'a CListCommitRequest> for ListCommitData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![Box::new(FetchingStage)]) -} - pub fn run(data: ListCommitData) -> Result<(Vec,), (ListCommitStateId, ListCommitError)> { 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 = assemble(); + let orchestrator = SequentialOrchestrator::new(vec![Box::new(FetchingStage)]); run_unmutated!( orchestrator, diff --git a/lib/lib/src/unmutated/list_history/error.rs b/lib/lib/src/unmutated/list_history/error.rs index d3827912..276cff2d 100644 --- a/lib/lib/src/unmutated/list_history/error.rs +++ b/lib/lib/src/unmutated/list_history/error.rs @@ -5,9 +5,11 @@ use upac_abi::error::ErrorKind; -use crate::database::error::DatabaseError; +use crate::database::error::{DatabaseError, DeployRecordError}; use crate::deploy::error::SysrootError; -use crate::errors::{CommonError, common_error_from, database_error_from, lock_error_from, sysroot_error_from}; +use crate::errors::{ + CommonError, common_error_from, database_error_from, deploy_record_error_from, lock_error_from, sysroot_error_from, +}; use crate::lock::LockError; #[derive(Debug, Clone, PartialEq, Eq)] @@ -19,6 +21,8 @@ common_error_from!(ListHistoryError); database_error_from!(ListHistoryError); +deploy_record_error_from!(ListHistoryError); + sysroot_error_from!(ListHistoryError); lock_error_from!(ListHistoryError); diff --git a/lib/lib/src/unmutated/list_history/fetching.rs b/lib/lib/src/unmutated/list_history/fetching.rs index bf1d37b8..ae1cfd23 100644 --- a/lib/lib/src/unmutated/list_history/fetching.rs +++ b/lib/lib/src/unmutated/list_history/fetching.rs @@ -5,16 +5,53 @@ use upac_abi::hook::{CancelToken, ProgressEventBuilder}; +use crate::database::error::DeployRecordError; +use crate::database::record::DeployRecord; +use crate::deploy::{Deploy, DeployMode}; use crate::orchestrator::Context; -use crate::orchestrator::stage::{RollbackGuard, Stage}; +use crate::orchestrator::stage::{NoRollback, RollbackGuard, Stage}; +use crate::types::{CommitEntry, HistoryEntry}; use crate::unmutated::list_history::ListHistoryError; pub struct FetchingStage; impl Stage for FetchingStage { fn run( - &self, _context: &mut Context, _cancel: &CancelToken, _progress: ProgressEventBuilder, + &self, context: &mut Context, _cancel: &CancelToken, progress: ProgressEventBuilder, ) -> Result<(ProgressEventBuilder, Box), ListHistoryError> { - todo!() + let deploy = Deploy::new(DeployMode::ReadOnly)?; + + let mut entries = Vec::new(); + + for prefix_digest in deploy.deploys()? { + let record = match DeployRecord::read(&deploy.deploy(&prefix_digest)) { + Ok(record) => record, + Err(DeployRecordError::NotFound) => continue, + Err(error) => return Err(error.into()), + }; + + let config_history = record + .config_history + .into_iter() + .map(|entry| CommitEntry { + config_digest: entry.config_digest, + subject: entry.subject, + message: entry.message, + }) + .collect(); + + entries.push(HistoryEntry { + prefix_digest: record.prefix_digest, + subject: record.subject, + message: record.message, + timestamp: record.timestamp, + working_config: Some(record.working_etc), + config_history, + }); + } + + context.put(entries); + + Ok((progress, Box::new(NoRollback))) } } diff --git a/lib/lib/src/unmutated/list_history/mod.rs b/lib/lib/src/unmutated/list_history/mod.rs index a390d542..bdf3d7ec 100644 --- a/lib/lib/src/unmutated/list_history/mod.rs +++ b/lib/lib/src/unmutated/list_history/mod.rs @@ -44,15 +44,11 @@ impl<'a> TryFrom<&'a CListHistoryRequest> for ListHistoryData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![Box::new(FetchingStage)]) -} - pub fn run(data: ListHistoryData) -> Result<(Vec,), (ListHistoryStateId, ListHistoryError)> { let mut context = Context::new(); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); - let orchestrator = assemble(); + let orchestrator = SequentialOrchestrator::new(vec![Box::new(FetchingStage)]); run_unmutated!( orchestrator, diff --git a/lib/lib/src/unmutated/list_packages/mod.rs b/lib/lib/src/unmutated/list_packages/mod.rs index 9d043bed..c7d9c137 100644 --- a/lib/lib/src/unmutated/list_packages/mod.rs +++ b/lib/lib/src/unmutated/list_packages/mod.rs @@ -44,15 +44,11 @@ impl<'a> TryFrom<&'a CListPackagesRequest> for ListPackagesData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![Box::new(FetchingStage)]) -} - pub fn run(data: ListPackagesData) -> Result<(Vec,), (ListPackagesStateId, ListPackagesError)> { let mut context = Context::new(); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); - let orchestrator = assemble(); + let orchestrator = SequentialOrchestrator::new(vec![Box::new(FetchingStage)]); run_unmutated!( orchestrator, diff --git a/lib/lib/src/unmutated/list_prefix/mod.rs b/lib/lib/src/unmutated/list_prefix/mod.rs index 72d3d095..7873ac5d 100644 --- a/lib/lib/src/unmutated/list_prefix/mod.rs +++ b/lib/lib/src/unmutated/list_prefix/mod.rs @@ -44,15 +44,11 @@ impl<'a> TryFrom<&'a CListPrefixRequest> for ListPrefixData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![Box::new(FetchingStage)]) -} - pub fn run(data: ListPrefixData) -> Result<(Vec,), (ListPrefixStateId, ListPrefixError)> { let mut context = Context::new(); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); - let orchestrator = assemble(); + let orchestrator = SequentialOrchestrator::new(vec![Box::new(FetchingStage)]); run_unmutated!( orchestrator, diff --git a/lib/lib/src/unmutated/search_files/mod.rs b/lib/lib/src/unmutated/search_files/mod.rs index 9e6d1c60..1e25bd14 100644 --- a/lib/lib/src/unmutated/search_files/mod.rs +++ b/lib/lib/src/unmutated/search_files/mod.rs @@ -48,15 +48,11 @@ impl<'a> TryFrom<&'a CSearchFilesRequest> for SearchFilesData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![Box::new(SearchingStage)]) -} - pub fn run(data: SearchFilesData) -> Result<(Vec,), (SearchFilesStateId, SearchFilesError)> { let mut context = Context::new(); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); - let orchestrator = assemble(); + let orchestrator = SequentialOrchestrator::new(vec![Box::new(SearchingStage)]); run_unmutated!( orchestrator, diff --git a/lib/lib/src/unmutated/search_meta/mod.rs b/lib/lib/src/unmutated/search_meta/mod.rs index caa19824..d370bd75 100644 --- a/lib/lib/src/unmutated/search_meta/mod.rs +++ b/lib/lib/src/unmutated/search_meta/mod.rs @@ -48,16 +48,12 @@ impl<'a> TryFrom<&'a CSearchMetaRequest> for SearchMetaData<'a> { } } -fn assemble() -> SequentialOrchestrator { - SequentialOrchestrator::new(vec![Box::new(SearchingStage)]) -} - pub fn run(data: SearchMetaData) -> Result<(Vec,), (SearchMetaStateId, SearchMetaError)> { let mut context = Context::new(); context.put(Search(data.search.to_owned())); context.put(Box::new(Message::new(data.hook_message, data.hook_message_context)) as Box); - let orchestrator = assemble(); + let orchestrator = SequentialOrchestrator::new(vec![Box::new(SearchingStage)]); run_unmutated!( orchestrator, diff --git a/lib/lib/tests/database_record.rs b/lib/lib/tests/database_record.rs index d946bbb5..f25972ad 100644 --- a/lib/lib/tests/database_record.rs +++ b/lib/lib/tests/database_record.rs @@ -23,14 +23,14 @@ fn sample_record() -> DeployRecord { message: Some("long-form commit message".to_string()), seq: 7, timestamp: 1_754_000_000, - etc_history: vec![ + config_history: vec![ EtcHistoryEntry { - etc_digest: "etc-digest-1".to_string(), + config_digest: "etc-digest-1".to_string(), subject: "first etc".to_string(), message: None, }, EtcHistoryEntry { - etc_digest: "etc-digest-2".to_string(), + config_digest: "etc-digest-2".to_string(), subject: "second etc".to_string(), message: Some("with a message".to_string()), }, diff --git a/lib/macro/src/common.rs b/lib/macro/src/common.rs index 59c166aa..3987286a 100644 --- a/lib/macro/src/common.rs +++ b/lib/macro/src/common.rs @@ -12,7 +12,7 @@ pub(crate) const PRIMITIVES: &[&str] = &[ "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize", "bool", "f32", "f64", ]; -pub(crate) const SHARED_TYPES: &[&str] = &["DiffKind"]; +pub(crate) const SHARED_TYPES: &[&str] = &["FileDiffKind", "PackageDiffKind"]; pub(crate) const VALIDATABLE_COMPOSITES: &[&str] = &[ "CVersion", @@ -21,6 +21,7 @@ pub(crate) const VALIDATABLE_COMPOSITES: &[&str] = &[ "CPackageInfo", "CDiffPrefixFileEntry", "CDiffConfigFileEntry", + "CDiffUntrackedFileEntry", "CCommitEntry", "CRequestBase", "CDependency", diff --git a/user/upac-cli/src/commands/commit/rollback.rs b/user/upac-cli/src/commands/commit/rollback.rs index a7c9a1f0..1255d3d2 100644 --- a/user/upac-cli/src/commands/commit/rollback.rs +++ b/user/upac-cli/src/commands/commit/rollback.rs @@ -18,10 +18,10 @@ pub struct Args { } pub fn run(args: Args, ctx: CommandContext) -> Result<()> { - let commit_hash = CString::new(args.commit)?; + let config_digest = CString::new(args.commit)?; let request = CMutatedRequest::for_rollback( - &commit_hash, + &config_digest, &ctx.config.paths.repo_path, &ctx.config.paths.root_path, &ctx.config.ostree.branch, diff --git a/user/upac-cli/src/commands/package/diff.rs b/user/upac-cli/src/commands/package/diff.rs index f6b4e80c..94b2d325 100644 --- a/user/upac-cli/src/commands/package/diff.rs +++ b/user/upac-cli/src/commands/package/diff.rs @@ -21,16 +21,16 @@ pub struct Args { } pub fn run(args: Args, ctx: CommandContext) -> Result<()> { - let from_commit_hash = CString::new(args.from)?; - let to_commit_hash = CString::new(args.to)?; + let from_config_digest = CString::new(args.from)?; + let to_config_digest = CString::new(args.to)?; let mut response = CUnmutatedResponse::empty(); let request = CUnmutatedRequest::for_diff( &ctx.config.paths.repo_path, &ctx.tmp_path, - &from_commit_hash, - &to_commit_hash, + &from_config_digest, + &to_config_digest, cancel_token_ptr(), ); diff --git a/user/upac-cli/src/ffi/request.rs b/user/upac-cli/src/ffi/request.rs index 1cbc33ce..bccc1ad3 100644 --- a/user/upac-cli/src/ffi/request.rs +++ b/user/upac-cli/src/ffi/request.rs @@ -45,7 +45,7 @@ pub struct CMutatedRequest { packages_count: usize, uninstall_packages: *const CPackageInfo, uninstall_packages_len: usize, - commit_hash: CSlice, + config_digest: CSlice, message: CSlice, files: *const CSlice, files_len: usize, @@ -71,7 +71,7 @@ impl CMutatedRequest { packages_count: 0, uninstall_packages: null(), uninstall_packages_len: 0, - commit_hash: CSlice::empty(), + config_digest: CSlice::empty(), message: CSlice::empty(), files: null(), files_len: 0, @@ -122,11 +122,11 @@ impl CMutatedRequest { #[allow(clippy::too_many_arguments)] pub fn for_rollback( - commit_hash: &CString, repo_path: &CString, root_path: &CString, branch: &CString, on_hook: Option, + config_digest: &CString, repo_path: &CString, root_path: &CString, branch: &CString, on_hook: Option, hook_ctx: *mut c_void, cancel_token: *mut CancelToken, ) -> Self { let mut req = Self::base(repo_path, root_path, branch, on_hook, hook_ctx, cancel_token); - req.commit_hash = CSlice::from_cstring(commit_hash); + req.config_digest = CSlice::from_cstring(config_digest); req } @@ -156,8 +156,8 @@ pub struct CUnmutatedRequest { root_path: CSlice, tmp_path: CSlice, branch: CSlice, - from_commit_hash: CSlice, - to_commit_hash: CSlice, + from_config_digest: CSlice, + to_config_digest: CSlice, search: CSlice, symlinks: *const CSlice, symlinks_len: usize, @@ -175,8 +175,8 @@ impl CUnmutatedRequest { root_path: CSlice::from_cstring(root_path), tmp_path: CSlice::empty(), branch: CSlice::from_cstring(branch), - from_commit_hash: CSlice::empty(), - to_commit_hash: CSlice::empty(), + from_config_digest: CSlice::empty(), + to_config_digest: CSlice::empty(), search: CSlice::empty(), symlinks: null(), symlinks_len: 0, @@ -192,8 +192,8 @@ impl CUnmutatedRequest { root_path: CSlice::from_cstring(root_path), tmp_path: CSlice::empty(), branch: CSlice::empty(), - from_commit_hash: CSlice::empty(), - to_commit_hash: CSlice::empty(), + from_config_digest: CSlice::empty(), + to_config_digest: CSlice::empty(), search: CSlice::empty(), symlinks: null(), symlinks_len: 0, @@ -212,8 +212,8 @@ impl CUnmutatedRequest { root_path: CSlice::empty(), tmp_path: CSlice::from_cstring(tmp_path), branch: CSlice::empty(), - from_commit_hash: CSlice::from_cstring(from_commit), - to_commit_hash: CSlice::from_cstring(to_commit), + from_config_digest: CSlice::from_cstring(from_commit), + to_config_digest: CSlice::from_cstring(to_commit), search: CSlice::empty(), symlinks: null(), symlinks_len: 0, @@ -229,8 +229,8 @@ impl CUnmutatedRequest { root_path: CSlice::from_cstring(root_path), tmp_path: CSlice::empty(), branch: CSlice::empty(), - from_commit_hash: CSlice::empty(), - to_commit_hash: CSlice::empty(), + from_config_digest: CSlice::empty(), + to_config_digest: CSlice::empty(), search: CSlice::from_cstring(query), symlinks: null(), symlinks_len: 0, @@ -249,8 +249,8 @@ impl CUnmutatedRequest { root_path: CSlice::from_cstring(root_path), tmp_path: CSlice::empty_str(), branch: CSlice::from_cstring(branch), - from_commit_hash: CSlice::empty(), - to_commit_hash: CSlice::empty(), + from_config_digest: CSlice::empty(), + to_config_digest: CSlice::empty(), search: CSlice::empty(), symlinks: if symlinks.is_empty() { null() } else { symlinks.as_ptr() }, symlinks_len: symlinks.len(),