Few bug fixes, minor improvements and cleanup.

- Container creation in sync module now only syncs targets as required
- More robust validation for --target parameters
- Targets marked for reinitialization can now accept a configuration
  from a file if specified.
- Cleaned up process module
- Prompt module no longer emboldens listed targets for confirmation
This commit is contained in:
Xavier Moffett 2024-03-09 23:56:06 -05:00
parent 68594a28e7
commit 264e9cec1f
11 changed files with 148 additions and 128 deletions

View file

@ -56,17 +56,18 @@ If a feature you see here is not completed, feel free to submit a PR; or submit
| Launch within existing namespace | Not yet implemented | ❌ |
| Container Configuration | Functional | ✅ |
| Container Creation | Functional | ✅ |
| Container Composition | Functional | ✅ |
| Container Runtime | Embedded runtime environment | ✅ |
| Container Schema | Container filesystem schema with version tracking | ✅ |
| Filesystem Deduplication | Retains filesystem state across containers with hardlinks | ✅ |
| Seccomp Filters | Application of seccomp filters to instances via libseccomp bindings | ✅ |
| Dbus Isolation | Functional - provided by xdg-dbus-proxy | ✅ |
| Networking Isolation | Not yet implemented | ❌ |
| Port to Rust | Script: pacwrap-utils | ⚠ |
| Configuration CLI (user friendly) | Not yet implemented | ❌ |
| Port to Rust | Completed | ✅ |
| Config CLI (user friendly) | Not yet implemented | ❌ |
| Process API | Container process enumeration | ✅ |
| Process CLI | Functional | ✅ |
| Utility CLI (native) | Not yet implemented | ❌ |
| Utility CLI | Functional | ✅ |
| Localization | Not yet implemented | ❌ |
## Manual

View file

@ -22,7 +22,7 @@ use std::{
io::{BufRead, BufReader, Read, Seek, SeekFrom},
};
use crate::{config::ContainerCache, constants::CONTAINER_DIR, err, utils::print_warning, Error, ErrorKind};
use crate::{config::ContainerCache, constants::CONTAINER_DIR, utils::print_warning, Error, ErrorGeneric};
use indexmap::IndexMap;
pub struct ProcessList {
@ -95,18 +95,9 @@ impl Process {
}
pub fn exec(&self) -> &str {
let mut index = 0;
for char in self.cmd[0].char_indices().rev() {
if char.1 == '/' {
index = char.0 + 1;
break;
}
}
match index {
0 => &self.cmd[0],
_ => &self.cmd[0].split_at(index).1,
match self.cmd[0].char_indices().filter(|c| c.1 == '/').last() {
Some((index, ..)) => &self.cmd[0].split_at(index + 1).1,
None => &self.cmd[0],
}
}
@ -157,10 +148,7 @@ impl ProcStat {
Some(Self {
thread_name: stat[1].into(),
parent: match stat[3].parse() {
Ok(val) => val,
Err(_) => 1,
},
parent: stat[3].parse().unwrap_or(1),
})
}
@ -192,13 +180,10 @@ pub fn list<'a>(cache: &'a ContainerCache<'a>) -> Result<ProcessList, Error> {
}
let check = qualify_process(&cmdlist, stat.parent(), &map);
let check = match check {
let (ins, depth, fork) = match check {
Some(instance) => instance,
None => continue,
};
let ins = check.0;
let depth = check.1;
let fork = check.2;
match groups.get_mut(&ins) {
Some(vec) => vec.push(pid),
@ -218,21 +203,14 @@ pub fn list<'a>(cache: &'a ContainerCache<'a>) -> Result<ProcessList, Error> {
}
fn procfs() -> Result<Vec<i32>, Error> {
match read_dir("/proc/") {
Ok(dir) => Ok(dir
.filter_map(|s| match s {
Ok(f) => match f.file_name().to_str() {
Some(str) => match str.parse() {
Ok(i) => Some(i),
Err(_) => None,
},
None => None,
},
Err(_) => None,
})
.collect()),
Err(error) => err!(ErrorKind::IOError("/proc/".into(), error.kind())),
}
Ok(read_dir("/proc/")
.prepend_io(|| "/proc/".into())?
.filter(|s| s.as_ref().is_ok_and(|s| s.metadata().is_ok_and(|m| m.is_dir())))
.filter_map(|e| match e.unwrap().file_name().to_str().unwrap().parse() {
Ok(val) => Some(val),
Err(_) => None,
})
.collect())
}
fn cmdlist(pid: i32) -> Option<Vec<String>> {
@ -251,10 +229,7 @@ fn cmdlist(pid: i32) -> Option<Vec<String>> {
}
data.remove(len - 1);
cmdlist.push(match String::from_utf8(data) {
Ok(string) => string,
Err(_) => "".into(),
});
cmdlist.push(String::from_utf8(data).unwrap_or_default());
index += len;
match list.seek(SeekFrom::Start(index as u64)) {

View file

@ -424,6 +424,27 @@ impl SyncType {
}
}
pub fn validate_fs_states<'a>(vec: &'a Vec<&'a str>) -> bool {
for instance in vec {
match check(instance) {
Ok(bool) =>
if bool {
return false;
},
Err(err) => {
err.warn();
return false;
}
}
}
true
}
pub fn create_blank_state(container: &str) -> Result<(), Error> {
serialize(container.into(), FileSystemState::new())
}
fn deserialize<R: Read, T: for<'de> Deserialize<'de>>(instance: &str, reader: R) -> Result<T, Error> {
match bincode::options()
.with_fixint_encoding()
@ -511,23 +532,6 @@ fn check(instance: &str) -> Result<bool, Error> {
Ok(magic != MAGIC_NUMBER || version != VERSION)
}
pub fn invalid_fs_states<'a>(vec: &'a Vec<&'a str>) -> bool {
for instance in vec {
match check(instance) {
Ok(bool) =>
if bool {
return true;
},
Err(err) => {
err.warn();
return true;
}
}
}
false
}
fn previous_state(map: Vec<Option<FileSystemState>>) -> FileSystemState {
let mut state = FileSystemState::new();

View file

@ -36,7 +36,7 @@ use crate::{
log::Logger,
sync::{
self,
filesystem::{invalid_fs_states, FileSystemStateSync},
filesystem::{validate_fs_states, FileSystemStateSync},
transaction::{Transaction, TransactionFlags, TransactionHandle, TransactionMetadata, TransactionState, TransactionType},
SyncError,
},
@ -106,7 +106,6 @@ impl<'a> TransactionAggregator<'a> {
}
TransactionType::Remove(..) => self.targets.is_some(),
};
let upstream = match self.targets.as_ref() {
Some(targets) => self.cache.filter_target(targets, vec![ContainerType::Base, ContainerType::Slice]),
None => self.cache.filter(vec![ContainerType::Base, ContainerType::Slice]),
@ -117,7 +116,7 @@ impl<'a> TransactionAggregator<'a> {
};
let are_downstream = self.cache.count(vec![ContainerType::Aggregate]) > 0;
if invalid_fs_states(&upstream) && are_downstream {
if !validate_fs_states(&upstream) && are_downstream {
let linker = self.fs_sync().unwrap();
linker.refresh_state();

View file

@ -58,13 +58,13 @@ fn create_prompt(message: String, prefix: &str, yn_prompt: bool) -> Result<Strin
return Input::with_theme(&theme).with_prompt(message).allow_empty(true).interact_text();
}
pub fn prompt_targets(targets: &Vec<&str>, ins_prompt: impl Into<String>, yn_prompt: bool) -> Result<(), ()> {
pub fn prompt_targets(targets: &Vec<&str>, ins_prompt: &str, yn_prompt: bool) -> Result<(), ()> {
eprintln!("{} {}Container{}{}\n", *BAR_RED, *BOLD, if targets.len() > 1 { "s" } else { "" }, *RESET);
for target in targets.iter() {
eprint!("{}{}{} ", *BOLD, target, *RESET);
eprint!("{} ", target);
}
eprintln!("\n");
prompt("::", ins_prompt, yn_prompt)
prompt("::", format!("{}{}", *BOLD, ins_prompt), yn_prompt)
}

View file

@ -77,9 +77,9 @@ fn delete_containers<'a>(
let message = format!("Deleting existing container{}?", if delete.len() > 1 { "s" } else { "" });
if flags.contains(TransactionFlags::NO_CONFIRM) {
println!("{} {}{}...{}", *BAR_GREEN, *BOLD, message, *RESET);
println!("{} {}{}...{}", *BAR_GREEN, *BOLD, &message, *RESET);
delete_roots(cache, logger, delete, force)?;
} else if let Ok(_) = prompt_targets(&delete, message, false) {
} else if let Ok(_) = prompt_targets(&delete, &message, false) {
delete_roots(cache, logger, delete, force)?;
}
@ -153,9 +153,8 @@ fn engage_aggregator<'a>(args: &mut Arguments) -> Result<()> {
while let Some(arg) = args.next() {
match arg {
Op::Short('t') | Op::Long("target") | Op::Long("from-config") => continue,
Op::Long("from-config") => continue,
Op::Long("noconfirm") => flags = flags | TransactionFlags::NO_CONFIRM,
Op::Long("force") => force = true,
Op::Long("reinitialize-all") =>
for instance in cache.registered() {
if let Some(handle) = cache.get_instance_option(instance) {
@ -166,32 +165,43 @@ fn engage_aggregator<'a>(args: &mut Arguments) -> Result<()> {
compose.insert(instance, None);
}
},
Op::Short('f') | Op::Long("force") => force = true,
Op::Short('r') | Op::Long("reinitialize") => reinitialize = true,
Op::ShortPos('t', target) | Op::LongPos("target", target) =>
if !reinitialize {
current_target = Some(target);
} else {
Op::Short('t') | Op::Long("target") => match args.next() {
Some(arg) => match arg {
Op::ShortPos('t', t) | Op::LongPos("target", t) => current_target = Some(t),
_ => args.invalid_operand()?,
},
None => err!(TargetUnspecified)?,
},
Op::LongPos(_, config) | Op::ShortPos(_, config) | Op::Value(config) => {
let target = match current_target {
Some(target) => target,
None => match config.char_indices().filter(|a| a.1 == '.').last() {
Some((index, ..)) => config.split_at(index).0,
None => config,
},
};
let config = if reinitialize {
let handle = cache.get_instance(target)?;
if Path::new(handle.vars().root()).exists() {
delete.push(target);
}
compose.insert(target, None);
reinitialize = false;
},
Op::LongPos(_, target) | Op::ShortPos(_, target) | Op::Value(target) => {
if !target.ends_with(".yml") {
err!(ErrorKind::Message("Unsupported file extension."))?;
}
if let Some(cur_target) = current_target {
compose.insert(cur_target, Some(target));
current_target = None;
} else {
Path::new(target).try_exists().prepend_io(|| target.into())?;
compose.insert(target.split_at(target.len() - 4).0, Some(target));
}
match current_target {
Some(_) => Some(config),
None => None,
}
} else {
Some(config)
};
compose.insert(target, config);
current_target = None;
reinitialize = false;
}
_ => args.invalid_operand()?,
}

View file

@ -358,7 +358,7 @@ fn create_placeholder(path: &str) -> Result<()> {
fn cleanup() -> Result<()> {
if Path::new(&*DBUS_SOCKET).exists() {
remove_file(&*DBUS_SOCKET).prepend_io(|| DBUS_SOCKET.to_string())?;
}
}
Ok(())
}

View file

@ -21,13 +21,14 @@ use std::{
str::FromStr,
};
use indexmap::IndexMap;
use nix::{
sys::signal::{kill, Signal},
unistd::Pid,
};
use pacwrap_core::{
config::cache,
constants::{ARROW_GREEN, BOLD, RESET},
constants::{ARROW_GREEN, BOLD, DIM, RESET},
err,
impl_error,
process::{self, Process},
@ -243,6 +244,7 @@ fn process_kill(args: &mut Arguments) -> Result<()> {
err!(InvalidArgument::TargetUnspecified)?
}
let mut instances = IndexMap::new();
let cache = cache::populate()?;
let list = process::list(&cache)?;
let list = match all {
@ -265,11 +267,18 @@ fn process_kill(args: &mut Arguments) -> Result<()> {
if process.fork() {
fork_warn(process);
}
match instances.get(process.instance()) {
Some(value) => instances.insert(process.instance(), value + 1),
None => instances.insert(process.instance(), 1),
};
}
let instances: Vec<String> = instances.iter().map(|a| format!("{} ({}{}{})", a.0, *DIM, a.1, *RESET)).collect();
if no_confirm {
kill_processes(&list, sigint)
} else if let Ok(_) = prompt_targets(&list.iter().map(|a| a.instance()).collect(), "Kill processes?", false) {
} else if let Ok(_) = prompt_targets(&instances.iter().map(|a| a.as_ref()).collect(), "Kill container processes?", false) {
kill_processes(&list, sigint)
} else {
Ok(())

View file

@ -26,7 +26,7 @@ use pacwrap_core::{
log::Logger,
sync::transaction::{TransactionAggregator, TransactionFlags, TransactionType},
utils::{
arguments::{Arguments, InvalidArgument, Operand as Op},
arguments::{Arguments, InvalidArgument::*, Operand as Op},
check_root,
},
};
@ -62,7 +62,7 @@ fn engage_aggregator<'a>(action_type: TransactionType, args: &'a mut Arguments,
let mut current_target = None;
if let Op::Nothing = args.next().unwrap_or_default() {
err!(InvalidArgument::OperationUnspecified)?
err!(OperationUnspecified)?
}
while let Some(arg) = args.next() {
@ -72,18 +72,23 @@ fn engage_aggregator<'a>(action_type: TransactionType, args: &'a mut Arguments,
| Op::Long("recursive")
| Op::Short('R')
| Op::Short('c')
| Op::Short('s')
| Op::Short('t') => continue,
| Op::Short('s') => continue,
Op::Long("dbonly") => flags = flags | TransactionFlags::DATABASE_ONLY,
Op::Long("noconfirm") => flags = flags | TransactionFlags::NO_CONFIRM,
Op::Long("force-foreign") => flags = flags | TransactionFlags::FORCE_DATABASE,
Op::Short('p') | Op::Long("preview") => flags = flags | TransactionFlags::PREVIEW,
Op::Short('f') | Op::Long("filesystem") => flags = flags | TransactionFlags::FILESYSTEM_SYNC,
Op::ShortPos('t', target) | Op::LongPos("target", target) => {
cache.get_instance(target)?;
current_target = Some(target);
targets.push(target);
}
Op::Short('t') | Op::Long("target") => match args.next() {
Some(arg) => match arg {
Op::ShortPos('t', target) | Op::LongPos("target", target) => {
cache.get_instance(target)?;
current_target = Some(target);
targets.push(target);
}
_ => args.invalid_operand()?,
},
None => err!(TargetUnspecified)?,
},
Op::LongPos(_, package) | Op::ShortPos(_, package) | Op::Value(package) =>
if let Some(target) = current_target {
match queue.get_mut(target) {
@ -98,7 +103,7 @@ fn engage_aggregator<'a>(action_type: TransactionType, args: &'a mut Arguments,
}
if let None = current_target {
err!(InvalidArgument::TargetUnspecified)?
err!(TargetUnspecified)?
}
Ok(TransactionAggregator::new(&cache, log, action_type)

View file

@ -117,12 +117,18 @@ fn acquire_depends<'a>(args: &mut Arguments<'a>) -> Result<IndexMap<&'a str, (Co
}
None => err!(TargetUnspecified)?,
},
Op::ShortPos('t', target) | Op::LongPos("target", target) => match instype {
Some(instype) => {
current_target = target;
deps.insert(current_target, (instype, vec![]));
}
None => err!(ErrorKind::Message("Container type not specified."))?,
Op::Short('t') | Op::Long("target") => match args.next() {
Some(arg) => match arg {
Op::ShortPos('t', target) | Op::LongPos("target", target) => match instype {
Some(instype) => {
current_target = target;
deps.insert(current_target, (instype, vec![]));
}
None => err!(ErrorKind::Message("Container type not specified."))?,
},
_ => args.invalid_operand()?,
},
None => err!(TargetUnspecified)?,
},
_ => continue,
}
@ -224,14 +230,12 @@ fn engage_aggregator<'a>(
Op::Short('a')
| Op::Short('s')
| Op::Short('d')
| Op::Short('t')
| Op::Short('y')
| Op::Short('u')
| Op::Short('c')
| Op::Long("aggregate")
| Op::Long("slice")
| Op::Long("dep")
| Op::Long("target")
| Op::Long("refresh")
| Op::Long("upgrade")
| Op::Long("create")
@ -243,16 +247,22 @@ fn engage_aggregator<'a>(
Op::Long("dbonly") => flags = flags | TransactionFlags::DATABASE_ONLY,
Op::Long("force-foreign") => flags = flags | TransactionFlags::FORCE_DATABASE,
Op::Long("noconfirm") => flags = flags | TransactionFlags::NO_CONFIRM,
Op::ShortPos('t', target) | Op::LongPos("target", target) => {
cache.get_instance(target)?;
current_target = target;
targets.insert(target);
Op::Short('t') | Op::Long("target") => match args.next() {
Some(arg) => match arg {
Op::ShortPos('t', target) | Op::LongPos("target", target) => {
cache.get_instance(target)?;
current_target = target;
targets.insert(target);
if base {
queue.insert(current_target.into(), vec!["base"]);
base = false;
}
}
if base {
queue.insert(current_target.into(), vec!["base"]);
base = false;
}
}
_ => args.invalid_operand()?,
},
None => err!(TargetUnspecified)?,
},
Op::LongPos(_, package) | Op::ShortPos(_, package) | Op::Value(package) =>
if current_target != "" {
if let Some(vec) = queue.get_mut(current_target) {
@ -265,18 +275,18 @@ fn engage_aggregator<'a>(
}
}
if flags.contains(TransactionFlags::CREATE) {
for cache in cache.registered_handles().iter().filter(|a| a.is_creation()) {
targets.extend(cache.metadata().dependencies());
}
}
let targets: Option<Vec<&str>> = match flags.contains(TransactionFlags::TARGET_ONLY) {
let targets: Option<Vec<&str>> = match flags.intersects(TransactionFlags::TARGET_ONLY | TransactionFlags::CREATE) {
true => {
if current_target == "" && !flags.contains(TransactionFlags::FILESYSTEM_SYNC) {
err!(TargetUnspecified)?
}
if flags.contains(TransactionFlags::CREATE) {
for cache in cache.registered_handles().iter().filter(|a| a.is_creation()) {
targets.extend(cache.metadata().dependencies());
}
}
match flags.contains(TransactionFlags::FILESYSTEM_SYNC) {
false => Some(targets.into_iter().collect()),
true => None,

View file

@ -20,15 +20,17 @@
use std::{
fmt::{Display, Formatter},
fs::remove_dir_all,
path::Path,
};
use pacwrap_core::{
config::{cache, ContainerCache},
constants::{ARROW_GREEN, BOLD, RESET},
constants::{ARROW_GREEN, BOLD, DATA_DIR, RESET},
err,
impl_error,
log::{Level::Info, Logger},
process,
sync::filesystem::create_blank_state,
utils::{arguments::Operand, prompt::prompt_targets, Arguments},
Error,
ErrorGeneric,
@ -58,7 +60,7 @@ pub fn remove_containers(args: &mut Arguments) -> Result<()> {
let mut targets = vec![];
let mut no_confirm = false;
let mut force = false;
let mut logger = Logger::new("pacwrap-utils");
let mut logger = Logger::new("pacwrap-utils").init()?;
while let Some(arg) = args.next() {
match arg {
@ -103,8 +105,13 @@ pub fn delete_roots(cache: &ContainerCache<'_>, logger: &mut Logger, targets: &V
for container in containers {
let root = container.vars().root();
let instance = container.vars().instance();
let state = &format!("{}/state/{instance}.dat", *DATA_DIR);
remove_dir_all(root).prepend(|| format!("Failed to delete container root '{root}':"))?;
if Path::new(state).exists() {
create_blank_state(instance)?;
}
eprintln!("{} Deleted container {}{}{} successfully.", *ARROW_GREEN, *BOLD, instance, *RESET);
logger.log(Info, &format!("Deleted container {instance}"))?;
}