CLI Configuration and Usage#
rmagic is a pure-Rust command-line tool for file type identification that serves as a memory-safe alternative to the GNU file command. The CLI uses magic rules to examine file contents rather than extensions, providing accurate file type detection for binaries, archives, images, and documents. The CLI was developed as part of the Phase 1 MVP focused on delivering a working command-line tool alongside library functionality, prioritizing memory safety and compatibility with GNU file.
The tool uses clap version 4.6.1 with derive macros for declarative argument parsing, implemented entirely in src/main.rs. The CLI supports multiple input modes (files, stdin), output formats (text, JSON), custom magic files, and built-in fallback rules. Configuration follows a clear flag-based precedence strategy with no configuration file support - all configuration is done via command-line arguments.
The rmagic CLI emphasizes robust error handling, actionable error messages, and Unix pipeline integration. It implements strict mode for CI/CD environments, timeout protection for complex evaluations, and graceful degradation when processing multiple files.
CLI Structure and Interface Design#
Basic Invocation#
rmagic [OPTIONS] <FILE>...
rmagic [OPTIONS] -
The CLI requires at least one positional argument: files to analyze or - for stdin. The tool uses the FileOrStdin type from clap-stdin to handle both file paths and stdin input seamlessly.
Functional Categories#
The CLI is organized around three functional categories:
- Output Format Selection – Control how results are displayed (
--text,--json) - Magic File Source Selection – Choose where magic rules come from (
--magic-file,--use-builtin) - Behavior Configuration – Control processing behavior (
--strict,--timeout-ms)
Command-Line Flags#
Output Format Flags (Mutually Exclusive)#
| Flag | Type | Default | Description |
|---|---|---|---|
--text | bool | true | Output results in text format (one line per file) |
--json | bool | false | Output results in JSON format |
The --text and --json flags are mutually exclusive, enforced via clap's conflicts_with = "text" attribute on the json field. Passing both flags produces a clap validation error before execution.
Magic File Selection Flags#
| Flag | Type | Default | Description |
|---|---|---|---|
--magic-file FILE | Option<PathBuf> | None | Use a custom magic file or directory |
--use-builtin | bool | false | Use built-in magic rules compiled into the binary |
When both --use-builtin and --magic-file are provided, --use-builtin takes precedence. This allows users to override a default magic file path with the built-in rules.
Behavior Configuration Flags#
| Flag | Type | Default | Description |
|---|---|---|---|
--strict | bool | false | Exit with non-zero code on processing failures |
--timeout-ms MS | Option<u64> | None | Per-file evaluation timeout in milliseconds |
Standard Flags#
| Flag | Description |
|---|---|
-h, --help | Print help information |
-V, --version | Print version information |
Configuration Precedence and Loading#
Magic File Resolution Order#
The CLI follows a strict precedence order when determining which magic rules to use:
--use-builtinflag (highest priority) – Use embedded magic rules regardless of other flags--magic-file PATHflag – Use explicitly specified custom magic file or directory- Platform-specific automatic discovery – Search standard system locations for magic files
- Error with actionable message – If no magic file is found, display suggested options
Platform-Specific Magic File Discovery#
When no explicit magic file is specified, rmagic implements an OpenBSD-inspired search strategy that prefers human-readable text files over compiled binary .mgc files.
Unix/Linux/macOS Search Order#
The search is defined in the MAGIC_FILE_CANDIDATES constant, with text files/directories checked first, binary .mgc files as fallbacks:
Text format (highest priority):
/usr/share/file/magic/Magdir(OpenBSD-style directory)/usr/share/file/magic(text directory/file)/usr/share/misc/magic(BSD text)/usr/local/share/misc/magic(FreeBSD/Homebrew)/etc/magic(system-wide text)/opt/local/share/file/magic(MacPorts)
Binary format (fallback):
7. /usr/share/file/magic.mgc
8. /usr/local/share/misc/magic.mgc
9. /opt/local/share/file/magic.mgc
10. /etc/magic.mgc
11. /usr/share/misc/magic.mgc
If none of these paths exist, rmagic falls back to /usr/share/file/magic.mgc.
Windows Search Order#
%APPDATA%\Magic\magicthird_party/magic.mgc(bundled fallback)
Environment Variables#
Environment variables are used only for internal magic file discovery and CI/CD detection, not exposed as CLI configuration:
| Variable | Platform | Purpose |
|---|---|---|
CI | Unix | CI/CD environment detection |
GITHUB_ACTIONS | Unix | GitHub Actions detection |
APPDATA | Windows | User application data directory |
No Configuration File Support#
The CLI does not support configuration files. All configuration must be provided via command-line flags.
Mutually Exclusive Options and Validation#
Clap-Level Validation#
Mutual exclusivity between --json and --text is enforced via clap's conflicts_with attribute, which prevents both flags from being specified simultaneously. This produces an immediate error before any processing:
$ rmagic --json --text file.bin
error: the argument '--json' cannot be used with '--text'
Application-Level Validation#
The validate_arguments() function performs additional validation:
- Files vector check – Ensures at least one file argument is provided (redundant with clap's
required = true) - Magic file path check – Validates that magic file paths are non-empty when provided
Configuration Validation#
The EvaluationConfig::validate() method enforces runtime constraints:
| Parameter | Valid Range | Default | Validation |
|---|---|---|---|
max_recursion_depth | 1-1,000 | 20 | Must be within bounds |
max_string_length | 1-1,048,576 bytes | 8,192 | Must be within bounds |
timeout_ms | 1-300,000 (5 min) | None | Must be within bounds when specified |
| Resource combination | - | - | Rejects high recursion (>100) + large strings (>65KB) |
Output Format Detection and Selection#
Text Output (Default)#
Text output prints one line per file in the format filename: description:
$ rmagic image.png document.pdf
image.png: PNG image data
document.pdf: PDF document
When a file type cannot be determined, the description is data:
$ rmagic unknown.bin
unknown.bin: data
JSON Output Format#
The CLI automatically selects between two JSON formats based on the number of input files:
Single File: Pretty-Printed JSON#
Single file analysis produces pretty-printed JSON with a matches array:
$ rmagic --json image.png
{
"matches": [
{
"description": "PNG image data",
"offset": 0,
"tags": ["image", "png"],
"mime_type": "image/png",
"score": 90
}
]
}
Multiple Files: JSON Lines Format#
Multiple files produce JSON Lines format (newline-delimited JSON):
$ rmagic --json file1.bin file2.txt
{"filename":"file1.bin","matches":[...]}
{"filename":"file2.txt","matches":[...]}
$ rmagic --json *.bin | jq -r '.filename + ": " + .matches[0].mime_type'
Built-in Magic Rules#
The --use-builtin flag was introduced in PR #28 to provide pre-compiled magic rules embedded in the binary. The built-in rules cover common file types: ELF, PE/DOS, ZIP, TAR, GZIP, JPEG, PNG, GIF, BMP, and PDF. They are compiled at build time and require no external files, making the CLI portable and reliable when system magic files are unavailable.
# Use built-in rules (no external magic file required)
$ rmagic --use-builtin binary.exe
binary.exe: PE32 executable
Built-in rules provide a fallback mechanism when magic file discovery fails, ensuring the tool remains functional in restricted environments.
Stdin Support#
PR #27 introduced stdin support via the clap-stdin crate. Use - as the filename argument to read input from stdin:
$ cat sample.bin | rmagic -
stdin: data
$ echo "hello" | rmagic -
stdin: data
Stdin Behavior#
The process_file() function handles stdin specially:
- Detects stdin via the
is_stdin()method - Reads input with automatic truncation at
max_string_length(8192 bytes by default) - Warns to stderr if truncation occurs:
Warning: stdin input truncated to 8192 bytes - Uses
"stdin"as the filename in output
Combining Stdin with Files#
Stdin can be combined with regular file arguments:
$ rmagic --use-builtin file1.bin - file2.txt < input.dat
file1.bin: ZIP archive data
stdin: data
file2.txt: ASCII text
Timeout Configuration#
The --timeout-ms flag was introduced in PR #27 to prevent indefinite hangs on complex magic rule evaluations. The flag sets a per-file timeout for magic rule evaluation, with each file receiving its own independent timeout window.
# Set a 500ms timeout per file
$ rmagic --timeout-ms 500 large_file.bin
# Combine with strict mode to fail on timeout
$ rmagic --strict --timeout-ms 1000 *.bin
Valid values range from 1 to 300,000 milliseconds (5 minutes). If evaluation exceeds the specified duration, the file is skipped with an error and exit code 5 (timeout) in strict mode.
Strict Mode and Error Handling#
Non-Strict Mode (Default)#
Without --strict, processing errors for individual files are printed to stderr but do not affect the exit code. The tool continues processing remaining files and exits 0 if the overall invocation succeeded:
$ rmagic good.png /nonexistent bad_perms.bin
good.png: PNG image data
Error processing /nonexistent: ...
Error processing bad_perms.bin: ...
$ echo $?
0
Strict Mode#
With --strict, the first processing error (I/O, parse, or evaluation) causes a non-zero exit code. The tool still processes all files and prints errors as they occur, but returns the exit code corresponding to the first error:
$ rmagic --strict good.png /nonexistent bad_perms.bin
good.png: PNG image data
Error processing /nonexistent: ...
Error processing bad_perms.bin: ...
$ echo $?
3
A "data" result (unknown file type) is never treated as an error, even in strict mode.
Exit Codes#
The handle_error() function maps errors to Unix-style exit codes:
| Code | Meaning | Error Types |
|---|---|---|
| 0 | Success | Normal completion |
| 1 | General error | EvaluationError, ConfigError |
| 2 | Invalid arguments | IoError with InvalidInput kind |
| 3 | File not found/access denied | IoError with NotFound or PermissionDenied |
| 4 | Magic file error | ParseError |
| 5 | Evaluation timeout | Timeout error |
Multiple File Processing#
When multiple files are provided, each file is processed sequentially with independent error handling. A failure in one file does not prevent processing of subsequent files:
$ rmagic --use-builtin image.png archive.zip README.md
image.png: PNG image data
archive.zip: Zip archive data
README.md: data
Errors for individual files are printed to stderr with the filename for context:
$ rmagic --use-builtin good.png /nonexistent bad_perms.bin
good.png: PNG image data
Error processing /nonexistent: File not found
Error processing bad_perms.bin: Permission denied
Usage Examples#
Basic File Identification#
# Identify a single file
$ rmagic document.pdf
document.pdf: PDF document, version 1.4
# Identify multiple files
$ rmagic file1.bin file2.exe file3.pdf
file1.bin: data
file2.exe: PE32 executable (console) Intel 80386
file3.pdf: PDF document, version 1.7
# Use built-in rules
$ rmagic --use-builtin archive.tar.gz
archive.tar.gz: gzip compressed data
JSON Output for Scripting#
# Single file with pretty JSON
$ rmagic --json executable.elf
{
"matches": [
{
"description": "ELF 64-bit LSB executable",
...
}
]
}
# Multiple files with JSON Lines
$ rmagic --json file1.bin file2.bin file3.bin
{"filename":"file1.bin","matches":[...]}
{"filename":"file2.bin","matches":[...]}
{"filename":"file3.bin","matches":[...]}
# Parse JSON output with jq
$ rmagic --json binary.exe | jq '.matches[0].mime_type'
"application/x-dosexec"
# Extract MIME types from multiple files
$ rmagic --json *.bin | jq -r '.filename + ": " + .matches[0].mime_type'
Custom Magic Files#
# Use specific magic file
$ rmagic --magic-file /path/to/custom.magic firmware.img
firmware.img: Custom firmware image
# Use magic directory (Magdir style)
$ rmagic --magic-file /usr/share/file/magic files/*
Error Handling and Strict Mode#
# Default: continue on errors, exit 0
$ rmagic file1.bin /nonexistent file2.bin
file1.bin: data
Error: /nonexistent: No such file or directory
file2.bin: ELF 64-bit LSB executable
$ echo $?
0
# Strict mode: fail on first error
$ rmagic --strict file1.bin /nonexistent file2.bin
file1.bin: data
Error: /nonexistent: No such file or directory
file2.bin: ELF 64-bit LSB executable
$ echo $?
3
Timeout Protection#
# Set per-file timeout
$ rmagic --timeout-ms 5000 large-file.bin
# Combine with strict mode
$ rmagic --strict --timeout-ms 10000 *.bin
# Batch processing with timeout protection
$ find . -type f | xargs rmagic --timeout-ms 1000 --use-builtin
Pipeline and Batch Operations#
# Find all ELF files in a directory
$ find . -type f -exec rmagic --use-builtin {} + | grep ELF
# Process files and output JSON Lines
$ for f in *.bin; do
rmagic --json "$f" >> results.jsonl
done
# Use with xargs
$ find . -name "*.dat" -print0 | xargs -0 rmagic --use-builtin
# Filter by MIME type
$ rmagic --json *.bin | jq -r 'select(.matches[0].mime_type == "application/x-executable") | .filename'
Stdin Processing#
# Read from stdin
$ cat sample.bin | rmagic -
stdin: data
# Combine stdin with files
$ rmagic --use-builtin file1.bin - file2.txt < input.dat
file1.bin: ZIP archive data
stdin: data
file2.txt: ASCII text
# Pipeline stdin processing
$ curl -s https://example.com/file | rmagic --json -
Scripting Examples#
#!/bin/bash
# Check if file is an image
if rmagic --use-builtin "$1" | grep -q "image"; then
echo "File is an image"
exit 0
else
echo "File is not an image"
exit 1
fi
#!/bin/bash
# Batch convert images based on detected format
for file in *; do
format=$(rmagic --json "$file" | jq -r '.matches[0].mime_type')
case "$format" in
image/png)
convert "$file" "${file%.png}.jpg"
;;
image/jpeg)
echo "Already JPEG: $file"
;;
esac
done
Implementation Files#
The rmagic CLI is implemented in the following source files:
Design Decisions and Rationale#
1. Text-First Magic File Discovery#
rmagic prefers human-readable text magic files over compiled binary .mgc files for better debuggability and version control compatibility. This OpenBSD-inspired approach makes it easier for users to inspect, modify, and debug magic rules without specialized tools.
2. Automatic JSON Format Selection#
The CLI automatically selects between pretty-printed JSON (single file) and JSON Lines format (multiple files) based on input count. This eliminates user configuration burden while optimizing for common use cases: human readability for single-file inspection and machine parsing for batch processing.
3. Independent Timeout Per File#
Each file receives its own independent timeout window rather than a global timeout, allowing flexible processing of mixed file sets without penalizing fast evaluations for the actions of slow ones.
4. Graceful Degradation in Non-Strict Mode#
Processing errors do not halt execution or affect exit code unless --strict is specified, enabling robust batch processing of potentially problematic files. This is appropriate for exploratory use cases, while strict mode serves CI/CD and validation scenarios.
5. Built-in Rules Always Available#
Pre-compiled magic rules for common formats are embedded in the binary via the --use-builtin flag, eliminating external dependencies and ensuring predictable behavior. This makes rmagic portable and reliable in restricted environments where system magic files may be unavailable.
6. No Configuration File Support#
The CLI deliberately avoids configuration file support, keeping the interface simple and explicit. All configuration happens via flags, making invocations self-documenting and reproducible.
7. Clap Derive for Declarative Argument Parsing#
The use of clap derive macros provides compile-time validation of CLI argument definitions, reducing runtime errors and making the CLI interface more maintainable. Mutual exclusivity is enforced declaratively via conflicts_with attributes.
8. Actionable Error Messages#
When magic file discovery fails, the CLI displays actionable error messages with suggested options and example commands, following best practices for user experience. This reduces friction for new users and makes troubleshooting straightforward.
Related Topics#
- Magic Rule Parsing – The parser implementation that loads and interprets magic file formats
- Evaluation Engine – The core engine that applies magic rules to file buffers
- Library API – The Rust library API for embedding libmagic-rs in applications
- Built-in Rules Generation – Build-time compilation of magic rules into the binary
- Output Formatting – Implementation of text and JSON output formats