feat: init structure, implemented pacfiles

This commit is contained in:
2026-04-22 19:03:08 +02:00
commit 27b7cd3813
9 changed files with 951 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
use clap::{ArgAction, Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
#[command(styles = get_clap_styles())]
pub struct Args {
/// Skips certain confirmation steps.
#[arg(long = "confirm", action = ArgAction::SetTrue)]
#[arg(long = "noconfirm", action = ArgAction::SetFalse, overrides_with = "confirm")]
pub confirm: bool,
#[command(subcommand)]
pub subcommand: Command,
}
#[derive(Subcommand, Debug)]
pub enum Command {
/// Looks for `.pacnew` and `.pacsave` files and allows editing of them using `$EDITOR -d <file>
/// <pacfile>`.
Pacfiles,
// Pacfiles {
// /// Override of `PACMAN_AUTH`.
// #[arg(short, long)]
// auth: Option<String>,
// },
// Dummy argument to start working
Dummy,
}
const fn get_clap_styles() -> clap::builder::Styles {
clap::builder::Styles::styled()
.usage(
anstyle::Style::new()
.bold()
.underline()
.fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::BrightBlue))),
)
.header(
anstyle::Style::new()
.bold()
.underline()
.fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::BrightBlue))),
)
.literal(
anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))),
)
.invalid(
anstyle::Style::new()
.bold()
.fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))),
)
.error(
anstyle::Style::new()
.bold()
.fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))),
)
.valid(
anstyle::Style::new()
.bold()
.underline()
.fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))),
)
.placeholder(
anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Blue))),
)
}
+161
View File
@@ -0,0 +1,161 @@
use std::{
ffi::OsStr,
io::{BufRead, Write},
process::{Command, Stdio},
};
use anyhow::Context;
use log::{error, info, warn};
use shlex::Shlex;
const DEFAULT_EDITOR: &[&str] = &["vim"];
const LOOK_FILES: &[&str] = &["/etc"];
pub enum CowCmd<'a> {
Owned(Vec<String>),
Borrowed(&'a [&'a str]),
}
impl CowCmd<'_> {
fn cmd<S, Ia, Sa>(program: S, args: Ia) -> Command
where
S: AsRef<OsStr>,
Ia: IntoIterator<Item = Sa>,
Sa: AsRef<OsStr>,
{
let mut cmd = Command::new(program);
cmd.args(args);
cmd
}
pub fn to_command(&self) -> Command {
match self {
CowCmd::Owned(items) => Self::cmd(&items[0], &items[1..]),
CowCmd::Borrowed(items) => Self::cmd(items[0], &items[1..]),
}
}
}
impl std::fmt::Debug for CowCmd<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CowCmd::Owned(items) => std::fmt::Debug::fmt(items, f),
CowCmd::Borrowed(items) => std::fmt::Debug::fmt(items, f),
}
}
}
pub fn pacfiles(args: &crate::args::Args) -> anyhow::Result<()> {
if unsafe { libc::getegid() } != 0 {
warn!("pacfiles should run as root to make sure it can read priviledged directories");
}
let editor = if let Ok(env_editor) = std::env::var("EDITOR") {
CowCmd::Owned(Shlex::new(&env_editor).collect())
} else {
CowCmd::Borrowed(DEFAULT_EDITOR)
};
info!("looking for pacfiles in: {LOOK_FILES:?}");
for dir in LOOK_FILES {
for entry in walkdir::WalkDir::new(dir)
.into_iter()
.filter_map(Result::ok)
.filter_map(|e| {
let path = e.path();
let ext = path.extension()?;
if ext == "pacsave" || ext == "pacnew" {
let wo_ext = path.with_extension("");
let exists = match std::fs::exists(&wo_ext) {
Ok(exists) => exists,
Err(err) => {
warn!("failed checking existnce of {}: {err}", wo_ext.display());
return None;
}
};
if !exists {
warn!("{} doesn't exist while {} does", wo_ext.display(), path.display());
return None;
}
Some((wo_ext, e))
} else {
None
}
})
{
let mut command = editor.to_command();
let path = entry.1.path();
info!("comparing {} and {}", entry.0.display(), path.display());
#[allow(clippy::unwrap_used)]
if args.confirm {
let mut stdout = std::io::stdout().lock();
let stdin = std::io::stdin().lock();
write!(stdout, "Press enter to confirm: ").unwrap();
stdout.flush().unwrap();
_ = stdin.lines().next().unwrap();
}
let status = command
.arg("-d")
.args([&entry.0, path])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
.context("error executing editor")?
.status
.code()
.context("error getting status code, assuming failure")?;
if status != 0 {
error!("{editor:?} exited with status code {status}, aborting");
break;
}
}
}
Ok(())
}
// pub fn pacfiles_user(args: &crate::args::Args, auth: Option<&str>) -> anyhow::Result<()> {
// let mut auth_args = if let Some(auth) = auth {
// let args = shlex::Shlex::new(auth).collect::<Vec<_>>();
// Cow::Owned(args)
// } else {
// let args = crate::sys::get_auth()?;
// Cow::Borrowed(args)
// };
// let [auth_arg0, auth_args @ ..] = auth_args.as_ref() else {
// panic!("auth command contains no arg0");
// };
// let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string());
// // info!("looking for pacfiles in {LOOK_FILES:?}");
// for dir in ["/etc"] {
// for entry in walkdir::WalkDir::new(dir)
// .into_iter()
// .filter_map(Result::ok)
// .filter_map(|e| {
// let path = e.path();
// let ext = path.extension()?;
// if ext == "pacsave" || ext == "pacnew" {
// let wo_ext = path.with_extension("");
// if !std::fs::exists(wo_ext).ok()? {
// // todo warning of ext and return None
// }
// Some((path.with_extension(""), path))
// } else { None }
// })
// {
// let base_file = entry.
// let mut command = Command::new(auth_arg0);
// command.args(auth_args).args([&editor, "-d"]);
// }
// //
// }
// Ok(())
// }
+35
View File
@@ -0,0 +1,35 @@
#![feature(iterator_try_collect, once_cell_try)]
use std::io::Write;
use clap::Parser;
mod args;
mod commands;
mod sys;
fn main() -> anyhow::Result<()> {
// if unsafe { libc::geteuid() } == 0 {
// return Err(anyhow::anyhow!(
// "the program is not designed to runn as root, priviledge escalation is done by the program itself"
// ));
// }
let args = args::Args::parse();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace"))
// .format(|buf, record| {
// writeln!(
// buf,
// "{}: {}",
// record.level(),
// record.args()
// )
// })
.format_timestamp(None)
.init();
match args.subcommand {
args::Command::Pacfiles => commands::pacfiles(&args),
args::Command::Dummy => unimplemented!(),
}
}
+38
View File
@@ -0,0 +1,38 @@
use std::{
io,
process::{Command, Stdio},
sync::OnceLock,
};
pub fn get_auth() -> io::Result<&'static [String]> {
pub fn get_auth() -> io::Result<Vec<String>> {
let mut cmd = Command::new("bash");
let output = cmd
.args([
"-euo",
"pipefail",
"-c",
"source /etc/makepkg.conf && \
printf \"%s\\0\" \"${PACMAN_AUTH[@]}\"",
])
.stdout(Stdio::piped())
.output()?;
if output.status.success() {
output
.stdout
.split(|&b| b == b'\0')
.filter(|slice| !slice.is_empty())
.map(Vec::from)
.map(String::from_utf8)
.try_collect()
.map_err(io::Error::other)
} else {
Err(io::Error::other("bash failed to source /etc/makepkg.conf"))
}
}
static PACMAN_AUTH_CELL: OnceLock<Vec<String>> = const { OnceLock::new() };
PACMAN_AUTH_CELL.get_or_try_init(get_auth).map(|v| v as &[_])
}