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 | ❌ | | Launch within existing namespace | Not yet implemented | ❌ |
| Container Configuration | Functional | ✅ | | Container Configuration | Functional | ✅ |
| Container Creation | Functional | ✅ | | Container Creation | Functional | ✅ |
| Container Composition | Functional | ✅ |
| Container Runtime | Embedded runtime environment | ✅ | | Container Runtime | Embedded runtime environment | ✅ |
| Container Schema | Container filesystem schema with version tracking | ✅ | | Container Schema | Container filesystem schema with version tracking | ✅ |
| Filesystem Deduplication | Retains filesystem state across containers with hardlinks | ✅ | | Filesystem Deduplication | Retains filesystem state across containers with hardlinks | ✅ |
| Seccomp Filters | Application of seccomp filters to instances via libseccomp bindings | ✅ | | Seccomp Filters | Application of seccomp filters to instances via libseccomp bindings | ✅ |
| Dbus Isolation | Functional - provided by xdg-dbus-proxy | ✅ | | Dbus Isolation | Functional - provided by xdg-dbus-proxy | ✅ |
| Networking Isolation | Not yet implemented | ❌ | | Networking Isolation | Not yet implemented | ❌ |
| Port to Rust | Script: pacwrap-utils | ⚠ | | Port to Rust | Completed | ✅ |
| Configuration CLI (user friendly) | Not yet implemented | ❌ | | Config CLI (user friendly) | Not yet implemented | ❌ |
| Process API | Container process enumeration | ✅ | | Process API | Container process enumeration | ✅ |
| Process CLI | Functional | ✅ | | Process CLI | Functional | ✅ |
| Utility CLI (native) | Not yet implemented | ❌ | | Utility CLI | Functional | ✅ |
| Localization | Not yet implemented | ❌ | | Localization | Not yet implemented | ❌ |
## Manual ## Manual

View file

@ -22,7 +22,7 @@ use std::{
io::{BufRead, BufReader, Read, Seek, SeekFrom}, 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; use indexmap::IndexMap;
pub struct ProcessList { pub struct ProcessList {
@ -95,18 +95,9 @@ impl Process {
} }
pub fn exec(&self) -> &str { pub fn exec(&self) -> &str {
let mut index = 0; match self.cmd[0].char_indices().filter(|c| c.1 == '/').last() {
Some((index, ..)) => &self.cmd[0].split_at(index + 1).1,
for char in self.cmd[0].char_indices().rev() { None => &self.cmd[0],
if char.1 == '/' {
index = char.0 + 1;
break;
}
}
match index {
0 => &self.cmd[0],
_ => &self.cmd[0].split_at(index).1,
} }
} }
@ -157,10 +148,7 @@ impl ProcStat {
Some(Self { Some(Self {
thread_name: stat[1].into(), thread_name: stat[1].into(),
parent: match stat[3].parse() { parent: stat[3].parse().unwrap_or(1),
Ok(val) => val,
Err(_) => 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 = qualify_process(&cmdlist, stat.parent(), &map);
let check = match check { let (ins, depth, fork) = match check {
Some(instance) => instance, Some(instance) => instance,
None => continue, None => continue,
}; };
let ins = check.0;
let depth = check.1;
let fork = check.2;
match groups.get_mut(&ins) { match groups.get_mut(&ins) {
Some(vec) => vec.push(pid), 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> { fn procfs() -> Result<Vec<i32>, Error> {
match read_dir("/proc/") { Ok(read_dir("/proc/")
Ok(dir) => Ok(dir .prepend_io(|| "/proc/".into())?
.filter_map(|s| match s { .filter(|s| s.as_ref().is_ok_and(|s| s.metadata().is_ok_and(|m| m.is_dir())))
Ok(f) => match f.file_name().to_str() { .filter_map(|e| match e.unwrap().file_name().to_str().unwrap().parse() {
Some(str) => match str.parse() { Ok(val) => Some(val),
Ok(i) => Some(i), Err(_) => None,
Err(_) => None, })
}, .collect())
None => None,
},
Err(_) => None,
})
.collect()),
Err(error) => err!(ErrorKind::IOError("/proc/".into(), error.kind())),
}
} }
fn cmdlist(pid: i32) -> Option<Vec<String>> { fn cmdlist(pid: i32) -> Option<Vec<String>> {
@ -251,10 +229,7 @@ fn cmdlist(pid: i32) -> Option<Vec<String>> {
} }
data.remove(len - 1); data.remove(len - 1);
cmdlist.push(match String::from_utf8(data) { cmdlist.push(String::from_utf8(data).unwrap_or_default());
Ok(string) => string,
Err(_) => "".into(),
});
index += len; index += len;
match list.seek(SeekFrom::Start(index as u64)) { 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> { fn deserialize<R: Read, T: for<'de> Deserialize<'de>>(instance: &str, reader: R) -> Result<T, Error> {
match bincode::options() match bincode::options()
.with_fixint_encoding() .with_fixint_encoding()
@ -511,23 +532,6 @@ fn check(instance: &str) -> Result<bool, Error> {
Ok(magic != MAGIC_NUMBER || version != VERSION) 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 { fn previous_state(map: Vec<Option<FileSystemState>>) -> FileSystemState {
let mut state = FileSystemState::new(); let mut state = FileSystemState::new();

View file

@ -36,7 +36,7 @@ use crate::{
log::Logger, log::Logger,
sync::{ sync::{
self, self,
filesystem::{invalid_fs_states, FileSystemStateSync}, filesystem::{validate_fs_states, FileSystemStateSync},
transaction::{Transaction, TransactionFlags, TransactionHandle, TransactionMetadata, TransactionState, TransactionType}, transaction::{Transaction, TransactionFlags, TransactionHandle, TransactionMetadata, TransactionState, TransactionType},
SyncError, SyncError,
}, },
@ -106,7 +106,6 @@ impl<'a> TransactionAggregator<'a> {
} }
TransactionType::Remove(..) => self.targets.is_some(), TransactionType::Remove(..) => self.targets.is_some(),
}; };
let upstream = match self.targets.as_ref() { let upstream = match self.targets.as_ref() {
Some(targets) => self.cache.filter_target(targets, vec![ContainerType::Base, ContainerType::Slice]), Some(targets) => self.cache.filter_target(targets, vec![ContainerType::Base, ContainerType::Slice]),
None => self.cache.filter(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; 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(); let linker = self.fs_sync().unwrap();
linker.refresh_state(); 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(); 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); eprintln!("{} {}Container{}{}\n", *BAR_RED, *BOLD, if targets.len() > 1 { "s" } else { "" }, *RESET);
for target in targets.iter() { for target in targets.iter() {
eprint!("{}{}{} ", *BOLD, target, *RESET); eprint!("{} ", target);
} }
eprintln!("\n"); 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 { "" }); let message = format!("Deleting existing container{}?", if delete.len() > 1 { "s" } else { "" });
if flags.contains(TransactionFlags::NO_CONFIRM) { if flags.contains(TransactionFlags::NO_CONFIRM) {
println!("{} {}{}...{}", *BAR_GREEN, *BOLD, message, *RESET); println!("{} {}{}...{}", *BAR_GREEN, *BOLD, &message, *RESET);
delete_roots(cache, logger, delete, force)?; 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)?; delete_roots(cache, logger, delete, force)?;
} }
@ -153,9 +153,8 @@ fn engage_aggregator<'a>(args: &mut Arguments) -> Result<()> {
while let Some(arg) = args.next() { while let Some(arg) = args.next() {
match arg { 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("noconfirm") => flags = flags | TransactionFlags::NO_CONFIRM,
Op::Long("force") => force = true,
Op::Long("reinitialize-all") => Op::Long("reinitialize-all") =>
for instance in cache.registered() { for instance in cache.registered() {
if let Some(handle) = cache.get_instance_option(instance) { 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); compose.insert(instance, None);
} }
}, },
Op::Short('f') | Op::Long("force") => force = true,
Op::Short('r') | Op::Long("reinitialize") => reinitialize = true, Op::Short('r') | Op::Long("reinitialize") => reinitialize = true,
Op::ShortPos('t', target) | Op::LongPos("target", target) => Op::Short('t') | Op::Long("target") => match args.next() {
if !reinitialize { Some(arg) => match arg {
current_target = Some(target); Op::ShortPos('t', t) | Op::LongPos("target", t) => current_target = Some(t),
} else { _ => 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)?; let handle = cache.get_instance(target)?;
if Path::new(handle.vars().root()).exists() { if Path::new(handle.vars().root()).exists() {
delete.push(target); 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())?; 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()?, _ => args.invalid_operand()?,
} }

View file

@ -21,13 +21,14 @@ use std::{
str::FromStr, str::FromStr,
}; };
use indexmap::IndexMap;
use nix::{ use nix::{
sys::signal::{kill, Signal}, sys::signal::{kill, Signal},
unistd::Pid, unistd::Pid,
}; };
use pacwrap_core::{ use pacwrap_core::{
config::cache, config::cache,
constants::{ARROW_GREEN, BOLD, RESET}, constants::{ARROW_GREEN, BOLD, DIM, RESET},
err, err,
impl_error, impl_error,
process::{self, Process}, process::{self, Process},
@ -243,6 +244,7 @@ fn process_kill(args: &mut Arguments) -> Result<()> {
err!(InvalidArgument::TargetUnspecified)? err!(InvalidArgument::TargetUnspecified)?
} }
let mut instances = IndexMap::new();
let cache = cache::populate()?; let cache = cache::populate()?;
let list = process::list(&cache)?; let list = process::list(&cache)?;
let list = match all { let list = match all {
@ -265,11 +267,18 @@ fn process_kill(args: &mut Arguments) -> Result<()> {
if process.fork() { if process.fork() {
fork_warn(process); 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 { if no_confirm {
kill_processes(&list, sigint) 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) kill_processes(&list, sigint)
} else { } else {
Ok(()) Ok(())

View file

@ -26,7 +26,7 @@ use pacwrap_core::{
log::Logger, log::Logger,
sync::transaction::{TransactionAggregator, TransactionFlags, TransactionType}, sync::transaction::{TransactionAggregator, TransactionFlags, TransactionType},
utils::{ utils::{
arguments::{Arguments, InvalidArgument, Operand as Op}, arguments::{Arguments, InvalidArgument::*, Operand as Op},
check_root, check_root,
}, },
}; };
@ -62,7 +62,7 @@ fn engage_aggregator<'a>(action_type: TransactionType, args: &'a mut Arguments,
let mut current_target = None; let mut current_target = None;
if let Op::Nothing = args.next().unwrap_or_default() { if let Op::Nothing = args.next().unwrap_or_default() {
err!(InvalidArgument::OperationUnspecified)? err!(OperationUnspecified)?
} }
while let Some(arg) = args.next() { 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::Long("recursive")
| Op::Short('R') | Op::Short('R')
| Op::Short('c') | Op::Short('c')
| Op::Short('s') | Op::Short('s') => continue,
| Op::Short('t') => continue,
Op::Long("dbonly") => flags = flags | TransactionFlags::DATABASE_ONLY, Op::Long("dbonly") => flags = flags | TransactionFlags::DATABASE_ONLY,
Op::Long("noconfirm") => flags = flags | TransactionFlags::NO_CONFIRM, Op::Long("noconfirm") => flags = flags | TransactionFlags::NO_CONFIRM,
Op::Long("force-foreign") => flags = flags | TransactionFlags::FORCE_DATABASE, Op::Long("force-foreign") => flags = flags | TransactionFlags::FORCE_DATABASE,
Op::Short('p') | Op::Long("preview") => flags = flags | TransactionFlags::PREVIEW, Op::Short('p') | Op::Long("preview") => flags = flags | TransactionFlags::PREVIEW,
Op::Short('f') | Op::Long("filesystem") => flags = flags | TransactionFlags::FILESYSTEM_SYNC, Op::Short('f') | Op::Long("filesystem") => flags = flags | TransactionFlags::FILESYSTEM_SYNC,
Op::ShortPos('t', target) | Op::LongPos("target", target) => { Op::Short('t') | Op::Long("target") => match args.next() {
cache.get_instance(target)?; Some(arg) => match arg {
current_target = Some(target); Op::ShortPos('t', target) | Op::LongPos("target", target) => {
targets.push(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) => Op::LongPos(_, package) | Op::ShortPos(_, package) | Op::Value(package) =>
if let Some(target) = current_target { if let Some(target) = current_target {
match queue.get_mut(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 { if let None = current_target {
err!(InvalidArgument::TargetUnspecified)? err!(TargetUnspecified)?
} }
Ok(TransactionAggregator::new(&cache, log, action_type) 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)?, None => err!(TargetUnspecified)?,
}, },
Op::ShortPos('t', target) | Op::LongPos("target", target) => match instype { Op::Short('t') | Op::Long("target") => match args.next() {
Some(instype) => { Some(arg) => match arg {
current_target = target; Op::ShortPos('t', target) | Op::LongPos("target", target) => match instype {
deps.insert(current_target, (instype, vec![])); Some(instype) => {
} current_target = target;
None => err!(ErrorKind::Message("Container type not specified."))?, deps.insert(current_target, (instype, vec![]));
}
None => err!(ErrorKind::Message("Container type not specified."))?,
},
_ => args.invalid_operand()?,
},
None => err!(TargetUnspecified)?,
}, },
_ => continue, _ => continue,
} }
@ -224,14 +230,12 @@ fn engage_aggregator<'a>(
Op::Short('a') Op::Short('a')
| Op::Short('s') | Op::Short('s')
| Op::Short('d') | Op::Short('d')
| Op::Short('t')
| Op::Short('y') | Op::Short('y')
| Op::Short('u') | Op::Short('u')
| Op::Short('c') | Op::Short('c')
| Op::Long("aggregate") | Op::Long("aggregate")
| Op::Long("slice") | Op::Long("slice")
| Op::Long("dep") | Op::Long("dep")
| Op::Long("target")
| Op::Long("refresh") | Op::Long("refresh")
| Op::Long("upgrade") | Op::Long("upgrade")
| Op::Long("create") | Op::Long("create")
@ -243,16 +247,22 @@ fn engage_aggregator<'a>(
Op::Long("dbonly") => flags = flags | TransactionFlags::DATABASE_ONLY, Op::Long("dbonly") => flags = flags | TransactionFlags::DATABASE_ONLY,
Op::Long("force-foreign") => flags = flags | TransactionFlags::FORCE_DATABASE, Op::Long("force-foreign") => flags = flags | TransactionFlags::FORCE_DATABASE,
Op::Long("noconfirm") => flags = flags | TransactionFlags::NO_CONFIRM, Op::Long("noconfirm") => flags = flags | TransactionFlags::NO_CONFIRM,
Op::ShortPos('t', target) | Op::LongPos("target", target) => { Op::Short('t') | Op::Long("target") => match args.next() {
cache.get_instance(target)?; Some(arg) => match arg {
current_target = target; Op::ShortPos('t', target) | Op::LongPos("target", target) => {
targets.insert(target); cache.get_instance(target)?;
current_target = target;
targets.insert(target);
if base { if base {
queue.insert(current_target.into(), vec!["base"]); queue.insert(current_target.into(), vec!["base"]);
base = false; base = false;
} }
} }
_ => args.invalid_operand()?,
},
None => err!(TargetUnspecified)?,
},
Op::LongPos(_, package) | Op::ShortPos(_, package) | Op::Value(package) => Op::LongPos(_, package) | Op::ShortPos(_, package) | Op::Value(package) =>
if current_target != "" { if current_target != "" {
if let Some(vec) = queue.get_mut(current_target) { if let Some(vec) = queue.get_mut(current_target) {
@ -265,18 +275,18 @@ fn engage_aggregator<'a>(
} }
} }
if flags.contains(TransactionFlags::CREATE) { let targets: Option<Vec<&str>> = match flags.intersects(TransactionFlags::TARGET_ONLY | 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) {
true => { true => {
if current_target == "" && !flags.contains(TransactionFlags::FILESYSTEM_SYNC) { if current_target == "" && !flags.contains(TransactionFlags::FILESYSTEM_SYNC) {
err!(TargetUnspecified)? 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) { match flags.contains(TransactionFlags::FILESYSTEM_SYNC) {
false => Some(targets.into_iter().collect()), false => Some(targets.into_iter().collect()),
true => None, true => None,

View file

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