0
0
Fork 0

redo output

This commit is contained in:
Adrian Wannenmacher 2023-09-24 14:25:46 +02:00
parent a806fb4de2
commit 02166a07ef
Signed by: tfld
GPG Key ID: 19D986ECB1E492D5
5 changed files with 105 additions and 125 deletions

View File

@ -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<u32>,
pub groups: BTreeSet<u32>,
}
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(())
}
}

View File

@ -1,31 +1,42 @@
use std::{fs::read_dir, os::linux::fs::MetadataExt, path::Path}; 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 clap::Parser;
use crate::{cli::Args, id::Ids, name::Names, output::Output}; use crate::{cli::Args, summary::Summary};
mod cli; mod cli;
mod id; mod summary;
mod name;
mod output;
fn main() -> Result<()> { fn main() -> Result<()> {
human_panic::setup_panic!(); human_panic::setup_panic!();
let args = Args::parse(); let args = Args::parse();
let mut ids = Ids::default(); let mut summary = Summary::default();
for root in args.roots { for root in args.roots {
fs_entry(&root, &mut ids)?; fs_entry(&root, &mut summary)?;
} }
let output: Box<dyn Output> = match args.raw { if !args.raw {
false => Box::new(Names::try_from(ids).context("failed to get names")?), let (uf, gf) = summary.lookup_names();
true => Box::new(ids),
}; 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 { let output = match args.json {
false => output.human_readable(), false => summary.to_string(),
true => output.json().context("failed json serialization")?, true => serde_json::to_string_pretty(&summary).context("json serialization failed")?,
}; };
println!("{output}"); println!("{output}");
@ -33,7 +44,7 @@ fn main() -> Result<()> {
} }
/// Perform gid & uid gathering for a file, or a directory and its children. /// 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(); let display = entry.display();
ensure!( ensure!(
entry.is_symlink() || entry.exists(), entry.is_symlink() || entry.exists(),
@ -43,8 +54,8 @@ fn fs_entry(entry: &Path, summary: &mut Ids) -> Result<()> {
let meta = entry let meta = entry
.symlink_metadata() .symlink_metadata()
.context(format!("failed to get metadata for {}", display))?; .context(format!("failed to get metadata for {}", display))?;
summary.users.insert(meta.st_uid()); summary.add_user(meta.st_uid());
summary.groups.insert(meta.st_gid()); summary.add_group(meta.st_gid());
if entry.is_dir() { if entry.is_dir() {
let children = read_dir(entry).context(format!("failed to read dir {}", display))?; let children = read_dir(entry).context(format!("failed to read dir {}", display))?;

View File

@ -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<String>,
pub groups: BTreeSet<String>,
}
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<Ids> for Names {
type Error = Error;
fn try_from(value: Ids) -> Result<Self> {
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::<Result<_>>()?,
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::<Result<_>>()?,
})
}
}

View File

@ -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<String, serde_json::Error>;
}
impl<T> Output for T
where
T: ToString + Serialize,
{
fn human_readable(&self) -> String {
self.to_string()
}
fn json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(&self)
}
}

78
src/summary.rs Normal file
View File

@ -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<u32, Option<String>>,
groups: BTreeMap<u32, Option<String>>,
}
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(())
}
}