From cec1d5409c18cbc27708e1ac769199ff7f2ebb0e Mon Sep 17 00:00:00 2001 From: Russell Date: Mon, 28 Jul 2025 17:26:35 +0000 Subject: [PATCH] Fixed the name so it's not a collision --- Cargo.lock | 26 +++--- Cargo.toml | 2 +- justfile | 67 ++++++++++++-- readme.md | 154 ++++---------------------------- src/cli/mod.rs | 2 +- src/cli/tree.rs | 4 +- src/main.rs | 26 +++--- src/package_managers/dnf.rs | 16 ++-- src/package_managers/flatpak.rs | 14 +-- src/package_managers/mod.rs | 21 +++-- 10 files changed, 134 insertions(+), 198 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53a59ef..960cb9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,19 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "carto" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "inotify", + "memmap2", + "regex", + "serde", + "walkdir", +] + [[package]] name = "cc" version = "1.2.30" @@ -614,19 +627,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "where-cmd" -version = "0.1.0" -dependencies = [ - "chrono", - "clap", - "inotify", - "memmap2", - "regex", - "serde", - "walkdir", -] - [[package]] name = "winapi-util" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 627d402..cdbf4e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "where-cmd" +name = "carto" version = "0.1.0" edition = "2021" diff --git a/justfile b/justfile index e7075eb..d36d1de 100644 --- a/justfile +++ b/justfile @@ -4,16 +4,71 @@ default: run mode="quiet": #! /bin/bash case "{{ mode }}" in - quiet) - RUSTFLAGS="-A warnings" cargo run --quiet - ;; - *) - cargo run - ;; + quiet) + RUSTFLAGS="-A warnings" cargo run --quiet + ;; + *) + cargo run + ;; esac build: cargo build --release +install: build + #! /bin/bash + # Build release binary + echo "Building release binary..." + + # Create local bin directory if it doesn't exist + mkdir -p ~/.local/bin + + # Copy binary to local bin (rename from carto to carto) + cp target/release/carto ~/.local/bin/carto + chmod +x ~/.local/bin/carto + + echo "Installed 'carto' to ~/.local/bin/carto" + echo "" + echo "Make sure ~/.local/bin is in your PATH:" + echo " echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.zshrc" + echo " source ~/.zshrc" + echo "" + echo "Usage examples:" + echo " carto packages --source flatpak" + echo " carto tree --package bash" + echo " carto file /usr/bin/bash" + +uninstall: + #! /bin/bash + if [ -f ~/.local/bin/carto ]; then + rm ~/.local/bin/carto + echo "Uninstalled 'carto' from ~/.local/bin" + else + echo "'carto' not found in ~/.local/bin" + fi + + # Clean up old names if they exist + if [ -f ~/.local/bin/where ]; then + rm ~/.local/bin/where + echo "Also removed old 'where' binary" + fi + +system-install: build + #! /bin/bash + # System-wide installation (requires sudo) + echo "Installing system-wide (requires sudo)..." + sudo cp target/release/carto /usr/local/bin/carto + sudo chmod +x /usr/local/bin/carto + echo "Installed 'carto' to /usr/local/bin/carto" + +system-uninstall: + #! /bin/bash + if [ -f /usr/local/bin/carto ]; then + sudo rm /usr/local/bin/carto + echo "Uninstalled 'carto' from /usr/local/bin" + else + echo "'carto' not found in /usr/local/bin" + fi + clean: cargo clean \ No newline at end of file diff --git a/readme.md b/readme.md index ef0f296..128437e 100644 --- a/readme.md +++ b/readme.md @@ -1,94 +1,43 @@ -# WHERE - Universal System File and Package Mapper +# CARTO - Universal System File and Package Mapper A command-line tool that provides comprehensive observability into your Linux system's package installations and file ownership. Built in Rust for performance and reliability. -## Features - -- **Universal Package Manager Support** - Works with DNF, APT, Flatpak, Snap, and more -- **File Ownership Tracking** - Instantly identify which package owns any file -- **Package Tree Visualization** - See all files installed by a package in tree format -- **Cross-Distribution Compatibility** - Single tool that works across Linux distributions -- **High Performance** - Memory-mapped binary format for fast queries - ## Installation ```bash git clone -cd where +cd carto cargo build --release +just install ``` - ## Usage -### Show Package File Tree -Display all files installed by a specific package in a hierarchical tree structure: +# Show package file tree +carto tree --package bash +carto tree --package "Signal Desktop (org.signal.Signal)" -```bash -# Show files installed by bash package -cargo run -- tree --package bash +# Package information +carto package bash -# Show files for Flatpak application -cargo run -- tree --package "Signal Desktop (org.signal.Signal)" +# File ownership +carto file /usr/bin/bash -# Show trees for first 5 packages (demo mode) -cargo run -- tree -``` +# List packages +carto packages +carto packages --source dnf +carto packages --source flatpak -### Package Information -Get detailed information about a specific package: - -```bash -# Show package details and file tree -cargo run -- package bash -``` - -### File Ownership -Find out which package owns a specific file: - -```bash -# Check who owns /usr/bin/bash -cargo run -- file /usr/bin/bash -``` - -### List All Packages -Display all installed packages from all managers: - -```bash -# List all packages from all managers -cargo run -- packages - -# List only DNF packages -cargo run -- packages --source dnf - -# List only Flatpak packages -cargo run -- packages --source flatpak -``` - -### Find Files -Search for files within packages: - -```bash -# Find all files in bash package -cargo run -- find --package bash - -# Find all files in Flatpak application -cargo run -- find --package "Signal Desktop (org.signal.Signal)" -``` +# Find files in package +carto find --package bash ## Example Output -### Package Tree -``` +```bash bash 5.2.15-6.fc41 ├── usr/ │ ├── bin/ │ │ └── bash │ └── share/ -│ ├── doc/ -│ │ └── bash/ -│ │ ├── CHANGES -│ │ ├── README -│ │ └── NEWS │ └── man/ │ └── man1/ │ └── bash.1.gz @@ -96,80 +45,13 @@ bash 5.2.15-6.fc41 └── skel/ └── .bashrc -Signal Desktop (org.signal.Signal) 7.63.0 -├── /var/lib/flatpak/app/org.signal.Signal/ -├── /var/lib/flatpak/exports/share/applications/org.signal.Signal.desktop -└── ~/.var/app/org.signal.Signal/ -``` - -### File Ownership -``` /usr/bin/bash is owned by package: bash -~/.var/app/org.signal.Signal is owned by package: org.signal.Signal -``` -### Package Listing -``` DNF packages (1,847 total): bash 5.2.15-6.fc41 glibc 2.37-4.fc38 - kernel 6.8.5-301.fc40 Flatpak packages (7 total): - Zen (app.zen_browser.zen) 1.14.9b Signal Desktop (org.signal.Signal) 7.63.0 GIMP (org.gimp.GIMP) 3.0.4 -``` - -## Architecture - -The tool is designed with a modular architecture supporting multiple package managers: - -- **Package Manager Abstraction** - Common interface for DNF, Flatpak, and future managers -- **Universal File Tracking** - Track files across traditional packages and sandboxed applications -- **Source-Aware Filtering** - Filter by package manager type (DNF, Flatpak, etc.) -- **Binary Database Format** - High-performance storage for file metadata (planned) -- **CLI Interface** - Clean command-line interface built with Clap -- **Tree Visualization** - Hierarchical display of package contents - -## Supported Package Managers - -- ✅ **DNF/RPM** (Fedora, RHEL, CentOS) - Full support -- ✅ **Flatpak** (Universal) - Full support -- 🚧 **APT** (Debian, Ubuntu) - Coming Soon -- 🚧 **Snap** - Coming Soon -- 🚧 **Pacman** (Arch Linux) - Coming Soon - -## Development Philosophy - -This tool is part of the **IntentOS** project, focused on creating systems where every component has clear provenance and purpose. The core principle is "know where everything is" - eliminating the entropy and "shrapnel" that accumulates in traditional Linux installations. - -## Contributing - -Built with Rust 2021 edition. Key dependencies: -- `clap` - Command line argument parsing -- `memmap2` - Memory-mapped file access -- `chrono` - Date/time handling - -## License - -[Add your license here] - -## Roadmap - -- [x] DNF/RPM support with file ownership tracking -- [x] Flatpak support with sandboxed application tracking -- [x] Universal package listing with source filtering -- [x] Package file tree visualization -- [ ] Snap support -- [ ] APT support for Debian/Ubuntu systems -- [ ] Real-time filesystem monitoring -- [ ] Binary database format for fast queries -- [ ] GUI interface -- [ ] Integration with IntentOS package manager -- [ ] Package installation profiles -- [ ] System entropy analysis tools - ---- - -*WHERE gives you complete visibility into your system's package installations - because you should know where everything comes from.* \ No newline at end of file +``` \ No newline at end of file diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 59d201f..69dff2b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -3,7 +3,7 @@ pub mod tree; use clap::{Parser, Subcommand}; #[derive(Parser)] -#[command(name = "where", about = "Universal system file and package mapper")] +#[command(name = "carto", about = "Universal system file and package mapper")] pub struct Cli { #[command(subcommand)] pub command: Commands, diff --git a/src/cli/tree.rs b/src/cli/tree.rs index 1eb8738..e8b9791 100644 --- a/src/cli/tree.rs +++ b/src/cli/tree.rs @@ -1,8 +1,8 @@ -use crate::{PackageManager, PackageSource, WhereError}; +use crate::{PackageManager, PackageSource, CartoError}; use std::collections::HashMap; use std::path::Path; -pub fn print_package_tree(manager: &dyn PackageManager, package_name: &str) -> Result<(), WhereError> { +pub fn print_package_tree(manager: &dyn PackageManager, package_name: &str) -> Result<(), CartoError> { // Get package info first let package_info = match manager.get_package_info(package_name)? { Some(info) => info, diff --git a/src/main.rs b/src/main.rs index 77489eb..46cb413 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ pub struct PackageInfo { } #[derive(Debug)] -pub enum WhereError { +pub enum CartoError { Io(std::io::Error), Utf8(std::string::FromUtf8Error), CommandFailed(String), @@ -30,32 +30,32 @@ pub enum WhereError { } // Implement From traits for error conversion -impl From for WhereError { +impl From for CartoError { fn from(err: std::io::Error) -> Self { - WhereError::Io(err) + CartoError::Io(err) } } -impl From for WhereError { +impl From for CartoError { fn from(err: std::string::FromUtf8Error) -> Self { - WhereError::Utf8(err) + CartoError::Utf8(err) } } -impl std::fmt::Display for WhereError { +impl std::fmt::Display for CartoError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - WhereError::Io(e) => write!(f, "IO error: {}", e), - WhereError::Utf8(e) => write!(f, "UTF-8 error: {}", e), - WhereError::CommandFailed(s) => write!(f, "Command failed: {}", s), - WhereError::PackageNotFound(s) => write!(f, "Package not found: {}", s), - WhereError::ManagerNotAvailable(s) => write!(f, "Manager not available: {}", s), - WhereError::InvalidDatabase => write!(f, "Invalid database format"), + CartoError::Io(e) => write!(f, "IO error: {}", e), + CartoError::Utf8(e) => write!(f, "UTF-8 error: {}", e), + CartoError::CommandFailed(s) => write!(f, "Command failed: {}", s), + CartoError::PackageNotFound(s) => write!(f, "Package not found: {}", s), + CartoError::ManagerNotAvailable(s) => write!(f, "Manager not available: {}", s), + CartoError::InvalidDatabase => write!(f, "Invalid database format"), } } } -impl std::error::Error for WhereError {} +impl std::error::Error for CartoError {} fn main() -> Result<(), Box> { let cli = Cli::parse(); diff --git a/src/package_managers/dnf.rs b/src/package_managers/dnf.rs index 2ca3156..8a2f818 100644 --- a/src/package_managers/dnf.rs +++ b/src/package_managers/dnf.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; use std::process::Command; -use crate::{PackageInfo, WhereError}; +use crate::{PackageInfo, CartoError}; use super::{PackageManager, PackageSource}; pub struct DnfManager; @@ -15,14 +15,14 @@ impl PackageManager for DnfManager { Path::new("/usr/bin/dnf").exists() || Path::new("/usr/bin/rpm").exists() } - fn get_installed_packages(&self) -> Result, WhereError> { + fn get_installed_packages(&self) -> Result, CartoError> { // Use rpm directly for better performance and parsing let output = Command::new("rpm") .args(["-qa", "--queryformat", "%{NAME}\t%{VERSION}-%{RELEASE}\t%{INSTALLTIME}\t%{SIZE}\n"]) .output()?; if !output.status.success() { - return Err(WhereError::CommandFailed("rpm query failed".to_string())); + return Err(CartoError::CommandFailed("rpm query failed".to_string())); } let stdout = String::from_utf8(output.stdout)?; @@ -44,13 +44,13 @@ impl PackageManager for DnfManager { .collect()) } - fn get_package_files(&self, package: &str) -> Result, WhereError> { + fn get_package_files(&self, package: &str) -> Result, CartoError> { let output = Command::new("rpm") .args(["-ql", package]) .output()?; if !output.status.success() { - return Err(WhereError::PackageNotFound(package.to_string())); + return Err(CartoError::PackageNotFound(package.to_string())); } let stdout = String::from_utf8(output.stdout)?; @@ -61,7 +61,7 @@ impl PackageManager for DnfManager { .collect()) } - fn get_file_owner(&self, path: &Path) -> Result, WhereError> { + fn get_file_owner(&self, path: &Path) -> Result, CartoError> { let output = Command::new("rpm") .args(["-qf", &path.to_string_lossy()]) .output()?; @@ -82,12 +82,12 @@ impl PackageManager for DnfManager { if stderr.contains("is not owned by any package") { Ok(None) } else { - Err(WhereError::CommandFailed(format!("rpm query failed: {}", stderr))) + Err(CartoError::CommandFailed(format!("rpm query failed: {}", stderr))) } } } - fn get_package_info(&self, package: &str) -> Result, WhereError> { + fn get_package_info(&self, package: &str) -> Result, CartoError> { let output = Command::new("rpm") .args(["-qi", package]) .output()?; diff --git a/src/package_managers/flatpak.rs b/src/package_managers/flatpak.rs index 1e438d1..aeb4890 100644 --- a/src/package_managers/flatpak.rs +++ b/src/package_managers/flatpak.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; use std::process::Command; -use crate::{PackageInfo, WhereError}; +use crate::{PackageInfo, CartoError}; use super::{PackageManager, PackageSource}; pub struct FlatpakManager; @@ -17,13 +17,13 @@ impl PackageManager for FlatpakManager { Command::new("flatpak").arg("--version").output().is_ok() } - fn get_installed_packages(&self) -> Result, WhereError> { + fn get_installed_packages(&self) -> Result, CartoError> { let output = Command::new("flatpak") .args(["list", "--columns=application,name,version,branch,installation"]) .output()?; if !output.status.success() { - return Err(WhereError::CommandFailed("flatpak list failed".to_string())); + return Err(CartoError::CommandFailed("flatpak list failed".to_string())); } let stdout = String::from_utf8(output.stdout)?; @@ -56,7 +56,7 @@ impl PackageManager for FlatpakManager { Ok(packages) } - fn get_package_files(&self, package: &str) -> Result, WhereError> { + fn get_package_files(&self, package: &str) -> Result, CartoError> { // Extract app ID from package name (format: "Name (app.id)") let app_id = if package.contains('(') && package.contains(')') { package.split('(').nth(1) @@ -89,7 +89,7 @@ impl PackageManager for FlatpakManager { Ok(files.into_iter().filter(|p| p.exists()).collect()) } - fn get_file_owner(&self, path: &Path) -> Result, WhereError> { + fn get_file_owner(&self, path: &Path) -> Result, CartoError> { let path_str = path.to_string_lossy(); // Check if it's in a Flatpak directory @@ -123,7 +123,7 @@ impl PackageManager for FlatpakManager { Ok(None) } - fn get_package_info(&self, package: &str) -> Result, WhereError> { + fn get_package_info(&self, package: &str) -> Result, CartoError> { // Extract app ID from package name let app_id = if package.contains('(') && package.contains(')') { package.split('(').nth(1) @@ -176,7 +176,7 @@ impl PackageManager for FlatpakManager { } impl FlatpakManager { - fn get_app_location(&self, app_id: &str) -> Result, WhereError> { + fn get_app_location(&self, app_id: &str) -> Result, CartoError> { let output = Command::new("flatpak") .args(["info", "--show-location", app_id]) .output()?; diff --git a/src/package_managers/mod.rs b/src/package_managers/mod.rs index 480f31c..40d752a 100644 --- a/src/package_managers/mod.rs +++ b/src/package_managers/mod.rs @@ -6,8 +6,7 @@ pub mod flatpak; use self::flatpak::FlatpakManager; use self::dnf::DnfManager; - -use crate::{PackageInfo, WhereError}; +use crate::{PackageInfo, CartoError}; #[derive(Debug, Clone, Copy, PartialEq)] pub enum PackageSource { @@ -62,10 +61,10 @@ pub trait PackageManager { fn name(&self) -> &'static str; fn source_type(&self) -> PackageSource; fn is_available(&self) -> bool; - fn get_installed_packages(&self) -> Result, WhereError>; - fn get_package_files(&self, package: &str) -> Result, WhereError>; - fn get_file_owner(&self, path: &Path) -> Result, WhereError>; - fn get_package_info(&self, package: &str) -> Result, WhereError>; + fn get_installed_packages(&self) -> Result, CartoError>; + fn get_package_files(&self, package: &str) -> Result, CartoError>; + fn get_file_owner(&self, path: &Path) -> Result, CartoError>; + fn get_package_info(&self, package: &str) -> Result, CartoError>; /// Priority for file ownership resolution (higher = preferred) fn priority(&self) -> u8 { @@ -108,7 +107,7 @@ pub fn detect_available_managers() -> Vec> { } /// Find which package owns a file, checking all available managers -pub fn find_file_owner(path: &Path) -> Result, WhereError> { +pub fn find_file_owner(path: &Path) -> Result, CartoError> { let managers = detect_available_managers(); for manager in managers { @@ -121,7 +120,7 @@ pub fn find_file_owner(path: &Path) -> Result, W } /// Get all installed packages from all available managers -pub fn get_all_packages() -> Result, WhereError> { +pub fn get_all_packages() -> Result, CartoError> { let managers = detect_available_managers(); let mut all_packages = Vec::new(); @@ -142,7 +141,7 @@ pub fn get_all_packages() -> Result, WhereError> { } /// Get package information by name, searching all managers -pub fn find_package(name: &str) -> Result, WhereError> { +pub fn find_package(name: &str) -> Result, CartoError> { let managers = detect_available_managers(); for manager in managers { @@ -155,7 +154,7 @@ pub fn find_package(name: &str) -> Result, WhereError> { } /// Get packages from a specific source type -pub fn get_packages_by_source(source: PackageSource) -> Result, WhereError> { +pub fn get_packages_by_source(source: PackageSource) -> Result, CartoError> { let managers = detect_available_managers(); for manager in managers { @@ -164,7 +163,7 @@ pub fn get_packages_by_source(source: PackageSource) -> Result, } } - Err(WhereError::ManagerNotAvailable(source.name().to_string())) + Err(CartoError::ManagerNotAvailable(source.name().to_string())) } /// Summary of available package managers