Fixed the name so it's not a collision

This commit is contained in:
Russell 2025-07-28 17:26:35 +00:00
parent be23d887cb
commit cec1d5409c
10 changed files with 134 additions and 198 deletions

26
Cargo.lock generated
View File

@ -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"

View File

@ -1,5 +1,5 @@
[package]
name = "where-cmd"
name = "carto"
version = "0.1.0"
edition = "2021"

View File

@ -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

152
readme.md
View File

@ -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 <repository-url>
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.*

View File

@ -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,

View File

@ -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,

View File

@ -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<std::io::Error> for WhereError {
impl From<std::io::Error> for CartoError {
fn from(err: std::io::Error) -> Self {
WhereError::Io(err)
CartoError::Io(err)
}
}
impl From<std::string::FromUtf8Error> for WhereError {
impl From<std::string::FromUtf8Error> 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<dyn std::error::Error>> {
let cli = Cli::parse();

View File

@ -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<Vec<PackageInfo>, WhereError> {
fn get_installed_packages(&self) -> Result<Vec<PackageInfo>, 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<Vec<PathBuf>, WhereError> {
fn get_package_files(&self, package: &str) -> Result<Vec<PathBuf>, 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<Option<String>, WhereError> {
fn get_file_owner(&self, path: &Path) -> Result<Option<String>, 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<Option<PackageInfo>, WhereError> {
fn get_package_info(&self, package: &str) -> Result<Option<PackageInfo>, CartoError> {
let output = Command::new("rpm")
.args(["-qi", package])
.output()?;

View File

@ -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<Vec<PackageInfo>, WhereError> {
fn get_installed_packages(&self) -> Result<Vec<PackageInfo>, 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<Vec<PathBuf>, WhereError> {
fn get_package_files(&self, package: &str) -> Result<Vec<PathBuf>, 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<Option<String>, WhereError> {
fn get_file_owner(&self, path: &Path) -> Result<Option<String>, 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<Option<PackageInfo>, WhereError> {
fn get_package_info(&self, package: &str) -> Result<Option<PackageInfo>, 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<Option<PathBuf>, WhereError> {
fn get_app_location(&self, app_id: &str) -> Result<Option<PathBuf>, CartoError> {
let output = Command::new("flatpak")
.args(["info", "--show-location", app_id])
.output()?;

View File

@ -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<Vec<PackageInfo>, WhereError>;
fn get_package_files(&self, package: &str) -> Result<Vec<PathBuf>, WhereError>;
fn get_file_owner(&self, path: &Path) -> Result<Option<String>, WhereError>;
fn get_package_info(&self, package: &str) -> Result<Option<PackageInfo>, WhereError>;
fn get_installed_packages(&self) -> Result<Vec<PackageInfo>, CartoError>;
fn get_package_files(&self, package: &str) -> Result<Vec<PathBuf>, CartoError>;
fn get_file_owner(&self, path: &Path) -> Result<Option<String>, CartoError>;
fn get_package_info(&self, package: &str) -> Result<Option<PackageInfo>, CartoError>;
/// Priority for file ownership resolution (higher = preferred)
fn priority(&self) -> u8 {
@ -108,7 +107,7 @@ pub fn detect_available_managers() -> Vec<Box<dyn PackageManager>> {
}
/// Find which package owns a file, checking all available managers
pub fn find_file_owner(path: &Path) -> Result<Option<(String, PackageSource)>, WhereError> {
pub fn find_file_owner(path: &Path) -> Result<Option<(String, PackageSource)>, CartoError> {
let managers = detect_available_managers();
for manager in managers {
@ -121,7 +120,7 @@ pub fn find_file_owner(path: &Path) -> Result<Option<(String, PackageSource)>, W
}
/// Get all installed packages from all available managers
pub fn get_all_packages() -> Result<Vec<PackageInfo>, WhereError> {
pub fn get_all_packages() -> Result<Vec<PackageInfo>, CartoError> {
let managers = detect_available_managers();
let mut all_packages = Vec::new();
@ -142,7 +141,7 @@ pub fn get_all_packages() -> Result<Vec<PackageInfo>, WhereError> {
}
/// Get package information by name, searching all managers
pub fn find_package(name: &str) -> Result<Option<PackageInfo>, WhereError> {
pub fn find_package(name: &str) -> Result<Option<PackageInfo>, CartoError> {
let managers = detect_available_managers();
for manager in managers {
@ -155,7 +154,7 @@ pub fn find_package(name: &str) -> Result<Option<PackageInfo>, WhereError> {
}
/// Get packages from a specific source type
pub fn get_packages_by_source(source: PackageSource) -> Result<Vec<PackageInfo>, WhereError> {
pub fn get_packages_by_source(source: PackageSource) -> Result<Vec<PackageInfo>, CartoError> {
let managers = detect_available_managers();
for manager in managers {
@ -164,7 +163,7 @@ pub fn get_packages_by_source(source: PackageSource) -> Result<Vec<PackageInfo>,
}
}
Err(WhereError::ManagerNotAvailable(source.name().to_string()))
Err(CartoError::ManagerNotAvailable(source.name().to_string()))
}
/// Summary of available package managers