diff --git a/src/id.rs b/src/id.rs deleted file mode 100644 index 7f61928..0000000 --- a/src/id.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::{collections::BTreeSet, fmt::Display}; - -use serde::Serialize; - -/// Unique `uid`s and `gid`s. -#[derive(Debug, Default, Serialize)] -pub struct Ids { - pub users: BTreeSet, - pub groups: BTreeSet, -} - -impl Display for Ids { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "users:")?; - for user in &self.users { - writeln!(f, " {user}")?; - } - - writeln!(f)?; - - writeln!(f, "groups:")?; - for group in &self.groups { - writeln!(f, " {group}")?; - } - - Ok(()) - } -} diff --git a/src/main.rs b/src/main.rs index 24da36a..e093ea6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,42 @@ use std::{fs::read_dir, os::linux::fs::MetadataExt, path::Path}; -use anyhow::{ensure, Context, Result}; +use anyhow::{anyhow, ensure, Context, Result}; use clap::Parser; -use crate::{cli::Args, id::Ids, name::Names, output::Output}; +use crate::{cli::Args, summary::Summary}; mod cli; -mod id; -mod name; -mod output; +mod summary; fn main() -> Result<()> { human_panic::setup_panic!(); let args = Args::parse(); - let mut ids = Ids::default(); + let mut summary = Summary::default(); for root in args.roots { - fs_entry(&root, &mut ids)?; + fs_entry(&root, &mut summary)?; } - let output: Box = match args.raw { - false => Box::new(Names::try_from(ids).context("failed to get names")?), - true => Box::new(ids), - }; + if !args.raw { + let (uf, gf) = summary.lookup_names(); + + for (uid, e) in uf { + eprintln!( + "{:#}", + anyhow!(e).context(format!("failed to get name for user {uid}")) + ); + } + + for (gid, e) in gf { + eprintln!( + "{:#}", + anyhow!(e).context(format!("failed to get name for group {gid}")) + ); + } + } let output = match args.json { - false => output.human_readable(), - true => output.json().context("failed json serialization")?, + false => summary.to_string(), + true => serde_json::to_string_pretty(&summary).context("json serialization failed")?, }; println!("{output}"); @@ -33,7 +44,7 @@ fn main() -> Result<()> { } /// Perform gid & uid gathering for a file, or a directory and its children. -fn fs_entry(entry: &Path, summary: &mut Ids) -> Result<()> { +fn fs_entry(entry: &Path, summary: &mut Summary) -> Result<()> { let display = entry.display(); ensure!( entry.is_symlink() || entry.exists(), @@ -43,8 +54,8 @@ fn fs_entry(entry: &Path, summary: &mut Ids) -> Result<()> { let meta = entry .symlink_metadata() .context(format!("failed to get metadata for {}", display))?; - summary.users.insert(meta.st_uid()); - summary.groups.insert(meta.st_gid()); + summary.add_user(meta.st_uid()); + summary.add_group(meta.st_gid()); if entry.is_dir() { let children = read_dir(entry).context(format!("failed to read dir {}", display))?; diff --git a/src/name.rs b/src/name.rs deleted file mode 100644 index a440d5c..0000000 --- a/src/name.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::{collections::BTreeSet, fmt::Display}; - -use anyhow::{anyhow, Error, Result}; -use file_owner::{Group, Owner}; -use serde::Serialize; - -use crate::id::Ids; - -/// Unique user and group names. -#[derive(Debug, Default, Serialize)] -pub struct Names { - pub users: BTreeSet, - pub groups: BTreeSet, -} - -impl Display for Names { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "users:")?; - for user in &self.users { - writeln!(f, " {user}")?; - } - - writeln!(f)?; - - writeln!(f, "groups:")?; - for group in &self.groups { - writeln!(f, " {group}")?; - } - - Ok(()) - } -} - -impl TryFrom for Names { - type Error = Error; - - fn try_from(value: Ids) -> Result { - Ok(Self { - users: value - .users - .into_iter() - .map(|u| match Owner::from_uid(u).name() { - Ok(Some(name)) => Ok(name), - Ok(None) => Err(anyhow!("no name for user {u}")), - Err(_) => Err(anyhow!("failed to get name for user {u}")), - }) - .collect::>()?, - groups: value - .groups - .into_iter() - .map(|g| match Group::from_gid(g).name() { - Ok(Some(name)) => Ok(name), - Ok(None) => Err(anyhow!("no name for group {g}")), - Err(_) => Err(anyhow!("failed to get name for group {g}")), - }) - .collect::>()?, - }) - } -} diff --git a/src/output.rs b/src/output.rs deleted file mode 100644 index fdd93ad..0000000 --- a/src/output.rs +++ /dev/null @@ -1,22 +0,0 @@ -use serde::Serialize; - -/// Data that can be printed to the console. -pub trait Output { - /// A human readable representation of the data. - fn human_readable(&self) -> String; - /// A `json` representation of the data. - fn json(&self) -> Result; -} - -impl Output for T -where - T: ToString + Serialize, -{ - fn human_readable(&self) -> String { - self.to_string() - } - - fn json(&self) -> Result { - serde_json::to_string_pretty(&self) - } -} diff --git a/src/summary.rs b/src/summary.rs new file mode 100644 index 0000000..e5ccdf0 --- /dev/null +++ b/src/summary.rs @@ -0,0 +1,78 @@ +use std::{collections::BTreeMap, fmt::Display}; + +use file_owner::{FileOwnerError, Group, Owner}; +use serde::Serialize; + +/// Lists of unique [User]s and [Group]s. +#[derive(Debug, Default, Serialize)] +pub struct Summary { + users: BTreeMap>, + groups: BTreeMap>, +} + +impl Summary { + /// Add a new [User] to the [Summary]. + pub fn add_user(&mut self, uid: u32) { + self.users.entry(uid).or_default(); + } + + /// Add a new [Group] to the [Summary]. + pub fn add_group(&mut self, gid: u32) { + self.groups.entry(gid).or_default(); + } + + /// Look up the names of all users and groups. + pub fn lookup_names(&mut self) -> (Vec<(u32, FileOwnerError)>, Vec<(u32, FileOwnerError)>) { + let mut user_failures = vec![]; + for (uid, name) in &mut self.users { + if name.is_some() { + continue; + } + + match Owner::from_uid(*uid).name() { + Ok(n) => *name = n, + Err(e) => user_failures.push((*uid, e)), + } + } + + let mut group_failures = vec![]; + for (gid, name) in &mut self.groups { + if name.is_some() { + continue; + } + + match Group::from_gid(*gid).name() { + Ok(n) => *name = n, + Err(e) => group_failures.push((*gid, e)), + } + } + + (user_failures, group_failures) + } +} + +impl Display for Summary { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { users, groups } = self; + + writeln!(f, "users:")?; + for (uid, name) in users { + match name { + None => writeln!(f, " {uid}")?, + Some(name) => writeln!(f, " {name} ({uid})")?, + } + } + + writeln!(f)?; + + writeln!(f, "groups:")?; + for (gid, name) in groups { + match name { + None => writeln!(f, " {gid}")?, + Some(name) => writeln!(f, " {name} ({gid})")?, + } + } + + Ok(()) + } +}