Initial commit of pacwrap-rust.

This commit is contained in:
Xavier Moffett 2023-06-18 00:07:56 -04:00
parent e855232848
commit ecab602dab
28 changed files with 1360 additions and 0 deletions

22
Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[package]
name = "pacwrap"
version = "0.1.0"
edition = "2021"
[dependencies]
command-fds = "0.2.2"
#tempfile = "3"
serde_json = "1.0"
envy = "0.4"
nix = "0.26.2"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"
typetag="0.2"
os_pipe = "1.1.4"
signal-hook = "0.3.15"
[profile.release]
lto = "thin"
opt-level = "s"

158
src/config.rs Normal file
View file

@ -0,0 +1,158 @@
#![allow(unused_variables)]
use serde::{Deserialize, Serialize};
use std::vec::Vec;
use std::env::var;
use std::path::Path;
use std::fs::File;
use std::process::exit;
use std::collections::HashMap;
use crate::config::filesystem::Filesystem;
use crate::config::permission::Permission;
use crate::config::permission::none::NONE;
use crate::config::filesystem::root::ROOT;
use crate::config::filesystem::home::HOME;
use crate::utils::print_error;
use crate::config::dbus::Dbus;
use crate::Arguments;
pub use vars::InsVars;
pub mod vars;
pub mod filesystem;
pub mod permission;
pub mod dbus;
#[derive(Serialize, Deserialize)]
pub struct Instance {
#[serde(default)]
container_type: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
dependencies: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
explicit_packages: Vec<String>,
#[serde(default)]
retain_session: bool,
#[serde(default)]
allow_forking: bool,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
filesystem: 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>>,
}
impl Instance {
pub fn new(ctype: String, pkg: Vec<String>, deps: Vec<String>) -> Self {
let mut fs: Vec<Box<dyn Filesystem>> = Vec::new();
let mut per: Vec<Box<dyn Permission>> = Vec::new();
fs.push(Box::new(ROOT {}));
fs.push(Box::new(HOME {}));
per.push(Box::new(NONE {}));
Self {
container_type: ctype,
dependencies: deps,
explicit_packages: pkg,
allow_forking: false,
retain_session: false,
permissions: per,
dbus: Vec::new(),
filesystem: fs,
}
}
fn set(&mut self, ctype: String, dep: Vec<String>, pkg: Vec<String>) {
self.container_type=ctype;
self.dependencies=dep;
self.explicit_packages=pkg;
}
pub fn container_type(&self) -> &String {&self.container_type}
pub fn permissions(&self) -> &Vec<Box<dyn Permission>> { &self.permissions }
pub fn filesystem(&self) -> &Vec<Box<dyn Filesystem>> { &self.filesystem }
pub fn dbus(&self) -> &Vec<Box<dyn Dbus>> { &self.dbus }
pub fn allow_forking(&self) -> &bool { &self.allow_forking }
pub fn retain_session(&self) -> &bool { &self.retain_session }
pub fn dependencies(&self) -> &Vec<String> { &self.dependencies }
pub fn explicit_packages(&self) -> &Vec<String> { &self.explicit_packages }
}
pub fn aux_config() {
let args = Arguments::new(1, "-", HashMap::from([("--save".into(), "s".into()),
("--load".into(),"l".into())]));
let instance = &args.get_targets()[0];
match args.get_switch().as_str() {
s if s.contains("s") => save_bash_configuration(instance),
s if s.contains("l") => bash_configuration(&instance),
&_ => print_error(format!("Invalid switch sequence.")),
}
}
pub fn save_bash_configuration(ins: &String) {
let pkgs: Vec<String> = var("PACWRAP_PKGS").unwrap().split_whitespace().map(str::to_string).collect();
let deps: Vec<String> = var("PACWRAP_DEPS").unwrap().split_whitespace().map(str::to_string).collect();
let ctype = var("PACWRAP_TYPE").unwrap();
let vars = InsVars::new(ins);
let mut instance: Instance;
let path: &str = vars.config_path().as_str();
match File::open(path) {
Ok(file) => instance = read_yaml(file),
Err(_) => instance = Instance::new(ctype.clone(), pkgs.clone(), deps.clone()),
}
instance.set(ctype, deps, pkgs);
save_configuration(&instance, path.to_string());
}
pub fn bash_configuration(instance: &String) {
let vars = InsVars::new(instance);
let ins = &load_configuration(vars.config_path());
let depends = ins.dependencies();
let pkgs = ins.explicit_packages();
let mut pkgs_string = String::new();
let mut depends_string = String::new();
println!("INSTANCE_CONFIG[{},0]={}", instance, ins.container_type());
for i in depends.iter() {
depends_string.push_str(&format!("{} ", i));
}
println!("INSTANCE_CONFIG[{},1]=\"{}\"", instance, depends_string);
for i in pkgs.iter() {
pkgs_string.push_str(&format!("{} ", i));
}
println!("INSTANCE_CONFIG[{},3]=\"{}\"", instance, pkgs_string);
}
pub fn save_configuration(ins: &Instance, config_path: String) {
let f = File::create(Path::new(&config_path)).expect("Couldn't open file");
serde_yaml::to_writer(f, &ins).unwrap();
}
fn read_yaml(file: File) -> Instance {
match serde_yaml::from_reader(file) {
Ok(file) => return file,
Err(error) => {
print_error(format!("{}", error));
exit(2);
}
}
}
pub fn load_configuration(config_path: &String) -> Instance {
let path: &str = config_path.as_str();
match File::open(path) {
Ok(file) => read_yaml(file),
Err(_) => Instance::new(format!("BASE"), Vec::new(), Vec::new()),
}
}

11
src/config/dbus.rs Normal file
View file

@ -0,0 +1,11 @@
use crate::exec::args::ExecutionArgs;
use crate::config::InsVars;
mod socket;
mod appindicator;
mod xdg_portal;
#[typetag::serde(tag = "permission")]
pub trait Dbus {
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars);
}

View file

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

25
src/config/dbus/socket.rs Normal file
View file

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

View file

@ -0,0 +1,17 @@
#![allow(non_camel_case_types)]
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::{InsVars, Dbus};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct XDG_PORTAL;
#[typetag::serde]
impl Dbus for XDG_PORTAL {
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
args.dbus("call", "org.freedesktop.portal.*=*");
args.dbus("broadcast", "org.freedesktop.portal.*=@/org/freedesktop/portal/*");
}
}

34
src/config/filesystem.rs Normal file
View file

@ -0,0 +1,34 @@
use crate::exec::args::ExecutionArgs;
use crate::config::InsVars;
pub mod home;
pub mod root;
mod to_home;
mod to_root;
mod dir;
pub struct Error {
error: String,
mod_name: String,
critical: bool
}
impl Error {
pub fn new(name: impl Into<String>, err: impl Into<String>, critical: bool) -> Self {
Self { error: err.into(), mod_name: name.into(), critical: critical }
}
pub fn error(&self) -> &String { &self.error }
pub fn module(&self) -> &String { &self.mod_name }
pub fn critical(&self) -> &bool { &self.critical }
}
#[typetag::serde(tag = "mount")]
pub trait Filesystem {
fn check(&self, vars: &InsVars) -> Result<(), Error>;
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars);
}
fn default_permission() -> String {
"ro".into()
}

View file

@ -0,0 +1,30 @@
#![allow(non_camel_case_types)]
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::InsVars;
use crate::config::filesystem::{Filesystem, Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NEW_DIR {
#[serde(default)]
path: Vec<String>
}
#[typetag::serde]
impl Filesystem for NEW_DIR {
fn check(&self, vars: &InsVars) -> Result<(), Error> {
if self.path.len() == 0 {
Err(Error::new("DIR", format!("Path not specified."), false))?
}
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
for dir in self.path.iter() {
args.dir(dir);
}
}
}

View file

@ -0,0 +1,26 @@
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::InsVars;
use crate::config::filesystem::{Filesystem, Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HOME;
#[typetag::serde]
impl Filesystem for HOME {
fn check(&self, vars: &InsVars) -> Result<(), Error> {
if ! Path::new(vars.home()).exists() {
Err(Error::new("HOME", format!("INSTANCE_HOME not found."), true))?
}
Ok(())
}
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());
}
}

View file

@ -0,0 +1,29 @@
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::InsVars;
use crate::config::filesystem::{Filesystem, Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ROOT;
#[typetag::serde]
impl Filesystem for ROOT {
fn check(&self, vars: &InsVars) -> Result<(), Error> {
if ! Path::new(vars.root()).exists() {
Err(Error::new("ROOT", format!("Container {} not found. ", vars.instance()), true))?
}
Ok(())
}
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");
args.symlink("/usr/lib", "/lib64");
args.symlink("/usr/bin", "/bin");
args.symlink("/usr/bin", "/sbin");
}
}

View file

@ -0,0 +1,54 @@
#![allow(non_camel_case_types)]
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::constants::return_home;
use crate::exec::args::ExecutionArgs;
use crate::config::InsVars;
use crate::config::filesystem::{Filesystem, Error, default_permission};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TO_HOME {
#[serde(default = "default_permission")]
permission: String,
#[serde(default)]
path: Vec<String>
}
#[typetag::serde]
impl Filesystem for TO_HOME {
fn check(&self, vars: &InsVars) -> Result<(), Error> {
let per = self.permission.to_lowercase();
if per != "ro" && per != "rw" {
Err(Error::new("TO_HOME", format!("{} is an invalid permission.", self.permission), true))?
}
if self.path.len() == 0 {
Err(Error::new("TO_HOME", format!("Path not specified."), false))?
}
if ! Path::new(&format!("{}/{}", return_home(), &self.path[0])).exists() {
Err(Error::new("TO_HOME", format!("~/{} not found.", self.path[0]), true))?
}
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
let src = &self.path[0];
let mut dest: &String = src;
if self.path.len() > 1 { dest = &self.path[1]; }
let path_src = format!("{}/{}", return_home(), &self.path[0]);
let path_dest = format!("{}/{}", vars.home_mount(), dest);
match self.permission.to_lowercase().as_str() {
p if p == "rw" => args.bind(path_src, path_dest),
&_ => args.robind(path_src, path_dest)
}
}
}

View file

@ -0,0 +1,49 @@
#![allow(non_camel_case_types)]
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::InsVars;
use crate::config::filesystem::{Filesystem, Error, default_permission};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TO_ROOT {
#[serde(default = "default_permission")]
permission: String,
#[serde(default)]
path: Vec<String>
}
#[typetag::serde]
impl Filesystem for TO_ROOT {
fn check(&self, vars: &InsVars) -> Result<(), Error> {
let per = self.permission.to_lowercase();
if per != "ro" && per != "rw" {
Err(Error::new("TO_ROOT", format!("{} is an invalid permission.", self.permission), true))?
}
if self.path.len() == 0 {
Err(Error::new("TO_ROOT", format!("Path not specified."), false))?
}
if ! Path::new(&self.path[0]).exists() {
Err(Error::new("TO_ROOT", format!("Source path not found."), true))?
}
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
let src = &self.path[0];
let mut dest: &String = src;
if self.path.len() > 1 { dest = &self.path[1]; }
match self.permission.to_lowercase().as_str() {
p if p == "rw" => args.bind(src, dest),
&_ => args.robind(src, dest)
}
}
}

31
src/config/permission.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::exec::args::ExecutionArgs;
use crate::config::vars::InsVars;
pub mod none;
mod x11;
mod env;
mod pulseaudio;
mod pipewire;
mod gpu;
mod net;
mod dev;
pub struct Error {
error: String,
mod_name: String
}
impl Error {
pub fn new(name: impl Into<String>, err: impl Into<String>) -> Self {
Self { error: err.into(), mod_name: name.into() }
}
pub fn error(&self) -> &String { &self.error }
pub fn module(&self) -> &String { &self.mod_name }
}
#[typetag::serde(tag = "permission")]
pub trait Permission {
fn check(&self) -> Result<(),Error>;
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars);
}

View file

@ -0,0 +1,26 @@
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::{InsVars, Permission, permission::Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct DEV {
device: String
}
#[typetag::serde]
impl Permission for DEV {
fn check(&self) -> Result<(),Error> {
if ! Path::new(&format!("/dev/{}",self.device)).exists() {
Err(Error::new("dev", format!("/dev/{} is inaccessible.", self.device)))?
}
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
args.dev(&format!("/dev/{}", self.device));
}
}

View file

@ -0,0 +1,39 @@
use serde::{Deserialize, Serialize};
use std::env;
use crate::exec::args::ExecutionArgs;
use crate::utils::print_warning;
use crate::config::{InsVars, Permission, permission::Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ENV {
var: String,
#[serde(default = "default_set")]
set: String
}
#[typetag::serde]
impl Permission for ENV {
fn check(&self) -> Result<(),Error> {
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
let mut set = self.set.to_owned();
if set == "" {
match env::var(&self.var) {
Ok(env) => set = env,
Err(_) => { print_warning(format!("Environment variable {} is empty.", &self.var))
}
}
}
args.env(&self.var, set);
}
}
fn default_set() -> String {
"".into()
}

View file

@ -0,0 +1,38 @@
use std::fs::read_dir;
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::{InsVars, Permission, permission::Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct GPU;
#[typetag::serde]
impl Permission for GPU {
fn check(&self) -> Result<(),Error> {
if ! Path::new("/dev/").exists() {
Err(Error::new("GPU", format!("/dev is inaccessible.")))?
}
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
for e in read_dir("/dev/").unwrap() {
match e {
Ok(e) => {
let file = e.file_name();
let dev = file.to_str().unwrap();
match dev {
p if p.starts_with("nvidia") => args.dev(&format!("/dev/{}",dev)),
p if p == "dri" => args.dev(&format!("/dev/{}",dev)),
&_ => {}
}
}
Err(_) => continue,
}
}
}
}

View file

@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::{InsVars, Permission, permission::Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NET;
#[typetag::serde]
impl Permission for NET {
fn check(&self) -> Result<(),Error> {
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
args.push_env("--share-net");
args.bind("/etc/resolv.conf", "/etc/resolv.conf");
}
}

View file

@ -0,0 +1,17 @@
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::{InsVars, Permission, permission::Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NONE;
#[typetag::serde]
impl Permission for NONE {
fn check(&self) -> Result<(),Error> {
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {}
}

View file

@ -0,0 +1,35 @@
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::{InsVars, Permission, permission::Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PIPEWIRE {
#[serde(skip_serializing_if = "is_default_socket", default = "default_socket")]
socket: String,
}
#[typetag::serde]
impl Permission for PIPEWIRE {
fn check(&self) -> Result<(),Error> {
if ! Path::new(&self.socket).exists() {
Err(Error::new("PIPEWIRE", format!("Pipewire socket not present.")))?
}
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
args.robind(&self.socket, default_socket());
}
}
fn is_default_socket(var: &String) -> bool {
let default: &String = &default_socket();
default == var
}
fn default_socket() -> String {
format!("/run/user/{}/pipewire-0", nix::unistd::geteuid())
}

View file

@ -0,0 +1,35 @@
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::{InsVars, Permission, permission::Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PULSEAUDIO {
#[serde(skip_serializing_if = "is_default_socket", default = "default_socket")]
socket: String,
}
#[typetag::serde]
impl Permission for PULSEAUDIO {
fn check(&self) -> Result<(),Error> {
if ! Path::new(&self.socket).exists() {
Err(Error::new("PULSEAUDIO", format!("Pulseaudio socket not present.")))?
}
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
args.robind(&self.socket, default_socket());
}
}
fn is_default_socket(var: &String) -> bool {
let default: &String = &default_socket();
default == var
}
fn default_socket() -> String {
format!("/run/user/{}/pulse", nix::unistd::geteuid())
}

View file

@ -0,0 +1,52 @@
use std::path::Path;
use std::env::var;
use serde::{Deserialize, Serialize};
use crate::exec::args::ExecutionArgs;
use crate::config::{InsVars, Permission, permission::Error};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct X11 {
#[serde(skip_serializing_if = "is_default", default = "default_display")]
display: i8,
}
#[typetag::serde]
impl Permission for X11 {
fn check(&self) -> Result<(),Error> {
if ! Path::new(&format!("/tmp/.X11-unix/X{}", self.display)).exists() {
Err(Error::new("X11", format!("Diaplay server :{} is not running.", self.display)))?
}
match var("XAUTHORITY") {
Ok(env) => {
if env == "" {
Err(Error::new("X11", format!("XAUTHORITY is unset.")))?
}
if ! Path::new(&env).exists() {
Err(Error::new("X11", format!("XAUTHORITY path is invalid.")))?
}
},
Err(_) => Err(Error::new("X11", format!("XAUTHORITY is unset.")))?
}
Ok(())
}
fn register(&self, args: &mut ExecutionArgs, vars: &InsVars) {
let xauth = var("XAUTHORITY").unwrap();
let container_xauth = format!("/run/user/{}/Xauthority", nix::unistd::geteuid());
let display = format!(":{}", self.display);
args.env("DISPLAY", display);
args.robind(xauth, &container_xauth);
args.env("XAUTHORITY", container_xauth);
}
}
fn is_default(var: &i8) -> bool {
var == &0
}
fn default_display() -> i8 {
0
}

98
src/config/vars.rs Normal file
View file

@ -0,0 +1,98 @@
use std::env::var;
use crate::constants::{PACWRAP_DATA_DIR, PACWRAP_CACHE_DIR, PACWRAP_CONFIG_DIR};
use crate::config::Instance;
pub struct LocationVars {
pub data: String,
pub cache: String,
pub conf: String,
pub home: String
}
impl LocationVars {
pub fn new() -> Self {
let home_dir = var("HOME").unwrap();
let mut dir = Self {
data: format!("{}{}", &home_dir, PACWRAP_DATA_DIR),
cache: format!("{}{}", &home_dir, PACWRAP_CACHE_DIR),
conf: format!("{}{}", &home_dir, PACWRAP_CONFIG_DIR),
home: home_dir
};
if let Ok(var) = var("PACWRAP_DATA_DIR") { dir.data=var; }
if let Ok(var) = var("PACWRAP_CACHE_DIR") { dir.cache=var; }
if let Ok(var) = var("PACWRAP_CONFIG_DIR") { dir.conf=var; }
dir
}
}
#[derive(Clone)]
pub struct InsVars {
home: String,
root: String,
user: String,
config: String,
instance: String,
home_mount: String,
pub pacman_sync: String,
pub pacman_cache: String,
pub pacman_gnupg: String,
pub pacman_mirrorlist: String,
sync: String,
syncdb: String
}
impl InsVars {
pub fn new(_i: impl Into<String>) -> Self {
let ins = _i.into();
let dir = LocationVars::new();
let mut vars = Self {
home: format!("{}/home/{}", dir.data, ins),
root: format!("{}/root/{}", dir.data, ins),
pacman_gnupg: format!("{}/pacman/gnupg", dir.data),
pacman_sync: format!("{}/pacman/sync", dir.data),
pacman_cache: format!("{}/pkg", dir.cache),
pacman_mirrorlist: format!("{}/pacman.d/mirrorlist", dir.conf),
sync: format!("{}/pacman/sync/pacman.{}.conf", dir.conf, ins),
syncdb: format!("{}/pacman/syncdb/pacman.{}.conf", dir.conf, ins),
config: format!("{}/instance/{}.yml", dir.conf, ins),
home_mount: format!("/home/{}", ins),
user: ins.clone(),
instance: ins.clone(),
};
if let Ok(var) = var("PACWRAP_HOME") { vars.home=var; }
vars
}
pub fn debug(&self, cfg: &Instance, switch: &String, runtime: &Vec<String>) {
print!("Arguments: "); for arg in runtime.iter() { print!("{} ", arg); } println!();
println!("Switch: -{}", switch);
println!("Instance: {}", self.instance);
println!("User: {}", var("USER").unwrap());
println!("Home: {}", var("HOME").unwrap());
println!("allow_forking: {}", cfg.allow_forking());
println!("retain_session: {}", cfg.retain_session());
println!("Config: {}", self.config);
println!("INSTANCE_USER: {}", self.user);
println!("INSTANCE_ROOT: {}", self.root);
println!("INSTANCE_HOME: {}", self.home);
println!("INSTANCE_HOME_MOUNT: {}", self.home_mount);
println!("INSTANCE_SYNC: {}", self.sync);
println!("INSTANCE_SYNCDB: {}", self.syncdb);
}
pub fn sync(&self) -> &String { &self.sync }
pub fn syncdb(&self) -> &String { &self.syncdb }
pub fn config_path(&self) -> &String { &self.config }
pub fn root(&self) -> &String { &self.root }
pub fn home(&self) -> &String { &self.home }
pub fn home_mount(&self) -> &String { &self.home_mount }
pub fn user(&self) -> &String { &self.user }
pub fn instance(&self) -> &String { &self.instance }
}

8
src/constants.rs Normal file
View file

@ -0,0 +1,8 @@
use std::env::var;
pub const BWRAP_EXECUTABLE: &str = "bwrap";
pub const PACWRAP_CONFIG_DIR: &str = "/.config/pacwrap";
pub const PACWRAP_DATA_DIR: &str = "/.local/share/pacwrap";
pub const PACWRAP_CACHE_DIR: &str = "/.cache/pacwrap";
pub fn return_home() -> String { var("HOME").unwrap() }

272
src/exec.rs Normal file
View file

@ -0,0 +1,272 @@
#![allow(unused_assignments)]
use std::env;
use std::process;
use std::process::{Command, Child, ExitStatus, exit};
use std::{thread, time::Duration};
use std::os::unix::io::AsRawFd;
use std::fs::{File, remove_file};
use std::io::{Read};
use std::path::Path;
use std::vec::Vec;
use std::collections::HashMap;
use signal_hook::{consts::SIGINT, iterator::Signals};
use os_pipe::{PipeReader, PipeWriter};
use std::cell::RefCell;
use command_fds::{CommandFdExt, FdMapping};
use serde_json::Value;
use crate::config::{self, Instance, vars::InsVars, filesystem::Filesystem, permission::Permission, dbus::Dbus};
use crate::utils::{print_error, print_warning, test_root, arguments::Arguments};
use crate::constants::BWRAP_EXECUTABLE;
use crate::exec::args::ExecutionArgs;
pub mod args;
pub fn execute() {
let args = Arguments::new(1, "-E", HashMap::from([("--exec".into(), "E".into()),
("--root".into(), "r".into()),
("--shell".into(), "s".into()),
("--command".into(),"c".into()),]));
let switch = args.get_switch();
let instance = args.get_targets()[0].clone();
let runtime = args.get_runtime();
let instance_vars = InsVars::new(&instance);
let cfg = config::load_configuration(&instance_vars.config_path());
if switch.contains("v") { instance_vars.debug(&cfg, &switch, &runtime); }
match switch.as_str() {
s if s.contains("rc") || s.contains("cr") => execute_fakeroot(instance_vars, runtime),
s if s.contains("rs") || s.contains("sr") => execute_fakeroot(instance_vars, &["bash".into()].to_vec()),
s if s.contains("s") => execute_container(instance_vars,&["bash".into()].to_vec(), cfg, switch),
&_ => execute_container(instance_vars, runtime, cfg, switch),
}
}
fn execute_container(vars: InsVars, arguments: &Vec<String>, cfg: Instance, switch: &String) {
let mut exec_args = ExecutionArgs::new(&["--tmpfs".into(), "/tmp".into()],
&["--dev".into(), "/dev".into(), "--proc".into(), "/proc".into()], &[]);
let mut jobs: Vec<RefCell<Child>> = Vec::new();
if ! cfg.allow_forking() { exec_args.push_env("--die-with-parent"); }
if ! cfg.retain_session() { exec_args.push_env("--new-session"); }
if switch.contains("s") { exec_args.env("TERM", "xterm"); }
if cfg.dbus().len() > 0 {
jobs.push(register_dbus(cfg.dbus(), &vars, &mut exec_args).into()); }
register_filesystems(cfg.filesystem(), &vars, &mut exec_args);
register_permissions(cfg.permissions(), &vars, &mut exec_args);
//TODO: Implement separate abstraction for path vars.
exec_args.env("PATH", "/usr/bin/:/bin");
exec_args.env("XDG_RUNTIME_DIR", format!("/run/user/{}/", nix::unistd::geteuid()));
if switch.contains("v") { println!("{:?} ",exec_args); }
let (reader, writer) = os_pipe::pipe().unwrap();
let fd = writer.as_raw_fd();
let mut proc = Command::new(BWRAP_EXECUTABLE);
proc.args(exec_args.get_bind()).args(exec_args.get_dev())
.arg("--proc").arg("/proc").arg("--unshare-all").arg("--clearenv")
.arg("--info-fd").arg(fd.to_string())
.args(exec_args.get_env()).args(arguments)
.fd_mappings(vec![FdMapping { parent_fd: fd, child_fd: fd }]).unwrap();
match proc.spawn() {
Ok(c) => wait_on_process(c, &read_info_json(reader, writer), *cfg.allow_forking(), jobs),
Err(_) => print_error(format!("Failed to initialise bwrap."))
}
}
fn read_info_json(mut reader: PipeReader, writer: PipeWriter) -> Value {
let mut output = String::new();
drop(writer);
reader.read_to_string(&mut output).unwrap();
match serde_json::from_str(&output) {
Ok(value) => value,
Err(_) => Value::Null
}
}
fn signal_init(child_id: String) {
let mut signals = Signals::new(&[SIGINT]).unwrap();
thread::spawn(move || {
for _sig in signals.forever() {
if Path::new(&format!("/proc/{}/", &child_id)).exists() {
Command::new("/usr/bin/kill").arg("-9").arg(&child_id).output().expect("Failed.");
}
clean_up_socket();
Command::new("/usr/bin/reset").arg("-w").output().expect("Failed.");
println!();
}
});
}
fn wait_on_process(mut process: Child, value: &Value, block: bool, jobs: Vec<RefCell<Child>>) {
if block {
signal_init(value["child-pid"].to_string());
}
match process.wait() {
Ok(status) => {
if block {
let proc = format!("/proc/{}/", value["child-pid"]);
while Path::new(&proc).exists() {
thread::sleep(Duration::from_millis(250));
}
}
if jobs.len() > 0 {
for job in jobs.iter() {
let mut child = job.borrow_mut();
let _ = child.kill();
}
}
clean_up_socket();
process_exit(status);
},
Err(_) => {
print_error(format!("bwrap process abnormally terminated."));
exit(2);
}
}
}
fn process_exit(status: ExitStatus) {
match status.code() {
Some(o) => exit(o),
None => {
println!();
eprintln!("bwrap process {}", status);
exit(2);
}
}
}
fn register_filesystems(per: &Vec<Box<dyn Filesystem>>, vars: &InsVars, args: &mut ExecutionArgs) {
for p in per.iter() {
match p.check(vars) {
Ok(_) => p.register(args, vars),
Err(e) => {
if *e.critical() {
print_error(format!("Failed to mount {}: {} ", e.module(), e.error()));
exit(1);
} else {
print_warning(format!("Failed to mount {}: {} ", e.module(), e.error()));
}
}
}
}
}
fn register_permissions(per: &Vec<Box<dyn Permission>>, vars: &InsVars, args: &mut ExecutionArgs) {
for p in per.iter() {
match p.check() {
Ok(_) => p.register(args, vars),
Err(e) => print_warning(format!("Failed to register permission {}: {} ", e.module(), e.error()))
}
}
}
fn register_dbus(per: &Vec<Box<dyn Dbus>>, vars: &InsVars, args: &mut ExecutionArgs) -> Child {
for p in per.iter() {
p.register(args, vars);
}
let dbus_socket_path = format!("/run/user/{}/bus", nix::unistd::geteuid());
let dbus_socket = create_dbus_socket();
let dbus_session = env!("DBUS_SESSION_BUS_ADDRESS", "Failure");
match Command::new("xdg-dbus-proxy")
.arg(dbus_session).arg(&dbus_socket)
.args(args.get_dbus()).spawn() {
Ok(child) => {
args.robind(dbus_socket, &dbus_socket_path);
args.symlink(&dbus_socket_path, "/run/dbus/system_bus_socket");
args.env("DBUS_SESSION_BUS_ADDRESS", format!("unix:path={}", &dbus_socket_path));
child
},
Err(_) => {
print_error("Activation of xdg-dbus-proxy failed.".into());
exit(2);
},
}
}
fn create_dbus_socket() -> String {
let socket_address = format!("/run/user/1000/pacwrap_dbus_{}", &process::id());
match File::create(&socket_address) {
Ok(file) => {
drop(file);
socket_address
},
Err(_) => {
print_error(format!("Failed to create dbus socket."));
eprintln!("Ensure you have write permissions to /run/user/.");
String::new()
}
}
}
fn clean_up_socket() {
let socket_address = format!("/run/user/1000/pacwrap_dbus_{}", &process::id());
if ! Path::new(&socket_address).exists() {
return;
}
if let Err(_) = remove_file(socket_address) {
print_error(format!("Failed to remove FD."));
}
}
fn execute_fakeroot(instance: InsVars, arguments: &Vec<String>) {
test_root(&instance);
match Command::new(BWRAP_EXECUTABLE)
.arg("--tmpfs").arg("/tmp")
.arg("--bind").arg(&instance.root()).arg("/")
.arg("--ro-bind").arg("/usr/lib/libfakeroot").arg("/usr/lib/libfakeroot/")
.arg("--ro-bind").arg("/usr/bin/fakeroot").arg("/usr/bin/fakeroot")
.arg("--ro-bind").arg("/usr/bin/fakechroot").arg("/usr/bin/fakechroot")
.arg("--ro-bind").arg("/usr/bin/faked").arg("/usr/bin/faked")
.arg("--ro-bind").arg("/etc/resolv.conf").arg("/etc/resolv.conf")
.arg("--ro-bind").arg("/etc/localtime").arg("/etc/localtime")
.arg("--bind").arg(&instance.pacman_sync).arg("/var/lib/pacman/sync")
.arg("--bind").arg(&instance.pacman_gnupg).arg("/etc/pacman.d/gnupg")
.arg("--bind").arg(&instance.pacman_cache).arg("/var/cache/pacman/pkg")
.arg("--ro-bind").arg(&instance.pacman_mirrorlist).arg("/etc/pacman.d/mirrorlist")
.arg("--ro-bind").arg(&instance.sync()).arg("/etc/pacman.conf")
.arg("--ro-bind").arg(&instance.syncdb()).arg("/tmp/pacman.conf")
.arg("--bind").arg(&instance.home()).arg(&instance.home_mount())
.arg("--dev").arg("/dev")
.arg("--proc").arg("/proc")
.arg("--unshare-all").arg("--share-net")
.arg("--clearenv")
.arg("--hostname").arg("FakeChroot")
.arg("--new-session")
.arg("--setenv").arg("TERM").arg("xterm")
.arg("--setenv").arg("PATH").arg("/usr/bin")
.arg("--setenv").arg("CWD").arg(&instance.home_mount())
.arg("--setenv").arg("HOME").arg(&instance.home_mount())
.arg("--setenv").arg("USER").arg(&instance.user())
.arg("--die-with-parent")
.arg("fakechroot")
.arg("fakeroot")
.args(arguments)
.spawn() {
Ok(process) => wait_on_process(process, &Value::Null, false, Vec::new()),
Err(_) => print_error(format!("Failed to initialise bwrap.")),
}
}

63
src/exec/args.rs Normal file
View file

@ -0,0 +1,63 @@
#[derive(Debug)]
pub struct ExecutionArgs {
bind: Vec<String>,
dev: Vec<String>,
envir: Vec<String>,
dbus: Vec<String>
}
impl ExecutionArgs {
pub fn new(b: &[String], d: &[String], e: &[String]) -> Self {
Self { bind: b.to_vec(), dev: d.to_vec(), envir: e.to_vec(), dbus: Vec::new() }
}
pub fn get_bind(&self) -> &Vec<String> { &self.bind }
pub fn get_dev(&self) -> &Vec<String> { &self.dev }
pub fn get_env(&self) -> &Vec<String> { &self.envir }
pub fn get_dbus(&self) -> &Vec<String> { &self.dbus }
pub fn push_env(&mut self, src: impl Into<String>) { self.envir.push(src.into()); }
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>) {
self.bind.push("--bind".into());
self.bind.push(src.into());
self.bind.push(dest.into());
}
pub fn robind(&mut self, src: impl Into<String>, dest: impl Into<String>) {
self.bind.push("--ro-bind".into());
self.bind.push(src.into());
self.bind.push(dest.into());
}
pub fn symlink(&mut self, src: impl Into<String>, dest: impl Into<String>) {
self.bind.push("--symlink".into());
self.bind.push(src.into());
self.bind.push(dest.into());
}
pub fn env(&mut self, src: impl Into<String>, dest: impl Into<String>) {
self.envir.push("--setenv".into());
self.envir.push(src.into());
self.envir.push(dest.into());
}
pub fn dev(&mut self, src: impl Into<String>) {
let dev = src.into();
self.dev.push("--dev-bind-try".into());
self.dev.push(dev.clone());
self.dev.push(dev.clone());
}
pub fn dbus(&mut self, per: impl Into<String>, socket: impl Into<String>) {
self.dbus.push("--".to_owned()+&per.into()+"="+&socket.into());
}
}

59
src/main.rs Normal file
View file

@ -0,0 +1,59 @@
use std::env;
use std::process::Command;
use std::collections::HashMap;
use utils::arguments::Arguments;
use utils::print_help_msg;
mod config;
mod exec;
mod constants;
mod utils;
fn main() {
let args = Arguments::new(0, "-", HashMap::from([("--exec".into(), "E".into()),
("--explicit".into(), "E".into()),
("--pacman".into(),"E".into()),
("--gen-cfg".into(),"Axc".into()),
("--version".into(),"V".into()),
("--create".into(),"C".into()),
("--sync".into(),"S".into()),
("--help".into(),"h".into()),
("--utils".into(),"U".into()),
("--process".into(),"P".into()),]));
match args.get_switch().as_str() {
s if s.starts_with("E") => exec::execute(),
s if s.starts_with("V") => print_version(),
s if s.starts_with("S") => execute_pacwrap_bash("pacwrap-sync".to_string()),
s if s.starts_with("U") => execute_pacwrap_bash("pacwrap-utils".to_string()),
s if s.starts_with("C") => execute_pacwrap_bash("pacwrap-create".to_string()),
s if s.starts_with("P") => execute_pacwrap_bash("pacwrap-ps".to_string()),
s if s.starts_with("v") => execute_pacwrap_bash("pacwrap-man".to_string()),
s if s.starts_with("h") => execute_pacwrap_bash("pacwrap-man".to_string()),
s if s.starts_with("Axc") => config::aux_config(),
&_ => {
let mut ar = String::new();
for arg in env::args().skip(1).collect::<Vec<_>>().iter() {
ar.push_str(&format!("{} ", &arg));
}
print_help_msg(&format!("Invalid arguments -- {}", ar));
}
}
}
fn print_version() {
println!("{} {}{} ", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_VERSION_MINOR"));
let info=concat!("Copyright (C) 2023 Xavier R.M.\n\n",
"Website: https://git.sapphirus.org/pacwrap\n",
"Github: https://github.com/sapphirusberyl/pacwrap\n\n",
"This program may be freely redistributed under\n",
"the terms of the GNU General Public License v3.\n");
print!("{}", info);
}
fn execute_pacwrap_bash(executable: String) {
let mut process = Command::new(&executable)
.args(env::args().skip(1).collect::<Vec<_>>())
.spawn().expect("Command failed.");
process.wait().expect("failed to wait on child");
}

51
src/utils.rs Normal file
View file

@ -0,0 +1,51 @@
use std::path::Path;
use std::process::exit;
use nix::unistd::isatty;
use crate::config::vars::InsVars;
pub mod arguments;
pub fn test_root(instance: &InsVars) {
if ! Path::new(&instance.root()).exists() || ! Path::new(&instance.home()).exists() {
print_error(format!("Target container {}: not found.", instance.instance()));
exit(2);
}
}
fn support_ansi_escape(fd: i32) -> bool {
match isatty(fd) {
Ok(b) => {
if b && env!("TERM") != "dumb" {
return true;
} else {
return false;
}
},
Err(_) => return false
}
}
pub fn print_warning(message: String) {
if support_ansi_escape(2) {
eprintln!("warning: {}", &message);
} else {
eprintln!("WARNING: {}", &message);
}
}
pub fn print_error(message: String) {
if support_ansi_escape(2) {
eprintln!("error: {}", &message);
} else {
eprintln!("ERROR: {}", &message);
}
}
pub fn print_help_msg(args: &str) {
println!("pacwrap error: {} ", args);
println!("Try 'pacwrap -h' for more information on valid operational parameters.");
exit(1);
}

48
src/utils/arguments.rs Normal file
View file

@ -0,0 +1,48 @@
use std::env;
use std::collections::HashMap;
use crate::utils;
#[derive(Clone)]
pub struct Arguments {
prefix: String,
switch: String,
runtime: Vec<String>,
targets: Vec<String>,
target: usize,
argument_map: HashMap<String, String>
}
impl Arguments {
pub fn new(amt: usize, arg: impl Into<String>, arg_map: HashMap<String, String>) -> Self {
let mut arguments = Self {
prefix: arg.into(),
switch: String::new(),
targets: Vec::new(),
runtime: Vec::new(),
target: amt,
argument_map: arg_map,
};
arguments.parse_arguments();
return arguments;
}
fn parse_arguments(&mut self) {
for string in env::args().skip(1) {
match string {
string if self.argument_map.contains_key(&string) => self.append_switch(self.argument_map.get(&string).unwrap().clone()),
string if string.starts_with(self.get_prefix()) => self.append_switch(&string[self.get_prefix().len()..]),
_ => { if ! self.target_reached() { self.targets.push(string); } else { self.runtime.push(string); } },
}
}
if ! self.target_reached() { utils::print_help_msg("Targets not specified. "); }
}
fn append_switch(&mut self, arg: impl Into<String>) { self.switch = self.switch.clone()+&arg.into(); }
fn target_reached(&self) -> bool { self.targets.len() == self.target }
pub fn get_runtime(&self) -> &Vec<String> { &self.runtime }
pub fn get_switch(&self) -> &String { &self.switch }
pub fn get_prefix(&self) -> &String { &self.prefix }
pub fn get_targets(&self) -> &Vec<String> { &self.targets }
}