Structural simplification, rustfmt configuration applied, and snake_case

for declared modules within tag vars

- For the sake of coherency, there are now four types of containers:
  Symbolic, Base, Slice, and Aggregate. These names better reflect the
  associated container types.
- Global configuration template is now written to disk prior to
  instantiation
- Inclusion of .rustfmt.toml with formatting applied to source files
- Breaking format change: SCREAMING_CASE has been replaced with
  snake_case for tag variables
- Simplified data structure in cache module
- InstanceCache type within the cache module replaces 'registered_base',
  'registered_dep', and 'registered_root', with 'filter'.
- Cleaned up argument parsing match statements in the front-end modules.
- Specifying dependencies with a comma deliniation is now supported
- Manual updated to reflect argument changes.
- query module updated to utilise internal APIs
- Some minor improvements to error handling.
- Removed redundant calls to --clear-env with bubblewrap.
- Scripting no longer outputs ANSI charcodes to unsupported terminals
This commit is contained in:
Xavier Moffett 2024-01-14 20:45:59 -05:00
parent 3b5951cea6
commit 9b495390d2
71 changed files with 2781 additions and 2398 deletions

16
.rustfmt.toml Normal file
View file

@ -0,0 +1,16 @@
unstable_features = true
indent_style = "Block"
imports_indent = "Block"
imports_layout = "HorizontalVertical"
imports_granularity = "Crate"
brace_style = "PreferSameLine"
match_arm_leading_pipes = "Never"
match_arm_blocks = false
condense_wildcard_suffixes = true
overflow_delimited_expr = false
spaces_around_ranges = true
reorder_imports = true
hard_tabs = false
max_width = 130
fn_call_width = 120
chain_width = 90

View file

@ -355,7 +355,7 @@ replicate_instance() {
[[ $type != BASE ]] && depend=$(return_dependency)
case $type in
ROOT) params+="r";;
ROOT) params+="a";;
BASE) params+="b";;
DEP) params+="s";;
LINK) ln -s "$INSTANCE_ROOT_DIR/$depend" "$INSTANCE_ROOT_DIR/$instance"

11
dist/tools/clean.sh vendored
View file

@ -18,10 +18,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
BOLD=$(tput bold)
GREEN=$(tput setaf 2)
RED=$(tput setaf 1)
RESET=$(tput sgr0)
if ! [[ -z $COLORTERM ]] || [[ $TERM == "dummy" ]]; then
BOLD=$(tput bold)
GREEN=$(tput setaf 2)
RED=$(tput setaf 1)
RESET=$(tput sgr0)
fi
DIST_RUNTIME="./dist/runtime"
DIST_BASE="./dist/pacwrap-base-dist"
DIST_REPO="./dist/repo"

11
dist/tools/runtime.sh vendored
View file

@ -21,10 +21,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
BOLD=$(tput bold)
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
RESET=$(tput sgr0)
if ! [[ -z $COLORTERM ]] || [[ $TERM == "dummy" ]]; then
BOLD=$(tput bold)
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
RESET=$(tput sgr0)
fi
LIB_DIR="/lib"
BIN_DIR="/bin"
DEST_DIR="./dist/runtime"

View file

@ -1,6 +1,6 @@
/*
* pacwrap-agent
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -18,7 +18,7 @@
*/
fn main() {
if ! cfg!(target_os="linux") || ! cfg!(target_family="unix") {
if !cfg!(target_os = "linux") || !cfg!(target_family = "unix") {
panic!("Unsupported build target. Please refer to the documentation for further information.")
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-agent
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -16,57 +16,64 @@
* 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::{fs::{self, File}, io::ErrorKind::NotFound, os::unix::prelude::FileExt, env};
use std::{
env,
fs::{self, File},
io::ErrorKind::NotFound,
os::unix::prelude::FileExt,
};
use serde::Deserialize;
use pacwrap_core::{err,
use pacwrap_core::{
config::Global,
err,
sync::{
self,
event::{
download::{self, DownloadEvent},
progress::{self, ProgressEvent},
query,
},
transaction::{TransactionHandle, TransactionMetadata, TransactionParameters, TransactionType, MAGIC_NUMBER},
utils::{erroneous_preparation, erroneous_transaction},
AlpmConfigData,
SyncError,
},
utils::{print_warning, read_le_32},
Error,
Result,
sync::{self,
SyncError,
AlpmConfigData,
utils::{erroneous_transaction,
erroneous_preparation},
transaction::{TransactionHandle,
TransactionType,
TransactionMetadata,
TransactionParameters,
MAGIC_NUMBER},
event::{download::{self, DownloadEvent},
progress::{self, ProgressEvent},
query}},
utils::{print_warning, read_le_32}, config::Global};
};
use crate::error::AgentError;
static AGENT_PARAMS: &'static str = "/mnt/agent_params";
pub fn transact() -> Result<()> {
let mut header_buffer = vec![0; 7];
let mut header_buffer = vec![0; 7];
let mut file = match File::open(AGENT_PARAMS) {
Ok(file) => file,
Err(error) => {
if let Ok(var) = env::var("SHELL") {
if ! var.is_empty() {
if !var.is_empty() {
err!(AgentError::DirectExecution)?
}
}
err!(AgentError::IOError(AGENT_PARAMS, error.kind()))?
},
};
}
};
if let Err(error) = file.read_exact_at(&mut header_buffer, 0) {
err!(AgentError::IOError(AGENT_PARAMS, error.kind()))?
}
decode_header(&header_buffer)?;
let params: TransactionParameters = deserialize(&mut file)?;
let config: Global = deserialize(&mut file)?;
let alpm_remotes: AlpmConfigData = deserialize(&mut file)?;
let mut metadata: TransactionMetadata = deserialize(&mut file)?;
let mut metadata: TransactionMetadata = deserialize(&mut file)?;
let alpm = sync::instantiate_alpm_agent(&config, &alpm_remotes);
let mut handle = TransactionHandle::new(&config, alpm, &mut metadata);
@ -74,11 +81,11 @@ pub fn transact() -> Result<()> {
}
fn conduct_transaction(config: &Global, handle: &mut TransactionHandle, agent: TransactionParameters) -> Result<()> {
let flags = handle.retrieve_flags();
let flags = handle.retrieve_flags();
let mode = agent.mode();
let action = agent.action();
let config = config.config();
let progress = config.progress();
let pkind = config.progress();
let bytes = agent.bytes();
let files = agent.files();
@ -86,12 +93,12 @@ fn conduct_transaction(config: &Global, handle: &mut TransactionHandle, agent: T
err!(SyncError::InitializationFailure(error.to_string().into()))?
}
handle.ignore(true);
handle.ignore(true);
if let TransactionType::Upgrade(upgrade, downgrade, _) = action {
if let TransactionType::Upgrade(upgrade, downgrade, _) = action {
if upgrade {
handle.alpm().sync_sysupgrade(downgrade).unwrap();
}
}
}
handle.prepare(&action, &flags.0.unwrap())?;
@ -100,17 +107,12 @@ fn conduct_transaction(config: &Global, handle: &mut TransactionHandle, agent: T
erroneous_preparation(error)?
}
let progress_cb = ProgressEvent::new()
.style(progress.0)
.configure(&action);
let download_cb = DownloadEvent::new()
.style(progress.0)
.total(bytes, files)
.configure(&mode, progress.1);
let progress_cb = ProgressEvent::new().style(pkind.0).configure(&action);
let download_cb = DownloadEvent::new().style(pkind.0).total(bytes, files).configure(&mode, pkind.1);
handle.alpm().set_question_cb((), query::callback);
handle.alpm().set_progress_cb(progress_cb, progress::callback(&mode, progress.0));
handle.alpm().set_dl_cb(download_cb, download::callback(progress.1));
handle.alpm().set_progress_cb(progress_cb, progress::callback(&mode, pkind.0));
handle.alpm().set_dl_cb(download_cb, download::callback(pkind.1));
if let Err(error) = handle.alpm_mut().trans_commit() {
erroneous_transaction(error)?
@ -121,7 +123,8 @@ fn conduct_transaction(config: &Global, handle: &mut TransactionHandle, agent: T
if let Err(error) = fs::copy("/etc/ld.so.cache", "/mnt/fs/etc/ld.so.cache") {
match error.kind() {
NotFound => (), _ => print_warning(format!("Failed to propagate ld.so.cache: {}", error)),
NotFound => (),
_ => print_warning(format!("Failed to propagate ld.so.cache: {}", error)),
}
}
@ -139,7 +142,7 @@ fn decode_header(buffer: &Vec<u8>) -> Result<()> {
}
if major.0 != major.1 || minor.0 != minor.1 || patch.0 != patch.1 {
err!(AgentError::InvalidVersion(major.0,minor.0,patch.0,major.1,minor.1,patch.1))?;
err!(AgentError::InvalidVersion(major.0, minor.0, patch.0, major.1, minor.1, patch.1))?;
}
Ok(())
@ -148,6 +151,6 @@ fn decode_header(buffer: &Vec<u8>) -> Result<()> {
fn deserialize<T: for<'de> Deserialize<'de>>(stdin: &mut File) -> Result<T> {
match bincode::deserialize_from::<&mut File, T>(stdin) {
Ok(meta) => Ok(meta),
Err(error) => err!(AgentError::DeserializationError(error.as_ref().to_string()))
Err(error) => err!(AgentError::DeserializationError(error.as_ref().to_string())),
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-agent
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,25 +19,30 @@
use std::fmt::{Display, Formatter};
use pacwrap_core::{ErrorTrait, constants::{RESET, BOLD}};
use pacwrap_core::{
constants::{BOLD, RESET},
ErrorTrait,
};
#[derive(Debug)]
pub enum AgentError {
DeserializationError(String),
InvalidVersion(u8,u8,u8,u8,u8,u8),
InvalidVersion(u8, u8, u8, u8, u8, u8),
InvalidMagic(u32, u32),
IOError(&'static str, std::io::ErrorKind),
DirectExecution,
}
impl Display for AgentError {
fn fmt(&self, fmter: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
fn fmt(&self, fmter: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
Self::DirectExecution => write!(fmter, "Direct execution of this binary is unsupported."),
Self::InvalidMagic(magic, comparator) => write!(fmter, "Magic mismatch {} != {}", magic, comparator),
Self::InvalidVersion(a, b, c, d, e, f) => write!(fmter, "Version mismatch {}.{}.{} != {}.{}.{}", a, b, c, d, e, f),
Self::InvalidVersion(a, b, c, d, e, f) => {
write!(fmter, "Version mismatch {}.{}.{} != {}.{}.{}", a, b, c, d, e, f)
}
Self::DeserializationError(error) => write!(fmter, "Deserilization error: {}", error),
Self::IOError(file, error) => write!(fmter, "'{}{}{}' {}", *BOLD, file, *RESET, error)
Self::IOError(file, error) => write!(fmter, "'{}{}{}' {}", *BOLD, file, *RESET, error),
}
}
}
@ -49,7 +54,7 @@ impl ErrorTrait for AgentError {
Self::InvalidVersion(..) => 4,
Self::DeserializationError(..) => 3,
Self::IOError(..) => 2,
_ => 1
_ => 1,
}
}
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-agent
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,20 +17,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use pacwrap_core::{err, Error, utils::{Arguments, arguments::Operand}};
use pacwrap_core::{
err,
utils::{arguments::Operand, Arguments},
Error,
};
use crate::error::AgentError;
mod error;
mod agent;
mod error;
fn main() {
let arguments = &mut Arguments::new().populate();
let param = arguments.next().unwrap_or_default();
let result = match param {
Operand::Value("transact") => agent::transact(), _ => err!(AgentError::DirectExecution)
Operand::Value("transact") => agent::transact(),
_ => err!(AgentError::DirectExecution),
};
if let Err(error) = result {
error.handle();
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -27,7 +27,7 @@ fn dist_repo() -> String {
}
fn main() {
if ! cfg!(target_os="linux") || ! cfg!(target_family="unix") {
if !cfg!(target_os = "linux") || !cfg!(target_family = "unix") {
panic!("Unsupported build target. Please refer to the documentation for further information.")
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,35 +17,42 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{fmt::Display,
io::{Write, ErrorKind::NotFound},
fmt::Formatter,
path::Path,
fs::File};
use std::{
fmt::{Display, Formatter},
fs::File,
io::{ErrorKind::NotFound, Write},
path::Path,
};
use serde::Serialize;
use crate::{err, impl_error, ErrorKind, error::*, constants::{BOLD, RESET, CONFIG_FILE}};
use crate::{
constants::{BOLD, CONFIG_FILE, RESET},
err,
error::*,
impl_error,
ErrorKind,
};
pub use self::{cache::InstanceCache,
instance::{Instance,
InstanceHandle,
InstanceType},
vars::InsVars,
filesystem::{Filesystem, BindError},
permission::{Permission, PermError},
pub use self::{
cache::InstanceCache,
dbus::Dbus,
global::{Global, CONFIG}};
filesystem::{BindError, Filesystem},
global::{Global, CONFIG},
instance::{Instance, InstanceHandle, InstanceType},
permission::{PermError, Permission},
vars::InsVars,
};
pub mod vars;
pub mod filesystem;
pub mod permission;
pub mod dbus;
pub mod cache;
pub mod instance;
pub mod init;
pub mod register;
pub mod dbus;
pub mod filesystem;
pub mod global;
pub mod init;
pub mod instance;
pub mod permission;
pub mod register;
pub mod vars;
#[derive(Debug, Clone)]
pub enum ConfigError {
@ -61,13 +68,13 @@ impl_error!(ConfigError);
impl Display for ConfigError {
fn fmt(&self, fmter: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
Self::Filesystem(module, err) => write!(fmter, "Failed to register filesystem {}: {} ", module, err),
Self::Permission(module, err) => write!(fmter, "Failed to register permission {}: {} ", module, err),
match self {
Self::Filesystem(module, err) => write!(fmter, "Failed to register filesystem '{}': {} ", module, err),
Self::Permission(module, err) => write!(fmter, "Failed to register permission '{}': {} ", module, err),
Self::Load(ins, error) => write!(fmter, "Failed to load '{ins}': {error}"),
Self::Save(ins, error) => write!(fmter, "Failed to save '{ins}': {error}"),
Self::AlreadyExists(ins) => write!(fmter, "Container {}{ins}{} already exists.", *BOLD, *RESET),
Self::ConfigNotFound(ins) => write!(fmter, "Configuration '{}{ins}{}.yml' not found.", *BOLD, *RESET)
Self::ConfigNotFound(ins) => write!(fmter, "Configuration '{}{ins}{}' not found.", *BOLD, *RESET),
}
}
}
@ -79,10 +86,10 @@ impl From<ConfigError> for String {
}
#[inline]
pub fn provide_handle(instance: &str) -> Result<InstanceHandle> {
pub fn provide_handle(instance: &str) -> Result<InstanceHandle> {
let vars = InsVars::new(instance);
if ! Path::new(vars.root()).exists() {
if !Path::new(vars.root()).exists() {
err!(ErrorKind::InstanceNotFound(instance.into()))?
}
@ -94,19 +101,19 @@ pub fn provide_new_handle(instance: &str) -> Result<InstanceHandle> {
handle(instance, InsVars::new(instance))
}
fn save<T: Serialize>(obj: &T, path: &str) -> Result<()> {
fn save<T: Serialize>(obj: &T, path: &str) -> Result<()> {
let mut f = match File::create(Path::new(path)) {
Ok(f) => f,
Err(error) => err!(ErrorKind::IOError(path.into(), error.kind()))?
Err(error) => err!(ErrorKind::IOError(path.into(), error.kind()))?,
};
let config = match serde_yaml::to_string(&obj) {
Ok(file) => file,
Err(error) => err!(ConfigError::Save(path.into(), error.to_string()))?
Err(error) => err!(ConfigError::Save(path.into(), error.to_string()))?,
};
match write!(f, "{}", config) {
Ok(_) => Ok(()),
Err(error) => err!(ErrorKind::IOError(path.into(), error.kind()))
Err(error) => err!(ErrorKind::IOError(path.into(), error.kind())),
}
}
@ -115,15 +122,15 @@ fn handle<'a>(instance: &str, vars: InsVars<'a>) -> Result<InstanceHandle<'a>> {
Ok(file) => {
let config = match serde_yaml::from_reader(&file) {
Ok(file) => file,
Err(error) => err!(ConfigError::Load(vars.instance().into(), error.to_string()))?
Err(error) => err!(ConfigError::Load(vars.instance().into(), error.to_string()))?,
};
Ok(InstanceHandle::new(config, vars))
},
}
Err(error) => match error.kind() {
NotFound => err!(ConfigError::ConfigNotFound(instance.into()))?,
_ => err!(ErrorKind::IOError(vars.config_path().into(), error.kind()))?,
}
},
}
}
@ -133,11 +140,11 @@ fn config() -> Result<Global> {
match File::open(*CONFIG_FILE) {
Ok(file) => match serde_yaml::from_reader(&file) {
Ok(file) => Ok(file),
Err(error) => err!(ConfigError::Load(CONFIG_FILE.to_string(), error.to_string()))?
Err(error) => err!(ConfigError::Load(CONFIG_FILE.to_string(), error.to_string()))?,
},
Err(error) => match error.kind() {
NotFound => err!(ConfigError::ConfigNotFound(CONFIG_FILE.to_string()))?,
_ => err!(ErrorKind::IOError(CONFIG_FILE.to_string(), error.kind()))?,
}
},
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,33 +19,24 @@
use std::{collections::HashMap, fs::read_dir};
use crate::{err,
ErrorKind,
use crate::{
config::{self, InstanceHandle},
constants::DATA_DIR,
err,
error::*,
constants::DATA_DIR,
config::{self, InstanceHandle}};
ErrorKind,
};
use super::{InsVars,
ConfigError,
Instance,
instance::InstanceType};
use super::{instance::InstanceType, ConfigError, InsVars, Instance};
pub struct InstanceCache<'a> {
instances: HashMap<&'a str, InstanceHandle<'a>>,
registered: Vec<&'a str>,
registered_base: Vec<&'a str>,
registered_dep: Vec<&'a str>,
registered_root: Vec<&'a str>
}
impl <'a>InstanceCache<'a> {
impl<'a> InstanceCache<'a> {
pub fn new() -> Self {
Self {
instances: HashMap::new(),
registered: Vec::new(),
registered_base: Vec::new(),
registered_dep: Vec::new(),
registered_root: Vec::new(),
}
}
@ -57,87 +48,84 @@ impl <'a>InstanceCache<'a> {
for dep in deps.iter() {
if let None = self.instances.get(dep) {
err!(ErrorKind::DependencyNotFound((*dep).into(), ins.into()))?
}
}
}
let deps = deps.iter().map(|a| (*a).into()).collect();
let handle = match config::provide_new_handle(ins) {
Ok(mut handle) => {
handle.metadata_mut().set(deps, vec!());
handle
},
Ok(mut handle) => {
handle.metadata_mut().set(deps, vec![]);
handle
}
Err(err) => match err.downcast::<ConfigError>() {
Ok(error) => match error {
ConfigError::ConfigNotFound(_) => {
let vars = InsVars::new(ins);
let cfg = Instance::new(instype, deps, vec!());
InstanceHandle::new(cfg, vars)
},
let vars = InsVars::new(ins);
let cfg = Instance::new(instype, deps, vec![]);
InstanceHandle::new(cfg, vars)
}
_ => Err(err)?,
}
},
_ => Err(err)?,
},
};
Ok(self.register(ins, handle))
Ok(self.register(ins, handle))
}
fn map(&mut self, ins: &'a str) -> Result<()> {
fn map(&mut self, ins: &'a str) -> Result<()> {
if let Some(_) = self.instances.get(ins) {
err!(ConfigError::AlreadyExists(ins.to_owned()))?
}
Ok(self.register(ins, match config::provide_handle(ins) {
Ok(ins) => ins,
Err(error) => {
error.warn();
return Ok(())
}
}))
Ok(self.register(
ins,
match config::provide_handle(ins) {
Ok(ins) => ins,
Err(error) => {
error.warn();
return Ok(());
}
},
))
}
fn register(&mut self, ins: &'a str, handle: InstanceHandle<'a>) {
match handle.metadata().container_type() {
InstanceType::BASE => self.registered_base.push(ins),
InstanceType::DEP => self.registered_dep.push(ins),
InstanceType::ROOT => self.registered_root.push(ins),
InstanceType::LINK => return,
}
if let InstanceType::Symbolic = handle.metadata().container_type() {
return;
}
self.instances.insert(ins, handle);
self.registered.push(ins);
}
pub fn registered(&self) -> &Vec<&'a str> {
&self.registered
}
pub fn registered_base(&self) -> &Vec<&'a str> {
&self.registered_base
}
pub fn registered_dep(&self) -> &Vec<&'a str> {
&self.registered_dep
pub fn registered(&self) -> Vec<&'a str> {
self.instances.iter().map(|a| *a.0).collect()
}
pub fn registered_root(&self) -> &Vec<&'a str> {
&self.registered_root
pub fn filter(&self, filter: Vec<InstanceType>) -> Vec<&'a str> {
self.instances
.iter()
.filter(|a| filter.contains(a.1.metadata().container_type()))
.map(|a| *a.0)
.collect()
}
pub fn obtain_base_handle(&self) -> Option<&InstanceHandle> {
match self.registered_base.get(0) {
Some(instance) => self.instances.get(instance), None => None,
match self.filter(vec![InstanceType::Base]).get(0) {
Some(instance) => self.instances.get(instance),
None => None,
}
}
pub fn get_instance(&self, ins: &str) -> Result<&InstanceHandle> {
pub fn get_instance(&self, ins: &str) -> Result<&InstanceHandle> {
match self.instances.get(ins) {
Some(ins) => Ok(ins), None => err!(ErrorKind::InstanceNotFound(ins.into())),
Some(ins) => Ok(ins),
None => err!(ErrorKind::InstanceNotFound(ins.into())),
}
}
pub fn get_instance_option(&self, ins: &str) -> Option<&InstanceHandle> {
pub fn get_instance_option(&self, ins: &str) -> Option<&InstanceHandle> {
self.instances.get(ins)
}
}
@ -156,22 +144,25 @@ pub fn populate<'a>() -> Result<InstanceCache<'a>> {
populate_from(&roots()?)
}
fn roots<'a>() -> Result<Vec<&'a str>> {
fn roots<'a>() -> Result<Vec<&'a str>> {
match read_dir(format!("{}/root", *DATA_DIR)) {
Ok(dir) => Ok(dir.filter(|f| match f {
Ok(f) => match f.metadata() {
Ok(meta) => meta.is_dir() | meta.is_symlink(), Err(_) => false
},
Err(_) => false
})
.map(|s| match s {
Ok(dir) => Ok(dir
.filter(|f| match f {
Ok(f) => match f.metadata() {
Ok(meta) => meta.is_dir() | meta.is_symlink(),
Err(_) => false,
},
Err(_) => false,
})
.map(|s| match s {
Ok(f) => match f.file_name().to_str() {
Some(f) => f.to_owned().leak(), None => "",
Some(f) => f.to_owned().leak(),
None => "",
},
Err(_) => "",
})
.filter(|e| ! e.is_empty())
.collect()),
.filter(|e| !e.is_empty())
.collect()),
Err(error) => err!(ErrorKind::IOError(format!("{}/root", *DATA_DIR), error.kind())),
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,13 +19,13 @@
use crate::exec::args::ExecutionArgs;
use dyn_clone::{DynClone, clone_trait_object};
use dyn_clone::{clone_trait_object, DynClone};
mod socket;
mod appindicator;
mod socket;
mod xdg_portal;
#[typetag::serde(tag = "permission")]
#[typetag::serde(tag = "module")]
pub trait Dbus: DynClone {
fn register(&self, args: &mut ExecutionArgs);
}

View file

@ -3,10 +3,10 @@ use serde::{Deserialize, Serialize};
use crate::{config::Dbus, exec::args::ExecutionArgs};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct APPINDICATOR;
struct AppIndicator;
#[typetag::serde]
impl Dbus for APPINDICATOR {
#[typetag::serde(name = "appindicator")]
impl Dbus for AppIndicator {
fn register(&self, args: &mut ExecutionArgs) {
args.dbus("broadcast", "org.kde.StatusNotifierWatcher=@/StatusNotifierWatcher");
}

View file

@ -3,22 +3,20 @@ use serde::{Deserialize, Serialize};
use crate::{config::Dbus, exec::args::ExecutionArgs};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SOCKET {
struct Socket {
socket: String,
address: Vec<String>
address: Vec<String>,
}
#[typetag::serde]
impl Dbus for SOCKET {
#[typetag::serde(name = "socket")]
impl Dbus for Socket {
fn register(&self, args: &mut ExecutionArgs) {
match self.socket.to_lowercase().as_str() {
p if p == "call" || p == "talk" || p == "see" || p == "own" || p == "broadcast" => {
p if p == "call" || p == "talk" || p == "see" || p == "own" || p == "broadcast" =>
for sock in self.address.iter() {
args.dbus(p, sock);
}
},
},
&_ => {}
}
}
}

View file

@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize};
use crate::{config::Dbus, exec::args::ExecutionArgs};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct XDG_PORTAL;
struct XdgPortal;
#[typetag::serde]
impl Dbus for XDG_PORTAL {
fn register(&self, args: &mut ExecutionArgs) {
#[typetag::serde(name = "xdg_portal")]
impl Dbus for XdgPortal {
fn register(&self, args: &mut ExecutionArgs) {
args.dbus("call", "org.freedesktop.portal.*=*");
args.dbus("broadcast", "org.freedesktop.portal.*=@/org/freedesktop/portal/*");
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,22 +19,21 @@
use std::fmt::{Display, Formatter};
use crate::exec::args::ExecutionArgs;
use crate::config::InsVars;
use crate::{config::InsVars, exec::args::ExecutionArgs};
use dyn_clone::{DynClone, clone_trait_object};
use dyn_clone::{clone_trait_object, DynClone};
mod dir;
pub mod home;
pub mod root;
mod sys;
mod to_home;
mod to_root;
mod dir;
mod sys;
pub enum Condition {
Success,
SuccessWarn(String),
Nothing
Nothing,
}
#[derive(Debug, Clone)]
@ -48,7 +47,6 @@ impl Display for BindError {
match self {
Self::Fail(error) => write!(fmter, "{}", error),
Self::Warn(error) => write!(fmter, "{}", error),
}
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,23 +19,27 @@
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::InsVars,
config::filesystem::{Filesystem, BindError}};
use crate::{
config::{
filesystem::{BindError, Filesystem},
InsVars,
},
exec::args::ExecutionArgs,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DIR {
pub struct Dir {
#[serde(default)]
path: Vec<String>
path: Vec<String>,
}
#[typetag::serde]
impl Filesystem for DIR {
#[typetag::serde(name = "dir")]
impl Filesystem for Dir {
fn check(&self, _vars: &InsVars) -> Result<(), BindError> {
if self.path.len() == 0 {
Err(BindError::Fail(format!("Path not specified.")))?
}
Ok(())
}
@ -46,6 +50,6 @@ impl Filesystem for DIR {
}
fn module(&self) -> &'static str {
"DIR"
"dir"
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -21,18 +21,22 @@ use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::InsVars,
config::filesystem::{Filesystem, BindError}};
use crate::{
config::{
filesystem::{BindError, Filesystem},
InsVars,
},
exec::args::ExecutionArgs,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HOME;
pub struct Home;
#[typetag::serde]
impl Filesystem for HOME {
#[typetag::serde(name = "home")]
impl Filesystem for Home {
fn check(&self, vars: &InsVars) -> Result<(), BindError> {
if ! Path::new(vars.home()).exists() {
Err(BindError::Fail(format!("INSTANCE_HOME not found.")))?
if !Path::new(vars.home()).exists() {
Err(BindError::Fail(format!("Specified home directory not found.")))?
}
Ok(())
}
@ -40,10 +44,10 @@ impl Filesystem for HOME {
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
args.bind(vars.home(), vars.home_mount());
args.env("HOME", vars.home_mount());
args.env("USER", vars.user());
args.env("USER", vars.user());
}
fn module(&self) -> &'static str {
"HOME"
"home"
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -21,23 +21,27 @@ use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::InsVars,
config::filesystem::{Filesystem, BindError}};
use crate::{
config::{
filesystem::{BindError, Filesystem},
InsVars,
},
exec::args::ExecutionArgs,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ROOT;
pub struct Root;
#[typetag::serde]
impl Filesystem for ROOT {
#[typetag::serde(name = "root")]
impl Filesystem for Root {
fn check(&self, vars: &InsVars) -> Result<(), BindError> {
if ! Path::new(vars.root()).exists() {
if !Path::new(vars.root()).exists() {
Err(BindError::Fail(format!("Container {} not found. ", vars.instance())))?
}
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
args.robind(format!("{}/usr", vars.root()), "/usr");
args.robind(format!("{}/etc", vars.root()), "/etc");
args.symlink("/usr/lib", "/lib");
@ -47,6 +51,6 @@ impl Filesystem for ROOT {
}
fn module(&self) -> &'static str {
"ROOT"
"root"
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -16,41 +16,44 @@
* 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::path::Path;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::InsVars,
config::filesystem::{Filesystem, BindError}};
use crate::{
config::{
filesystem::{BindError, Filesystem},
InsVars,
},
exec::args::ExecutionArgs,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SYSFS {
#[serde(skip_serializing_if = "is_default_path", default = "default_path")]
path: Vec<String>
struct System {
#[serde(skip_serializing_if = "is_default_path", default = "default_path")]
path: Vec<String>,
}
#[typetag::serde]
impl Filesystem for SYSFS {
fn check(&self, _vars: &InsVars) -> Result<(), BindError> {
#[typetag::serde(name = "sysfs")]
impl Filesystem for System {
fn check(&self, _vars: &InsVars) -> Result<(), BindError> {
for dir in self.path.iter() {
if ! Path::new(&format!("/sys/{}",dir)).exists() {
if !Path::new(&format!("/sys/{}", dir)).exists() {
Err(BindError::Fail(format!("/sys/{} is inaccessible.", dir)))?
}
}
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, _: &InsVars) {
for dir in self.path.iter() {
fn register(&self, args: &mut ExecutionArgs, _: &InsVars) {
for dir in self.path.iter() {
args.robind(format!("/sys/{}", dir), format!("/sys/{}", dir));
}
}
fn module(&self) -> &'static str {
"SUSFS"
"sysfs"
}
}
@ -59,9 +62,5 @@ fn is_default_path(path: &Vec<String>) -> bool {
}
fn default_path() -> Vec<String> {
vec!("block".into(),
"bus".into(),
"class".into(),
"dev".into(),
"devices".into())
vec!["block".into(), "bus".into(), "class".into(), "dev".into(), "devices".into()]
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -16,57 +16,48 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#![allow(non_camel_case_types)]
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::InsVars,
config::filesystem::{Filesystem,
BindError,
default_permission,
is_default_permission},
constants::HOME};
use crate::{
config::{
filesystem::{default_permission, is_default_permission, BindError, Filesystem},
InsVars,
},
constants::HOME,
exec::args::ExecutionArgs,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TO_HOME {
#[serde(skip_serializing_if = "is_default_permission", default = "default_permission")]
permission: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
path: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
filesystem: Vec<Mount>
pub struct ToHome {
#[serde(skip_serializing_if = "Vec::is_empty", default, rename = "volumes")]
mounts: Vec<Mount>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Mount {
#[serde(skip_serializing_if = "is_default_permission", default = "default_permission")]
#[serde(skip_serializing_if = "is_default_permission", default = "default_permission")]
permission: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
path: Vec<String>
#[serde(skip_serializing_if = "String::is_empty", default)]
path: String,
#[serde(skip_serializing_if = "String::is_empty", default)]
dest: String,
}
#[typetag::serde]
impl Filesystem for TO_HOME {
#[typetag::serde(name = "to_home")]
impl Filesystem for ToHome {
fn check(&self, _vars: &InsVars) -> Result<(), BindError> {
if self.path.len() > 0 {
if let Err(e) = check_mount(&self.permission, &self.path[0]) {
return Err(e);
}
} else {
if self.filesystem.len() == 0 {
Err(BindError::Warn(format!("Filesystem paths are undeclared.")))?
}
if self.mounts.len() == 0 {
Err(BindError::Warn(format!("Mount volumes undeclared.")))?
}
for m in self.filesystem.iter() {
for m in self.mounts.iter() {
if m.path.len() == 0 {
Err(BindError::Warn(format!("Filesystem paths are undeclared.")))?
Err(BindError::Warn(format!("Mount volumes undeclared.")))?
}
if let Err(e) = check_mount(&m.permission, &m.path[0]) {
if let Err(e) = check_mount(&m.permission, &m.path) {
return Err(e);
}
}
@ -75,45 +66,38 @@ impl Filesystem for TO_HOME {
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
if self.path.len() > 0 {
bind_filesystem(args,vars, &self.permission, &self.path);
}
for m in self.filesystem.iter() {
bind_filesystem(args,vars, &m.permission, &m.path);
for m in self.mounts.iter() {
bind_filesystem(args, vars, &m.permission, &m.path, &m.dest);
}
}
fn module(&self) -> &'static str {
"TO_HOME"
"to_home"
}
}
fn bind_filesystem(args: &mut ExecutionArgs, vars: &InsVars, permission: &str, path: &Vec<String>) {
let src = &path[0];
let mut dest: &String = src;
if path.len() > 1 {
dest = &path[1];
}
let path_src = format!("{}/{}", *HOME, path[0]);
let path_dest = format!("{}/{}", vars.home_mount(), dest);
fn bind_filesystem(args: &mut ExecutionArgs, vars: &InsVars, permission: &str, src: &str, dest: &str) {
let dest = match dest.is_empty() {
false => dest,
true => src,
};
let dest = format!("{}/{}", vars.home_mount(), dest);
let src = format!("{}/{}", *HOME, src);
match permission == "rw" {
false => args.robind(path_src, path_dest),
true => args.bind(path_src, path_dest),
false => args.robind(src, dest),
true => args.bind(src, dest),
}
}
fn check_mount(permission: &String, path: &String) -> Result<(), BindError> {
let per = permission.to_lowercase();
let per = permission.to_lowercase();
if per != "ro" && per != "rw" {
Err(BindError::Fail(format!("{} is an invalid permission.", permission)))?
}
if ! Path::new(&format!("{}/{}", *HOME, &path)).exists() {
if !Path::new(&format!("{}/{}", *HOME, &path)).exists() {
Err(BindError::Fail(format!("~/{} not found.", path)))?
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -16,56 +16,47 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#![allow(non_camel_case_types)]
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::InsVars,
config::filesystem::{Filesystem,
BindError,
default_permission,
is_default_permission}};
use crate::{
config::{
filesystem::{default_permission, is_default_permission, BindError, Filesystem},
InsVars,
},
exec::args::ExecutionArgs,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TO_ROOT {
#[serde(skip_serializing_if = "is_default_permission", default = "default_permission")]
permission: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
path: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
filesystem: Vec<Mount>
pub struct ToRoot {
#[serde(skip_serializing_if = "Vec::is_empty", default, rename = "volumes")]
mounts: Vec<Mount>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Mount {
#[serde(skip_serializing_if = "is_default_permission", default = "default_permission")]
#[serde(skip_serializing_if = "is_default_permission", default = "default_permission")]
permission: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
path: Vec<String>
#[serde(skip_serializing_if = "String::is_empty", default)]
path: String,
#[serde(skip_serializing_if = "String::is_empty", default)]
dest: String,
}
#[typetag::serde]
impl Filesystem for TO_ROOT {
#[typetag::serde(name = "to_root")]
impl Filesystem for ToRoot {
fn check(&self, _vars: &InsVars) -> Result<(), BindError> {
if self.path.len() > 0 {
if let Err(e) = check_mount(&self.permission, &self.path[0]) {
return Err(e);
}
} else {
if self.filesystem.len() == 0 {
Err(BindError::Warn(format!("Filesystem paths are undeclared.")))?
}
if self.mounts.len() == 0 {
Err(BindError::Warn(format!("Mount volumes undeclared.")))?
}
for m in self.filesystem.iter() {
for m in self.mounts.iter() {
if m.path.len() == 0 {
Err(BindError::Warn(format!("Filesystem paths are undeclared.")))?
Err(BindError::Warn(format!("Mount volumes undeclared.")))?
}
if let Err(e) = check_mount(&m.permission, &m.path[0]) {
if let Err(e) = check_mount(&m.permission, &m.path) {
return Err(e);
}
}
@ -74,28 +65,22 @@ impl Filesystem for TO_ROOT {
}
fn register(&self, args: &mut ExecutionArgs, _vars: &InsVars) {
if self.path.len() > 0 {
bind_filesystem(args, &self.permission, &self.path);
}
for m in self.filesystem.iter() {
bind_filesystem(args, &m.permission, &m.path);
for m in self.mounts.iter() {
bind_filesystem(args, &m.permission, &m.path, &m.dest);
}
}
fn module(&self) -> &'static str {
"TO_HOME"
"to_root"
}
}
fn bind_filesystem(args: &mut ExecutionArgs, permission: &str, path: &Vec<String>) {
let src = &path[0];
let mut dest: &String = src;
fn bind_filesystem(args: &mut ExecutionArgs, permission: &str, src: &str, dest: &str) {
let dest = match dest.is_empty() {
true => src,
false => dest,
};
if path.len() > 1 {
dest = &path[1];
}
match permission == "rw" {
false => args.robind(src, dest),
true => args.bind(src, dest),
@ -103,15 +88,15 @@ fn bind_filesystem(args: &mut ExecutionArgs, permission: &str, path: &Vec<String
}
fn check_mount(permission: &String, path: &String) -> Result<(), BindError> {
let per = permission.to_lowercase();
let per = permission.to_lowercase();
if per != "ro" && per != "rw" {
Err(BindError::Fail(format!("{} is an invalid permission.", permission)))?
Err(BindError::Fail(format!("{} is an invalid permission.", permission)))?
}
if ! Path::new(path).exists() {
Err(BindError::Fail(format!("Source path not found.")))?
if !Path::new(path).exists() {
Err(BindError::Fail(format!("Source path not found.")))?
}
Ok(())
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -20,9 +20,13 @@
use std::process::exit;
use lazy_static::lazy_static;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
use crate::{Result, config::{config, save}, constants::CONFIG_FILE};
use crate::{
config::{config, save},
constants::CONFIG_FILE,
Result,
};
lazy_static! {
pub static ref CONFIG: Global = Global::load();
@ -58,27 +62,27 @@ impl Default for ProgressKind {
#[derive(Serialize, Deserialize, Clone)]
pub struct Global {
#[serde(default = "Configuration::new")]
#[serde(default = "Configuration::new")]
config: Configuration,
#[serde(default = "AlpmConfiguration::new")]
#[serde(default = "AlpmConfiguration::new")]
alpm: AlpmConfiguration,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Progress {
#[serde(default = "ProgressKind::default")]
#[serde(default = "ProgressKind::default")]
transact: ProgressKind,
#[serde(default = "ProgressKind::default")]
#[serde(default = "ProgressKind::default")]
download: ProgressKind,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Configuration {
#[serde(default = "Verbosity::default")]
#[serde(default = "Verbosity::default")]
summary: Verbosity,
#[serde(default = "Verbosity::default")]
#[serde(default = "Verbosity::default")]
logging: Verbosity,
#[serde(default = "Progress::new")]
#[serde(default = "Progress::new")]
progress: Progress,
}
@ -88,15 +92,15 @@ pub struct AlpmConfiguration {
ignore_pkg: Vec<String>,
#[serde(default = "hold_pkg")]
hold_pkg: Vec<String>,
#[serde(default = "sig_level")]
#[serde(default = "sig_level")]
sig_level: String,
#[serde(default = "sig_level_opt")]
#[serde(default = "sig_level_opt")]
sig_level_local: String,
#[serde(default = "parallel_downloads")]
#[serde(default = "parallel_downloads")]
parallel_downloads: u32,
#[serde(default = "default_true")]
#[serde(default = "default_true")]
check_space: bool,
#[serde(default = "default_true")]
#[serde(default = "default_true")]
download_timeout: bool,
}
@ -126,7 +130,7 @@ impl Progress {
fn new() -> Self {
Self {
transact: ProgressKind::CondensedForeign,
download: ProgressKind::CondensedForeign,
download: ProgressKind::CondensedForeign,
}
}
}
@ -136,8 +140,8 @@ impl AlpmConfiguration {
Self {
ignore_pkg: ignore_pkg(),
hold_pkg: hold_pkg(),
sig_level: sig_level(),
sig_level_local: sig_level_opt(),
sig_level: sig_level(),
sig_level_local: sig_level_opt(),
parallel_downloads: parallel_downloads(),
check_space: true,
download_timeout: true,
@ -149,7 +153,7 @@ impl AlpmConfiguration {
}
pub fn download_timeout(&self) -> bool {
! self.download_timeout
!self.download_timeout
}
pub fn parallel_downloads(&self) -> u32 {
@ -210,7 +214,7 @@ fn sig_level() -> String {
}
fn sig_level_opt() -> String {
"Optional".into()
"Optional".into()
}
fn parallel_downloads() -> u32 {

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,14 +17,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{path::Path, fs::File, io::Write};
use std::{fs::File, io::Write, path::Path};
use crate::{err,
Error,
Result,
ErrorKind,
use crate::{
config::global::CONFIG,
constants::{CACHE_DIR, CONFIG_DIR, DATA_DIR},
config::global::CONFIG};
err,
Error,
ErrorKind,
Result,
};
static REPO_CONF_DEFAULT: &'static str = r###"## See the pacman.conf(5) manpage for information on repository directives.
## All other libalpm-related options therein are ignored.
@ -72,7 +74,7 @@ impl DirectoryLayout {
fn instantiate(self) -> Result<()> {
for dir in self.dirs {
let path: &str = &format!("{}{}", self.root, dir);
if Path::new(path).exists() {
continue;
}
@ -88,22 +90,22 @@ impl DirectoryLayout {
fn data_layout() -> DirectoryLayout {
DirectoryLayout {
dirs: vec!("/root", "/home", "/state", "/pacman/sync"),
dirs: vec!["/root", "/home", "/state", "/pacman/sync"],
root: *DATA_DIR,
}
}
fn cache_layout() -> DirectoryLayout {
DirectoryLayout {
dirs: vec!("/pkg"),
root: *CACHE_DIR
dirs: vec!["/pkg"],
root: *CACHE_DIR,
}
}
fn config_layout() -> DirectoryLayout {
DirectoryLayout {
dirs: vec!("/instance"),
root: *CONFIG_DIR
dirs: vec!["/instance"],
root: *CONFIG_DIR,
}
}
@ -114,9 +116,9 @@ fn write_to_file(location: &str, contents: &str) -> Result<()> {
let mut f = match File::create(&location) {
Ok(f) => f,
Err(error) => err!(ErrorKind::IOError(location.into(), error.kind()))?
Err(error) => err!(ErrorKind::IOError(location.into(), error.kind()))?,
};
if let Err(error) = write!(f, "{contents}") {
err!(ErrorKind::IOError(location.into(), error.kind()))?
}
@ -125,11 +127,13 @@ fn write_to_file(location: &str, contents: &str) -> Result<()> {
}
pub fn init() -> Result<()> {
let _ = *CONFIG;
config_layout().instantiate()?;
data_layout().instantiate()?;
cache_layout().instantiate()?;
write_to_file(&format!("{}/repositories.conf", *CONFIG_DIR), REPO_CONF_DEFAULT)?;
write_to_file(&format!("{}/pacwrap.yml", *CONFIG_DIR), PACWRAP_CONF_DEFAULT)
write_to_file(&format!("{}/pacwrap.yml", *CONFIG_DIR), PACWRAP_CONF_DEFAULT)?;
let _ = *CONFIG;
Ok(())
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,33 +17,39 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{borrow::Cow,
fmt::{Display, Debug, Formatter},
vec::Vec};
use std::{
borrow::Cow,
fmt::{Debug, Display, Formatter},
vec::Vec,
};
use serde::{Deserialize, Serialize};
use crate::{Result,
config::{permission::{Permission, none::NONE},
filesystem::{Filesystem, root::ROOT, home::HOME},
use crate::{
config::{
dbus::Dbus,
vars::InsVars,
save},
constants::UNIX_TIMESTAMP};
filesystem::{home::Home, root::Root, Filesystem},
permission::{none::None, Permission},
save,
vars::InsVars,
},
constants::UNIX_TIMESTAMP,
Result,
};
#[derive(Serialize, Deserialize, Clone)]
pub struct Instance<'a> {
#[serde(flatten)]
#[serde(flatten)]
metadata: InstanceMetadata<'a>,
#[serde(flatten)]
runtime: InstanceRuntime,
}
impl <'a>Instance<'a> {
impl<'a> Instance<'a> {
pub fn new(ctype: InstanceType, deps: Vec<&'a str>, pkgs: Vec<&'a str>) -> Self {
Self {
metadata: InstanceMetadata::new(ctype,deps,pkgs),
runtime: InstanceRuntime::new(),
metadata: InstanceMetadata::new(ctype, deps, pkgs),
runtime: InstanceRuntime::new(),
}
}
}
@ -54,7 +60,7 @@ pub struct InstanceHandle<'a> {
vars: InsVars<'a>,
}
impl <'a>InstanceHandle<'a> {
impl<'a> InstanceHandle<'a> {
pub fn new(ins: Instance<'a>, ins_vars: InsVars<'a>) -> Self {
Self {
instance: ins,
@ -87,7 +93,7 @@ impl <'a>InstanceHandle<'a> {
}
}
impl <'a>Debug for InstanceHandle<'a> {
impl<'a> Debug for InstanceHandle<'a> {
fn fmt(&self, fmter: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(fmter, "{:?}", self.vars())?;
write!(fmter, "{:?}", self.config())
@ -96,31 +102,31 @@ impl <'a>Debug for InstanceHandle<'a> {
#[derive(Serialize, Deserialize, Clone)]
pub struct InstanceRuntime {
#[serde(default)]
enable_userns: bool,
#[serde(default)]
#[serde(default)]
enable_userns: bool,
#[serde(default)]
retain_session: bool,
#[serde(default = "default_true")]
seccomp: bool,
#[serde(default)]
allow_forking: bool,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
#[serde(default)]
allow_forking: bool,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
filesystems: Vec<Box<dyn Filesystem>>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
permissions: Vec<Box<dyn Permission>>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
dbus: Vec<Box<dyn Dbus>>,
dbus: Vec<Box<dyn Dbus>>,
}
impl InstanceRuntime {
pub fn new() -> Self {
let default_fs: [Box<dyn Filesystem>; 2] = [Box::new(ROOT {}), Box::new(HOME {})];
let default_per: [Box<dyn Permission>; 1] = [Box::new(NONE {})];
let default_fs: [Box<dyn Filesystem>; 2] = [Box::new(Root {}), Box::new(Home {})];
let default_per: [Box<dyn Permission>; 1] = [Box::new(None {})];
let fs: Vec<Box<dyn Filesystem>> = Vec::from(default_fs);
let per: Vec<Box<dyn Permission>> = Vec::from(default_per);
let per: Vec<Box<dyn Permission>> = Vec::from(default_per);
Self {
seccomp: true,
seccomp: true,
allow_forking: false,
retain_session: false,
enable_userns: false,
@ -130,28 +136,28 @@ impl InstanceRuntime {
}
}
pub fn permissions(&self) -> &Vec<Box<dyn Permission>> {
&self.permissions
pub fn permissions(&self) -> &Vec<Box<dyn Permission>> {
&self.permissions
}
pub fn filesystem(&self) -> &Vec<Box<dyn Filesystem>> {
&self.filesystems
pub fn filesystem(&self) -> &Vec<Box<dyn Filesystem>> {
&self.filesystems
}
pub fn dbus(&self) -> &Vec<Box<dyn Dbus>> {
&self.dbus
}
pub fn allow_forking(&self) -> &bool {
&self.allow_forking
pub fn dbus(&self) -> &Vec<Box<dyn Dbus>> {
&self.dbus
}
pub fn enable_userns(&self) -> &bool {
&self.enable_userns
}
pub fn retain_session(&self) -> &bool {
&self.retain_session
pub fn allow_forking(&self) -> &bool {
&self.allow_forking
}
pub fn enable_userns(&self) -> &bool {
&self.enable_userns
}
pub fn retain_session(&self) -> &bool {
&self.retain_session
}
pub fn seccomp(&self) -> &bool {
@ -170,30 +176,19 @@ impl Debug for InstanceRuntime {
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug)]
pub enum InstanceType {
LINK,
BASE,
DEP,
ROOT
Symbolic,
Base,
Slice,
Aggregate,
}
#[allow(dead_code)]
impl InstanceType {
pub fn new(instype: &str) -> Self {
match instype {
"BASE" => Self::BASE,
"ROOT" => Self::ROOT,
"DEP" => Self::DEP,
"LINK" => Self::LINK,
_ => Self::BASE
}
}
fn as_str<'a>(&self) -> &'a str {
match self {
Self::ROOT => "ROOT",
Self::LINK => "LINK",
Self::BASE => "BASE",
Self::DEP => "DEP"
Self::Symbolic => "LINK",
Self::Base => "BASE",
Self::Slice => "DEP",
Self::Aggregate => "ROOT",
}
}
}
@ -206,23 +201,23 @@ impl Display for InstanceType {
impl Default for InstanceType {
fn default() -> Self {
Self::BASE
Self::Base
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct InstanceMetadata<'a> {
#[serde(default)]
container_type: InstanceType,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
dependencies: Vec<Cow<'a, str>>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
#[serde(default)]
container_type: InstanceType,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
dependencies: Vec<Cow<'a, str>>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
explicit_packages: Vec<Cow<'a, str>>,
#[serde(default = "time_as_seconds")]
meta_version: u64,
}
impl <'a>InstanceMetadata<'a> {
impl<'a> InstanceMetadata<'a> {
fn new(ctype: InstanceType, deps: Vec<&'a str>, pkgs: Vec<&'a str>) -> Self {
Self {
container_type: ctype,
@ -238,15 +233,15 @@ impl <'a>InstanceMetadata<'a> {
self.meta_version = *UNIX_TIMESTAMP;
}
pub fn container_type(&self) -> &InstanceType {
&self.container_type
pub fn container_type(&self) -> &InstanceType {
&self.container_type
}
pub fn dependencies(&'a self) -> Vec<&'a str> {
pub fn dependencies(&'a self) -> Vec<&'a str> {
self.dependencies.iter().map(|a| a.as_ref()).collect()
}
pub fn explicit_packages(&'a self) -> Vec<&'a str> {
pub fn explicit_packages(&'a self) -> Vec<&'a str> {
self.explicit_packages.iter().map(|a| a.as_ref()).collect()
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -21,21 +21,21 @@ use std::fmt::{Display, Formatter};
use crate::exec::args::ExecutionArgs;
use dyn_clone::{DynClone, clone_trait_object};
use dyn_clone::{clone_trait_object, DynClone};
pub mod none;
mod dev;
mod display;
mod pulseaudio;
mod pipewire;
mod env;
mod gpu;
mod net;
mod dev;
pub mod none;
mod pipewire;
mod pulseaudio;
pub enum Condition {
Success,
SuccessWarn(String),
Nothing
Nothing,
}
#[derive(Debug, Clone)]
@ -44,7 +44,7 @@ pub enum PermError {
Warn(String),
}
#[typetag::serde(tag = "permission")]
#[typetag::serde(tag = "module")]
pub trait Permission: DynClone {
fn check(&self) -> Result<Option<Condition>, PermError>;
fn register(&self, args: &mut ExecutionArgs);
@ -56,7 +56,6 @@ impl Display for PermError {
match self {
Self::Fail(error) => write!(fmter, "{}", error),
Self::Warn(error) => write!(fmter, "{}", error),
}
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -21,28 +21,32 @@ use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::{Permission, permission::*},
config::permission::{Condition::Success, PermError::Fail}};
use crate::{
config::{
permission::{Condition::Success, PermError::Fail, *},
Permission,
},
exec::args::ExecutionArgs,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct DEV {
devices: Vec<String>
struct Dev {
devices: Vec<String>,
}
#[typetag::serde]
impl Permission for DEV {
fn check(&self) -> Result<Option<Condition>, PermError> {
#[typetag::serde(name = "dev")]
impl Permission for Dev {
fn check(&self) -> Result<Option<Condition>, PermError> {
for device in self.devices.iter() {
if ! Path::new(&format!("/dev/{}", device)).exists() {
if !Path::new(&format!("/dev/{}", device)).exists() {
Err(Fail(format!("/dev/{} is inaccessible.", device)))?
}
}
Ok(Some(Success))
}
fn register(&self, args: &mut ExecutionArgs) {
fn register(&self, args: &mut ExecutionArgs) {
for device in self.devices.iter() {
args.dev(&format!("/dev/{}", device));
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -21,34 +21,42 @@ use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::{Permission, permission::*},
config::permission::{
Condition::{Success, SuccessWarn},
PermError::Fail},
constants::{XDG_RUNTIME_DIR, WAYLAND_SOCKET, WAYLAND_DISPLAY, X11_DISPLAY, XAUTHORITY},
utils::check_socket};
use crate::{
config::{
permission::{
Condition::{Success, SuccessWarn},
PermError::Fail,
*,
},
Permission,
},
constants::{WAYLAND_DISPLAY, WAYLAND_SOCKET, X11_DISPLAY, XAUTHORITY, XDG_RUNTIME_DIR},
exec::args::ExecutionArgs,
utils::check_socket,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct DISPLAY;
struct Display;
#[typetag::serde]
impl Permission for DISPLAY {
#[typetag::serde(name = "display")]
impl Permission for Display {
fn check(&self) -> Result<Option<Condition>, PermError> {
let mut bound = None;
match validate_wayland_socket() {
Ok(b) => if let Some(con) = b {
bound = Some(con);
},
Err(e) => Err(e)?
match validate_wayland_socket() {
Ok(b) =>
if let Some(con) = b {
bound = Some(con);
},
Err(e) => Err(e)?,
}
match validate_xorg_socket() {
Ok(b) => if let Some(con) = b {
bound = Some(con);
},
Err(e) => Err(e)?
match validate_xorg_socket() {
Ok(b) =>
if let Some(con) = b {
bound = Some(con);
},
Err(e) => Err(e)?,
}
if let None = bound {
@ -56,30 +64,30 @@ impl Permission for DISPLAY {
} else {
Ok(bound)
}
}
fn register(&self, args: &mut ExecutionArgs) {
if ! WAYLAND_DISPLAY.is_empty() {
configure_wayland(args);
}
}
if ! X11_DISPLAY.is_empty() {
configure_xorg(args);
}
fn register(&self, args: &mut ExecutionArgs) {
if !WAYLAND_DISPLAY.is_empty() {
configure_wayland(args);
}
if !X11_DISPLAY.is_empty() {
configure_xorg(args);
}
}
fn module(&self) -> &'static str {
"DISPLAY"
"display"
}
}
fn validate_wayland_socket() -> Result<Option<Condition>, PermError> {
if ! WAYLAND_DISPLAY.is_empty() {
if ! Path::new(&*WAYLAND_SOCKET).exists() {
if !WAYLAND_DISPLAY.is_empty() {
if !Path::new(&*WAYLAND_SOCKET).exists() {
Err(Fail(format!("Wayland socket '{}' not found.", &*WAYLAND_SOCKET)))?
}
if ! check_socket(&*WAYLAND_SOCKET) {
if !check_socket(&*WAYLAND_SOCKET) {
Err(Fail(format!("'{}' is not a valid UNIX socket.", &*WAYLAND_SOCKET)))?
}
@ -89,30 +97,30 @@ fn validate_wayland_socket() -> Result<Option<Condition>, PermError> {
Ok(None)
}
fn validate_xorg_socket() -> Result<Option<Condition>, PermError> {
if ! X11_DISPLAY.is_empty() {
fn validate_xorg_socket() -> Result<Option<Condition>, PermError> {
if !X11_DISPLAY.is_empty() {
let display: Vec<&str> = X11_DISPLAY.split(":").collect();
let xorg_socket = format!("/tmp/.X11-unix/X{}", display[1]);
if XAUTHORITY.is_empty() {
Err(Fail(format!("XAUTHORITY environment variable unspecified.")))?
Err(Fail(format!("XAUTHORITY environment variable unspecified.")))?
}
if ! Path::new(*XAUTHORITY).exists() {
if !Path::new(*XAUTHORITY).exists() {
Err(Fail(format!("Xauthority file '{}' not found.", *XAUTHORITY)))?
}
if display[0].is_empty() || display[0] == "unix" {
if display[0].is_empty() || display[0] == "unix" {
if Path::new(&xorg_socket).exists() {
if ! check_socket(&xorg_socket) {
if !check_socket(&xorg_socket) {
Err(Fail(format!("'{}' is not a valid UNIX socket.", &xorg_socket)))?
}
return Ok(Some(Success));
} else {
Err(Fail(format!("X11 socket '{}' not found.", &xorg_socket)))?
}
} else {
}
} else {
return Ok(Some(SuccessWarn(format!("Connecting to TCP X11 socket at '{}'", *X11_DISPLAY))));
}
}
@ -121,7 +129,7 @@ fn validate_xorg_socket() -> Result<Option<Condition>, PermError> {
}
fn configure_wayland(args: &mut ExecutionArgs) {
let wayland_socket = format!("{}/{}", *XDG_RUNTIME_DIR, *WAYLAND_DISPLAY);
let wayland_socket = format!("{}/{}", *XDG_RUNTIME_DIR, *WAYLAND_DISPLAY);
args.env("WAYLAND_DISPLAY", *WAYLAND_DISPLAY);
args.robind(&wayland_socket, &wayland_socket);
@ -130,13 +138,13 @@ fn configure_wayland(args: &mut ExecutionArgs) {
fn configure_xorg(args: &mut ExecutionArgs) {
let display: Vec<&str> = X11_DISPLAY.split(":").collect();
let xorg_socket = format!("/tmp/.X11-unix/X{}", display[1]);
let container_xauth = format!("{}/Xauthority", *XDG_RUNTIME_DIR);
let container_xauth = format!("{}/Xauthority", *XDG_RUNTIME_DIR);
args.env("DISPLAY", *X11_DISPLAY);
args.env("XAUTHORITY", &container_xauth);
args.env("XAUTHORITY", &container_xauth);
args.robind(*XAUTHORITY, &container_xauth);
if display[0].is_empty() || display[0] == "unix" {
if display[0].is_empty() || display[0] == "unix" {
args.robind(&xorg_socket, &xorg_socket);
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -21,39 +21,42 @@ use std::env;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
use crate::{
config::{
permission::{Condition::Success, *},
Permission,
},
exec::args::ExecutionArgs,
utils::print_warning,
config::{Permission, permission::*},
config::permission::Condition::Success};
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ENV {
#[serde(skip_serializing_if = "String::is_empty", default)]
pub struct Environment {
#[serde(skip_serializing_if = "String::is_empty", default)]
var: String,
#[serde(skip_serializing_if = "String::is_empty", default)]
#[serde(skip_serializing_if = "String::is_empty", default)]
set: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
variables: Vec<Var>
#[serde(skip_serializing_if = "Vec::is_empty", default)]
variables: Vec<Var>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Var {
var: String,
#[serde(skip_serializing_if = "String::is_empty", default)]
set: String
#[serde(skip_serializing_if = "String::is_empty", default)]
set: String,
}
#[typetag::serde]
impl Permission for ENV {
#[typetag::serde(name = "env")]
impl Permission for Environment {
fn check(&self) -> Result<Option<Condition>, PermError> {
Ok(Some(Success))
}
fn register(&self, args: &mut ExecutionArgs) {
fn register(&self, args: &mut ExecutionArgs) {
if self.var != "" {
let set = env_var(&self.var, &self.set);
args.env(&self.var, set);
args.env(&self.var, set);
}
for v in self.variables.iter() {
@ -63,20 +66,20 @@ impl Permission for ENV {
}
fn module(&self) -> &'static str {
"ENV"
"env"
}
}
fn env_var(var: &String, set: &String) -> String {
if set != "" {
return set.to_owned();
if set != "" {
return set.to_owned();
}
match env::var(&var) {
Ok(env) => env,
match env::var(&var) {
Ok(env) => env,
Err(_) => {
print_warning(format!("Environment variable {} is unset.", var));
"".into()
}
}
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,45 +17,48 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::fs::read_dir;
use std::path::Path;
use std::{fs::read_dir, path::Path};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::{Permission, permission::*},
config::permission::{Condition::Success, PermError::Fail}};
use crate::{
config::{
permission::{Condition::Success, PermError::Fail, *},
Permission,
},
exec::args::ExecutionArgs,
};
lazy_static! {
static ref GPU_DEV: Vec<String> = populate_dev();
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct GPU;
struct Graphics;
#[typetag::serde]
impl Permission for GPU {
fn check(&self) -> Result<Option<Condition>, PermError> {
if ! Path::new("/dev").exists() {
#[typetag::serde(name = "gpu")]
impl Permission for Graphics {
fn check(&self) -> Result<Option<Condition>, PermError> {
if !Path::new("/dev").exists() {
Err(Fail(format!("/dev is inaccessible.")))?
}
if GPU_DEV.len() == 0 {
Err(Fail(format!("No graphics devices are available.")))?
Err(Fail(format!("No graphics devices are available.")))?
}
Ok(Some(Success))
}
fn register(&self, args: &mut ExecutionArgs) {
fn register(&self, args: &mut ExecutionArgs) {
for dev in GPU_DEV.iter() {
args.dev(dev);
}
}
fn module(&self) -> &'static str {
"GPU"
"gpu"
}
}
@ -67,7 +70,7 @@ fn populate_dev() -> Vec<String> {
let file = f.file_name();
let dev = file.to_str().unwrap();
if dev.starts_with("nvidia") || dev == "dri" {
vec.push(format!("/dev/{}",dev));
vec.push(format!("/dev/{}", dev));
}
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,15 +19,19 @@
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::{Permission, permission::*},
config::permission::Condition::Success};
use crate::{
config::{
permission::{Condition::Success, *},
Permission,
},
exec::args::ExecutionArgs,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NET;
pub struct Network;
#[typetag::serde]
impl Permission for NET {
#[typetag::serde(name = "net")]
impl Permission for Network {
fn check(&self) -> Result<Option<Condition>, PermError> {
Ok(Some(Success))
}
@ -38,6 +42,6 @@ impl Permission for NET {
}
fn module(&self) -> &'static str {
"NET"
"net"
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,22 +19,26 @@
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::{Permission, permission::*},
config::permission::Condition::Success};
use crate::{
config::{
permission::{Condition::Success, *},
Permission,
},
exec::args::ExecutionArgs,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NONE;
pub struct None;
#[typetag::serde]
impl Permission for NONE {
fn check(&self) -> Result<Option<Condition>, PermError> {
Ok(Some(Success))
#[typetag::serde(name = "none")]
impl Permission for None {
fn check(&self) -> Result<Option<Condition>, PermError> {
Ok(Some(Success))
}
fn register(&self, _: &mut ExecutionArgs) {}
fn module(&self) -> &'static str {
"NONE"
fn module(&self) -> &'static str {
"none"
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -21,38 +21,42 @@ use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::{Permission, permission::*},
config::permission::{Condition::Success, PermError::Warn},
use crate::{
config::{
permission::{Condition::Success, PermError::Warn, *},
Permission,
},
constants::XDG_RUNTIME_DIR,
utils::check_socket};
exec::args::ExecutionArgs,
utils::check_socket,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PIPEWIRE {
struct Pipewire {
#[serde(skip_serializing_if = "is_default_socket", default = "default_socket")]
socket: String,
}
#[typetag::serde]
impl Permission for PIPEWIRE {
fn check(&self) -> Result<Option<Condition>, PermError> {
if ! Path::new(&self.socket).exists() {
#[typetag::serde(name = "pipewire")]
impl Permission for Pipewire {
fn check(&self) -> Result<Option<Condition>, PermError> {
if !Path::new(&self.socket).exists() {
Err(Warn(format!("Pipewire socket not found.")))?
}
if ! check_socket(&self.socket) {
if !check_socket(&self.socket) {
Err(Warn(format!("'{}' is not a valid UNIX socket.", &self.socket)))?
}
Ok(Some(Success))
}
fn register(&self, args: &mut ExecutionArgs) {
fn register(&self, args: &mut ExecutionArgs) {
args.robind(&self.socket, default_socket());
}
fn module(&self) -> &'static str {
"PIPEWIRE"
"pipewire"
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -21,38 +21,42 @@ use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::{exec::args::ExecutionArgs,
config::{Permission, permission::*},
config::permission::{Condition::Success, PermError::Warn},
use crate::{
config::{
permission::{Condition::Success, PermError::Warn, *},
Permission,
},
constants::XDG_RUNTIME_DIR,
utils::check_socket};
exec::args::ExecutionArgs,
utils::check_socket,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PULSEAUDIO {
struct Pulseaudio {
#[serde(skip_serializing_if = "is_default_socket", default = "default_socket")]
socket: String,
}
#[typetag::serde]
impl Permission for PULSEAUDIO {
fn check(&self) -> Result<Option<Condition>, PermError> {
if ! Path::new(&self.socket).exists() {
#[typetag::serde(name = "pulseaudio")]
impl Permission for Pulseaudio {
fn check(&self) -> Result<Option<Condition>, PermError> {
if !Path::new(&self.socket).exists() {
Err(Warn(format!("Pulseaudio socket not found.")))?
}
if ! check_socket(&self.socket) {
if !check_socket(&self.socket) {
Err(Warn(format!("'{}' is not a valid UNIX socket.", &self.socket)))?
}
Ok(Some(Success))
}
fn register(&self, args: &mut ExecutionArgs) {
fn register(&self, args: &mut ExecutionArgs) {
args.robind(&self.socket, default_socket());
}
fn module(&self) -> &'static str {
"PULSEAUDIO"
"pulseaudio"
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,26 +19,30 @@
use std::vec::Vec;
use crate::{exec::args::ExecutionArgs,
config::{InsVars,
Permission,
Dbus,
use crate::{
config::{
filesystem::{BindError, Filesystem},
permission::*,
filesystem::{Filesystem, BindError}},
utils::print_warning,
Dbus,
InsVars,
Permission,
},
err,
error::*,
err};
exec::args::ExecutionArgs,
utils::print_warning,
};
use super::ConfigError;
pub fn register_filesystems(per: &Vec<Box<dyn Filesystem>>, vars: &InsVars, args: &mut ExecutionArgs) -> Result<()> {
for p in per.iter() {
match p.check(vars) {
match p.check(vars) {
Ok(_) => p.register(args, vars),
Err(condition) => match condition {
BindError::Warn(_) => print_warning(ConfigError::Filesystem(p.module(), condition)),
BindError::Fail(_) => err!(ConfigError::Filesystem(p.module(), condition))?
}
BindError::Fail(_) => err!(ConfigError::Filesystem(p.module(), condition))?,
},
}
}
@ -51,18 +55,18 @@ pub fn register_permissions(per: &Vec<Box<dyn Permission>>, args: &mut Execution
Ok(condition) => match condition {
Some(b) => {
p.register(args);
if let Condition::SuccessWarn(warning) = b {
print_warning(format!("{}: {} ", p.module(), warning));
}
},
None => continue
}
None => continue,
},
Err(condition) => match condition {
PermError::Warn(_) => print_warning(ConfigError::Permission(p.module(), condition)),
PermError::Fail(_) => err!(ConfigError::Permission(p.module(), condition))?
}
}
PermError::Fail(_) => err!(ConfigError::Permission(p.module(), condition))?,
},
}
}
Ok(())

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,9 +17,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::borrow::Cow;
use std::env::var;
use std::fmt::{Debug, Formatter};
use std::{
borrow::Cow,
env::var,
fmt::{Debug, Formatter},
};
use crate::constants::{CACHE_DIR, CONFIG_DIR, DATA_DIR};
@ -35,61 +37,63 @@ pub struct InsVars<'a> {
pacman_gnupg: Cow<'a, str>,
}
impl <'a>InsVars<'a> {
impl<'a> InsVars<'a> {
pub fn new(ins: &'a str) -> Self {
Self {
home: match var("PACWRAP_HOME") {
Err(_) => format!("{}/home/{ins}", *DATA_DIR), Ok(var) => var
}.into(),
home: match var("PACWRAP_HOME") {
Err(_) => format!("{}/home/{ins}", *DATA_DIR),
Ok(var) => var,
}
.into(),
root: format!("{}/root/{ins}", *DATA_DIR).into(),
pacman_gnupg: format!("{}/pacman/gnupg", *DATA_DIR).into(),
pacman_cache: format!("{}/pkg", *CACHE_DIR).into(),
config: format!("{}/instance/{ins}.yml", *CONFIG_DIR).into(),
home_mount: format!("/home/{ins}").into(),
config: format!("{}/instance/{ins}.yml", *CONFIG_DIR).into(),
home_mount: format!("/home/{ins}").into(),
user: ins.into(),
instance: ins.into(),
}
}
pub fn pacman_cache(&self) -> &str {
&self.pacman_cache
pub fn pacman_cache(&self) -> &str {
&self.pacman_cache
}
pub fn pacman_gnupg(&self) -> &str {
&self.pacman_gnupg
pub fn pacman_gnupg(&self) -> &str {
&self.pacman_gnupg
}
pub fn config_path(&self) -> &str {
&self.config
pub fn config_path(&self) -> &str {
&self.config
}
pub fn root(&self) -> &str {
&self.root
pub fn root(&self) -> &str {
&self.root
}
pub fn home(&self) -> &str {
&self.home
pub fn home(&self) -> &str {
&self.home
}
pub fn home_mount(&self) -> &str {
&self.home_mount
pub fn home_mount(&self) -> &str {
&self.home_mount
}
pub fn user(&self) -> &str {
&self.user
pub fn user(&self) -> &str {
&self.user
}
pub fn instance(&self) -> &str {
&self.instance
pub fn instance(&self) -> &str {
&self.instance
}
}
impl <'a>Debug for InsVars<'a> {
impl<'a> Debug for InsVars<'a> {
fn fmt(&self, fmter: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
writeln!(fmter, "Instance: {}", self.instance)?;
writeln!(fmter, "Instance User: {}", self.user)?;
writeln!(fmter, "Instance Config: {}", self.config)?;
writeln!(fmter, "Instance Root: {}", self.root)?;
writeln!(fmter, "Instance Config: {}", self.config)?;
writeln!(fmter, "Instance Root: {}", self.root)?;
writeln!(fmter, "Instance Home: {} -> {}", self.home, self.home_mount)
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -20,14 +20,21 @@
use std::{env::var, process::id};
use lazy_static::lazy_static;
use nix::unistd::{geteuid, getegid};
use nix::unistd::{getegid, geteuid};
use crate::{error, Error, ErrorKind, utils::{is_color_terminal, is_truecolor_terminal, unix_time_as_seconds}};
use crate::{
error,
utils::{is_color_terminal, is_truecolor_terminal, unix_time_as_seconds},
Error,
ErrorKind,
};
pub const BWRAP_EXECUTABLE: &str = "bwrap";
pub const DBUS_PROXY_EXECUTABLE: &str = "xdg-dbus-proxy";
pub const DEFAULT_PATH: &str = "/usr/local/bin:/usr/bin/:/bin";
pub const PACMAN_KEY_SCRIPT: &'static str = "pacman-key";
pub const PACMAN_KEY_SCRIPT: &str = "pacman-key";
pub const RUNTIME_DIRECTORY: &str = "/usr/share/pacwrap/runtime";
pub const RUNTIME_TLS_STORE: &str = "/etc/ca-certificates/extracted/tls-ca-bundle.pem";
const PACWRAP_CONFIG_DIR: &str = "/.config/pacwrap";
const PACWRAP_DATA_DIR: &str = "/.local/share/pacwrap";
@ -43,26 +50,26 @@ macro_rules! format_str {
#[macro_export]
macro_rules! to_static_str {
( $x:expr ) => {
$x.to_string().leak()
$x.to_string().leak()
};
}
lazy_static! {
pub static ref UID: u32 = geteuid().as_raw();
pub static ref GID: u32 = getegid().as_raw();
pub static ref PWD: &'static str = env_opt("PWD");
pub static ref PWD: &'static str = env_opt("PWD");
pub static ref HOME: &'static str = env("HOME");
pub static ref USER: &'static str = env("USER");
pub static ref TERM: &'static str = env_opt("TERM");
pub static ref COLORTERM: &'static str = env_opt("COLORTERM");
pub static ref LANG: &'static str = env_default("LAMG", "en_US.UTF-8");
pub static ref WAYLAND_DISPLAY: &'static str = env_opt("WAYLAND_DISPLAY");
pub static ref X11_DISPLAY: &'static str = env_opt("DISPLAY");
pub static ref X11_DISPLAY: &'static str = env_opt("DISPLAY");
pub static ref XAUTHORITY: &'static str = env_opt("XAUTHORITY");
pub static ref CACHE_DIR: &'static str = env_default_dir("PACWRAP_CACHE_DIR", PACWRAP_CACHE_DIR);
pub static ref CONFIG_DIR: &'static str = env_default_dir("PACWRAP_CONFIG_DIR", PACWRAP_CONFIG_DIR);
pub static ref DATA_DIR: &'static str = env_default_dir("PACWRAP_DATA_DIR", PACWRAP_DATA_DIR);
pub static ref CONFIG_FILE: &'static str = format_str!("{}/pacwrap.yml", *CONFIG_DIR);
pub static ref CONFIG_FILE: &'static str = format_str!("{}/pacwrap.yml", *CONFIG_DIR);
pub static ref XDG_RUNTIME_DIR: String = format!("/run/user/{}", *UID);
pub static ref DBUS_SOCKET: String = format!("/run/user/{}/pacwrap_dbus_{}", *UID, &id());
pub static ref WAYLAND_SOCKET: String = format!("{}{}", *XDG_RUNTIME_DIR, *WAYLAND_DISPLAY);
@ -76,82 +83,137 @@ lazy_static! {
pub static ref BOLD_WHITE: &'static str = bold_white();
pub static ref BOLD_YELLOW: &'static str = bold_yellow();
pub static ref BOLD_RED: &'static str = bold_red();
pub static ref BOLD_GREEN: &'static str = bold_green();
pub static ref BAR_GREEN: &'static str = bar_green();
pub static ref BAR_CYAN: &'static str = bar_cyan();
pub static ref ARROW_CYAN: &'static str = arrow_cyan();
pub static ref ARROW_RED: &'static str = arrow_red();
pub static ref ARROW_GREEN: &'static str = arrow_green();
pub static ref BOLD_GREEN: &'static str = bold_green();
pub static ref BAR_GREEN: &'static str = bar_green();
pub static ref BAR_CYAN: &'static str = bar_cyan();
pub static ref ARROW_CYAN: &'static str = arrow_cyan();
pub static ref ARROW_RED: &'static str = arrow_red();
pub static ref ARROW_GREEN: &'static str = arrow_green();
}
fn arrow_red() -> &'static str {
if *IS_COLOR_TERMINLAL { "->" } else { "->" }
if *IS_COLOR_TERMINLAL {
"->"
} else {
"->"
}
}
fn arrow_cyan() -> &'static str {
if *IS_COLOR_TERMINLAL { "->" } else { "->" }
if *IS_COLOR_TERMINLAL {
"->"
} else {
"->"
}
}
fn arrow_green() -> &'static str {
if *IS_COLOR_TERMINLAL { "->" } else { "->" }
if *IS_COLOR_TERMINLAL {
"->"
} else {
"->"
}
}
fn bar_green() -> &'static str {
if *IS_COLOR_TERMINLAL { "::" } else { "::" }
if *IS_COLOR_TERMINLAL {
"::"
} else {
"::"
}
}
fn bar_cyan() -> &'static str {
if *IS_COLOR_TERMINLAL { "::" } else { "::" }
if *IS_COLOR_TERMINLAL {
"::"
} else {
"::"
}
}
fn dim() -> &'static str {
if *IS_COLOR_TERMINLAL { "" } else { "" }
if *IS_COLOR_TERMINLAL {
""
} else {
""
}
}
fn bold() -> &'static str {
if *IS_COLOR_TERMINLAL { "" } else { "" }
if *IS_COLOR_TERMINLAL {
""
} else {
""
}
}
fn bold_white() -> &'static str {
if *IS_COLOR_TERMINLAL { "" } else { "" }
if *IS_COLOR_TERMINLAL {
""
} else {
""
}
}
fn bold_red() -> &'static str {
if *IS_COLOR_TERMINLAL { "" } else { "" }
if *IS_COLOR_TERMINLAL {
""
} else {
""
}
}
fn bold_yellow() -> &'static str {
if *IS_COLOR_TERMINLAL { "" } else { "" }
if *IS_COLOR_TERMINLAL {
""
} else {
""
}
}
fn bold_green() -> &'static str {
if *IS_COLOR_TERMINLAL { "" } else { "" }
if *IS_COLOR_TERMINLAL {
""
} else {
""
}
}
fn reset() -> &'static str {
if *IS_COLOR_TERMINLAL { "" } else { "" }
if *IS_COLOR_TERMINLAL {
""
} else {
""
}
}
fn env(env: &'static str) -> &'static str {
match var(env) {
Ok(var) => var.leak(), Err(_) => { error!(ErrorKind::EnvVarUnset(env)).handle(); "" }
Ok(var) => var.leak(),
Err(_) => {
error!(ErrorKind::EnvVarUnset(env)).handle();
""
}
}
}
fn env_opt(env: &str) -> &'static str {
match var(env) {
Ok(var) => var.leak(), Err(_) => "",
Ok(var) => var.leak(),
Err(_) => "",
}
}
fn env_default(env: &str, default: &'static str) -> &'static str {
match var(env) {
Ok(var) => var.leak(), Err(_) => default,
Ok(var) => var.leak(),
Err(_) => default,
}
}
fn env_default_dir(env: &str, default: &str) -> &'static str {
match var(env) {
Ok(var) => var.leak(), Err(_) => format_str!("{}{}", *HOME, default),
Ok(var) => var.leak(),
Err(_) => format_str!("{}{}", *HOME, default),
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,9 +17,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{any::Any, process::exit, fmt::{Display, Debug}};
use std::{
any::Any,
fmt::{Debug, Display},
process::exit,
};
use crate::{utils::{print_error, print_warning}, sync::SyncError};
use crate::{
sync::SyncError,
utils::{print_error, print_warning},
};
pub type Result<T> = std::result::Result<T, Error>;
@ -39,11 +46,11 @@ macro_rules! error {
#[macro_export]
macro_rules! impl_error {
( $x:ident ) => {
( $x:ident ) => {
impl ErrorTrait for $x {
fn code(&self) -> i32 {
1
}
}
}
};
}
@ -63,9 +70,7 @@ pub struct Error {
impl Error {
pub fn new(err: Box<dyn ErrorTrait>) -> Self {
Self {
kind: err,
}
Self { kind: err }
}
pub fn handle(&self) {
@ -73,8 +78,8 @@ impl Error {
match self.downcast::<SyncError>() {
Ok(error) => match error {
SyncError::TransactionFailureAgent => (),
_ => print_error(&self.kind),
}
_ => print_error(&self.kind),
},
Err(_) => print_error(&self.kind),
}
@ -82,7 +87,7 @@ impl Error {
}
pub fn error(&self) -> i32 {
print_error(&self.kind);
print_error(&self.kind);
self.kind.code()
}
@ -92,12 +97,16 @@ impl Error {
pub fn downcast<T: 'static>(&self) -> std::result::Result<&T, &Self> {
match self.kind.as_any().downcast_ref::<T>() {
Some(inner) => Ok(inner), None => Err(self),
Some(inner) => Ok(inner),
None => Err(self),
}
}
}
impl<T> Downcast for T where T: ErrorTrait + 'static {
impl<T> Downcast for T
where
T: ErrorTrait + 'static,
{
fn as_any(&self) -> &dyn Any {
self
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,31 +17,52 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{process::{Child, Command, Stdio}, fmt::{Formatter, Display}};
use std::{
fmt::{Display, Formatter},
process::{Child, Command, Stdio},
};
use command_fds::{CommandFdExt, FdMapping};
use lazy_static::lazy_static;
use crate::{err,
impl_error,
to_static_str,
ErrorTrait,
ErrorKind,
Error,
Result,
constants::{LOG_LOCATION, BWRAP_EXECUTABLE, BOLD, RESET, GID, UID, TERM, LANG, COLORTERM, PACMAN_KEY_SCRIPT},
use crate::{
config::InstanceHandle,
sync::transaction::{TransactionParameters, TransactionMetadata},
exec::{utils::{wait_on_process, agent_params}, seccomp::{provide_bpf_program, FilterType::*}}};
constants::{
BOLD,
BWRAP_EXECUTABLE,
COLORTERM,
GID,
LANG,
LOG_LOCATION,
PACMAN_KEY_SCRIPT,
RESET,
RUNTIME_DIRECTORY,
RUNTIME_TLS_STORE,
TERM,
UID,
},
err,
exec::{
seccomp::{provide_bpf_program, FilterType::*},
utils::{agent_params, wait_on_process},
},
impl_error,
sync::transaction::{TransactionMetadata, TransactionParameters},
to_static_str,
Error,
ErrorKind,
ErrorTrait,
Result,
};
pub mod args;
pub mod utils;
pub mod seccomp;
pub mod utils;
lazy_static! {
static ref ID: (&'static str, &'static str) = (to_static_str!(UID), to_static_str!(GID));
static ref DIST_IMG: &'static str = option_env!("PACWRAP_DIST_IMG").unwrap_or("/usr/share/pacwrap/runtime");
static ref DIST_TLS: &'static str = option_env!("PACWRAP_DIST_TLS").unwrap_or("/etc/ca-certificates/extracted/tls-ca-bundle.pem");
static ref DIST_IMG: &'static str = option_env!("PACWRAP_DIST_IMG").unwrap_or(RUNTIME_DIRECTORY);
static ref DIST_TLS: &'static str = option_env!("PACWRAP_DIST_TLS").unwrap_or(RUNTIME_TLS_STORE);
}
#[derive(Debug, Clone)]
@ -59,126 +80,136 @@ impl_error!(ExecutionError);
impl Display for ExecutionError {
fn fmt(&self, fmter: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
match self {
Self::InvalidPathVar(dir, err) => write!(fmter, "Invalid {}PATH{} variable '{dir}': {err}", *BOLD, *RESET),
Self::ExecutableUnavailable(exec) => write!(fmter, "'{}': Not available in container {}PATH{}.", exec, *BOLD, *RESET),
Self::UnabsolutePath(path) => write!(fmter, "'{}': {}PATH{} variable must be absolute", path, *BOLD, *RESET),
Self::UnabsoluteExec(path) => write!(fmter, "'{}': Executable path must be absolute.", path),
Self::UnabsoluteExec(path) => write!(fmter, "'{}': Executable path must be absolute.", path),
Self::DirectoryNotExecutable(path) => write!(fmter, "'{}': Directories are not executables.", path),
Self::SocketTimeout(socket) => write!(fmter, "Socket '{socket}': timed out."),
Self::RuntimeArguments => write!(fmter, "Invalid runtime arguments."),
Self::RuntimeArguments => write!(fmter, "Invalid runtime arguments."),
}
}
}
#[rustfmt::skip]
pub fn fakeroot_container(ins: &InstanceHandle, arguments: Vec<&str>) -> Result<Child> {
let (sec_reader, sec_writer) = os_pipe::pipe().unwrap();
let (sec_reader, sec_writer) = os_pipe::pipe().unwrap();
let sec_fd = provide_bpf_program(vec![Standard, Namespaces], &sec_reader, sec_writer).unwrap();
let fd_mappings = vec![FdMapping { parent_fd: sec_fd, child_fd: sec_fd }];
let fd_mappings = vec![FdMapping {
parent_fd: sec_fd,
child_fd: sec_fd }];
match Command::new(BWRAP_EXECUTABLE)
.env_clear()
.arg("--tmpfs").arg("/tmp")
match Command::new(BWRAP_EXECUTABLE).env_clear()
.arg("--tmpfs").arg("/tmp")
.arg("--bind").arg(ins.vars().root()).arg("/")
.arg("--ro-bind").arg(format!("{}/lib", *DIST_IMG)).arg("/tmp/runtime")
.arg("--ro-bind").arg("/etc/resolv.conf").arg("/etc/resolv.conf")
.arg("--ro-bind").arg("/etc/localtime").arg("/etc/localtime")
.arg("--bind").arg(ins.vars().pacman_gnupg()).arg("/etc/pacman.d/gnupg")
.arg("--bind").arg(ins.vars().pacman_cache()).arg("/var/cache/pacman/pkg")
.arg("--bind").arg(ins.vars().home()).arg(ins.vars().home_mount())
.arg("--dev").arg("/dev")
.arg("--proc").arg("/proc")
.arg("--unshare-all").arg("--share-net")
.arg("--hostname").arg("FakeChroot")
.arg("--new-session")
.arg("--setenv").arg("TERM").arg("xterm")
.arg("--setenv").arg("PATH").arg("/usr/local/bin:/usr/bin")
.arg("--setenv").arg("CWD").arg(ins.vars().home_mount())
.arg("--setenv").arg("HOME").arg(ins.vars().home_mount())
.arg("--setenv").arg("USER").arg(ins.vars().user())
.arg("--die-with-parent")
.arg("--disable-userns")
.arg("--unshare-user")
.arg("--seccomp")
.arg(sec_fd.to_string())
.arg("fakechroot")
.arg("fakeroot")
.args(arguments)
.fd_mappings(fd_mappings)
.unwrap()
.spawn() {
Ok(child) => Ok(child),
Err(err) => err!(ErrorKind::ProcessInitFailure(BWRAP_EXECUTABLE, err.kind())),
.arg("--ro-bind").arg(format!("{}/lib", *DIST_IMG)).arg("/tmp/runtime")
.arg("--ro-bind").arg("/etc/resolv.conf").arg("/etc/resolv.conf")
.arg("--ro-bind").arg("/etc/localtime").arg("/etc/localtime")
.arg("--bind").arg(ins.vars().pacman_gnupg()).arg("/etc/pacman.d/gnupg")
.arg("--bind").arg(ins.vars().pacman_cache()).arg("/var/cache/pacman/pkg")
.arg("--bind").arg(ins.vars().home()).arg(ins.vars().home_mount())
.arg("--dev").arg("/dev")
.arg("--proc").arg("/proc")
.arg("--unshare-all").arg("--share-net")
.arg("--hostname").arg("FakeChroot")
.arg("--new-session")
.arg("--setenv").arg("TERM").arg("xterm")
.arg("--setenv").arg("PATH").arg("/usr/local/bin:/usr/bin")
.arg("--setenv").arg("CWD").arg(ins.vars().home_mount())
.arg("--setenv").arg("HOME").arg(ins.vars().home_mount())
.arg("--setenv").arg("USER").arg(ins.vars().user())
.arg("--die-with-parent")
.arg("--disable-userns")
.arg("--unshare-user")
.arg("--seccomp")
.arg(sec_fd.to_string())
.arg("fakechroot")
.arg("fakeroot")
.args(arguments)
.fd_mappings(fd_mappings)
.unwrap()
.spawn()
{
Ok(child) => Ok(child),
Err(err) => err!(ErrorKind::ProcessInitFailure(BWRAP_EXECUTABLE, err.kind())),
}
}
#[rustfmt::skip]
pub fn transaction_agent(ins: &InstanceHandle, params: &TransactionParameters, metadata: &TransactionMetadata) -> Result<Child> {
let (sec_reader, sec_writer) = os_pipe::pipe().unwrap();
let (sec_reader, sec_writer) = os_pipe::pipe().unwrap();
let (params_reader, params_writer) = os_pipe::pipe().unwrap();
let params_fd = agent_params(&params_reader, &params_writer, params, metadata)?;
let sec_fd = provide_bpf_program(vec![Standard, Namespaces], &sec_reader, sec_writer).unwrap();
let fd_mappings = vec![
FdMapping { parent_fd: sec_fd, child_fd: sec_fd },
FdMapping { parent_fd: params_fd, child_fd: params_fd },
FdMapping {
parent_fd: sec_fd,
child_fd: sec_fd
},
FdMapping {
parent_fd: params_fd,
child_fd: params_fd
},
];
match Command::new(BWRAP_EXECUTABLE)
.env_clear()
.arg("--bind").arg(&ins.vars().root()).arg("/mnt/fs")
.arg("--symlink").arg("/mnt/fs/usr").arg("/usr")
.arg("--ro-bind").arg(format!("{}/lib", *DIST_IMG)).arg("/lib64")
.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/localtime").arg("/etc/localtime")
.arg("--ro-bind").arg(*DIST_TLS).arg("/etc/ssl/certs/ca-certificates.crt")
.arg("--bind").arg(*LOG_LOCATION).arg("/mnt/share/pacwrap.log")
.arg("--bind").arg(ins.vars().pacman_gnupg()).arg("/mnt/share/gnupg")
.arg("--bind").arg(ins.vars().pacman_cache()).arg("/mnt/share/cache")
.arg("--ro-bind").arg(env!("PACWRAP_DIST_REPO")).arg("/mnt/share/dist-repo")
.arg("--dev").arg("/dev")
.arg("--dev").arg("/mnt/fs/dev")
.arg("--proc").arg("/mnt/fs/proc")
.arg("--unshare-all")
.arg("--share-net")
.arg("--clearenv")
.arg("--hostname").arg("pacwrap-agent")
.arg("--new-session")
.arg("--setenv").arg("HOME").arg("/tmp")
.arg("--setenv").arg("PATH").arg("/bin")
.arg("--setenv").arg("TERM").arg(*TERM)
.arg("--setenv").arg("LANG").arg(*LANG)
.arg("--setenv").arg("COLORTERM").arg(*COLORTERM)
match Command::new(BWRAP_EXECUTABLE).env_clear()
.arg("--bind").arg(&ins.vars().root()).arg("/mnt/fs")
.arg("--symlink").arg("/mnt/fs/usr").arg("/usr")
.arg("--ro-bind").arg(format!("{}/lib", *DIST_IMG)).arg("/lib64")
.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/localtime").arg("/etc/localtime")
.arg("--ro-bind").arg(*DIST_TLS).arg("/etc/ssl/certs/ca-certificates.crt")
.arg("--bind").arg(*LOG_LOCATION).arg("/mnt/share/pacwrap.log")
.arg("--bind").arg(ins.vars().pacman_gnupg()).arg("/mnt/share/gnupg")
.arg("--bind").arg(ins.vars().pacman_cache()).arg("/mnt/share/cache")
.arg("--ro-bind").arg(env!("PACWRAP_DIST_REPO")).arg("/mnt/share/dist-repo")
.arg("--dev").arg("/dev")
.arg("--dev").arg("/mnt/fs/dev")
.arg("--proc").arg("/mnt/fs/proc")
.arg("--unshare-all")
.arg("--share-net")
.arg("--hostname").arg("pacwrap-agent")
.arg("--new-session")
.arg("--setenv").arg("HOME").arg("/tmp")
.arg("--setenv").arg("PATH").arg("/bin")
.arg("--setenv").arg("TERM").arg(*TERM)
.arg("--setenv").arg("LANG").arg(*LANG)
.arg("--setenv").arg("COLORTERM").arg(*COLORTERM)
.arg("--setenv").arg("LD_LIBRARY_PATH").arg("/lib64:/usr/lib")
.arg("--setenv").arg("LD_PRELOAD").arg("/lib64/libfakeroot.so:/lib64/libfakechroot.so")
.arg("--setenv").arg("PACWRAP_REAL_UID").arg(ID.0)
.arg("--setenv").arg("PACWRAP_REAL_GID").arg(ID.1)
.arg("--setenv").arg("LD_PRELOAD").arg("/lib64/libfakeroot.so:/lib64/libfakechroot.so")
.arg("--setenv").arg("PACWRAP_REAL_UID").arg(ID.0)
.arg("--setenv").arg("PACWRAP_REAL_GID").arg(ID.1)
.arg("--setenv").arg("RUST_BACKTRACE").arg("1")
.arg("--die-with-parent")
.arg("--unshare-user")
.arg("--disable-userns")
.arg("--seccomp")
.arg(sec_fd.to_string())
.arg("--ro-bind-data")
.arg(params_fd.to_string())
.arg("/mnt/agent_params")
.arg("agent")
.arg("transact")
.fd_mappings(fd_mappings)
.unwrap()
.spawn() {
Ok(child) => Ok(child),
Err(err) => err!(ErrorKind::ProcessInitFailure(BWRAP_EXECUTABLE, err.kind())),
}
.arg("--die-with-parent")
.arg("--unshare-user")
.arg("--disable-userns")
.arg("--seccomp")
.arg(sec_fd.to_string())
.arg("--ro-bind-data")
.arg(params_fd.to_string())
.arg("/mnt/agent_params")
.arg("agent")
.arg("transact")
.fd_mappings(fd_mappings)
.unwrap()
.spawn()
{
Ok(child) => Ok(child),
Err(err) => err!(ErrorKind::ProcessInitFailure(BWRAP_EXECUTABLE, err.kind())),
}
}
pub fn pacman_key(path: &str, cmd: Vec<&str>) -> Result<()> {
match Command::new(PACMAN_KEY_SCRIPT)
.stderr(Stdio::null())
.stderr(Stdio::null())
.env("EUID", "0")
.arg("--gpgdir")
.arg(path)
.args(cmd)
.spawn() {
.spawn()
{
Ok(proc) => wait_on_process(PACMAN_KEY_SCRIPT, proc),
Err(error) => err!(ErrorKind::ProcessInitFailure(PACMAN_KEY_SCRIPT, error.kind()))?,
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,34 +17,37 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{fmt::{Formatter, Debug}, collections::HashMap};
use std::{
collections::HashMap,
fmt::{Debug, Formatter},
};
pub struct ExecutionArgs {
bind: Vec<String>,
dev: Vec<String>,
env: Vec<String>,
dbus: Vec<String>,
vars: HashMap<String, String>,
vars: HashMap<String, String>,
}
//TODO: This entire structure needs to be rethought
impl ExecutionArgs {
pub fn new() -> Self {
Self {
bind: Vec::new(),
dev: Vec::new(),
env: Vec::new(),
Self {
bind: Vec::new(),
dev: Vec::new(),
env: Vec::new(),
dbus: Vec::new(),
vars: HashMap::new(),
}
}
pub fn dir(&mut self, dest: impl Into<String>) {
pub fn dir(&mut self, dest: impl Into<String>) {
self.bind.push("--dir".into());
self.bind.push(dest.into());
}
pub fn bind(&mut self, src: impl Into<String>, dest: impl Into<String>) {
pub fn bind(&mut self, src: impl Into<String>, dest: impl Into<String>) {
self.bind.push("--bind".into());
self.bind.push(src.into());
self.bind.push(dest.into());
@ -84,27 +87,27 @@ impl ExecutionArgs {
self.dbus.push(format!("--{}={}", per.into(), socket.into()));
}
pub fn push_env(&mut self, src: impl Into<String>) {
self.env.push(src.into());
pub fn push_env(&mut self, src: impl Into<String>) {
self.env.push(src.into());
}
pub fn get_bind(&self) -> &Vec<String> {
&self.bind
pub fn get_bind(&self) -> &Vec<String> {
&self.bind
}
pub fn get_dev(&self) -> &Vec<String> {
&self.dev
pub fn get_dev(&self) -> &Vec<String> {
&self.dev
}
pub fn get_env(&self) -> &Vec<String> {
pub fn get_env(&self) -> &Vec<String> {
&self.env
}
pub fn get_dbus(&self) -> &Vec<String> {
&self.dbus
pub fn get_dbus(&self) -> &Vec<String> {
&self.dbus
}
//TODO: Temporary workaround until structure is rebuilt
//TODO: Temporary workaround until structure is rebuilt
pub fn get_var(&self, key: &str) -> Option<&String> {
self.vars.get(key)
}
@ -112,11 +115,11 @@ impl ExecutionArgs {
impl Debug for ExecutionArgs {
fn fmt(&self, fmter: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
writeln!(fmter, "bind: {:?}", self.bind)?;
writeln!(fmter, "bind: {:?}", self.bind)?;
writeln!(fmter, "env: {:?}", self.env)?;
if self.dev.len() > 0 {
writeln!(fmter, "dev: {:?}", self.dev)?;
writeln!(fmter, "dev: {:?}", self.dev)?;
}
if self.dbus.len() > 0 {

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,14 +19,16 @@
use std::os::fd::AsRawFd;
use nix::libc;
use libseccomp::{ScmpArgCompare as Compare,
ScmpAction as Action,
use libseccomp::{
ScmpAction as Action,
ScmpArch,
ScmpArgCompare as Compare,
ScmpCompareOp as Op,
ScmpSyscall as Syscall,
ScmpFilterContext,
ScmpArch};
use os_pipe::{PipeWriter, PipeReader};
ScmpSyscall as Syscall,
};
use nix::libc;
use os_pipe::{PipeReader, PipeWriter};
use crate::config::instance::InstanceRuntime;
@ -40,72 +42,76 @@ pub enum FilterType {
}
static EPERM: Action = Action::Errno(libc::EPERM);
static ENOSYS: Action = Action::Errno(libc::ENOSYS);
static ENOSYS: Action = Action::Errno(libc::ENOSYS);
/*
/*
* Personality values obtained from personality.h in the Linux kernel
*
* https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/uapi/linux/personality.h
*/
static PERSONALITY: u64 = if cfg!(target_pointer_width = "64") { 0x0000 } else { 0x0000 | 0x0800000 };
static PERSONALITY: u64 = if cfg!(target_pointer_width = "64") {
0x0000
} else {
0x0000 | 0x0800000
};
/*
/*
* Syscall blocklists derived from flatpak-run.c in the flatpak project.
*
* https://github.com/flatpak/flatpak/blob/main/common/flatpak-run.c#L1835
*
* Please do not open issue reports, esplicitly regarding lessened security, regarding filters
* that of which can be toggled. When the relevant options are activated, users are warned of
* the potential ramifications of so doing.
*
*
* Please do not open issue reports, esplicitly regarding lessened security, regarding filters
* that of which can be toggled. When the relevant options are activated, users are warned of
* the potential ramifications of so doing.
*
* This encumbers a great responsibility upon the user when exercising this great power.
*/
static RULES: [(FilterType, &'static str, Action); 28] = [
(Standard, "syslog", EPERM),
(Standard, "uselib", EPERM),
(Standard, "acct", EPERM),
(Standard, "syslog", EPERM),
(Standard, "uselib", EPERM),
(Standard, "acct", EPERM),
(Standard, "quotactl", EPERM),
(Standard, "add_key", EPERM),
(Standard, "keyctl", EPERM),
(Standard, "request_key", EPERM),
(Standard, "move_pages", EPERM),
(Standard, "mbind", EPERM),
(Standard, "get_mempolicy", EPERM),
(Standard, "set_mempolicy", EPERM),
(Standard, "migrate_pages", EPERM),
(Standard, "clone3", ENOSYS),
(Standard, "open_tree", ENOSYS),
(Standard, "move_mount", ENOSYS),
(Standard, "add_key", EPERM),
(Standard, "keyctl", EPERM),
(Standard, "request_key", EPERM),
(Standard, "move_pages", EPERM),
(Standard, "mbind", EPERM),
(Standard, "get_mempolicy", EPERM),
(Standard, "set_mempolicy", EPERM),
(Standard, "migrate_pages", EPERM),
(Standard, "clone3", ENOSYS),
(Standard, "open_tree", ENOSYS),
(Standard, "move_mount", ENOSYS),
(Standard, "fsopen", ENOSYS),
(Standard, "fsconfig", ENOSYS),
(Standard, "fsmount", ENOSYS),
(Standard, "fspick", ENOSYS),
(Standard, "mount_setattr", ENOSYS),
(Standard, "perf_event_open", ENOSYS),
(Standard, "fsconfig", ENOSYS),
(Standard, "fsmount", ENOSYS),
(Standard, "fspick", ENOSYS),
(Standard, "mount_setattr", ENOSYS),
(Standard, "perf_event_open", ENOSYS),
(Standard, "ptrace", ENOSYS),
(Namespaces, "unshare", EPERM),
(Namespaces, "setns", EPERM),
(Namespaces, "mount", EPERM),
(Namespaces, "unshare", EPERM),
(Namespaces, "setns", EPERM),
(Namespaces, "mount", EPERM),
(Namespaces, "umount2", EPERM),
(Namespaces, "pivot_root", EPERM),
(Namespaces, "chroot", EPERM),
(Namespaces, "chroot", EPERM),
];
static RULES_COND: [(FilterType, &'static str, Action, Compare); 4] = [
(TtyControl, "ioctl", EPERM, Compare::new(1, Op::MaskedEqual(libc::TIOCLINUX), libc::TIOCLINUX)),
(TtyControl, "ioctl", EPERM, Compare::new(1, Op::MaskedEqual(libc::TIOCLINUX), libc::TIOCLINUX)),
(TtyControl, "ioctl", EPERM, Compare::new(1, Op::MaskedEqual(libc::TIOCSTI), libc::TIOCSTI)),
(Namespaces, "clone", EPERM, Compare::new(0, Op::MaskedEqual(libc::CLONE_NEWUSER as u64), libc::CLONE_NEWUSER as u64)),
(Standard, "personality", EPERM, Compare::new(0, Op::NotEqual, PERSONALITY))
(Namespaces, "clone", EPERM, Compare::new(0, Op::MaskedEqual(libc::CLONE_NEWUSER as u64), libc::CLONE_NEWUSER as u64)),
(Standard, "personality", EPERM, Compare::new(0, Op::NotEqual, PERSONALITY)),
];
// Provide configuration parameters for berkley filtering program generation
// Provide configuration parameters for berkley filtering program generation
pub fn configure_bpf_program(instance: &InstanceRuntime) -> Vec<FilterType> {
let mut filters = vec![Standard];
if ! instance.enable_userns() {
if !instance.enable_userns() {
filters.push(Namespaces)
}
if ! instance.retain_session() {
if !instance.retain_session() {
filters.push(TtyControl)
}
@ -113,16 +119,22 @@ pub fn configure_bpf_program(instance: &InstanceRuntime) -> Vec<FilterType> {
}
// Generate berkley packet filtering program to pass into the namespaces container
pub fn provide_bpf_program(types: Vec<FilterType>, reader: &PipeReader, mut writer: PipeWriter) -> Result<i32, Box<dyn std::error::Error>> {
pub fn provide_bpf_program(
types: Vec<FilterType>,
reader: &PipeReader,
mut writer: PipeWriter,
) -> Result<i32, Box<dyn std::error::Error>> {
let mut filter = ScmpFilterContext::new_filter(Action::Allow)?;
let rules = RULES.iter()
.filter(|a| types.contains(&a.0))
.map(|a| (a.1, a.2))
.collect::<Vec<(&str, Action)>>();
let rules_cond = RULES_COND.iter()
.filter(|a| types.contains(&a.0))
.map(|a| (a.1, a.2, a.3))
.collect::<Vec<(&str, Action, Compare)>>();
let rules = RULES
.iter()
.filter(|a| types.contains(&a.0))
.map(|a| (a.1, a.2))
.collect::<Vec<(&str, Action)>>();
let rules_cond = RULES_COND
.iter()
.filter(|a| types.contains(&a.0))
.map(|a| (a.1, a.2, a.3))
.collect::<Vec<(&str, Action, Compare)>>();
if cfg!(target_arch = "x86_64") {
filter.add_arch(ScmpArch::X86)?;
@ -133,10 +145,10 @@ pub fn provide_bpf_program(types: Vec<FilterType>, reader: &PipeReader, mut writ
for rule in rules {
filter.add_rule(rule.1, Syscall::from_name(rule.0)?)?;
}
for rule in rules_cond {
filter.add_rule_conditional(rule.1, Syscall::from_name(rule.0)?, &[rule.2])?;
}
for rule in rules_cond {
filter.add_rule_conditional(rule.1, Syscall::from_name(rule.0)?, &[rule.2])?;
}
filter.export_bpf(&mut writer).unwrap();

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,52 +17,61 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{process::Child, io::Read, os::fd::AsRawFd};
use std::{io::Read, os::fd::AsRawFd, process::Child};
use os_pipe::{PipeReader, PipeWriter};
use serde::Serialize;
use serde_yaml::Value;
use crate::{err,
error::*,
ErrorKind,
constants::BWRAP_EXECUTABLE,
use crate::{
config::{InstanceHandle, CONFIG},
sync::{SyncError, transaction::{TransactionParameters,
TransactionMetadata},
DEFAULT_ALPM_CONF}};
constants::BWRAP_EXECUTABLE,
err,
error::*,
sync::{
transaction::{TransactionMetadata, TransactionParameters},
SyncError,
DEFAULT_ALPM_CONF,
},
ErrorKind,
};
pub fn execute_fakeroot_container(ins: &InstanceHandle, arguments: Vec<&str>) -> Result<()> {
match super::fakeroot_container(ins, arguments)?.wait() {
Ok(_) => Ok(()),
Err(err) => err!(ErrorKind::ProcessWaitFailure(BWRAP_EXECUTABLE, err.kind()))
Err(err) => err!(ErrorKind::ProcessWaitFailure(BWRAP_EXECUTABLE, err.kind())),
}
}
pub fn bwrap_json(mut reader: PipeReader, writer: PipeWriter) -> Result<i32> {
pub fn bwrap_json(mut reader: PipeReader, writer: PipeWriter) -> Result<i32> {
let mut output = String::new();
drop(writer);
reader.read_to_string(&mut output).unwrap();
reader.read_to_string(&mut output).unwrap();
match serde_yaml::from_str::<Value>(&output) {
Ok(value) => match value["child-pid"].as_u64() {
Some(value) => Ok(value as i32),
Some(value) => Ok(value as i32),
None => err!(ErrorKind::Message("Unable to acquire child pid from bwrap process.")),
},
Err(_) => err!(ErrorKind::Message("Unable to acquire child pid from bwrap process.")),
}
}
pub fn agent_params(reader: &PipeReader, writer: &PipeWriter, params: &TransactionParameters, metadata: &TransactionMetadata) -> Result<i32> {
serialize(params, writer)?;
pub fn agent_params(
reader: &PipeReader,
writer: &PipeWriter,
params: &TransactionParameters,
metadata: &TransactionMetadata,
) -> Result<i32> {
serialize(params, writer)?;
serialize(&*CONFIG, writer)?;
serialize(&*DEFAULT_ALPM_CONF, writer)?;
serialize(metadata, writer)?;
Ok(reader.as_raw_fd())
}
fn serialize<T: for<'de> Serialize>(input: &T, file: &PipeWriter) -> Result<()> {
fn serialize<T: for<'de> Serialize>(input: &T, file: &PipeWriter) -> Result<()> {
match bincode::serialize_into::<&PipeWriter, T>(file, input) {
Ok(()) => Ok(()),
Err(error) => err!(SyncError::TransactionFailure(format!("Agent data serialization failed: {}", error))),
@ -76,9 +85,9 @@ pub fn handle_process(name: &'static str, result: std::result::Result<Child, std
}
}
pub fn wait_on_process(name: &'static str, mut child: Child) -> Result<()> {
pub fn wait_on_process(name: &'static str, mut child: Child) -> Result<()> {
match child.wait() {
Ok(_) => Ok(()),
Err(error) => err!(ErrorKind::ProcessWaitFailure(name, error.kind()))
Err(error) => err!(ErrorKind::ProcessWaitFailure(name, error.kind())),
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,17 +17,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::fmt::{Display, Formatter, Debug};
use std::fmt::{Debug, Display, Formatter};
use crate::constants::{BOLD, RESET};
pub mod config;
pub mod constants;
pub mod error;
pub mod exec;
pub mod log;
pub mod sync;
pub mod utils;
pub mod constants;
pub mod config;
pub mod log;
pub mod exec;
pub mod error;
pub use error::*;
@ -36,32 +36,33 @@ pub enum ErrorKind {
EnvVarUnset(&'static str),
ProcessInitFailure(&'static str, std::io::ErrorKind),
ProcessWaitFailure(&'static str, std::io::ErrorKind),
IOError(String, std::io::ErrorKind),
IOError(String, std::io::ErrorKind),
Message(&'static str),
Termios(nix::errno::Errno),
InstanceNotFound(String),
DependencyNotFound(String, String),
InstanceNotFound(String),
DependencyNotFound(String, String),
LinkerUninitialized,
ThreadPoolUninitialized,
ElevatedPrivileges,
}
impl Display for ErrorKind {
fn fmt(&self, fmter: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
fn fmt(&self, fmter: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
Self::DependencyNotFound(dep, ins) =>
write!(fmter, "Instance '{}{}{}': Dependency {}{}{} not found.", *BOLD, ins, *RESET, *BOLD, dep, *RESET),
Self::Message(err) => write!(fmter, "{}", err),
Self::EnvVarUnset(var) => write!(fmter, "${}{var}{} is unset.", *BOLD, *RESET),
Self::ProcessInitFailure(exec, err) => write!(fmter, "Unable to initialize '{exec}': {err}"),
Self::ProcessWaitFailure(exec, err) => write!(fmter, "Unable to wait on '{exec}': {err}"),
Self::ProcessInitFailure(exec, err) => write!(fmter, "Unable to initialize '{exec}': {err}"),
Self::ProcessWaitFailure(exec, err) => write!(fmter, "Unable to wait on '{exec}': {err}"),
Self::InstanceNotFound(ins) => write!(fmter, "Instance {}{ins}{} not found.", *BOLD, *RESET),
Self::DependencyNotFound(dep,ins) => write!(fmter, "Dependency {}{dep}{} not found for {}{ins}{}.", *BOLD, *RESET, *BOLD, *RESET),
Self::IOError(ins, error) => write!(fmter, "'{ins}': {error}"),
Self::IOError(ins, error) => write!(fmter, "'{ins}': {error}"),
Self::ThreadPoolUninitialized => write!(fmter, "Threadpool uninitialized"),
Self::LinkerUninitialized => write!(fmter, "Filesystem synchronization structure is uninitialized."),
Self::Termios(errno) => write!(fmter, "Failed to restore termios parameters: {errno}."),
Self::LinkerUninitialized => write!(fmter, "Filesystem synchronization structure is uninitialized."),
Self::Termios(errno) => write!(fmter, "Failed to restore termios parameters: {errno}."),
Self::ElevatedPrivileges => write!(fmter, "Execution with elevated privileges is not supported."),
}?;
if let Self::Message(_) = self {
write!(fmter, "\nTry 'pacwrap -h' for more information on valid operational parameters.")?;
}
@ -71,9 +72,10 @@ impl Display for ErrorKind {
}
impl ErrorTrait for ErrorKind {
fn code(&self) -> i32 {
fn code(&self) -> i32 {
match self {
ErrorKind::IOError(_,_) => 2, _ => 1,
ErrorKind::IOError(..) => 2,
_ => 1,
}
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,23 +17,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{path::Path,
fs::{OpenOptions, File},
io::Write, fmt::{Display, Formatter}};
use std::{
fmt::{Display, Formatter},
fs::{File, OpenOptions},
io::Write,
path::Path,
};
use time::{OffsetDateTime,
format_description::FormatItem,
macros::format_description, UtcOffset};
use time::{format_description::FormatItem, macros::format_description, OffsetDateTime, UtcOffset};
use crate::{err,
impl_error,
Error,
ErrorKind,
ErrorTrait,
Result,
constants::LOG_LOCATION};
use crate::{constants::LOG_LOCATION, err, impl_error, Error, ErrorKind, ErrorTrait, Result};
const DATE_FORMAT: &[FormatItem<'static>] = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second][offset_hour][offset_minute]");
const DATE_FORMAT: &[FormatItem<'static>] =
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second][offset_hour][offset_minute]");
const UTC_OFFSET: &[FormatItem<'static>] = format_description!("[offset_hour]");
impl_error!(LoggerError);
@ -58,20 +54,20 @@ pub struct Logger {
}
impl Logger {
pub fn new(module_name: &'static str) -> Self {
/*
* In order to deal with the potentiality of a race condition occurring
* between libalpm and the time crate, we cache the offset during the
* initalisation of this struct.
*/
pub fn new(module_name: &'static str) -> Self {
/*
* In order to deal with the potentiality of a race condition occurring
* between libalpm and the time crate, we cache the offset during the
* initalisation of this struct.
*/
let ofs = OffsetDateTime::now_local()
.unwrap_or(OffsetDateTime::now_utc())
.format(UTC_OFFSET)
.unwrap();
let ofs = UtcOffset::parse(ofs.as_str(), UTC_OFFSET).unwrap();
Self {
file: None,
Self {
file: None,
module: module_name,
offset: ofs,
}
@ -79,29 +75,24 @@ impl Logger {
pub fn init(mut self) -> Result<Self> {
let path = Path::new(*LOG_LOCATION);
let file = OpenOptions::new()
.create(true)
.write(true)
.append(true)
.truncate(false)
.open(path);
let file = OpenOptions::new().create(true).write(true).append(true).truncate(false).open(path);
self.file = Some(match file {
Ok(file) => file,
Err(error) => err!(ErrorKind::IOError(LOG_LOCATION.to_string(), error.kind()))?,
Err(error) => err!(ErrorKind::IOError(LOG_LOCATION.to_string(), error.kind()))?,
});
Ok(self)
}
pub fn log(&mut self, msg: impl Into<String> + std::fmt::Display) -> Result<()> {
/*
* We then attempt to update it here.
*
* If that fails, we use the previously cached value. This compromise ensures
* a stale offset value will eventually be updated to reflect the system's
* time offset if a change were to occur whilst this application is running.
*/
pub fn log(&mut self, msg: impl Into<String> + std::fmt::Display) -> Result<()> {
/*
* We then attempt to update it here.
*
* If that fails, we use the previously cached value. This compromise ensures
* a stale offset value will eventually be updated to reflect the system's
* time offset if a change were to occur whilst this application is running.
*/
if let Ok(local) = OffsetDateTime::now_local() {
self.offset = UtcOffset::parse(local.format(UTC_OFFSET).unwrap().as_str(), UTC_OFFSET).unwrap();
}
@ -109,7 +100,7 @@ impl Logger {
let time: OffsetDateTime = OffsetDateTime::now_utc().to_offset(self.offset);
let write = match self.file.as_mut() {
Some(file) => file.write(format!("[{}] [{}] {}\n", time.format(DATE_FORMAT).unwrap(), self.module, msg).as_bytes()),
None => err!(LoggerError::Uninitialized)?
None => err!(LoggerError::Uninitialized)?,
};
match write {

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -16,36 +16,41 @@
* 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::{fmt::{Display, Formatter}, path::Path};
use std::{
fmt::{Display, Formatter},
path::Path,
process::exit,
};
use alpm::{Alpm, SigLevel, Usage};
use alpm::{Alpm, SigLevel, Usage};
use lazy_static::lazy_static;
use pacmanconf;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
use crate::{err,
impl_error,
use crate::{
config::{cache::InstanceCache, global::ProgressKind, Global, InsVars, InstanceHandle, CONFIG},
constants::{ARROW_RED, BAR_GREEN, BOLD, CACHE_DIR, CONFIG_DIR, DATA_DIR, RESET},
err,
error,
error::*,
ErrorKind,
utils::print_warning,
exec::pacman_key,
constants::{BAR_GREEN, RESET, BOLD, CACHE_DIR, DATA_DIR, CONFIG_DIR, ARROW_RED},
impl_error,
sync::event::download::{self, DownloadEvent},
config::{InsVars,
InstanceHandle,
cache::InstanceCache, CONFIG, Global, global::ProgressKind}};
utils::print_warning,
ErrorKind,
};
pub mod event;
pub mod utils;
pub mod transaction;
pub mod filesystem;
mod resolver;
mod resolver_local;
pub mod transaction;
pub mod utils;
lazy_static! {
static ref PACMAN_CONF: pacmanconf::Config = pacmanconf::Config::from_file(format!("{}/repositories.conf", *CONFIG_DIR)).unwrap();
static ref DEFAULT_SIGLEVEL: SigLevel = signature(&CONFIG.alpm().sig_level(), SigLevel::PACKAGE | SigLevel::DATABASE_OPTIONAL);
pub static ref DEFAULT_ALPM_CONF: AlpmConfigData = AlpmConfigData::new();
static ref PACMAN_CONF: pacmanconf::Config = load_repositories();
static ref DEFAULT_SIGLEVEL: SigLevel = default_signature();
}
#[derive(Serialize, Deserialize, Clone, Debug)]
@ -67,36 +72,41 @@ pub enum SyncError {
InternalError(String),
NoCompatibleRemotes,
UnableToLocateKeyrings,
RepoConfError(String, String),
}
impl_error!(SyncError);
impl Display for SyncError {
fn fmt(&self, fmter: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
Self::DependentContainerMissing(u) => write!(fmter, "Dependent container '{}{u}{}' is misconfigured or otherwise is missing.", *BOLD, *RESET),
fn fmt(&self, fmter: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
Self::DependentContainerMissing(u) =>
write!(fmter, "Dependent container '{}{u}{}' is misconfigured or otherwise is missing.", *BOLD, *RESET),
Self::TargetNotAvailable(pkg) =>
write!(fmter, "Target package {}{pkg}{}: Not available in sync databases.", *BOLD, *RESET),
Self::TargetUpstream(pkg) =>
write!(fmter, "Target package {}{pkg}{}: Installed in upstream container.", *BOLD, *RESET),
Self::RecursionDepthExceeded(u) => write!(fmter, "Recursion depth exceeded maximum of {}{u}{}.", *BOLD, *RESET),
Self::TargetNotInstalled(pkg) => write!(fmter, "Target package {}{pkg}{}: Not installed.", *BOLD, *RESET),
Self::TargetNotAvailable(pkg) => write!(fmter, "Target package {}{pkg}{}: Not available in sync databases.", *BOLD, *RESET),
Self::TargetUpstream(pkg) => write!(fmter, "Target package {}{pkg}{}: Installed in upstream container.", *BOLD, *RESET),
Self::NoCompatibleRemotes => write!(fmter, "No compatible containers available to synchronize remote database."),
Self::InvalidMagicNumber => write!(fmter, "Deserialization of input parameters failed: Invalid magic number."),
Self::TargetNotInstalled(pkg) => write!(fmter, "Target package {}{pkg}{}: Not installed.", *BOLD, *RESET),
Self::InitializationFailure(msg) => write!(fmter, "Failure to initialize transaction: {msg}"),
Self::PreparationFailure(msg) => write!(fmter, "Failure to prepare 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::NoCompatibleRemotes => write!(fmter, "No compatible containers available to synchronize remote database."),
Self::InvalidMagicNumber => write!(fmter, "Deserialization of input parameters failed: Invalid magic number."),
Self::InternalError(msg) => write!(fmter, "Internal failure: {msg}"),
Self::UnableToLocateKeyrings => write!(fmter, "Unable to locate pacman keyrings."),
Self::RepoConfError(path, err) => write!(fmter, "'{}': {}", path, err),
Self::NothingToDo(_) => write!(fmter, "Nothing to do."),
_ => Ok(()),
}?;
//Temporary until command and control pipeline is implemented for pacwrap-agent
//Temporary until command and control pipeline is implemented for pacwrap-agent
match self {
Self::TransactionFailureAgent => Ok(()),
_ => write!(fmter, "\n{} Transaction failed.", *ARROW_RED)
Self::TransactionFailureAgent => Ok(()),
_ => write!(fmter, "\n{} Transaction failed.", *ARROW_RED),
}
}
}
@ -126,50 +136,55 @@ impl AlpmConfigData {
remotes.push((repo.name.clone(), signature(&repo.sig_level, *DEFAULT_SIGLEVEL).bits(), repo.servers.clone()));
}
remotes.push(("pacwrap".into(),
(SigLevel::PACKAGE_MARGINAL_OK | SigLevel::DATABASE_MARGINAL_OK).bits(),
vec![format!("file://{}", env!("PACWRAP_DIST_REPO")), format!("file:///mnt/share/dist-repo/")]));
Self {
repos: remotes,
}
remotes.push((
"pacwrap".into(),
(SigLevel::PACKAGE_MARGINAL_OK | SigLevel::DATABASE_MARGINAL_OK).bits(),
vec![
format!("file://{}", env!("PACWRAP_DIST_REPO")),
format!("file:///mnt/share/dist-repo/"),
],
));
Self { repos: remotes }
}
}
pub fn instantiate_alpm_agent(config: &Global, remotes: &AlpmConfigData) -> Alpm {
let mut handle = Alpm::new("/mnt/fs", "/mnt/fs/var/lib/pacman/").unwrap();
handle.set_logfile("/mnt/share/pacwrap.log").unwrap();
handle.set_hookdirs(vec!["/mnt/fs/usr/share/libalpm/hooks/", "/mnt/fs/etc/pacman.d/hooks/"].iter()).unwrap();
handle.set_logfile("/mnt/share/pacwrap.log").unwrap();
handle
.set_hookdirs(vec!["/mnt/fs/usr/share/libalpm/hooks/", "/mnt/fs/etc/pacman.d/hooks/"].iter())
.unwrap();
handle.set_cachedirs(vec!["/mnt/share/cache"].iter()).unwrap();
handle.set_gpgdir("/mnt/share/gnupg").unwrap();
handle.set_logfile("/mnt/share/pacwrap.log").unwrap();
handle.set_check_space(false);
handle.set_disable_dl_timeout(config.alpm().download_timeout());
handle.set_parallel_downloads(config.alpm().parallel_downloads());
handle = register_remote(handle, remotes);
handle = register_remote(handle, remotes);
handle
}
pub fn instantiate_alpm(inshandle: &InstanceHandle) -> Alpm {
pub fn instantiate_alpm(inshandle: &InstanceHandle) -> Alpm {
alpm_handle(inshandle.vars(), format!("{}/var/lib/pacman/", inshandle.vars().root()), &*DEFAULT_ALPM_CONF)
}
fn alpm_handle(insvars: &InsVars, db_path: String, remotes: &AlpmConfigData) -> Alpm {
fn alpm_handle(insvars: &InsVars, db_path: String, remotes: &AlpmConfigData) -> Alpm {
let root = insvars.root();
let mut handle = Alpm::new(root, &db_path).unwrap();
handle.set_cachedirs(vec![format!("{}/pkg", *CACHE_DIR)].iter()).unwrap();
handle.set_gpgdir(format!("{}/pacman/gnupg", *DATA_DIR)).unwrap();
handle.set_logfile(format!("{}/pacwrap.log", *DATA_DIR)).unwrap();
handle.set_parallel_downloads(CONFIG.alpm().parallel_downloads());
handle.set_parallel_downloads(CONFIG.alpm().parallel_downloads());
handle.set_check_space(CONFIG.alpm().check_space());
handle.set_disable_dl_timeout(CONFIG.alpm().download_timeout());
handle = register_remote(handle, remotes);
handle = register_remote(handle, remotes);
handle
}
//TODO: Port pacman-key to Rust
//TODO: Port pacman-key to Rust
pub fn instantiate_trust() -> Result<()> {
let path = &format!("{}/pacman/gnupg/", *DATA_DIR);
@ -180,7 +195,7 @@ pub fn instantiate_trust() -> Result<()> {
println!("{} {}Initializing package trust database...{}", *BAR_GREEN, *BOLD, *RESET);
if ! Path::new("/usr/share/pacman/keyrings").exists() {
if !Path::new("/usr/share/pacman/keyrings").exists() {
err!(SyncError::UnableToLocateKeyrings)?
}
@ -192,10 +207,9 @@ pub fn instantiate_trust() -> Result<()> {
pacman_key(path, vec!["--populate"])
}
fn register_remote(mut handle: Alpm, config: &AlpmConfigData) -> Alpm {
fn register_remote(mut handle: Alpm, config: &AlpmConfigData) -> Alpm {
for repo in &config.repos {
let core = handle.register_syncdb_mut(repo.0.clone(),
SigLevel::from_bits(repo.1).unwrap()).unwrap();
let core = handle.register_syncdb_mut(repo.0.clone(), SigLevel::from_bits(repo.1).unwrap()).unwrap();
for server in &repo.2 {
core.add_server(server.as_str()).unwrap();
@ -208,32 +222,32 @@ fn register_remote(mut handle: Alpm, config: &AlpmConfigData) -> Alpm {
}
fn synchronize_database(cache: &InstanceCache, force: bool) -> Result<()> {
match cache.obtain_base_handle() {
match cache.obtain_base_handle() {
Some(ins) => {
let db_path = format!("{}/pacman/", *DATA_DIR);
let mut handle = alpm_handle(&ins.vars(), db_path, &*DEFAULT_ALPM_CONF);
println!("{} {}Synchronizing package databases...{}", *BAR_GREEN, *BOLD, *RESET);
println!("{} {}Synchronizing package databases...{}", *BAR_GREEN, *BOLD, *RESET);
handle.set_dl_cb(DownloadEvent::new().style(&ProgressKind::Verbose), download::event);
if let Err(err) = handle.syncdbs_mut().update(force) {
err!(SyncError::InitializationFailure(err.to_string()))?
}
Alpm::release(handle).unwrap();
Alpm::release(handle).unwrap();
for i in cache.registered().iter() {
let ins: &InstanceHandle = cache.get_instance(i).unwrap();
let vars: &InsVars = ins.vars();
let src = &format!("{}/pacman/sync/pacwrap.db",*DATA_DIR);
let src = &format!("{}/pacman/sync/pacwrap.db", *DATA_DIR);
let dest = &format!("{}/var/lib/pacman/sync/pacwrap.db", vars.root());
if let Err(error) = filesystem::create_hard_link(src, dest) {
print_warning(error);
print_warning(error);
}
for repo in PACMAN_CONF.repos.iter() {
let src = &format!("{}/pacman/sync/{}.db",*DATA_DIR, repo.name);
let src = &format!("{}/pacman/sync/{}.db", *DATA_DIR, repo.name);
let dest = &format!("{}/var/lib/pacman/sync/{}.db", vars.root(), repo.name);
if let Err(error) = filesystem::create_hard_link(src, dest) {
print_warning(error);
@ -242,8 +256,8 @@ fn synchronize_database(cache: &InstanceCache, force: bool) -> Result<()> {
}
Ok(())
},
None => err!(SyncError::NoCompatibleRemotes),
}
None => err!(SyncError::NoCompatibleRemotes),
}
}
@ -252,25 +266,45 @@ fn signature(sigs: &Vec<String>, default: SigLevel) -> SigLevel {
let mut sig = SigLevel::empty();
for level in sigs {
sig = sig | if level == "Required" || level == "PackageRequired" {
SigLevel::PACKAGE
} else if level == "DatabaseRequired" || level == "DatabaseTrustedOnly" {
SigLevel::DATABASE
} else if level == "PackageOptional" {
SigLevel::PACKAGE_OPTIONAL
} else if level == "PackageTrustAll" {
SigLevel::PACKAGE_UNKNOWN_OK | SigLevel::DATABASE_MARGINAL_OK
} else if level == "DatabaseOptional" {
SigLevel::DATABASE_OPTIONAL
} else if level == "DatabaseTrustAll" {
SigLevel::DATABASE_UNKNOWN_OK | SigLevel::PACKAGE_MARGINAL_OK
} else {
SigLevel::empty()
}
sig = sig
| if level == "Required" || level == "PackageRequired" {
SigLevel::PACKAGE
} else if level == "DatabaseRequired" || level == "DatabaseTrustedOnly" {
SigLevel::DATABASE
} else if level == "PackageOptional" {
SigLevel::PACKAGE_OPTIONAL
} else if level == "PackageTrustAll" {
SigLevel::PACKAGE_UNKNOWN_OK | SigLevel::DATABASE_MARGINAL_OK
} else if level == "DatabaseOptional" {
SigLevel::DATABASE_OPTIONAL
} else if level == "DatabaseTrustAll" {
SigLevel::DATABASE_UNKNOWN_OK | SigLevel::PACKAGE_MARGINAL_OK
} else {
SigLevel::empty()
}
}
sig
sig
} else {
default
}
}
fn load_repositories() -> pacmanconf::Config {
let path = format!("{}/repositories.conf", *CONFIG_DIR);
match pacmanconf::Config::from_file(&path) {
Ok(config) => config,
Err(error) => {
let error = error.to_string();
let error = error.split("error: ").collect::<Vec<_>>()[1].split("\n").collect::<Vec<&str>>()[0];
let error = error!(SyncError::RepoConfError(path, error.to_string()));
exit(error.error());
}
}
}
fn default_signature() -> SigLevel {
signature(&CONFIG.alpm().sig_level(), SigLevel::PACKAGE | SigLevel::DATABASE_OPTIONAL)
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -18,23 +18,23 @@
*/
pub mod download;
pub mod query;
pub mod progress;
pub mod query;
fn whitespace(total: usize, current: usize) -> String {
let total = log10(total);
let current = log10(current);
let current = log10(current);
let mut whitespace = String::new();
let difference = total-current;
for _ in 0..difference {
let difference = total - current;
for _ in 0 .. difference {
whitespace.push_str(" ");
}
}
whitespace
}
fn log10(mut value: usize) -> usize {
fn log10(mut value: usize) -> usize {
let mut length = 0;
while value > 0 {

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -21,38 +21,42 @@ use std::collections::HashMap;
use alpm::{AnyDownloadEvent, DownloadEvent as Event};
use dialoguer::console::Term;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle, ProgressDrawTarget};
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
use lazy_static::lazy_static;
use simplebyteunit::simplebyteunit::*;
use crate::{constants::{ARROW_CYAN, BOLD, RESET}, config::global::ProgressKind, sync::transaction::TransactionMode};
use crate::{
config::global::ProgressKind,
constants::{ARROW_CYAN, BOLD, RESET},
sync::transaction::TransactionMode,
};
use super::whitespace;
lazy_static!{
static ref INIT: ProgressStyle = ProgressStyle::with_template(" {spinner:.green} {msg}").unwrap();
static ref UP_TO_DATE: ProgressStyle = ProgressStyle::with_template(" {spinner:.green} {msg} is up-to-date!")
lazy_static! {
static ref INIT: ProgressStyle = ProgressStyle::with_template(" {spinner:.green} {msg}").unwrap();
static ref UP_TO_DATE: ProgressStyle = ProgressStyle::with_template(" {spinner:.green} {msg} is up-to-date!")
.unwrap()
.progress_chars("#-")
.tick_strings(&[" ",""]);
.tick_strings(&[" ", ""]);
}
#[derive(Clone)]
pub struct DownloadEvent {
total: usize,
position: usize,
total_bar: Option<ProgressBar>,
total_bar: Option<ProgressBar>,
condensed: bool,
progress: MultiProgress,
bars: HashMap<&'static str, ProgressBar>,
style: Option<ProgressStyle>,
style: Option<ProgressStyle>,
}
impl DownloadEvent {
pub fn new() -> Self {
pub fn new() -> Self {
Self {
total: 0,
position: 0,
position: 0,
total_bar: None,
condensed: false,
progress: MultiProgress::new(),
@ -62,17 +66,20 @@ impl DownloadEvent {
}
pub fn style(mut self, kind: &ProgressKind) -> Self {
self.style = match kind {
self.style = match kind {
ProgressKind::Simple => None,
_ => Some({
let size = Term::size(&Term::stdout());
let width = ((size.1 / 2) - 14).to_string();
ProgressStyle::with_template(&(" {spinner:.green} {msg:<".to_owned()+&width
+ "} {bytes:>11} {bytes_per_sec:>12} {elapsed_precise:>5} [{wide_bar}] {percent:<3}%"))
.unwrap()
.progress_chars("#-")
.tick_strings(&[" ", ""])
ProgressStyle::with_template(
&(" {spinner:.green} {msg:<".to_owned()
+ &width
+ "} {bytes:>11} {bytes_per_sec:>12} {elapsed_precise:>5} [{wide_bar}] {percent:<3}%"),
)
.unwrap()
.progress_chars("#-")
.tick_strings(&[" ", ""])
}),
};
self
@ -82,10 +89,13 @@ impl DownloadEvent {
self.total = files;
self.total_bar = match (bytes > 0 && files > 1, self.style.as_ref()) {
(true, Some(style)) => Some({
let bar = self.progress.add(ProgressBar::new(bytes)
.with_style(style.clone())
.with_message(format!("Total ({}0/{})", whitespace(files, 1), files)));
let bar = ProgressBar::new(bytes).with_style(style.clone()).with_message(format!(
"Total ({}0/{})",
whitespace(files, 1),
files
));
let bar = self.progress.add(bar);
bar.set_position(0);
bar
}),
@ -113,34 +123,35 @@ impl DownloadEvent {
fn increment(&mut self, progress: u64) {
let bar = match self.total_bar.as_mut() {
Some(bar) => bar, None => return,
Some(bar) => bar,
None => return,
};
self.position += 1;
let total = self.total;
let pos = self.position;
let whitespace = whitespace(total, pos);
bar.inc(progress);
bar.set_message(format!("Total ({}{}/{})", whitespace, pos, total));
if total == pos {
bar.finish();
}
bar.set_message(format!("Total ({}{}/{})", whitespace, pos, total));
if total == pos {
bar.finish();
}
}
fn insert(&mut self, file: &str) {
let pb = match self.total_bar.as_mut() {
fn insert(&mut self, file: &str) {
let pb = match self.total_bar.as_mut() {
Some(total) => match self.condensed {
true => self.progress.insert_after(&total, ProgressBar::new(0)),
true => self.progress.insert_after(&total, ProgressBar::new(0)),
false => self.progress.insert_before(&total, ProgressBar::new(0)),
},
None => self.progress.add(ProgressBar::new(0))
},
None => self.progress.add(ProgressBar::new(0)),
};
pb.set_style(INIT.clone());
pb.set_message(message(file));
pb.set_message(message(file));
/*
* alpm-rs expects our callback signature to provide a struct bound by a 'static lifetime,
@ -152,7 +163,7 @@ impl DownloadEvent {
pub fn simple(file: &str, download: AnyDownloadEvent, this: &mut DownloadEvent) {
if file.ends_with(".sig") {
return;
return;
}
if let Event::Completed(progress) = download.event() {
@ -161,72 +172,73 @@ pub fn simple(file: &str, download: AnyDownloadEvent, this: &mut DownloadEvent)
let size = progress.total.abs().to_byteunit(SI);
let total = this.total;
let pos = this.position;
let whitespace = whitespace(total, pos);
let whitespace = whitespace(total, pos);
let message = message(file);
eprintln!("{} ({}{whitespace}{pos}{}/{}{total}{}) {message} downloaded ({size})", *ARROW_CYAN, *BOLD, *RESET, *BOLD, *RESET);
eprintln!(
"{} ({}{whitespace}{pos}{}/{}{total}{}) {message} downloaded ({size})",
*ARROW_CYAN, *BOLD, *RESET, *BOLD, *RESET
);
}
}
pub fn event(file: &str, download: AnyDownloadEvent, this: &mut DownloadEvent) {
if file.ends_with(".sig") {
return;
return;
}
match download.event() {
Event::Progress(progress) => {
Event::Progress(progress) =>
if let Some(pb) = this.bars.get_mut(file) {
if pb.length().unwrap() == 0 {
pb.set_length(progress.total.unsigned_abs());
pb.set_style(this.style.as_ref().unwrap().clone());
}
pb.set_position(progress.downloaded.unsigned_abs());
}
},
Event::Completed(progress) => {
if let Some(pb) = this.bars.remove(file) {
if pb.length().unwrap() == 0 {
pb.set_position(progress.downloaded.unsigned_abs());
},
Event::Completed(progress) =>
if let Some(pb) = this.bars.remove(file) {
if pb.length().unwrap() == 0 {
pb.set_style(UP_TO_DATE.clone());
}
pb.finish();
pb.finish();
this.increment(progress.total.unsigned_abs());
if this.condensed {
pb.set_draw_target(ProgressDrawTarget::hidden());
}
}
},
},
Event::Init(progress) => {
if progress.optional {
return;
}
this.insert(file);
},
Event::Retry(_) => {
if let Some(pb) = this.bars.get_mut(file) {
}
Event::Retry(_) =>
if let Some(pb) = this.bars.get_mut(file) {
pb.set_position(0);
pb.set_style(INIT.clone());
}
},
},
}
}
pub fn callback(progress: &ProgressKind) -> for<'a, 'b, 'c> fn(file: &'a str, AnyDownloadEvent<'b>, this: &'c mut DownloadEvent) {
pub fn callback(progress: &ProgressKind) -> for<'a, 'b, 'c> fn(file: &'a str, AnyDownloadEvent<'b>, this: &'c mut DownloadEvent) {
match progress {
ProgressKind::Simple => simple, _ => event
ProgressKind::Simple => simple,
_ => event,
}
}
fn message(filename: &str) -> String {
let name: Vec<&str> = filename.split(".pkg.tar.").collect();
let name: Vec<&str> = filename.split(".pkg.tar.").collect();
let mut msg_name: String = name[0].to_string();
if msg_name.ends_with(".db") {
msg_name.truncate(msg_name.len()-3);
msg_name.truncate(msg_name.len() - 3);
}
msg_name
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -21,9 +21,14 @@ use alpm::Progress as Event;
use dialoguer::console::Term;
use indicatif::{ProgressBar, ProgressStyle};
use crate::{constants::{BOLD, RESET, ARROW_CYAN},
sync::{event::whitespace, transaction::{TransactionType, TransactionMode}},
config::global::ProgressKind};
use crate::{
config::global::ProgressKind,
constants::{ARROW_CYAN, BOLD, RESET},
sync::{
event::whitespace,
transaction::{TransactionMode, TransactionType},
},
};
#[derive(Clone)]
pub struct ProgressEvent {
@ -34,12 +39,12 @@ pub struct ProgressEvent {
}
impl ProgressEvent {
pub fn new() -> Self {
pub fn new() -> Self {
Self {
current: None,
progress: None,
current: None,
progress: None,
style: None,
offset: 0,
offset: 0,
}
}
@ -49,15 +54,16 @@ impl ProgressEvent {
}
pub fn style(mut self, kind: &ProgressKind) -> Self {
self.style = match kind {
self.style = match kind {
ProgressKind::Simple => None,
_ => Some({
let size = Term::size(&Term::stdout());
let width = (size.1 / 2).to_string();
ProgressStyle::with_template(&(" {spinner:.green} {msg:<".to_owned()
+ &width +"} [{wide_bar}] {percent:<3}%"))
.unwrap().progress_chars("#-").tick_strings(&[" ", ""])
ProgressStyle::with_template(&(" {spinner:.green} {msg:<".to_owned() + &width + "} [{wide_bar}] {percent:<3}%"))
.unwrap()
.progress_chars("#-")
.tick_strings(&[" ", ""])
}),
};
self
@ -65,11 +71,11 @@ impl ProgressEvent {
fn bar(&mut self, ident: Option<String>, name: &str, howmany: usize, current: usize, size: usize) {
let pos = current + self.offset;
let total = howmany + self.offset;
let total = howmany + self.offset;
let whitespace = whitespace(total, pos);
let progress = ProgressBar::new(size as u64);
progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {name}", *BOLD, *RESET, *BOLD, *RESET));
progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {name}", *BOLD, *RESET, *BOLD, *RESET));
progress.set_style(self.style.as_ref().unwrap().clone());
self.progress = Some(progress);
@ -78,46 +84,51 @@ impl ProgressEvent {
}
pub fn event(event: Event, pkgname: &str, percent: i32, howmany: usize, current: usize, this: &mut ProgressEvent) {
let ident = ident(event,pkgname);
let ident = ident(event, pkgname);
match this.current.as_deref() {
Some(current_ident) => if ident != current_ident {
this.bar(Some(ident), &name(event,pkgname), howmany, current, 100)
},
None => this.bar(Some(ident), &name(event,pkgname), howmany, current, 100),
Some(current_ident) =>
if ident != current_ident {
this.bar(Some(ident), &name(event, pkgname), howmany, current, 100)
},
None => this.bar(Some(ident), &name(event, pkgname), howmany, current, 100),
};
let progress = match this.progress.as_mut() {
Some(progress) => progress, None => return,
Some(progress) => progress,
None => return,
};
progress.set_position(percent as u64);
if percent == 100 {
progress.finish();
progress.finish();
}
}
pub fn simple(progress: Event, pkgname: &str, percent: i32, howmany: usize, current: usize, this: &mut ProgressEvent) {
if percent == 0 {
let pos = current + this.offset;
let total = howmany + this.offset;
let progress_name: String = name(progress,pkgname);
let total = howmany + this.offset;
let progress_name: String = name(progress, pkgname);
let whitespace = whitespace(total, pos);
eprintln!("{} ({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *ARROW_CYAN, *BOLD, *RESET, *BOLD, *RESET);
eprintln!("{} ({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *ARROW_CYAN, *BOLD, *RESET, *BOLD, *RESET);
if current == howmany {
eprintln!("{} ({}{whitespace}{pos}{}/{}{total}{}) Synchronization complete", *ARROW_CYAN, *BOLD, *RESET, *BOLD, *RESET);
eprintln!(
"{} ({}{whitespace}{pos}{}/{}{total}{}) Synchronization complete",
*ARROW_CYAN, *BOLD, *RESET, *BOLD, *RESET
);
}
}
}
pub fn condensed(kind: Event, pkgname: &str, percent: i32, howmany: usize, current: usize, this: &mut ProgressEvent) {
if let Event::AddStart | Event::RemoveStart | Event::UpgradeStart = kind {
if let Event::AddStart | Event::RemoveStart | Event::UpgradeStart = kind {
let pos = current + this.offset;
let total = howmany + this.offset;
let progress_name: String = name(kind,pkgname);
let total = howmany + this.offset;
let progress_name: String = name(kind, pkgname);
let whitespace = whitespace(total, pos);
if let Some(_) = this.current {
@ -125,16 +136,20 @@ pub fn condensed(kind: Event, pkgname: &str, percent: i32, howmany: usize, curre
}
let progress = match this.progress.as_mut() {
Some(progress) => progress, None => return,
Some(progress) => progress,
None => return,
};
progress.set_position(current as u64);
progress.set_position(current as u64);
if current != howmany {
progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *BOLD, *RESET, *BOLD, *RESET));
progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *BOLD, *RESET, *BOLD, *RESET));
} else {
progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) Synchronization complete", *BOLD, *RESET, *BOLD, *RESET));
progress.finish();
progress.set_message(format!(
"({}{whitespace}{pos}{}/{}{total}{}) Synchronization complete",
*BOLD, *RESET, *BOLD, *RESET
));
progress.finish();
}
} else {
event(kind, pkgname, percent, howmany, current, this)
@ -142,13 +157,13 @@ pub fn condensed(kind: Event, pkgname: &str, percent: i32, howmany: usize, curre
}
fn name(progress: Event, pkgname: &str) -> String {
match progress {
Event::UpgradeStart => format!("Upgrading {}", pkgname),
match progress {
Event::UpgradeStart => format!("Upgrading {}", pkgname),
Event::AddStart => format!("Installing {}", pkgname),
Event::RemoveStart => format!("Removing {}", pkgname),
Event::DowngradeStart => format!("Downgrading {}", pkgname),
Event::ReinstallStart => format!("Reinstalling {}", pkgname),
Event::KeyringStart => format!("Loading keyring"),
Event::KeyringStart => format!("Loading keyring"),
Event::IntegrityStart => format!("Checking integrity"),
Event::LoadStart => format!("Loading packages"),
Event::ConflictsStart => format!("Checking conflicts"),
@ -162,12 +177,15 @@ fn ident(progress: Event, pkgname: &str) -> String {
Event::IntegrityStart => "integrity",
Event::LoadStart => "loadstart",
Event::ConflictsStart => "conflicts",
_ => pkgname
}.to_owned()
_ => pkgname,
}
.to_owned()
}
pub fn callback(state: &TransactionMode, kind: &ProgressKind) -> for<'a, 'b> fn(Event, &'a str, i32, usize, usize, &'b mut ProgressEvent) {
pub fn callback(
state: &TransactionMode,
kind: &ProgressKind,
) -> for<'a, 'b> fn(Event, &'a str, i32, usize, usize, &'b mut ProgressEvent) {
match kind {
ProgressKind::Simple => simple,
ProgressKind::CondensedForeign => match state {

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -29,20 +29,20 @@ pub fn callback(question: AnyQuestion, _: &mut ()) {
let pkg_a = x.conflict().package1();
let pkg_b = x.conflict().package2();
let prompt_string = format!("Conflict between {pkg_a} and {pkg_b}; Remove {pkg_b}?");
if let Ok(_) = prompt("->", prompt_string, false) {
x.set_remove(true);
}
},
Replace(x) => {
}
Replace(x) => {
let old = x.oldpkg().name();
let new = x.newpkg().name();
let prompt_string = format!("Replace package {old} with {new}?");
if let Ok(_) = prompt("->", prompt_string, false) {
x.set_replace(true);
}
},
}
Corrupted(mut x) => {
let filepath = x.filepath();
let filename = Path::new(filepath).file_name().unwrap().to_str().unwrap();
@ -52,18 +52,18 @@ pub fn callback(question: AnyQuestion, _: &mut ()) {
if let Ok(_) = prompt("::", prompt_string, true) {
x.set_remove(true);
}
},
}
ImportKey(mut x) => {
let key = x.key();
let fingerprint = key.fingerprint();
let email = key.email();
let name = key.name();
let prompt_string = format!("Import key {fingerprint},\"{name} <{email}>\" to keyring?");
if let Ok(_) = prompt("->", prompt_string, true) {
x.set_import(true);
}
},
}
}
_ => (),
}
}

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -17,27 +17,33 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{fs::{self, File, Metadata},
use std::{
collections::{HashMap, HashSet},
fs::{self, File, Metadata},
io::Read,
os::unix::fs::symlink,
os::unix::prelude::MetadataExt,
os::unix::{fs::symlink, prelude::MetadataExt},
path::Path,
sync::{Arc, mpsc::{Sender, self, Receiver}},
collections::{HashMap, HashSet}};
sync::{
mpsc::{self, Receiver, Sender},
Arc,
},
};
use dialoguer::console::Term;
use rayon::{prelude::*, {ThreadPool, ThreadPoolBuilder}};
use indexmap::IndexMap;
use indicatif::{ProgressBar, ProgressStyle, ProgressDrawTarget};
use serde::{Serialize, Deserialize};
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
use rayon::{prelude::*, ThreadPool, ThreadPoolBuilder};
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
use crate::{err,
ErrorKind,
use crate::{
config::{InstanceCache, InstanceHandle, InstanceType::*},
constants::{ARROW_CYAN, BAR_GREEN, BOLD, DATA_DIR, RESET},
err,
utils::{print_error, print_warning, read_le_32},
Error,
config::{InstanceHandle, InstanceCache, InstanceType::*},
constants::{RESET, BOLD, ARROW_CYAN, BAR_GREEN, DATA_DIR},
utils::{print_warning, print_error, read_le_32}};
ErrorKind,
};
static VERSION: u32 = 1;
static MAGIC_NUMBER: u32 = 408948530;
@ -46,7 +52,7 @@ static MAGIC_NUMBER: u32 = 408948530;
struct FileSystemState {
magic: u32,
version: u32,
files: IndexMap<Arc<str>, (FileType, Arc<str>)>
files: IndexMap<Arc<str>, (FileType, Arc<str>)>,
}
impl FileSystemState {
@ -73,7 +79,7 @@ impl From<i8> for FileType {
2 => Self::Directory,
1 => Self::SymLink,
0 => Self::HardLink,
_ => Self::Invalid(integer)
_ => Self::Invalid(integer),
}
}
}
@ -96,27 +102,28 @@ enum SyncMessage {
}
pub struct FileSystemStateSync<'a> {
state_map: HashMap<Arc<str>, FileSystemState>,
state_map: HashMap<Arc<str>, FileSystemState>,
state_map_prev: HashMap<Arc<str>, FileSystemState>,
linked: HashSet<Arc<str>>,
queued: HashSet<&'a str>,
progress: ProgressBar,
cache: &'a InstanceCache<'a>,
pool: Option<ThreadPool>,
max_chars: u16,
max_chars: u16,
}
impl <'a>FileSystemStateSync<'a> {
impl<'a> FileSystemStateSync<'a> {
pub fn new(inscache: &'a InstanceCache) -> Self {
let size = Term::size(&Term::stdout());
let column_half = size.1 / 2;
let style = ProgressStyle::with_template(&(" {spinner:.green} {msg:<".to_owned()
+column_half.to_string().as_str()+"} [{wide_bar}] {percent:<3}%"))
.unwrap()
.progress_chars("#-")
.tick_strings(&[">", ""]);
let style = ProgressStyle::with_template(
&(" {spinner:.green} {msg:<".to_owned() + column_half.to_string().as_str() + "} [{wide_bar}] {percent:<3}%"),
)
.unwrap()
.progress_chars("#-")
.tick_strings(&[">", ""]);
let pr = ProgressBar::new(0).with_style(style);
pr.set_draw_target(ProgressDrawTarget::hidden());
Self {
@ -132,17 +139,21 @@ impl <'a>FileSystemStateSync<'a> {
}
pub fn engage(&mut self, containers: &Vec<&'a str>) -> Result<(), Error> {
let (tx, rx) = self.link(containers, mpsc::channel())?;
drop(tx);
let (tx, rx) = self.link(containers, mpsc::channel())?;
drop(tx);
while let Ok(()) = rx.recv() {}
Ok(())
}
fn link(&mut self, containers: &Vec<&'a str>, mut write_chan: (Sender<()>, Receiver<()>)) -> Result<(Sender<()>, Receiver<()>), Error> {
fn link(
&mut self,
containers: &Vec<&'a str>,
mut write_chan: (Sender<()>, Receiver<()>),
) -> Result<(Sender<()>, Receiver<()>), Error> {
let (tx, rx): (Sender<SyncMessage>, Receiver<SyncMessage>) = mpsc::channel();
for ins in containers {
for ins in containers {
if self.queued.contains(ins) {
continue;
}
@ -150,11 +161,11 @@ impl <'a>FileSystemStateSync<'a> {
let inshandle = self.cache.get_instance(ins)?;
write_chan = self.link(&inshandle.metadata().dependencies(), write_chan)?;
if let ROOT = inshandle.metadata().container_type() {
self.link_instance(inshandle, tx.clone())?;
if let Aggregate = inshandle.metadata().container_type() {
self.link_instance(inshandle, tx.clone())?;
} else {
self.obtain_slice(inshandle, tx.clone())?;
self.obtain_slice(inshandle, tx.clone())?;
}
self.queued.insert(ins);
@ -165,18 +176,18 @@ impl <'a>FileSystemStateSync<'a> {
Ok(write_chan)
}
fn wait(&mut self, mut queue: HashSet<&'a str>, rx: Receiver<SyncMessage>, write_chan: &(Sender<()>, Receiver<()>)) {
fn wait(&mut self, mut queue: HashSet<&'a str>, rx: Receiver<SyncMessage>, write_chan: &(Sender<()>, Receiver<()>)) {
while let Ok(recv) = rx.recv() {
match recv {
SyncMessage::LinkComplete(ins) => {
let instance = ins.as_ref();
let status = queue_status(&queue, instance, self.max_chars as usize);
queue.remove(instance);
self.linked.insert(ins);
self.progress.set_message(status);
self.progress.inc(1);
},
}
SyncMessage::SaveState(dep, fs_state) => {
if let Some(_) = self.state_map.get(&dep) {
continue;
@ -185,30 +196,30 @@ impl <'a>FileSystemStateSync<'a> {
if fs_state.files.len() == 0 {
continue;
}
self.state_map.insert(dep.clone(), fs_state.clone());
self.write(write_chan.0.clone(), fs_state, dep);
self.write(write_chan.0.clone(), fs_state, dep);
}
}
}
}
}
fn previous_state(&mut self, instance: &Arc<str>) -> FileSystemState {
if let Some(st) = self.state_map_prev.get(instance) {
return st.clone()
return st.clone();
}
let mut header_buffer = vec![0; 8];
let mut header_buffer = vec![0; 8];
let path = format!("{}/state/{}.dat", *DATA_DIR, instance);
let mut file = match File::open(&path) {
let mut file = match File::open(&path) {
Ok(file) => file,
Err(err) => {
Err(err) => {
if err.kind() != std::io::ErrorKind::NotFound {
print_error(format!("'{}': {}", path, err.kind()));
}
return self.blank_state(instance);
},
return self.blank_state(instance);
}
};
if let Err(error) = file.read_exact(&mut header_buffer) {
@ -221,19 +232,24 @@ impl <'a>FileSystemStateSync<'a> {
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);
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());
state
},
Err(err) => {
print_error(format!("Deserialization failure occurred with '{}{instance}{}.dat': {}", *BOLD, *RESET, err.as_ref()));
return self.blank_state(instance);
}
Err(err) => {
print_error(format!(
"Deserialization failure occurred with '{}{instance}{}.dat': {}",
*BOLD,
*RESET,
err.as_ref()
));
return self.blank_state(instance);
}
}
}
@ -241,8 +257,8 @@ impl <'a>FileSystemStateSync<'a> {
fn blank_state(&mut self, instance: &Arc<str>) -> FileSystemState {
let state = FileSystemState::new();
self.state_map_prev.insert(instance.clone(), state.clone());
state
self.state_map_prev.insert(instance.clone(), state.clone());
state
}
fn write(&mut self, tx: Sender<()>, ds: FileSystemState, dep: Arc<str>) {
@ -250,26 +266,26 @@ impl <'a>FileSystemStateSync<'a> {
let output = match File::create(path) {
Ok(file) => file,
Err(err) => {
print_warning(format!("Writing '{}': {}", path, err.kind()));
print_warning(format!("Writing '{}': {}", path, err.kind()));
return;
}
};
self.pool().unwrap().spawn(move ||{
self.pool().unwrap().spawn(move || {
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()));
}
drop(tx);
});
}
}
fn obtain_slice(&mut self, inshandle: &InstanceHandle, tx: Sender<SyncMessage>) -> Result<(), Error> {
let instance: Arc<str> = inshandle.vars().instance().into();
let root = inshandle.vars().root().into();
self.previous_state(&instance);
Ok(self.pool()?.spawn(move ||{
Ok(self.pool()?.spawn(move || {
let mut state = FileSystemState::new();
obtain_state(root, &mut state);
@ -280,24 +296,24 @@ impl <'a>FileSystemStateSync<'a> {
}
fn link_instance(&mut self, inshandle: &InstanceHandle, tx: Sender<SyncMessage>) -> Result<(), Error> {
let mut map = Vec::new();
let mut map = Vec::new();
let mut prev = Vec::new();
let instance: Arc<str> = inshandle.vars().instance().into();
let root: Arc<str> = inshandle.vars().root().into();
let state = FileSystemState::new();
for dep in inshandle.metadata().dependencies() {
let dephandle = self.cache.get_instance(dep).unwrap();
let state = match self.state_map.get(dep) {
let state = match self.state_map.get(dep) {
Some(state) => state.clone(),
None => FileSystemState::new()
None => FileSystemState::new(),
};
prev.push(self.previous_state(&Arc::from(dep.as_ref())));
map.push((dephandle.vars().root().into(), state));
}
Ok(self.pool()?.spawn(move ||{
Ok(self.pool()?.spawn(move || {
let state = filesystem_state(state, map);
let state_prev = previous_state(prev);
@ -311,30 +327,29 @@ impl <'a>FileSystemStateSync<'a> {
fn pool(&self) -> Result<&ThreadPool, Error> {
match self.pool.as_ref() {
Some(pool) => Ok(pool),
None => err!(ErrorKind::ThreadPoolUninitialized)
Some(pool) => Ok(pool),
None => err!(ErrorKind::ThreadPoolUninitialized),
}
}
pub fn prepare_single(&mut self) {
println!("{} Synchronizing container state...", *ARROW_CYAN);
println!("{} Synchronizing container state...", *ARROW_CYAN);
if let None = self.pool {
self.pool = Some(ThreadPoolBuilder::new()
.thread_name(|f| { format!("PW-LINKER-{}", f) })
.num_threads(2)
.build()
.unwrap());
self.pool = Some(
ThreadPoolBuilder::new()
.thread_name(|f| format!("PW-LINKER-{}", f))
.num_threads(2)
.build()
.unwrap(),
);
}
}
pub fn prepare(&mut self, length: usize) {
println!("{} {}Synchronizing container filesystems...{} ",*BAR_GREEN, *BOLD, *RESET);
println!("{} {}Synchronizing container filesystems...{} ", *BAR_GREEN, *BOLD, *RESET);
self.pool = Some(ThreadPoolBuilder::new()
.thread_name(|f| { format!("PW-LINKER-{}", f) })
.build()
.unwrap());
self.pool = Some(ThreadPoolBuilder::new().thread_name(|f| format!("PW-LINKER-{}", f)).build().unwrap());
self.progress.set_draw_target(ProgressDrawTarget::stdout());
self.progress.set_message("Synhcronizing containers..");
self.progress.set_position(0);
@ -346,7 +361,7 @@ impl <'a>FileSystemStateSync<'a> {
}
pub fn finish(&mut self) {
self.progress.set_message("Synchronization complete.");
self.progress.set_message("Synchronization complete.");
self.progress.finish();
self.pool = None;
}
@ -381,25 +396,23 @@ fn filesystem_state(mut state: FileSystemState, map: Vec<(Arc<str>, FileSystemSt
fn obtain_state(root: Arc<str>, state: &mut FileSystemState) {
let len = root.len();
let entries = WalkDir::new(root.as_ref())
.into_iter()
.filter_map(|e| e.ok());
let entries = WalkDir::new(root.as_ref()).into_iter().filter_map(|e| e.ok());
for entry in entries {
for entry in entries {
let src: Arc<str> = entry.path().to_str().unwrap().into();
let src_tr: Arc<str> = src.split_at(len).1.into();
if let Some(_) = state.files.get(&src_tr) {
continue;
}
if src.contains("/var/lib/pacman")
|| src.ends_with("/etc/ld.so.cache") {
if src.contains("/var/lib/pacman") || src.ends_with("/etc/ld.so.cache") {
continue;
}
let metadata = match entry.metadata() {
Ok(meta) => meta, Err(_) => continue
Ok(meta) => meta,
Err(_) => continue,
};
state.files.insert(src_tr, (FileType::from(metadata), src));
@ -408,31 +421,31 @@ fn obtain_state(root: Arc<str>, state: &mut FileSystemState) {
fn link_filesystem(state: &FileSystemState, root: &str) {
state.files.par_iter().for_each(|file| {
if let FileType::SymLink = file.1.0 {
if let Err(error) = create_soft_link(&file.1.1, &format!("{}{}", root, file.0)) {
if let FileType::SymLink = file.1 .0 {
if let Err(error) = create_soft_link(&file.1 .1, &format!("{}{}", root, file.0)) {
print_warning(error);
}
} else if let FileType::HardLink = file.1.0 {
if let Err(error) = create_hard_link(&file.1.1, &format!("{}{}", root, file.0)) {
} else if let FileType::HardLink = file.1 .0 {
if let Err(error) = create_hard_link(&file.1 .1, &format!("{}{}", root, file.0)) {
print_warning(error);
}
}
});
}
fn delete_files(state: &FileSystemState, state_res: &FileSystemState, root: &str) {
fn delete_files(state: &FileSystemState, state_res: &FileSystemState, root: &str) {
let (tx, rx) = mpsc::sync_channel(0);
let tx_clone: mpsc::SyncSender<()> = tx.clone();
state_res.files.par_iter().for_each(|file| {
let _ = tx_clone;
state_res.files.par_iter().for_each(|file| {
let _ = tx_clone;
if let None = state.files.get(file.0) {
let path: &str = &format!("{}{}", root, file.0);
let path = Path::new(path);
let path = Path::new(path);
if ! path.exists() {
if let FileType::SymLink = file.1.0 {
if !path.exists() {
if let FileType::SymLink = file.1 .0 {
if let Err(error) = remove_symlink(path) {
print_warning(error);
}
@ -440,34 +453,34 @@ fn delete_files(state: &FileSystemState, state_res: &FileSystemState, root: &str
return;
}
if let FileType::HardLink = file.1.0 {
if let Err(error) = remove_file(path) {
print_warning(error);
if let FileType::HardLink = file.1 .0 {
if let Err(error) = remove_file(path) {
print_warning(error);
}
}
}
}
});
drop(tx);
rx.try_iter();
}
fn delete_directories(state: &FileSystemState, state_res: &FileSystemState, root: &str) {
fn delete_directories(state: &FileSystemState, state_res: &FileSystemState, root: &str) {
let (tx, rx) = mpsc::sync_channel(0);
let tx_clone: mpsc::SyncSender<()> = tx.clone();
state_res.files.par_iter().for_each(move |file| {
state_res.files.par_iter().for_each(move |file| {
let _ = tx_clone;
if let None = state.files.get(file.0) {
let path: &str = &format!("{}{}", root, file.0);
let path = Path::new(path);
if ! path.exists() {
let path = Path::new(path);
if !path.exists() {
return;
}
if let FileType::Directory = file.1.0 {
if let FileType::Directory = file.1 .0 {
remove_directory(path).ok();
}
}
@ -477,7 +490,7 @@ fn delete_directories(state: &FileSystemState, state_res: &FileSystemState, root
rx.try_iter();
}
fn create_soft_link(src: &str, dest: &str) -> Result<(),String> {
fn create_soft_link(src: &str, dest: &str) -> Result<(), String> {
let dest_path = Path::new(&dest);
let src_path = match fs::read_link(src) {
Ok(path) => path,
@ -495,11 +508,11 @@ fn create_soft_link(src: &str, dest: &str) -> Result<(),String> {
} else if dest_path.exists() {
remove_file(dest_path)
} else {
remove_symlink(dest_path)
remove_symlink(dest_path)
}?;
if let Some(path) = dest_path.parent() {
if ! path.exists() {
if !path.exists() {
create_directory(&path)?;
}
}
@ -507,19 +520,19 @@ fn create_soft_link(src: &str, dest: &str) -> Result<(),String> {
soft_link(&src_path, dest_path)
}
pub fn create_hard_link(src: &str, dest: &str) -> Result<(), String> {
let src_path = Path::new(&src);
let dest_path = Path::new(&dest);
pub fn create_hard_link(src: &str, dest: &str) -> Result<(), String> {
let src_path = Path::new(&src);
let dest_path = Path::new(&dest);
if ! src_path.exists() {
if !src_path.exists() {
Err(format!("Source file '{}': entity not found.", &src))?
}
if ! dest_path.exists() {
if !dest_path.exists() {
if let Some(path) = dest_path.parent() {
if ! path.exists() {
if !path.exists() {
remove_symlink(&path)?;
create_directory(&path)?;
create_directory(&path)?;
}
}
@ -527,7 +540,7 @@ pub fn create_hard_link(src: &str, dest: &str) -> Result<(), String> {
hard_link(src_path, dest_path)
} else {
let meta_dest = metadata(&dest_path)?;
let meta_src = metadata(&src_path)?;
let meta_src = metadata(&src_path)?;
if meta_src.ino() != meta_dest.ino() {
if meta_dest.is_dir() {
@ -547,30 +560,30 @@ fn queue_status(queue: &HashSet<&str>, compare: &str, max_chars: usize) -> Strin
let mut char_amt = 0;
let mut diff = 0;
let mut string = String::new();
let mut strs: Vec<&str> = Vec::new();
let mut strs: Vec<&str> = Vec::new();
for contrast in queue {
for contrast in queue {
let contrast: &str = contrast.as_ref();
if compare == contrast {
continue;
}
char_amt += contrast.len();
char_amt += contrast.len();
if char_amt >= max_chars - contrast.len() {
diff = queue.len()-strs.len();
diff = queue.len() - strs.len();
break;
}
strs.push(contrast);
}
for idx in 0..strs.len() {
for idx in 0 .. strs.len() {
let str = strs.get(idx).unwrap();
if idx > 0 {
string.push_str(format!(", {str}").as_str());
string.push_str(format!(", {str}").as_str());
} else {
string.push_str(format!("{str}").as_str());
}
@ -587,24 +600,24 @@ fn queue_status(queue: &HashSet<&str>, compare: &str, max_chars: usize) -> Strin
string
}
fn metadata(path: &Path) -> Result<Metadata,String> {
fn metadata(path: &Path) -> Result<Metadata, String> {
match fs::metadata(path) {
Ok(meta) => Ok(meta),
Err(err) => Err(format!("Failed to obtain metadata for '{}': {}", path.to_str().unwrap(), err.kind())),
}
}
fn hard_link(src_path: &Path, dest_path: &Path) -> Result<(),String> {
if let Err(err) = fs::hard_link(src_path,dest_path) {
Err(format!("Failed to link '{}': {}", dest_path.to_str().unwrap(), err.kind()))?
fn hard_link(src_path: &Path, dest_path: &Path) -> Result<(), String> {
if let Err(err) = fs::hard_link(src_path, dest_path) {
Err(format!("Failed to link '{}': {}", dest_path.to_str().unwrap(), err.kind()))?
}
Ok(())
}
fn soft_link<'a>(src_path: &'a Path, dest_path: &'a Path) -> Result<(),String> {
fn soft_link<'a>(src_path: &'a Path, dest_path: &'a Path) -> Result<(), String> {
if let Err(err) = symlink(src_path, dest_path) {
Err(format!("Failed to create symlink '{}': {}", dest_path.to_str().unwrap(), err.kind()))?
Err(format!("Failed to create symlink '{}': {}", dest_path.to_str().unwrap(), err.kind()))?
}
Ok(())
@ -616,7 +629,7 @@ fn create_directory(path: &Path) -> Result<(), String> {
}
Ok(())
}
}
fn remove_directory(path: &Path) -> Result<(), String> {
if let Err(err) = fs::remove_dir_all(path) {
@ -624,21 +637,21 @@ fn remove_directory(path: &Path) -> Result<(), String> {
}
Ok(())
}
}
fn remove_file(path: &Path) -> Result<(), String> {
if let Err(err) = fs::remove_file(path) {
if let Err(err) = fs::remove_file(path) {
Err(format!("Failed to remove file '{}': {}", path.to_str().unwrap(), err.kind()))?
}
Ok(())
}
fn remove_symlink(path: &Path) -> Result<(),String> {
fn remove_symlink(path: &Path) -> Result<(), String> {
if let Ok(_) = fs::read_link(path) {
if let Err(err) = fs::remove_file(path) {
if let Err(err) = fs::remove_file(path) {
Err(format!("Failed to delete symlink '{}': {}", path.to_str().unwrap(), err.kind()))?
}
}
}
Ok(())

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,9 +19,13 @@
use std::collections::HashSet;
use alpm::{Package, Alpm};
use alpm::{Alpm, Package};
use crate::{err, Error, sync::{SyncError, utils::AlpmUtils}};
use crate::{
err,
sync::{utils::AlpmUtils, SyncError},
Error,
};
pub struct DependencyResolver<'a> {
resolved: HashSet<&'a str>,
@ -30,9 +34,9 @@ pub struct DependencyResolver<'a> {
ignored: &'a HashSet<String>,
handle: &'a Alpm,
depth: isize,
}
}
impl <'a>DependencyResolver<'a> {
impl<'a> DependencyResolver<'a> {
pub fn new(alpm: &'a Alpm, ignorelist: &'a HashSet<String>) -> Self {
Self {
resolved: HashSet::new(),
@ -45,17 +49,17 @@ impl <'a>DependencyResolver<'a> {
}
fn check_depth(&mut self) -> Result<(), Error> {
if self.depth == 50 {
if self.depth == 50 {
err!(SyncError::RecursionDepthExceeded(self.depth))?
}
self.depth += 1;
Ok(())
}
pub fn enumerate(mut self, packages: &Vec<&'a str>) -> Result<(Option<Vec<String>>, Vec<Package<'a>>), Error> {
let mut synchronize: Vec<&'a str> = Vec::new();
let mut synchronize: Vec<&'a str> = Vec::new();
for pkg in packages {
if let Some(_) = self.resolved.get(pkg) {
continue;
@ -65,28 +69,29 @@ impl <'a>DependencyResolver<'a> {
continue;
}
if let Some(pkg) = self.handle.get_package(pkg) {
if let Some(pkg) = self.handle.get_package(pkg) {
self.packages.push(pkg);
self.resolved.insert(pkg.name());
synchronize.extend(pkg.depends()
.iter()
.filter_map(|p|
match self.handle.get_local_package(p.name()) {
None => match self.handle.get_package(p.name()) {
Some(dep) => Some(dep.name()), None => None,
synchronize.extend(
pkg.depends()
.iter()
.filter_map(|p| match self.handle.get_local_package(p.name()) {
None => match self.handle.get_package(p.name()) {
Some(dep) => Some(dep.name()),
None => None,
},
Some(_) => None,
}
)
.collect::<Vec<&str>>());
})
.collect::<Vec<&str>>(),
);
if self.depth > 0 {
self.keys.push(pkg.name().into());
}
}
}
}
if synchronize.len() > 0 {
if synchronize.len() > 0 {
self.check_depth()?;
self.enumerate(&synchronize)
} else {

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,9 +19,13 @@
use std::collections::HashSet;
use alpm::{Package, Alpm, PackageReason};
use alpm::{Alpm, Package, PackageReason};
use crate::{err, Error, sync::{SyncError, transaction::TransactionType, utils::AlpmUtils}};
use crate::{
err,
sync::{transaction::TransactionType, utils::AlpmUtils, SyncError},
Error,
};
pub struct LocalDependencyResolver<'a> {
resolved: HashSet<&'a str>,
@ -30,9 +34,9 @@ pub struct LocalDependencyResolver<'a> {
handle: &'a Alpm,
depth: isize,
flags: (bool, bool, bool),
}
}
impl <'a>LocalDependencyResolver<'a> {
impl<'a> LocalDependencyResolver<'a> {
pub fn new(alpm: &'a Alpm, ignorelist: &'a HashSet<String>, trans_type: &TransactionType) -> Self {
Self {
resolved: HashSet::new(),
@ -51,14 +55,14 @@ impl <'a>LocalDependencyResolver<'a> {
if self.depth == 50 {
err!(SyncError::RecursionDepthExceeded(self.depth))?
}
self.depth += 1;
Ok(())
}
pub fn enumerate(mut self, packages: &Vec<&'a str>) -> Result<Vec<Package<'a>>, Error> {
let mut synchronize: Vec<&'a str> = Vec::new();
for pkg in packages {
if let Some(_) = self.resolved.get(pkg) {
continue;
@ -68,50 +72,46 @@ impl <'a>LocalDependencyResolver<'a> {
continue;
}
if let Some(pkg) = self.handle.get_local_package(pkg) {
if let Some(pkg) = self.handle.get_local_package(pkg) {
if self.depth > 0 {
//TODO: Implement proper explicit package handling
if ! self.flags.1
&& pkg.reason() == PackageReason::Explicit {
if !self.flags.1 && pkg.reason() == PackageReason::Explicit {
continue;
}
if pkg.required_by()
if pkg
.required_by()
.iter()
.filter_map(|p|
match self.resolved.get(p) {
None => Some(()), Some(_) => None
.filter_map(|p| match self.resolved.get(p) {
None => Some(()),
Some(_) => None,
})
.count() > 0 {
.count()
> 0
{
continue;
}
}
self.packages.push(pkg);
self.resolved.insert(pkg.name());
if ! self.flags.0 {
if !self.flags.0 {
continue;
}
synchronize.extend(pkg.depends()
.iter()
.map(|pkg| pkg.name())
.collect::<Vec<&str>>());
synchronize.extend(pkg.depends().iter().map(|pkg| pkg.name()).collect::<Vec<&str>>());
if ! self.flags.1 {
if !self.flags.1 {
continue;
}
for package in self.handle.localdb().pkgs() {
if package.depends()
.iter()
.filter_map(|d| self.resolved.get(d.name()))
.count() > 0 {
for package in self.handle.localdb().pkgs() {
if package.depends().iter().filter_map(|d| self.resolved.get(d.name())).count() > 0 {
synchronize.push(package.name());
}
}
}
}
}
if synchronize.len() > 0 && self.flags.0 {

View file

@ -1,6 +1,6 @@
/*
* pacwrap-core
*
*
* Copyright (C) 2023-2024 Xavier R.M. <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
@ -19,31 +19,32 @@
use std::{borrow::Cow, collections::HashSet};
use bitflags::bitflags;
use alpm::{Alpm, PackageReason, TransFlag};
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use crate::{err,
Error,
config::{InstanceHandle, Global},
constants::{RESET, BOLD, ARROW_CYAN, BAR_CYAN, BOLD_YELLOW, BOLD_GREEN},
sync::{SyncError,
transaction::{stage::Stage,
commit::Commit,
prepare::Prepare,
uptodate::UpToDate},
resolver_local::LocalDependencyResolver,
use crate::{
config::{Global, InstanceHandle},
constants::{ARROW_CYAN, BAR_CYAN, BOLD, BOLD_GREEN, BOLD_YELLOW, RESET},
err,
sync::{
resolver::DependencyResolver,
utils::AlpmUtils},
utils::print_warning};
resolver_local::LocalDependencyResolver,
transaction::{commit::Commit, prepare::Prepare, stage::Stage, uptodate::UpToDate},
utils::AlpmUtils,
SyncError,
},
utils::print_warning,
Error,
};
pub use self::aggregator::TransactionAggregator;
pub mod aggregator;
mod commit;
mod prepare;
mod uptodate;
mod stage;
mod uptodate;
pub type Result<T> = crate::Result<T>;
pub static MAGIC_NUMBER: u32 = 663445956;
@ -52,7 +53,7 @@ pub enum TransactionState {
Complete(bool),
Prepare,
UpToDate,
PrepareForeign,
PrepareForeign(bool),
Stage,
StageForeign,
Commit(bool),
@ -68,7 +69,7 @@ pub enum TransactionType {
#[derive(Serialize, Deserialize, Copy, Clone)]
pub enum TransactionMode {
Foreign,
Local
Local,
}
pub enum SyncReqResult {
@ -77,14 +78,21 @@ pub enum SyncReqResult {
}
pub trait Transaction {
fn new(new: TransactionState, ag: &TransactionAggregator) -> Box<Self> where Self: Sized;
fn engage(&self, ag: &mut TransactionAggregator, handle: &mut TransactionHandle, inshandle: &InstanceHandle) -> Result<TransactionState>;
fn new(new: TransactionState, ag: &TransactionAggregator) -> Box<Self>
where
Self: Sized;
fn engage(
&self,
ag: &mut TransactionAggregator,
handle: &mut TransactionHandle,
inshandle: &InstanceHandle,
) -> Result<TransactionState>;
}
bitflags! {
pub struct TransactionFlags: u8 {
const NONE = 0;
const TARGET_ONLY = 0b0000001;
const TARGET_ONLY = 0b0000001;
const PREVIEW = 0b0000010;
const NO_CONFIRM = 0b0000100;
const FORCE_DATABASE = 0b0001000;
@ -104,11 +112,11 @@ pub struct TransactionHandle<'a> {
#[derive(Serialize, Deserialize, Clone)]
pub struct TransactionMetadata<'a> {
foreign_pkgs: HashSet<String>,
foreign_pkgs: HashSet<String>,
resident_pkgs: HashSet<String>,
queue: Vec<Cow<'a, str>>,
mode: TransactionMode,
flags: (u8, u32)
flags: (u8, u32),
}
#[derive(Serialize, Deserialize)]
@ -120,8 +128,7 @@ pub struct TransactionParameters {
bytes: u64,
files: u64,
action: TransactionType,
mode: TransactionMode,
mode: TransactionMode,
}
impl TransactionMode {
@ -137,10 +144,10 @@ impl TransactionState {
fn from(self, ag: &TransactionAggregator) -> Box<dyn Transaction> {
match self {
Self::Prepare => Prepare::new(self, ag),
Self::PrepareForeign => Prepare::new(self, ag),
Self::PrepareForeign(_) => Prepare::new(self, ag),
Self::UpToDate => UpToDate::new(self, ag),
Self::Stage => Stage::new(self, ag),
Self::StageForeign => Stage::new(self, ag),
Self::StageForeign => Stage::new(self, ag),
Self::Commit(_) => Commit::new(self, ag),
Self::CommitForeign => Commit::new(self, ag),
Self::Complete(_) => unreachable!(),
@ -151,23 +158,23 @@ impl TransactionState {
match self {
Self::Commit(_) => "resident",
Self::CommitForeign => "foreign",
_ => ""
_ => "",
}
}
}
impl TransactionType {
impl TransactionType {
pub fn pr_offset(&self) -> usize {
match self {
Self::Upgrade(..) => 1,
Self::Remove(..) => 0
Self::Remove(..) => 0,
}
}
fn as_str(&self) -> &str {
match self {
Self::Upgrade(..) => "installation",
Self::Remove(..) => "removal"
Self::Remove(..) => "removal",
}
}
@ -175,11 +182,11 @@ impl TransactionType {
let message = match self {
Self::Upgrade(..) => match state {
TransactionMode::Foreign => "Synchronizing foreign database...",
TransactionMode::Local => "Synchronizing resident container..."
},
TransactionMode::Local => "Synchronizing resident container...",
},
Self::Remove(..) => match state {
TransactionMode::Foreign => "Preparing foreign package removal...",
TransactionMode::Local => "Preparing package removal..."
TransactionMode::Local => "Preparing package removal...",
},
};
@ -189,95 +196,96 @@ impl TransactionType {
fn begin_message(&self, inshandle: &InstanceHandle) {
let instance = inshandle.vars().instance();
let message = match self {
Self::Upgrade(upgrade,..) => match upgrade {
Self::Upgrade(upgrade, ..) => match upgrade {
true => format!("Checking {instance} for updates..."),
false => format!("Transacting {instance}...")
}
Self::Remove(..) => format!("Transacting {instance}...")
false => format!("Transacting {instance}..."),
},
Self::Remove(..) => format!("Transacting {instance}..."),
};
println!("{} {}{message}{}", *BAR_CYAN, *BOLD, *RESET);
}
}
impl <'a>TransactionMetadata<'a> {
impl<'a> TransactionMetadata<'a> {
fn new(queue: Vec<&'a str>) -> TransactionMetadata {
Self {
Self {
foreign_pkgs: HashSet::new(),
resident_pkgs: HashSet::new(),
resident_pkgs: HashSet::new(),
mode: TransactionMode::Local,
queue: queue.iter().map(|q| (*q).into()).collect::<Vec<_>>(),
flags: (0, 0),
}
}
}
}
impl <'a>TransactionHandle<'a> {
impl<'a> TransactionHandle<'a> {
pub fn new(global: &'a Global, alpm_handle: Alpm, metadata: &'a mut TransactionMetadata<'a>) -> Self {
Self {
meta: metadata,
alpm: Some(alpm_handle),
deps: None,
deps: None,
fail: true,
config: global,
}
}
}
fn is_sync_req(&self, mode: TransactionMode) -> SyncReqResult {
let alpm = self.alpm();
let ignored = match mode {
let ignored = match mode {
TransactionMode::Foreign => &self.meta.resident_pkgs,
TransactionMode::Local => &self.meta.foreign_pkgs,
};
for pkg in alpm.localdb().pkgs() {
for pkg in alpm.localdb().pkgs() {
if let Some(_) = ignored.get(pkg.name()) {
continue;
}
if let Some(_) = pkg.sync_new_version(alpm.syncdbs()) {
return SyncReqResult::Required
}
if let Some(_) = pkg.sync_new_version(alpm.syncdbs()) {
return SyncReqResult::Required;
}
}
SyncReqResult::NotRequired
}
fn enumerate_package_lists(&mut self, dep_handle: &Alpm, queue: bool) {
let foreign_pkgs = dep_handle.localdb()
.pkgs()
.iter()
.filter(|p| ! self.meta.foreign_pkgs.contains(p.name()))
.map(|p| p.name().to_owned())
.collect::<Vec<_>>();
let resident_pkgs = self.alpm()
let foreign_pkgs = dep_handle
.localdb()
.pkgs()
.iter()
.filter(|p| ! self.meta.foreign_pkgs.contains(p.name())
&& ! self.meta.resident_pkgs.contains(p.name()))
.filter(|p| !self.meta.foreign_pkgs.contains(p.name()))
.map(|p| p.name().to_owned())
.collect::<Vec<_>>();
let resident_pkgs = self
.alpm()
.localdb()
.pkgs()
.iter()
.filter(|p| !self.meta.foreign_pkgs.contains(p.name()) && !self.meta.resident_pkgs.contains(p.name()))
.map(|a| a.name().to_owned())
.collect::<Vec<_>>();
if queue {
self.meta.queue.extend(foreign_pkgs.iter()
.map(|p| p.to_owned().into())
.collect::<Vec<_>>());
if queue {
let to_queue = foreign_pkgs.iter().map(|p| p.to_owned().into()).collect::<Vec<_>>();
self.meta.queue.extend(to_queue);
self.fail = false;
}
self.meta.foreign_pkgs.extend(foreign_pkgs);
self.meta.foreign_pkgs.extend(foreign_pkgs);
self.meta.resident_pkgs.extend(resident_pkgs);
}
}
pub fn ignore(&mut self, silent: bool) {
let mut fail = self.fail;
let mut fail = self.fail;
let alpm = self.alpm.as_mut().unwrap();
let ignore = match self.meta.mode {
let ignore = match self.meta.mode {
TransactionMode::Foreign => &self.meta.resident_pkgs,
TransactionMode::Local => &self.meta.foreign_pkgs,
};
let unignore = match self.meta.mode {
let unignore = match self.meta.mode {
TransactionMode::Local => &self.meta.resident_pkgs,
TransactionMode::Foreign => &self.meta.foreign_pkgs,
};
@ -298,10 +306,14 @@ impl <'a>TransactionHandle<'a> {
.localdb()
.pkgs()
.iter()
.filter(|a| ! ignore.contains(a.name())
&& self.config.alpm().ignored().contains(&a.name())) {
let new = match package.sync_new_version(alpm.syncdbs()) {
Some(new) => { fail = false; new }, None => continue,
.filter(|a| !ignore.contains(a.name()) && self.config.alpm().ignored().contains(&a.name()))
{
let new = match package.sync_new_version(alpm.syncdbs()) {
Some(new) => {
fail = false;
new
}
None => continue,
};
if silent {
@ -309,16 +321,13 @@ impl <'a>TransactionHandle<'a> {
}
let name = package.name();
let ver = package.version();
let ver = package.version();
let ver_new = new.version();
print_warning(format!("{}{name}{}: Ignoring package upgrade ({}{ver}{} => {}{ver_new}{})",
*BOLD,
*RESET,
*BOLD_YELLOW,
*RESET,
*BOLD_GREEN,
*RESET));
print_warning(format!(
"{}{name}{}: Ignoring package upgrade ({}{ver}{} => {}{ver_new}{})",
*BOLD, *RESET, *BOLD_YELLOW, *RESET, *BOLD_GREEN, *RESET
));
}
self.fail = fail;
@ -326,49 +335,40 @@ impl <'a>TransactionHandle<'a> {
pub fn prepare(&mut self, trans_type: &TransactionType, flags: &TransactionFlags) -> Result<()> {
let alpm = self.alpm.as_mut().unwrap();
let ignored = match self.meta.mode {
let ignored = match self.meta.mode {
TransactionMode::Foreign => &self.meta.resident_pkgs,
TransactionMode::Local => &self.meta.foreign_pkgs,
};
let queue = self.meta.queue.iter()
.map(|i| i.as_ref())
.collect::<Vec<_>>();
let queue = self.meta.queue.iter().map(|i| i.as_ref()).collect::<Vec<_>>();
if let TransactionMode::Local = self.meta.mode {
let upstream = queue.iter()
.map(|a| *a)
.filter(|a| ignored.contains(*a))
.collect::<Vec<&str>>();
let upstream = queue.iter().map(|a| *a).filter(|a| ignored.contains(*a)).collect::<Vec<&str>>();
if ! flags.contains(TransactionFlags::FORCE_DATABASE)
&& ! upstream.is_empty() {
err!(SyncError::TargetUpstream(upstream[0].into()))?
if !flags.contains(TransactionFlags::FORCE_DATABASE) && !upstream.is_empty() {
err!(SyncError::TargetUpstream(upstream[0].into()))?
}
}
match trans_type {
TransactionType::Remove(..) => {
let not_installed = queue.iter()
.map(|a| *a)
.filter(|a| ! ignored.contains(*a)
&& alpm.get_local_package(a).is_none())
TransactionType::Remove(..) => {
let not_installed = queue
.iter()
.map(|a| *a)
.filter(|a| !ignored.contains(*a) && alpm.get_local_package(a).is_none())
.collect::<Vec<&str>>();
if ! not_installed.is_empty() {
if !not_installed.is_empty() {
err!(SyncError::TargetNotInstalled(not_installed[0].into()))?
}
for pkg in LocalDependencyResolver::new(alpm, &ignored, trans_type).enumerate(&queue)? {
alpm.trans_remove_pkg(pkg).unwrap();
for pkg in LocalDependencyResolver::new(alpm, &ignored, trans_type).enumerate(&queue)? {
alpm.trans_remove_pkg(pkg).unwrap();
}
},
Transactio