- Error logging is now present in pacwrap-agent - Agent error code handling now correctly traps runtime panics. - impl of DisplayTrait for TransactionError no longer handles the display of transaction failure messages. - Error handling for TransactionAggregation is split into three categories: Fatal, Error, and Non-Fatal Error. While two former are immediately trapped and the program is terminated, the latter can safely terminate the program without consequence.
268 lines
10 KiB
Rust
268 lines
10 KiB
Rust
/*
|
|
* pacwrap
|
|
*
|
|
* Copyright (C) 2023-2024 Xavier Moffett <sapphirus@azorium.net>
|
|
* SPDX-License-Identifier: GPL-3.0-only
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
use indexmap::IndexMap;
|
|
use pacwrap_core::{
|
|
config::{cache, init::init, ConfigError::AlreadyExists, ContainerCache, ContainerType},
|
|
constants::{ARROW_GREEN, BAR_GREEN, BOLD, RESET},
|
|
err,
|
|
error::*,
|
|
lock::Lock,
|
|
log::{Level::Info, Logger},
|
|
sync::{
|
|
instantiate_container,
|
|
instantiate_trust,
|
|
transaction::{TransactionAggregator, TransactionFlags, TransactionType},
|
|
},
|
|
utils::{
|
|
arguments::{Arguments, InvalidArgument::*, Operand as Op},
|
|
check_root,
|
|
print_warning,
|
|
},
|
|
ErrorKind,
|
|
};
|
|
|
|
pub fn synchronize(args: &mut Arguments) -> Result<()> {
|
|
check_root()?;
|
|
init()?;
|
|
|
|
let mut logger = Logger::new("pacwrap-sync").init()?;
|
|
let mut cache = cache::populate()?;
|
|
let (action, create) = action(args);
|
|
let lock = Lock::new().lock()?;
|
|
let result = engage_aggregator(&mut cache, &mut logger, args, &lock, action, create);
|
|
|
|
if let Err(error) = lock.unlock() {
|
|
eprintln!("{}", ErrorType::Error(&error));
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
fn action(args: &mut Arguments) -> (TransactionType, bool) {
|
|
let (mut y, mut u, mut i) = (0, 0, false);
|
|
|
|
if let Op::Value("init") = args[0] {
|
|
(y, u, i) = (1, 1, true);
|
|
}
|
|
|
|
for arg in args.by_ref() {
|
|
match arg {
|
|
Op::Short('y') | Op::Long("refresh") => y += 1,
|
|
Op::Short('u') | Op::Long("upgrade") => u += 1,
|
|
_ => continue,
|
|
}
|
|
}
|
|
|
|
(TransactionType::Upgrade(u > 0, y > 0, y > 1), i)
|
|
}
|
|
|
|
fn instantiate<'a>(
|
|
cache: &mut ContainerCache<'a>,
|
|
lock: &'a Lock,
|
|
logger: &mut Logger,
|
|
action_type: &TransactionType,
|
|
targets: IndexMap<&'a str, (ContainerType, Vec<&'a str>)>,
|
|
) -> Result<()> {
|
|
if targets.is_empty() {
|
|
err!(OperationUnspecified)?;
|
|
}
|
|
|
|
if let TransactionType::Upgrade(upgrade, refresh, _) = action_type {
|
|
if !refresh {
|
|
err!(UnsuppliedOperand("--refresh", "Required for container creation."))?
|
|
} else if !upgrade {
|
|
err!(UnsuppliedOperand("--upgrade", "Required for container creation."))?
|
|
}
|
|
}
|
|
|
|
for (container, (container_type, deps)) in targets.iter() {
|
|
if let (ContainerType::Base, true) = (container_type, !deps.is_empty()) {
|
|
err!(ErrorKind::Message("Dependencies cannot be assigned to base containers."))?
|
|
} else if let (ContainerType::Aggregate | ContainerType::Slice, true) = (container_type, deps.is_empty()) {
|
|
err!(ErrorKind::Message("Dependencies not specified."))?
|
|
} else if cache.get_instance_option(container).is_some() {
|
|
err!(AlreadyExists(container.to_string()))?;
|
|
}
|
|
}
|
|
|
|
lock.assert()?;
|
|
println!("{} {}Instantiating container{}...{}", *BAR_GREEN, *BOLD, if targets.len() > 1 { "s" } else { "" }, *RESET);
|
|
|
|
for (container, (container_type, deps)) in targets {
|
|
cache.add(container, container_type, deps)?;
|
|
instantiate_container(cache.get_instance(container)?)?;
|
|
logger.log(Info, &format!("Instantiation of {container} complete."))?;
|
|
println!("{} Instantiation of {container} complete.", *ARROW_GREEN);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn acquire_targets<'a>(
|
|
cache: &'a ContainerCache<'a>,
|
|
flags: &TransactionFlags,
|
|
mut targets: HashSet<&'a str>,
|
|
) -> Result<Option<Vec<&'a str>>> {
|
|
Ok(if flags.intersects(TransactionFlags::TARGET_ONLY | TransactionFlags::CREATE) {
|
|
if flags.contains(TransactionFlags::CREATE) {
|
|
for cache in cache.registered_handles().iter().filter(|a| a.is_creation()) {
|
|
targets.extend(cache.metadata().dependencies());
|
|
}
|
|
}
|
|
|
|
match flags.contains(TransactionFlags::FILESYSTEM_SYNC) {
|
|
false => {
|
|
if targets.is_empty() {
|
|
err!(TargetUnspecified)?;
|
|
}
|
|
|
|
Some(targets.into_iter().collect())
|
|
}
|
|
true => None,
|
|
}
|
|
} else {
|
|
None
|
|
})
|
|
}
|
|
|
|
fn engage_aggregator<'a>(
|
|
cache: &mut ContainerCache<'a>,
|
|
log: &'a mut Logger,
|
|
args: &'a mut Arguments,
|
|
lock: &'a Lock,
|
|
action_type: TransactionType,
|
|
init: bool,
|
|
) -> Result<()> {
|
|
let mut flags = TransactionFlags::NONE;
|
|
let mut create_targets: IndexMap<&'a str, (ContainerType, Vec<&'a str>)> = IndexMap::new();
|
|
let mut targets = HashSet::new();
|
|
let mut queue = HashMap::new();
|
|
let mut current_target = None;
|
|
let mut container_type = None;
|
|
let mut create = init;
|
|
|
|
if let Op::Nothing = args.next().unwrap_or_default() {
|
|
err!(OperationUnspecified)?
|
|
}
|
|
|
|
while let Some(arg) = args.next() {
|
|
match arg {
|
|
Op::Short('y') | Op::Short('u') | Op::Long("refresh") | Op::Long("upgrade") => continue,
|
|
Op::Long("debug") => flags |= TransactionFlags::DEBUG,
|
|
Op::Long("dbonly") => flags |= TransactionFlags::DATABASE_ONLY,
|
|
Op::Long("noconfirm") => flags |= TransactionFlags::NO_CONFIRM,
|
|
Op::Long("force-foreign") => flags |= TransactionFlags::FORCE_DATABASE,
|
|
Op::Long("disable-sandbox") => flags |= TransactionFlags::NO_ALPM_SANDBOX,
|
|
Op::Short('l') | Op::Long("lazy-load") => flags |= TransactionFlags::LAZY_LOAD_DB,
|
|
Op::Short('o') | Op::Long("target-only") => flags |= TransactionFlags::TARGET_ONLY,
|
|
Op::Short('f') | Op::Long("filesystem") => flags |= TransactionFlags::FILESYSTEM_SYNC,
|
|
Op::Short('p') | Op::Long("preview") => flags |= TransactionFlags::PREVIEW,
|
|
Op::Short('b') | Op::Long("base") => container_type = Some(ContainerType::Base),
|
|
Op::Short('s') | Op::Long("slice") => container_type = Some(ContainerType::Slice),
|
|
Op::Short('a') | Op::Long("aggregate") => container_type = Some(ContainerType::Aggregate),
|
|
Op::Short('c') | Op::Long("create") => {
|
|
container_type = None;
|
|
create = true;
|
|
}
|
|
Op::Short('d') | Op::Long("dep") => match args.next() {
|
|
Some(arg) => match arg {
|
|
Op::ShortPos('d', dep) | Op::LongPos("dep", dep) => match container_type {
|
|
Some(_) => {
|
|
let current_target = match current_target {
|
|
Some(target) => target,
|
|
None => err!(TargetUnspecified)?,
|
|
};
|
|
let (_, deps) = create_targets.get_mut(current_target).unwrap();
|
|
|
|
if dep.contains(",") {
|
|
for dep in dep.split(",").filter(|a| !a.is_empty()) {
|
|
deps.push(dep);
|
|
}
|
|
} else {
|
|
deps.push(dep);
|
|
}
|
|
}
|
|
None => err!(ErrorKind::Message("Container type not specified."))?,
|
|
},
|
|
_ => args.invalid_operand()?,
|
|
},
|
|
None => err!(ErrorKind::Message("Dependencies not specified."))?,
|
|
},
|
|
Op::Short('t') | Op::Long("target") => match args.next() {
|
|
Some(arg) => match arg {
|
|
Op::ShortPos('t', target) | Op::LongPos("target", target) => {
|
|
current_target = Some(target);
|
|
targets.insert(target);
|
|
|
|
if let (true, Some(container_type)) = (create, container_type) {
|
|
if let ContainerType::Base = container_type {
|
|
queue.insert(target, vec!["base"]);
|
|
}
|
|
|
|
create_targets.insert(target, (container_type, vec![]));
|
|
create = init;
|
|
} else if let (true, None) = (create, container_type) {
|
|
err!(ErrorKind::Message("Container type not specified."))?;
|
|
} else if let ContainerType::Symbolic = cache.get_instance(target)?.metadata().container_type() {
|
|
err!(ErrorKind::Message("Symbolic containers cannot be transacted."))?;
|
|
}
|
|
}
|
|
_ => args.invalid_operand()?,
|
|
},
|
|
None => err!(TargetUnspecified)?,
|
|
},
|
|
Op::LongPos(_, package) | Op::ShortPos(_, package) | Op::Value(package) =>
|
|
if let Some(current_target) = current_target {
|
|
if let Some(vec) = queue.get_mut(current_target) {
|
|
vec.push(package);
|
|
} else {
|
|
queue.insert(current_target, vec![package]);
|
|
}
|
|
},
|
|
_ => args.invalid_operand()?,
|
|
}
|
|
}
|
|
|
|
if flags.contains(TransactionFlags::LAZY_LOAD_DB) {
|
|
print_warning("Database lazy-loading triggered by `-l/--lazy-load`; this feature is experimental.");
|
|
print_warning("In future, manual intervention may be required for missing dependencies.");
|
|
print_warning("See `--help sync` or the pacwrap(1) man page for further information.");
|
|
}
|
|
|
|
if !create_targets.is_empty() || init {
|
|
if flags.intersects(TransactionFlags::PREVIEW) {
|
|
err!(ErrorKind::Message("Container creation cannot be previewed."))?;
|
|
}
|
|
|
|
flags = flags | TransactionFlags::CREATE | TransactionFlags::FORCE_DATABASE;
|
|
instantiate_trust()?;
|
|
instantiate(cache, lock, log, &action_type, create_targets)?;
|
|
}
|
|
|
|
TransactionAggregator::new(cache, log, action_type)
|
|
.assert_lock(lock)?
|
|
.target(acquire_targets(cache, &flags, targets)?)
|
|
.queue(queue)
|
|
.flag(flags)
|
|
.progress()
|
|
.aggregate()
|
|
}
|