Ciborium replaced with bincode, and pacwrap-agent attained UX parity

- Refactored libalpm events into their own module
 - Agent parameters are acquired via a temporary file attached to the
   container (Still TODO: Provide a shared communication channel)
 - filesystem state files now contain magic values with file header
   verification
 - Miscellaneous tidy up work
This commit is contained in:
Xavier Moffett 2023-12-23 23:00:31 -05:00
parent f3b2b3861d
commit b84f90d17d
15 changed files with 283 additions and 216 deletions

View file

@ -1,54 +1,71 @@
use std::{fs, io::Stdin, process::exit}; use std::{fs::{self, File}, process::exit, os::unix::prelude::FileExt, env};
use serde::Deserialize; use serde::Deserialize;
use pacwrap_core::{constants::{BOLD, RESET}, use pacwrap_core::{sync::{self, AlpmConfigData,
utils::{print_error, print_warning},
sync::{self, AlpmConfigData,
query_event,
progress_event::{self, ProgressEvent},
utils::{erroneous_transaction, utils::{erroneous_transaction,
erroneous_preparation}, erroneous_preparation},
transaction::{TransactionMetadata, transaction::{TransactionHandle,
TransactionHandle,
TransactionType, TransactionType,
TransactionMetadata,
TransactionParameters,
Error, Error,
Result}}}; Result, MAGIC_NUMBER},
event::{download::{DownloadCallback, download_event}, progress::{ProgressEvent, callback}, query::questioncb}},
utils::{print_error, print_warning, read_le_32}, constants::{RESET, BOLD}};
pub fn transact() { pub fn transact() {
let mut stdin = std::io::stdin(); let mut header_buffer = vec![0; 7];
let alpm_remotes: AlpmConfigData = deserialize_stdin(&mut stdin); let mut file = match File::open("/tmp/agent_params") {
let mode: TransactionType = deserialize_stdin(&mut stdin); Ok(file) => file,
let alpm = sync::instantiate_alpm_agent(&alpm_remotes); Err(_) => {
let mut meta: TransactionMetadata = deserialize_stdin(&mut stdin); if let Ok(var) = env::var("SHELL") {
let mut handle = TransactionHandle::new(alpm, &mut meta); if ! var.is_empty() {
print_error("Direct execution of this binary is unsupported.");
}
}
if let Err(error) = conduct_transaction(&mut handle, mode) { exit(2);
},
};
if let Err(error) = file.read_exact_at(&mut header_buffer, 0) {
print_error(format!("'{}/tmp/agent_params{}': {error}", *BOLD, *RESET));
exit(3);
}
let magic = read_le_32(&header_buffer, 0);
let major: u8 = env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap();
let minor: u8 = env!("CARGO_PKG_VERSION_MINOR").parse().unwrap();
let patch: u8 = env!("CARGO_PKG_VERSION_PATCH").parse().unwrap();
if magic != MAGIC_NUMBER {
print_error(format!("Magic number {magic} != {MAGIC_NUMBER}"));
exit(4);
}
if major != header_buffer[4] || minor != header_buffer[5] || patch != header_buffer[6] {
print_error(format!("{major}.{minor}.{patch} != {}.{}.{}", header_buffer[4], header_buffer[5], header_buffer[6]));
exit(5);
}
let params: TransactionParameters = deserialize(&mut file);
let alpm_remotes: AlpmConfigData = deserialize(&mut file);
let mut metadata: TransactionMetadata = deserialize(&mut file);
let alpm = sync::instantiate_alpm_agent(&alpm_remotes);
let mut handle = TransactionHandle::new(alpm, &mut metadata);
if let Err(error) = conduct_transaction(&mut handle, params) {
print_error(error); print_error(error);
handle.alpm_mut().trans_release().ok(); handle.alpm_mut().trans_release().ok();
exit(1); exit(1);
} }
} }
fn deserialize_stdin<T: for<'de> Deserialize<'de>>(stdin: &mut Stdin) -> T { fn conduct_transaction(handle: &mut TransactionHandle, agent: TransactionParameters) -> Result<()> {
match ciborium::from_reader::<T, &mut Stdin>(stdin) {
Ok(meta) => meta,
Err(err) => {
if let ciborium::de::Error::Semantic(_, error) = err {
match error.contains("integer `10`") {
false => print_error(format!("Deserialization failure occurred with input from {}STDIN{}: {error}", *BOLD, *RESET)),
true => print_error("Interactive user input is not supported by this program."),
}
}
exit(1);
}
}
}
fn conduct_transaction(handle: &mut TransactionHandle, action: TransactionType) -> Result<()> {
let flags = handle.retrieve_flags(); let flags = handle.retrieve_flags();
let mode = handle.get_mode().clone(); let mode = agent.mode();
let action = agent.action();
if let Err(error) = handle.alpm_mut().trans_init(flags.1.unwrap()) { if let Err(error) = handle.alpm_mut().trans_init(flags.1.unwrap()) {
Err(Error::InitializationFailure(error.to_string().into()))? Err(Error::InitializationFailure(error.to_string().into()))?
@ -68,9 +85,9 @@ fn conduct_transaction(handle: &mut TransactionHandle, action: TransactionType)
erroneous_preparation(error)? erroneous_preparation(error)?
} }
handle.alpm().set_progress_cb(ProgressEvent::new(&action), progress_event::callback(&mode)); handle.alpm().set_progress_cb(ProgressEvent::new(&action), callback(&mode));
handle.alpm().set_question_cb((), query_event::questioncb); handle.alpm().set_question_cb((), questioncb);
//handle.alpm().set_dl_cb(DownloadCallback::new(), dl_event::download_event); handle.alpm().set_dl_cb(DownloadCallback::new(agent.bytes(), agent.files()), download_event);
if let Err(error) = handle.alpm_mut().trans_commit() { if let Err(error) = handle.alpm_mut().trans_commit() {
erroneous_transaction(error)? erroneous_transaction(error)?
@ -85,3 +102,13 @@ fn conduct_transaction(handle: &mut TransactionHandle, action: TransactionType)
Ok(()) Ok(())
} }
fn deserialize<T: for<'de> Deserialize<'de>>(stdin: &mut File) -> T {
match bincode::deserialize_from::<&mut File, T>(stdin) {
Ok(meta) => meta,
Err(error) => {
print_error(format!("Deserilization error: {}", error.as_ref()));
exit(3);
}
}
}

View file

@ -1,5 +1,4 @@
use pacwrap_core::utils::{print_error, Arguments, arguments::Operand}; use pacwrap_core::utils::{print_error, Arguments, arguments::Operand};
use serde::{Serialize, Deserialize};
mod agent; mod agent;
@ -9,14 +8,6 @@ fn main() {
match param { match param {
Operand::Value("transact") => agent::transact(), Operand::Value("transact") => agent::transact(),
_ => print_error(arguments.invalid_operand().to_string()) _ => print_error("Direct execution of this binary is unsupported.")
} }
} }
#[derive(Serialize, Deserialize, Clone)]
struct Test {
string: String,
version_major: i16,
version_minor: i16,
}

View file

@ -18,6 +18,7 @@ lazy_static! {
pub static ref CACHE_DIR: &'static str = env_default("PACWRAP_CACHE_DIR", PACWRAP_CACHE_DIR); pub static ref CACHE_DIR: &'static str = env_default("PACWRAP_CACHE_DIR", PACWRAP_CACHE_DIR);
pub static ref CONFIG_DIR: &'static str = env_default("PACWRAP_CONFIG_DIR", PACWRAP_CONFIG_DIR); pub static ref CONFIG_DIR: &'static str = env_default("PACWRAP_CONFIG_DIR", PACWRAP_CONFIG_DIR);
pub static ref DATA_DIR: &'static str = env_default("PACWRAP_DATA_DIR", PACWRAP_DATA_DIR); pub static ref DATA_DIR: &'static str = env_default("PACWRAP_DATA_DIR", PACWRAP_DATA_DIR);
pub static ref PACWRAP_AGENT_FILE: &'static str = format!("/run/user/{}/pacwrap_agent_{}", geteuid(), &id()).leak();
pub static ref XDG_RUNTIME_DIR: String = format!("/run/user/{}", geteuid()); pub static ref XDG_RUNTIME_DIR: String = format!("/run/user/{}", geteuid());
pub static ref DBUS_SOCKET: String = format!("/run/user/{}/pacwrap_dbus_{}", geteuid(), &id()); pub static ref DBUS_SOCKET: String = format!("/run/user/{}/pacwrap_dbus_{}", geteuid(), &id());
pub static ref LOG_LOCATION: &'static str = format!("{}/pacwrap.log", *DATA_DIR).leak(); pub static ref LOG_LOCATION: &'static str = format!("{}/pacwrap.log", *DATA_DIR).leak();

View file

@ -1,6 +1,6 @@
use std::{process::{Child, Command, Stdio}, io::Error, env::var}; use std::{process::{Child, Command}, io::Error, env::var};
use crate::{constants::{BWRAP_EXECUTABLE, self}, use crate::{constants::{BWRAP_EXECUTABLE, self, PACWRAP_AGENT_FILE},
config::InstanceHandle, config::InstanceHandle,
ErrorKind}; ErrorKind};
@ -10,11 +10,11 @@ pub fn execute_agent(ins: &InstanceHandle) -> Result<Child,Error> {
Command::new(BWRAP_EXECUTABLE) Command::new(BWRAP_EXECUTABLE)
.env_clear() .env_clear()
.stdin(Stdio::piped())
.arg("--bind").arg(&ins.vars().root()).arg("/mnt") .arg("--bind").arg(&ins.vars().root()).arg("/mnt")
.arg("--tmpfs").arg("/tmp") .arg("--tmpfs").arg("/tmp")
.arg("--tmpfs").arg("/etc") .arg("--tmpfs").arg("/etc")
.arg("--symlink").arg("/mnt/usr").arg("/usr") .arg("--symlink").arg("/mnt/usr").arg("/usr")
.arg("--ro-bind").arg(*PACWRAP_AGENT_FILE).arg("/tmp/agent_params")
.arg("--ro-bind").arg(format!("{}/lib", dist_img)).arg("/lib64") .arg("--ro-bind").arg(format!("{}/lib", dist_img)).arg("/lib64")
.arg("--ro-bind").arg(format!("{}/bin", dist_img)).arg("/bin") .arg("--ro-bind").arg(format!("{}/bin", dist_img)).arg("/bin")
.arg("--ro-bind").arg("/etc/resolv.conf").arg("/etc/resolv.conf") .arg("--ro-bind").arg("/etc/resolv.conf").arg("/etc/resolv.conf")

View file

@ -7,7 +7,7 @@ use serde::{Serialize, Deserialize};
use crate::{utils::{print_warning, print_error}, use crate::{utils::{print_warning, print_error},
constants::{BAR_GREEN, RESET, BOLD, ARROW_RED, CACHE_DIR, DATA_DIR, CONFIG_DIR}, constants::{BAR_GREEN, RESET, BOLD, ARROW_RED, CACHE_DIR, DATA_DIR, CONFIG_DIR},
sync::dl_event::DownloadCallback, sync::event::download::{DownloadCallback, download_event},
config::{InsVars, config::{InsVars,
InstanceHandle, InstanceHandle,
cache::InstanceCache}}; cache::InstanceCache}};
@ -18,9 +18,7 @@ lazy_static! {
static ref DEFAULT_ALPM_CONF: AlpmConfigData = AlpmConfigData::new(); static ref DEFAULT_ALPM_CONF: AlpmConfigData = AlpmConfigData::new();
} }
pub mod progress_event; pub mod event;
pub mod dl_event;
pub mod query_event;
pub mod utils; pub mod utils;
pub mod transaction; pub mod transaction;
pub mod filesystem; pub mod filesystem;
@ -104,7 +102,7 @@ fn synchronize_database(cache: &InstanceCache, force: bool) {
let mut handle = alpm_handle(&ins.vars(), db_path, &*DEFAULT_ALPM_CONF); let mut handle = alpm_handle(&ins.vars(), db_path, &*DEFAULT_ALPM_CONF);
println!("{} {}Synchronising package databases...{}", *BAR_GREEN, *BOLD, *RESET); println!("{} {}Synchronising package databases...{}", *BAR_GREEN, *BOLD, *RESET);
handle.set_dl_cb(DownloadCallback::new(0, 0), dl_event::download_event); handle.set_dl_cb(DownloadCallback::new(0, 0), download_event);
if let Err(err) = handle.syncdbs_mut().update(force) { if let Err(err) = handle.syncdbs_mut().update(force) {
print_error(format!("Unable to initialize transaction: {}.",err.to_string())); print_error(format!("Unable to initialize transaction: {}.",err.to_string()));

View file

@ -0,0 +1,16 @@
pub mod download;
pub mod query;
pub mod progress;
fn whitespace(total: usize, current: usize) -> String {
let mut whitespace = String::new();
let difference = total-current;
if difference > 0 {
for _ in 0..difference {
whitespace.push_str(" ");
}
}
whitespace
}

View file

@ -5,7 +5,7 @@ use dialoguer::console::Term;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use super::utils::whitespace; use super::whitespace;
lazy_static!{ lazy_static!{
static ref INIT: ProgressStyle = ProgressStyle::with_template(" {spinner:.green} {msg}") static ref INIT: ProgressStyle = ProgressStyle::with_template(" {spinner:.green} {msg}")
@ -92,7 +92,8 @@ pub fn download_event(file: &str, download: AnyDownloadEvent, this: &mut Downloa
this.files_done += 1; this.files_done += 1;
total.set_message(format!("Total ({}{}/{})", total.set_message(format!("Total ({}{}/{})",
whitespace(this.total_files_len, this.files_done.to_string().len()), whitespace(this.total_files_len,
this.files_done.to_string().len()),
this.files_done, this.files_done,
this.total_files)); this.total_files));

View file

@ -1,19 +1,16 @@
use std::rc::Rc;
use alpm::Progress; use alpm::Progress;
use dialoguer::console::Term; use dialoguer::console::Term;
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
use crate::constants::{BOLD, RESET}; use crate::{constants::{BOLD, RESET}, sync::transaction::{TransactionType, TransactionMode}};
use super::whitespace;
use super::{utils::whitespace, transaction::{TransactionType, TransactionMode}};
#[derive(Clone)] #[derive(Clone)]
pub struct ProgressEvent { pub struct ProgressEvent {
progress: ProgressBar, progress: ProgressBar,
offset: usize, offset: usize,
style: ProgressStyle, style: ProgressStyle,
current: Rc<str>, current: String,
} }
impl ProgressEvent { impl ProgressEvent {
@ -60,7 +57,7 @@ pub fn condensed(progress: Progress, pkgname: &str, percent: i32, howmany: usize
let progress_name: String = name(progress,pkgname); let progress_name: String = name(progress,pkgname);
let whitespace = whitespace(total.to_string().len(), pos.to_string().len()); let whitespace = whitespace(total.to_string().len(), pos.to_string().len());
if this.current.as_ref() != "" { if this.current != "" {
this.progress = ProgressBar::new(howmany as u64); this.progress = ProgressBar::new(howmany as u64);
this.progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *BOLD, *RESET, *BOLD, *RESET)); this.progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *BOLD, *RESET, *BOLD, *RESET));
this.progress.set_style(this.style.clone()); this.progress.set_style(this.style.clone());
@ -95,7 +92,7 @@ fn name(progress: Progress, pkgname: &str) -> String {
} }
} }
fn ident(progress: Progress, pkgname: &str) -> Rc<str> { fn ident(progress: Progress, pkgname: &str) -> String {
match progress { match progress {
Progress::KeyringStart => "keyring", Progress::KeyringStart => "keyring",
Progress::IntegrityStart => "integrity", Progress::IntegrityStart => "integrity",

View file

@ -1,84 +1,53 @@
use std::fmt; use std::{fs::{self, File, Metadata},
use std::fs::{self, File, Metadata}; io::Read,
use std::os::unix::fs::symlink; os::unix::fs::symlink,
use std::os::unix::prelude::MetadataExt; os::unix::prelude::MetadataExt,
use std::path::Path; path::Path,
use std::sync::Arc; sync::{Arc, mpsc::{Sender, self, Receiver}},
use std::sync::mpsc::{Sender, self, Receiver}; collections::{HashMap, HashSet}};
use dialoguer::console::Term; use dialoguer::console::Term;
use rayon::prelude::*; use rayon::{prelude::*, {ThreadPool, ThreadPoolBuilder}};
use rayon::{ThreadPool, ThreadPoolBuilder};
use indexmap::IndexMap; use indexmap::IndexMap;
use indicatif::{ProgressBar, ProgressStyle, ProgressDrawTarget}; use indicatif::{ProgressBar, ProgressStyle, ProgressDrawTarget};
use serde::{Serialize, Deserialize, Deserializer, Serializer, de::Visitor}; use serde::{Serialize, Deserialize};
use walkdir::WalkDir; use walkdir::WalkDir;
use std::collections::{HashMap, HashSet};
use crate::ErrorKind; use crate::{ErrorKind,
use crate::config::{InstanceHandle, InstanceCache, InstanceType::*}; config::{InstanceHandle, InstanceCache, InstanceType::*},
use crate::constants::{RESET, BOLD, ARROW_CYAN, BAR_GREEN, DATA_DIR}; constants::{RESET, BOLD, ARROW_CYAN, BAR_GREEN, DATA_DIR},
use crate::utils::{print_warning, print_error}; utils::{print_warning, print_error, read_le_32}};
impl Serialize for FileType { static VERSION: u32 = 1;
fn serialize<D: Serializer>(&self, serializer: D) -> Result<D::Ok, D::Error> static MAGIC_NUMBER: u32 = 408948530;
where D: serde::Serializer {
serializer.serialize_i64(self.as_integer())
}
}
impl <'de>Deserialize<'de> for FileType {
fn deserialize<D: Deserializer<'de>>(serializer: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de> {
serializer.deserialize_i64(FileTypeVisitor)
}
}
struct FileTypeVisitor;
impl<'de> Visitor<'de> for FileTypeVisitor {
type Value = FileType;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "an integer between `0` and `2`")
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where E: serde::de::Error, {
let value = v.into();
if let FileType::Invalid(v) = value {
Err(E::invalid_value(serde::de::Unexpected::Signed(v), &self))?
}
Ok(value)
}
}
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
struct FileSystemState { struct FileSystemState {
magic: u32,
version: u32,
files: IndexMap<Arc<str>, (FileType, Arc<str>)> files: IndexMap<Arc<str>, (FileType, Arc<str>)>
} }
impl FileSystemState { impl FileSystemState {
fn new() -> Self { fn new() -> Self {
Self { Self {
files: IndexMap::new() magic: MAGIC_NUMBER,
version: VERSION,
files: IndexMap::new(),
} }
} }
} }
#[derive(Clone)] #[derive(Serialize, Deserialize, Clone)]
enum FileType { enum FileType {
HardLink, HardLink,
SymLink, SymLink,
Directory, Directory,
Invalid(i64), Invalid(i8),
} }
impl From<i64> for FileType { impl From<i8> for FileType {
fn from(integer: i64) -> Self { fn from(integer: i8) -> Self {
match integer { match integer {
2 => Self::Directory, 2 => Self::Directory,
1 => Self::SymLink, 1 => Self::SymLink,
@ -100,23 +69,11 @@ impl From<Metadata> for FileType {
} }
} }
impl FileType {
fn as_integer(&self) -> i64 {
match self {
Self::Directory => 2,
Self::SymLink => 1,
Self::HardLink => 0,
Self::Invalid(i) => *i,
}
}
}
enum SyncMessage { enum SyncMessage {
LinkComplete(Arc<str>), LinkComplete(Arc<str>),
SaveState(Arc<str>, FileSystemState), SaveState(Arc<str>, FileSystemState),
} }
#[allow(dead_code)]
pub struct FileSystemStateSync<'a> { pub struct FileSystemStateSync<'a> {
state_map: HashMap<Arc<str>, FileSystemState>, state_map: HashMap<Arc<str>, FileSystemState>,
state_map_prev: HashMap<Arc<str>, FileSystemState>, state_map_prev: HashMap<Arc<str>, FileSystemState>,
@ -228,38 +185,52 @@ impl <'a>FileSystemStateSync<'a> {
return st.clone() return st.clone()
} }
let mut header_buffer = vec![0; 8];
let path = format!("{}/state/{}.dat", *DATA_DIR, instance); let path = format!("{}/state/{}.dat", *DATA_DIR, instance);
let file = match File::open(&path) { let mut file = match File::open(&path) {
Ok(file) => file, Ok(file) => file,
Err(err) => { Err(err) => {
let state = FileSystemState::new();
if err.kind() != std::io::ErrorKind::NotFound { if err.kind() != std::io::ErrorKind::NotFound {
print_warning(format!("Reading '{}': {}", path, err.kind())); print_error(format!("'{}': {}", path, err.kind()));
} }
self.state_map_prev.insert(instance.clone(), state.clone()); return self.blank_state(instance);
return state
}, },
}; };
match ciborium::from_reader::<FileSystemState, File>(file) { if let Err(error) = file.read_exact(&mut header_buffer) {
print_error(format!("'{}{instance}{}.dat': {error}", *BOLD, *RESET));
return self.blank_state(instance);
}
let magic = read_le_32(&header_buffer, 0);
let version = read_le_32(&header_buffer, 4);
if magic != MAGIC_NUMBER {
print_warning(format!("'{}{instance}{}.dat': Magic number mismatch ({MAGIC_NUMBER} != {magic})", *BOLD, *RESET));
return self.blank_state(instance);
} else if version != VERSION {
return self.blank_state(instance);
}
match bincode::deserialize_from::<&File, FileSystemState>(&file) {
Ok(state) => { Ok(state) => {
self.state_map_prev.insert(instance.clone(), state.clone()); self.state_map_prev.insert(instance.clone(), state.clone());
state state
}, },
Err(err) => { Err(err) => {
let state = FileSystemState::new(); print_error(format!("Deserialization failure occurred with '{}{instance}{}.dat': {}", *BOLD, *RESET, err.as_ref()));
return self.blank_state(instance);
if let ciborium::de::Error::Semantic(_, error) = err {
print_error(format!("Deserialization failure occurred with '{}{instance}{}.dat': {error}", *BOLD, *RESET));
} }
}
}
fn blank_state(&mut self, instance: &Arc<str>) -> FileSystemState {
let state = FileSystemState::new();
self.state_map_prev.insert(instance.clone(), state.clone()); self.state_map_prev.insert(instance.clone(), state.clone());
state state
} }
}
}
fn write(&mut self, tx: Sender<()>, ds: FileSystemState, dep: Arc<str>) { fn write(&mut self, tx: Sender<()>, ds: FileSystemState, dep: Arc<str>) {
let path: &str = &format!("{}/state/{}.dat", *DATA_DIR, dep); let path: &str = &format!("{}/state/{}.dat", *DATA_DIR, dep);
@ -272,7 +243,7 @@ impl <'a>FileSystemStateSync<'a> {
}; };
self.pool().unwrap().spawn(move ||{ self.pool().unwrap().spawn(move ||{
if let Err(err) = ciborium::into_writer(&ds, output) { if let Err(err) = bincode::serialize_into(output, &ds) {
print_error(format!("Serialization failure occurred with '{}{dep}{}.dat': {}", *BOLD, *RESET, err.to_string())); print_error(format!("Serialization failure occurred with '{}{dep}{}.dat': {}", *BOLD, *RESET, err.to_string()));
} }

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::HashSet, fmt::{Display, Formatter}}; use std::{borrow::Cow, collections::HashSet, fmt::{Display, Formatter}, fs::remove_file};
use bitflags::bitflags; use bitflags::bitflags;
use alpm::{Alpm, PackageReason, TransFlag}; use alpm::{Alpm, PackageReason, TransFlag};
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::{config, use crate::{config,
config::InstanceHandle, config::InstanceHandle,
constants::{RESET, BOLD, ARROW_CYAN, BAR_CYAN, ARROW_RED}, constants::{RESET, BOLD, ARROW_CYAN, BAR_CYAN, ARROW_RED, PACWRAP_AGENT_FILE},
sync::{ sync::{
resolver_local::LocalDependencyResolver, resolver_local::LocalDependencyResolver,
resolver::DependencyResolver, resolver::DependencyResolver,
@ -29,8 +29,15 @@ mod stage;
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
pub static MAGIC_NUMBER: u32 = 663445956;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum Error { pub enum Error {
AgentError, TransactionFailureAgent,
ParameterAcquisitionFailure,
DeserializationFailure,
InvalidMagicNumber,
AgentVersionMismatch,
NothingToDo, NothingToDo,
DependentContainerMissing(String), DependentContainerMissing(String),
RecursionDepthExceeded(isize), RecursionDepthExceeded(isize),
@ -104,6 +111,19 @@ pub struct TransactionMetadata<'a> {
flags: (u8, u32) flags: (u8, u32)
} }
#[derive(Serialize, Deserialize)]
pub struct TransactionParameters {
magic: u32,
ver_major: u8,
ver_minor: u8,
ver_patch: u8,
bytes: u64,
files: u64,
action: TransactionType,
mode: TransactionMode,
}
impl TransactionMode { impl TransactionMode {
fn bool(&self) -> bool { fn bool(&self) -> bool {
match self { match self {
@ -188,6 +208,10 @@ impl Display for Error {
Self::InitializationFailure(msg) => write!(fmter, "Failure to initialize transaction: {msg}"), Self::InitializationFailure(msg) => write!(fmter, "Failure to initialize transaction: {msg}"),
Self::PreparationFailure(msg) => write!(fmter, "Failure to prepare transaction: {msg}"), Self::PreparationFailure(msg) => write!(fmter, "Failure to prepare transaction: {msg}"),
Self::TransactionFailure(msg) => write!(fmter, "Failure to commit transaction: {msg}"), Self::TransactionFailure(msg) => write!(fmter, "Failure to commit transaction: {msg}"),
Self::DeserializationFailure => write!(fmter, "Deserialization of input parameters failed."),
Self::ParameterAcquisitionFailure => write!(fmter, "Failure to acquire agent runtime parameters."),
Self::AgentVersionMismatch => write!(fmter, "Agent binary mismatch."),
Self::InvalidMagicNumber => write!(fmter, "Deserialization of input parameters failed: Invalid magic number."),
Self::InternalError(msg) => write!(fmter, "Internal failure: {msg}"), Self::InternalError(msg) => write!(fmter, "Internal failure: {msg}"),
_ => write!(fmter, "Nothing to do."), _ => write!(fmter, "Nothing to do."),
} }
@ -396,14 +420,16 @@ impl <'a>TransactionHandle<'a> {
fn release_on_fail(self, error: Error) { fn release_on_fail(self, error: Error) {
match error { match error {
Error::AgentError => (), _ => print_error(error), Error::TransactionFailureAgent => (), _ => print_error(error),
} }
remove_file(*PACWRAP_AGENT_FILE).ok();
println!("{} Transaction failed.", *ARROW_RED); println!("{} Transaction failed.", *ARROW_RED);
drop(self); drop(self);
} }
pub fn release(self) { pub fn release(self) {
remove_file(*PACWRAP_AGENT_FILE).ok();
drop(self); drop(self);
} }
@ -439,3 +465,34 @@ impl <'a>TransactionHandle<'a> {
&self.meta &self.meta
} }
} }
impl TransactionParameters {
fn new(t_type: TransactionType, t_mode: TransactionMode, dl_size: u64, amount: usize) -> Self {
Self {
magic: MAGIC_NUMBER,
ver_major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
ver_minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
ver_patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
bytes: dl_size,
files: amount as u64,
action: t_type,
mode: t_mode,
}
}
pub fn bytes(&self) -> u64 {
self.bytes
}
pub fn files(&self) -> usize {
self.files as usize
}
pub fn mode(&self) -> TransactionMode {
self.mode
}
pub fn action(&self) -> TransactionType {
self.action
}
}

View file

@ -1,24 +1,28 @@
use std::process::ChildStdin; use std::{fs::File, path::Path};
use alpm::Alpm; use alpm::Alpm;
use dialoguer::console::Term; use dialoguer::console::Term;
use serde::Serialize; use serde::Serialize;
use crate::{exec::utils::execute_agent, sync::{DEFAULT_ALPM_CONF, utils::erroneous_preparation, self}};
use simplebyteunit::simplebyteunit::{SI, ToByteUnit}; use simplebyteunit::simplebyteunit::{SI, ToByteUnit};
use crate::constants::{RESET, BOLD, DIM}; use crate::{exec::utils::execute_agent,
sync::{DEFAULT_ALPM_CONF, utils::erroneous_preparation, self},
ErrorKind,
utils::prompt::prompt,
constants::{PACWRAP_AGENT_FILE, RESET, BOLD, DIM},
config::InstanceHandle,
};
use crate::utils::prompt::prompt;
use crate::config::InstanceHandle;
use super::{Transaction, use super::{Transaction,
TransactionState, TransactionState,
TransactionHandle, TransactionHandle,
TransactionAggregator, TransactionAggregator,
TransactionFlags, TransactionFlags,
TransactionParameters,
Result, Result,
Error}; Error};
#[allow(dead_code)]
pub struct Commit { pub struct Commit {
state: TransactionState, state: TransactionState,
keyring: bool, keyring: bool,
@ -53,23 +57,23 @@ impl Transaction for Commit {
erroneous_preparation(error)? erroneous_preparation(error)?
} }
if let Some(result) = confirm(&self.state, ag, handle) { let result = confirm(&self.state, ag, handle);
let download = result.1.unwrap_or((0,0));
if let Some(result) = result.0 {
return result; return result;
} }
handle.set_alpm(None); handle.set_alpm(None);
write_agent_params(ag, handle, download)?;
match execute_agent(inshandle) { let mut agent = match execute_agent(inshandle) {
Ok(mut child) => { Ok(child) => child,
let stdin = child.stdin.take().unwrap(); Err(error) => Err(Error::TransactionFailure(format!("Execution of agent failed: {}", error)))?,
};
write_to_stdin(&*DEFAULT_ALPM_CONF, &stdin)?; match agent.wait() {
write_to_stdin(ag.action(), &stdin)?; Ok(exit_status) => match exit_status.code().unwrap_or(-1) {
write_to_stdin(handle.metadata(), &stdin)?;
match child.wait() {
Ok(exit_status) => match exit_status.code().unwrap_or(0) {
1 => Err(Error::AgentError),
0 => { 0 => {
if self.keyring { if self.keyring {
ag.keyring_update(inshandle)?; ag.keyring_update(inshandle)?;
@ -80,29 +84,45 @@ impl Transaction for Commit {
ag.logger().log(format!("container {instance}'s {state} transaction complete")).ok(); ag.logger().log(format!("container {instance}'s {state} transaction complete")).ok();
state_transition(&self.state, handle, true) state_transition(&self.state, handle, true)
}, },
_ => Err(Error::TransactionFailure(format!("Generic failure of agent: Exit code {}", exit_status.code().unwrap_or(0))))?, 1 => Err(Error::TransactionFailureAgent),
}, 2 => Err(Error::ParameterAcquisitionFailure),
Err(error) => Err(Error::TransactionFailure(format!("Execution of agent failed: {}", error)))?, 3 => Err(Error::DeserializationFailure),
} 4 => Err(Error::InvalidMagicNumber),
5 => Err(Error::AgentVersionMismatch),
_ => Err(Error::TransactionFailure(format!("Generic failure of agent: Exit code {}", exit_status.code().unwrap_or(-1))))?,
}, },
Err(error) => Err(Error::TransactionFailure(format!("Execution of agent failed: {}", error)))?, Err(error) => Err(Error::TransactionFailure(format!("Execution of agent failed: {}", error)))?,
} }
} }
} }
fn write_to_stdin<T: for<'de> Serialize>(input: &T, stdin: &ChildStdin) -> Result<()> { fn write_agent_params(ag: &TransactionAggregator, handle: &TransactionHandle, download: (u64, usize)) -> Result<()> {
match ciborium::into_writer::<T, &ChildStdin>(input, stdin) { let f = match File::create(Path::new(*PACWRAP_AGENT_FILE)) {
Ok(f) => f,
Err(error) => Err(ErrorKind::IOError((*PACWRAP_AGENT_FILE).into(), error.kind()))?
};
serialize(&TransactionParameters::new(*ag.action(), *handle.get_mode(), download.0, download.1), &f)?;
serialize(&*DEFAULT_ALPM_CONF, &f)?;
serialize(handle.metadata(), &f)?;
Ok(())
}
fn serialize<T: for<'de> Serialize>(input: &T, file: &File) -> Result<()> {
match bincode::serialize_into::<&File, T>(file, input) {
Ok(()) => Ok(()), Ok(()) => Ok(()),
Err(error) => Err(Error::TransactionFailure(format!("Agent data serialization failed: {}", error))), Err(error) => Err(Error::TransactionFailure(format!("Agent data serialization failed: {}", error))),
} }
} }
fn confirm(state: &TransactionState, ag: &mut TransactionAggregator, handle: &mut TransactionHandle) -> Option<Result<TransactionState>> { fn confirm(state: &TransactionState, ag: &mut TransactionAggregator, handle: &mut TransactionHandle) -> (Option<Result<TransactionState>>, Option<(u64, usize)>) {
let mut download = None;
if ! handle.get_mode().bool() || ag.flags().intersects(TransactionFlags::DATABASE_ONLY | TransactionFlags::FORCE_DATABASE) { if ! handle.get_mode().bool() || ag.flags().intersects(TransactionFlags::DATABASE_ONLY | TransactionFlags::FORCE_DATABASE) {
summary(handle.alpm()); download = Some(summary(handle.alpm()));
if ag.flags().contains(TransactionFlags::PREVIEW) { if ag.flags().contains(TransactionFlags::PREVIEW) {
return Some(state_transition(state, handle, false)); return (Some(state_transition(state, handle, false)), None);
} }
if ! ag.flags().contains(TransactionFlags::NO_CONFIRM) { if ! ag.flags().contains(TransactionFlags::NO_CONFIRM) {
@ -110,13 +130,13 @@ fn confirm(state: &TransactionState, ag: &mut TransactionAggregator, handle: &mu
let query = format!("Proceed with {action}?"); let query = format!("Proceed with {action}?");
if let Err(_) = prompt("::", format!("{}{query}{}", *BOLD, *RESET), true) { if let Err(_) = prompt("::", format!("{}{query}{}", *BOLD, *RESET), true) {
return Some(state_transition(state, handle, false)); return (Some(state_transition(state, handle, false)), None);
} }
} }
} }
handle.alpm_mut().trans_release().ok(); handle.alpm_mut().trans_release().ok();
None (None, download)
} }
fn state_transition<'a>(state: &TransactionState, handle: &mut TransactionHandle, updated: bool) -> Result<TransactionState> { fn state_transition<'a>(state: &TransactionState, handle: &mut TransactionHandle, updated: bool) -> Result<TransactionState> {
@ -129,8 +149,7 @@ fn state_transition<'a>(state: &TransactionState, handle: &mut TransactionHandle
}) })
} }
#[allow(unused_variables)] fn summary(handle: &Alpm) -> (u64, usize) {
fn summary(handle: &Alpm) {
let mut installed_size_old: i64 = 0; let mut installed_size_old: i64 = 0;
let mut installed_size: i64 = 0; let mut installed_size: i64 = 0;
let mut download: i64 = 0; let mut download: i64 = 0;
@ -184,8 +203,8 @@ fn summary(handle: &Alpm) {
if download > 0 { if download > 0 {
println!("{}Total Download Size{}: {}", *BOLD, *RESET, download.to_byteunit(SI)); println!("{}Total Download Size{}: {}", *BOLD, *RESET, download.to_byteunit(SI));
//handle.set_dl_cb(DownloadCallback::new(download as u64, files_to_download), dl_event::download_event);
} }
println!(); println!();
(download as u64, files_to_download)
} }

View file

@ -60,18 +60,6 @@ impl AlpmUtils for Alpm {
} }
} }
pub fn whitespace(total: usize, current: usize) -> String {
let difference = total-current;
let mut whitespace = String::new();
if difference > 0 {
for _ in 0..difference {
whitespace.push_str(" ");
}
}
whitespace
}
pub fn erroneous_transaction<'a>(error: (CommitResult<'a>, alpm::Error)) -> Result<()> { pub fn erroneous_transaction<'a>(error: (CommitResult<'a>, alpm::Error)) -> Result<()> {
match error.0 { match error.0 {
CommitResult::FileConflict(file) => { CommitResult::FileConflict(file) => {

View file

@ -52,7 +52,6 @@ pub fn is_color_terminal() -> bool {
is_dumb && isatty(0).is_ok() && isatty(1).is_ok() is_dumb && isatty(0).is_ok() && isatty(1).is_ok()
} }
pub fn is_truecolor_terminal() -> bool { pub fn is_truecolor_terminal() -> bool {
let colorterm = match var("COLORTERM") { let colorterm = match var("COLORTERM") {
Ok(value) => { Ok(value) => {
@ -66,6 +65,10 @@ pub fn is_truecolor_terminal() -> bool {
is_color_terminal() && colorterm is_color_terminal() && colorterm
} }
pub fn read_le_32(vec: &Vec<u8>, pos: usize) -> u32 {
((vec[pos+0] as u32) << 0) + ((vec[pos+1] as u32) << 8) + ((vec[pos+2] as u32) << 16) + ((vec[pos+3] as u32) << 24)
}
fn wait_on_process(mut child: Child) { fn wait_on_process(mut child: Child) {
child.wait().ok(); child.wait().ok();
} }

View file

@ -154,5 +154,3 @@ impl Display for InvalidArgument {
} }
} }
} }