From 9b4b894b8222f3a39654368377b59c51d07b4ded Mon Sep 17 00:00:00 2001 From: Vincent de Phily Date: Sun, 8 Oct 2023 21:53:50 +0100 Subject: [PATCH] process/predict: Find portage_tmpdir using emerge process's open fds Required permission should be the same as for reading `build.log`. --- CHANGELOG.md | 4 +++- src/cli.rs | 14 ++++++++----- src/commands.rs | 7 ++++--- src/parse/current.rs | 9 ++++---- src/proces.rs | 49 +++++++++++++++++++++++++++++++++++--------- 5 files changed, 60 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c77117..7b00051 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ * Support searching by multiple terms - eg `emlop s -e gcc clang llvm rust` -* Support multiple `--tmpdir`s +* Improve predict's `tmpdir`: + - Autodetect `tmpdir` using currently running emerge processes + - Support multiple `--tmpdir` arguments * Upgraded clap dependency - Inline help styling/content changed a bit diff --git a/src/cli.rs b/src/cli.rs index b3b94b5..91eca0e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,5 @@ use clap::{builder::styling, crate_version, value_parser, Arg, ArgAction::*, Command}; +use std::path::PathBuf; /// Generate cli argument parser without the `complete` subcommand. pub fn build_cli_nocomplete() -> Command { @@ -267,10 +268,12 @@ pub fn build_cli_nocomplete() -> Command { .num_args(1) .action(Append) .default_value("/var/tmp") + .value_parser(value_parser!(PathBuf)) .display_order(2) .help("Location of portage tmpdir") .long_help("Location of portage tmpdir\n\ - Multiple folders can be provided"); + Multiple folders can be provided\n\ + Emlop also looks for tmpdir using current emerge processes"); let resume = Arg::new("resume").long("resume") .value_name("source") .value_parser(value_parser!(crate::ResumeKind)) @@ -421,7 +424,7 @@ mod test { } macro_rules! many { ($t: ty, $k: expr, $a: expr) => { - matches($a).get_many::<$t>($k).map(|v| v.map(AsRef::as_ref).collect::>()) + matches($a).get_many::<$t>($k).map(|r| r.cloned().collect::>()) }; } @@ -444,8 +447,9 @@ mod test { assert_eq!(one!(ColorStyle, "color", "l --color n"), Some(&ColorStyle::Never)); assert_eq!(one!(ColorStyle, "color", "l --color never"), Some(&ColorStyle::Never)); - assert_eq!(many!(String, "tmpdir", "p"), Some(vec!["/var/tmp"])); - assert_eq!(many!(String, "tmpdir", "p --tmpdir a"), Some(vec!["a"])); - assert_eq!(many!(String, "tmpdir", "p --tmpdir a --tmpdir b"), Some(vec!["a", "b"])); + let pathvec = |s: &str| Some(s.split_whitespace().map(PathBuf::from).collect()); + assert_eq!(many!(PathBuf, "tmpdir", "p"), pathvec("/var/tmp/portage")); + assert_eq!(many!(PathBuf, "tmpdir", "p --tmpdir a"), pathvec("a")); + assert_eq!(many!(PathBuf, "tmpdir", "p --tmpdir a --tmpdir b"), pathvec("a b")); } } diff --git a/src/commands.rs b/src/commands.rs index 165ca28..78b1a48 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,6 +1,7 @@ use crate::{datetime::*, parse::*, proces::*, table::*, *}; use std::{collections::{BTreeMap, HashMap}, - io::stdin}; + io::stdin, + path::PathBuf}; /// Straightforward display of merge events /// @@ -324,11 +325,11 @@ pub fn cmd_predict(args: &ArgMatches) -> Result { let avg = *args.get_one("avg").unwrap(); let resume = *args.get_one("resume").unwrap(); let mut tbl = Table::new(st).align_left(0).align_left(2).margin(2, " ").last(last); - let tmpdirs = args.get_many::("tmpdir").unwrap().cloned().collect(); + let mut tmpdirs: Vec = args.get_many("tmpdir").unwrap().cloned().collect(); // Gather and print info about current merge process. let mut cms = std::i64::MAX; - for i in get_all_info(Some("emerge")) { + for i in get_all_info(Some("emerge"), &mut tmpdirs) { cms = std::cmp::min(cms, i.start); if show.emerge { tbl.row([&[&i], &[&FmtDur(now - i.start)], &[]]); diff --git a/src/parse/current.rs b/src/parse/current.rs index 37328d6..4bf19fd 100644 --- a/src/parse/current.rs +++ b/src/parse/current.rs @@ -7,7 +7,8 @@ use regex::Regex; use serde::Deserialize; use serde_json::from_reader; use std::{fs::File, - io::{BufRead, BufReader, Read}}; + io::{BufRead, BufReader, Read}, + path::PathBuf}; /// Package name and version #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -91,11 +92,11 @@ fn get_resume_priv(kind: ResumeKind, file: &str) -> Option> { /// Retrieve summary info from the build log -pub fn get_buildlog(pkg: &Pkg, portdirs: &Vec) -> Option { +pub fn get_buildlog(pkg: &Pkg, portdirs: &Vec) -> Option { for portdir in portdirs { - let name = format!("{}/portage/{}/temp/build.log", portdir, pkg.ebuild_version()); + let name = portdir.join("portage").join(pkg.ebuild_version()).join("temp/build.log"); if let Ok(file) = File::open(&name).map_err(|e| warn!("Cannot open {name:?}: {e}")) { - info!("Build log: {name}"); + info!("Build log: {}", name.display()); return Some(read_buildlog(file, 50)); } } diff --git a/src/proces.rs b/src/proces.rs index ba5f571..7bb6396 100644 --- a/src/proces.rs +++ b/src/proces.rs @@ -9,7 +9,8 @@ use crate::{datetime::*, *}; use anyhow::{ensure, Context}; use std::{fs::{read_dir, DirEntry, File}, - io::prelude::*}; + io::prelude::*, + path::PathBuf}; #[derive(Debug)] pub struct Info { @@ -37,7 +38,8 @@ impl std::fmt::Display for Info { fn get_proc_info(filter: Option<&str>, entry: &DirEntry, clocktick: i64, - time_ref: i64) + time_ref: i64, + tmpdirs: &mut Vec) -> Option { // Parse pid. // At this stage we expect `entry` to not always correspond to a process. @@ -57,18 +59,44 @@ fn get_proc_info(filter: Option<&str>, let mut cmdline = String::new(); File::open(entry.path().join("cmdline")).ok()?.read_to_string(&mut cmdline).ok()?; cmdline = cmdline.replace('\0', " ").trim().into(); + // Find portage tmpdir + extend_tmpdirs(entry.path(), tmpdirs); // Done Some(Info { cmdline, start: time_ref + start_time / clocktick, pid }) } +/// Find tmpdir by looking for "build.log" in the process fds, and add it to the provided vector. +fn extend_tmpdirs(proc: PathBuf, tmpdirs: &mut Vec) { + if let Ok(entries) = read_dir(proc.join("fd")) { + let procstr = proc.to_string_lossy(); + for d in entries.filter_map(|e| { + let p = e.ok()?.path().canonicalize().ok()?; + if p.file_name() != Some(std::ffi::OsStr::new("build.log")) { + return None; + } + let d = p.parent()?.parent()?.parent()?.parent()?.parent()?; + debug!("Tmpdir {} found in {}", d.to_string_lossy(), procstr); + Some(d.to_path_buf()) + }) + { + if !tmpdirs.contains(&d) { + // Insert at the front because it's a better candidate than cli/default tmpdir + tmpdirs.insert(0, d) + } + } + } +} + /// Get command name, arguments, start time, and pid for all processes. -pub fn get_all_info(filter: Option<&str>) -> Vec { - get_all_info_result(filter).unwrap_or_else(|e| { - log_err(e); - vec![] - }) +pub fn get_all_info(filter: Option<&str>, tmpdirs: &mut Vec) -> Vec { + get_all_info_result(filter, tmpdirs).unwrap_or_else(|e| { + log_err(e); + vec![] + }) } -fn get_all_info_result(filter: Option<&str>) -> Result, Error> { +fn get_all_info_result(filter: Option<&str>, + tmpdirs: &mut Vec) + -> Result, Error> { // clocktick and time_ref are needed to interpret stat.start_time. time_ref should correspond to // the system boot time; not sure why it doesn't, but it's still usable as a reference. // SAFETY: returns a system constant, only failure mode should be a zero/negative value @@ -83,7 +111,7 @@ fn get_all_info_result(filter: Option<&str>) -> Result, Error> { // Now iterate through /proc/ let mut ret: Vec = Vec::new(); for entry in read_dir("/proc/").context("Listing /proc/")? { - if let Some(i) = get_proc_info(filter, &entry?, clocktick, time_ref) { + if let Some(i) = get_proc_info(filter, &entry?, clocktick, time_ref, tmpdirs) { ret.push(i) } } @@ -109,8 +137,9 @@ mod tests { #[test] #[rustfmt::skip] fn start_time() { // First get the system's process start times using our implementation + let mut tmpdirs = vec![]; let mut info: BTreeMap,Option)> = //pid => (cmd, rust_time, ps_time) - get_all_info(None) + get_all_info(None, &mut tmpdirs) .iter() .fold(BTreeMap::new(), |mut a,i| {a.insert(i.pid, (i.cmdline.clone(),Some(i.start),None)); a}); // Then get them using the ps implementation (merging them into the same data structure)