feat: init structure, implemented pacfiles
This commit is contained in:
+68
@@ -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
@@ -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
@@ -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
@@ -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 &[_])
|
||||
}
|
||||
Reference in New Issue
Block a user