walkdir_minimal 1.3.0

Fast, minimalist, and POSIX-compliant recursive directory traverser with zero external dependencies.
Documentation
<p align="center">
  <img src="https://raw.githubusercontent.com/LinuxProativo/walkdir_minimal/refs/heads/master/logo.png" width="300"/>
</p>

<h1 align="center">WalkDir Minimal - A lightweight, POSIX-only directory walker</h1> 
<h3 align="center">A minimal, 100% safe Rust directory walker for POSIX systems that prioritizes determinism and zero dependencies.</h3>

<p align="center">
  <img src="https://img.shields.io/badge/Platform-POSIX-FCC624?&logo=linux&style=flat-square"/>
  <img src="https://img.shields.io/github/actions/workflow/status/LinuxProativo/walkdir_minimal/rust.yml?label=Test&style=flat-square&logo=github"/>
  <img src="https://img.shields.io/badge/RustC-1.85+-orange?style=flat-square&logo=rust"/>
  <img src="https://img.shields.io/github/languages/code-size/LinuxProativo/walkdir_minimal?style=flat-square&logo=rust&label=Code Size"/>
</p>

## ๐Ÿ” Overview

`walkdir_minimal` is a **lightweight, POSIX-only directory walker** written in
**100% safe Rust**, designed for **maximum portability**, **robust error handling**,
and **predictable iteration order** across UNIX-like systems.

Unlike the popular [`walkdir`](https://crates.io/crates/walkdir) crate, which
offers extensive configurability and Windows support, `walkdir_minimal` aims to
provide a **clean, dependency-free** and **fully deterministic** implementation
that follows the UNIX filesystem model precisely โ€” no abstractions, no hidden
buffering, no non-POSIX extensions.

## โœจ Key Features

* ๐Ÿงฑ **POSIX-only**: Works on Linux, FreeBSD, OpenBSD, NetBSD, and Solaris.
* โš™๏ธ **No dependencies**: Implemented using only `std::fs`, `std::path`, and
  minimal data structures.
* ๐Ÿฆฆ **Lightweight and predictable**: The walker uses a manual stack (no recursion),
  allowing predictable memory and performance behavior.
* ๐Ÿฆ‰ **Configurable options** via `WalkOptions`:
    * `follow_links`: whether to follow symbolic links to directories.
    * `max_depth`: optional limit on traversal depth.
    * `ignore_errors`: silently skip all I/O errors and detected loops.
    * `ignore_permission_denied`: silently skip `EACCES` errors without suppressing other I/O errors.
    * `detect_loops`: detect and prevent infinite loops caused by symbolic link cycles.

* ๐Ÿง  **Cycle detection**: Detects and prevents infinite loops caused by symbolic
  links that form cycles.
* ๐Ÿšซ **Graceful handling of I/O errors**: Broken symlinks, permission-denied
  directories, and other errors are returned as `Err(WalkError::Io)`.
* ๐Ÿฆฆ **Filtering**: Supports entry-level filtering with a user-provided closure.
* ๐Ÿงซ **Deterministic**: The order of traversal follows the order provided by the
  filesystem's `readdir(3)` implementation โ€” consistent across runs on the same system.
* ๐Ÿงช **Minimal yet robust**: Designed for projects that require reliable,
  low-level control rather than high-level abstraction.


## ๐Ÿชถ Design Philosophy

`walkdir_minimal` is built under the following principles:

1. **POSIX compliance first** โ€” all filesystem operations map directly to their POSIX
   equivalents (`lstat`, `stat`, `opendir`, `readdir`, etc., via Rust's `std::fs`).
2. **Deterministic behavior** โ€” the iterator never hides errors, skips entries
   silently, or spawns threads.
3. **No allocations beyond what's necessary** โ€” uses `Vec` for the manual stack
   and `HashSet` for visited inode/device pairs (loop detection).
4. **No recursion** โ€” prevents stack overflows and maintains stable memory usage
   even for deeply nested trees.
5. **Minimalism** โ€” the crate is intentionally limited to features that can be
   reasoned about and verified easily.
6. **Transparency** โ€” the API surfaces raw I/O results instead of silently
   ignoring or swallowing them.

`walkdir_minimal` embodies **clarity over complexity**. Its goal is not to compete
with feature-rich crates, but to provide a **clean reference implementation**
of a POSIX-only directory walker.

## โš–๏ธ Comparison with `walkdir`

| Feature                    | `walkdir`                      | `walkdir_minimal`                     |
|----------------------------|--------------------------------|---------------------------------------|
| Cross-platform             | โœ… (Windows, macOS, Linux)      | โŒ POSIX only                          |
| Dependencies               | Many (e.g., same-file, winapi) | โŒ None                                |
| Error handling             | Complex iterator states        | Simple `Result<Entry, WalkError>`     |
| Loop detection             | Optional, platform-specific    | Deterministic `(dev, ino)` hashing    |
| Symbolic links             | Optional follow                | Optional follow                       |
| Custom sorting             | Supported                      | Not supported (filesystem order only) |
| Performance                | Optimized for general use      | Optimized for predictability          |
| Safety                     | 100% safe Rust                 | 100% safe Rust                        |
| Recursion                  | Implicit                       | Manual stack                          |
| Binary size                | Larger                         | Tiny                                  |
| Filter API                 | Supported (`filter_entry`)     | Supported                             |
| Error type                 | `walkdir::Error`               | `WalkError`                           |
| Metadata caching           | Yes                            | No (on-demand)                        |
| Thread safety              | Yes                            | No (intentionally minimal)            |
| `ignore_permission_denied` | No                             | Yes                                   |

## ๐Ÿฆ‰ Example Usage

```rust
use walkdir_minimal::{WalkDir, WalkError};

fn main() -> Result<(), WalkError> {
    for entry in WalkDir::new(".")?.follow_links(false) {
        match entry {
            Ok(e) => println!("{}", e.path().display()),
            Err(err) => eprintln!("Error: {}", err),
        }
    }
    Ok(())
}
```

Output example:

```
.
./src
./src/lib.rs
./src/entry.rs
./src/error.rs
./src/walkdir.rs
```

## ๐Ÿ“ฆ WalkOptions

```rust
#[derive(Clone, Debug)]
pub struct WalkOptions {
    /// Follow symbolic links to directories during traversal.
    pub follow_links: bool,
    /// Maximum number of directory levels to descend (root is depth 0).
    pub max_depth: usize,
    /// Silently skip all I/O errors and detected symlink loops.
    pub ignore_errors: bool,
    /// Silently skip EACCES errors without suppressing other I/O errors.
    pub ignore_permission_denied: bool,
    /// Detect and prevent infinite loops caused by symbolic link cycles.
    pub detect_loops: bool,
}
```

* **`follow_links`** โ€” When `true`, symbolic links to directories are followed.
* **`max_depth`** โ€” Limits recursion depth. The root is always depth `0`; direct
  children are depth `1`, and so on. Defaults to `512`.
* **`ignore_errors`** โ€” When `true`, all I/O errors and detected symlink loops are
  silently skipped instead of being surfaced as `Err` items.
* **`ignore_permission_denied`** โ€” When `true`, only `EACCES` errors are silently
  skipped; all other I/O errors are still surfaced normally. Takes no effect when
  `ignore_errors` is already `true`.
* **`detect_loops`** โ€” When `true`, tracks visited `(dev, ino)` pairs to prevent
  infinite recursion through cyclic symlinks. Enabled by default.

## ๐Ÿ”— Entry API

```rust
pub struct Entry {
    path: PathBuf,
    depth: usize,
}

impl Entry {
    pub fn path(&self) -> &Path;
    pub fn depth(&self) -> usize;
    pub fn metadata(&self) -> io::Result<fs::Metadata>;
    pub fn symlink_metadata(&self) -> io::Result<fs::Metadata>;
    pub fn file_type(&self) -> io::Result<fs::FileType>;
}
```

* `metadata()` calls `fs::metadata`, following symlinks.
* `symlink_metadata()` calls `fs::symlink_metadata`, **not** following symlinks.
* `file_type()` reports the symbolic link type correctly.

## ๐Ÿฆ‰ Error Handling

```rust
pub enum WalkError {
    Io(io::Error, PathBuf),
    LoopDetected(PathBuf),
}
```

* **`Io(io::Error, PathBuf)`** โ€” Covers all I/O-related errors, carrying the
  offending path alongside the error for actionable debug context. Includes:

    * Broken symbolic links (`ENOENT`)
    * Permission-denied directories (`EACCES`)
    * Filesystem read errors

* **`LoopDetected(PathBuf)`** โ€” Reported when a cyclic symbolic link is
  detected (only if loop detection is enabled).

## โš™๏ธ Default Behavior Summary

| Case                            | Behavior                                                      |
|---------------------------------|---------------------------------------------------------------|
| **Broken symlink**              | Yields `Err(WalkError::Io)`                                   |
| **Permission denied directory** | Yields the entry, then `Err(WalkError::Io)` on the next step |
| **Loop via symlink**            | Yields `Err(WalkError::LoopDetected)` if detection is on      |
| **Regular file as root**        | Returns file directly, no traversal                           |
| **Unreadable entry**            | Returns `Err(WalkError::Io)`                                  |
| **Exceeds `max_depth`**         | Skips entry silently (depth-guarded)                          |
| **`ignore_permission_denied`**  | Skips `EACCES` silently; other errors still surface           |

## ๐Ÿ” Technical Details

### ๐Ÿ“Œ Core Design

`walkdir_minimal` implements a depth-first directory traversal without relying on
any external dependencies, using only POSIX APIs available through Rust's standard
library. The iterator is built around a manual stack-based traversal that mimics
recursion, avoiding stack overflows for deeply nested directories.

* **Stack-based iteration:** Uses an internal vector of `StackEntry` structs,
  each holding an active `ReadDir` handle and its depth.
* **Loop detection:** Uses a `HashSet<(dev, ino)>` to detect and skip cyclic
  symlinks, preventing infinite recursion.
* **Filter callbacks:** Optional user-provided closures (`filter_entry`) allow
  pruning of the traversal tree dynamically.
* **Error resilience:** Each I/O operation is wrapped in `Result`, and errors
  are surfaced as `WalkError` variants (`Io`, `LoopDetected`).

### ๐Ÿ“Œ Error Handling Philosophy

`walkdir_minimal` follows a **fail-soft** philosophy:

* Broken symlinks are returned as `Ok(Entry)` unless metadata is explicitly requested.
* Directories without permission to read (`EACCES`) yield the directory entry first,
  then surface an `Err(WalkError::Io)` on the following iteration โ€” so the caller
  always sees the entry before its associated error.
* Files disappearing mid-iteration yield `Err(WalkError::Io)` gracefully.
* When `ignore_permission_denied` is set, `EACCES` errors are silently dropped
  without affecting other error types โ€” unlike `ignore_errors`, which suppresses
  everything including symlink loops.

This mirrors `walkdir`'s behavior but keeps it predictable and minimal.

### ๐Ÿ“Œ Metadata Access

`Entry` deliberately does **not** cache metadata by default. This ensures:

* Minimal memory overhead.
* Consistent behavior with file system changes.
* Full control for users who may wish to query `metadata()` or
  `symlink_metadata()` selectively.

```rust
let entry = Entry::new(path, depth);
if let Ok(meta) = entry.metadata() {
    println!("File size: {} bytes", meta.len());
}
```

### ๐Ÿ“Œ Platform Scope

`walkdir_minimal` targets **POSIX systems only** โ€” this includes:

* GNU/Linux
* *BSD family (FreeBSD, OpenBSD, NetBSD, DragonFly)
* Solaris and Illumos

It relies on `MetadataExt` for device/inode access, which is non-portable
to Windows. No attempt is made to support non-POSIX environments.

### ๐Ÿ“Œ Performance Characteristics

* Single `ReadDir` handle open at a time per stack frame.
* Minimal heap allocations aside from the stack and visited set.
* No synchronization primitives โ€” designed for **single-threaded deterministic traversal**.
* Filtering and loop detection incur negligible overhead for typical file trees.

### ๐Ÿ“Œ Safety & Reliability

* No unsafe code.
* Uses standard library types exclusively (`HashSet`, `Vec`, `ReadDir`, etc.).
* All system calls are wrapped in safe Rust abstractions.
* Loop detection ensures full reliability even on pathological file systems.

## ๐Ÿ“š Practical Use Cases

* Static analysis tools.
* POSIX-friendly installers and archivers.
* File packers and dependency scanners.
* System recovery tools that must run without external crates.

Example: skipping hidden files and following symlinks safely:

```rust
use walkdir_minimal::WalkDir;

let iter = WalkDir::new("/usr")
    .unwrap()
    .follow_links(true)
    .filter_entry(|e| !e.path().file_name().map(|n| n.to_string_lossy().starts_with('.')).unwrap_or(false));

for entry in iter {
    match entry {
        Ok(e) => println!("{}", e.path().display()),
        Err(err) => eprintln!("Error: {}", err),
    }
}
```

Example: walking a directory tree while silently skipping inaccessible paths:

```rust
use walkdir_minimal::WalkDir;

for entry in WalkDir::new("/etc").unwrap().ignore_permission_denied(true) {
    match entry {
        Ok(e) => println!("{}", e.path().display()),
        Err(err) => eprintln!("Error: {}", err),
    }
}
```

## ๐Ÿ’ก Why Choose `walkdir_minimal`?

* Ideal for **small binaries**, **system utilities**, and **initramfs tools**.
* Zero build dependencies (fast compile times).
* Deterministic and predictable traversal order.
* Designed to be readable and hackable.

## ๐Ÿงฉ Implementation Notes

* Loop detection uses `(dev, ino)` pairs to identify unique directories.
* When `follow_links` is disabled, symlink loops are naturally impossible.
* `max_depth` limits traversal, excluding deeper entries.
* The iterator yields entries as soon as they are discovered โ€” no preloading
  or buffering.
* A directory entry is always yielded before any error associated with opening
  it โ€” this ensures callers can distinguish between "entry exists but is inaccessible"
  and a hard filesystem failure.

## ๐Ÿค Contributing

Contributions are very welcome! Whether it's fixing a bug, improving
documentation, or adding new features that align with the minimalist and
POSIX-only philosophy, your input is appreciated.

Please follow these guidelines when contributing:

1. **Keep it minimal** โ€” avoid adding dependencies or non-POSIX abstractions.
2. **Preserve safety** โ€” no `unsafe` code will be accepted.
3. **Document behavior clearly** โ€” especially for error cases and edge conditions.
4. **Add tests** โ€” every feature or bug fix should include a minimal test.

If you find a bug or have a suggestion for improvement:

* Open an issue describing the behavior or proposal clearly.
* Include steps to reproduce (for bugs) or examples (for feature requests).

Pull requests should target the `main` branch and include clear commit messages.

## ๐Ÿงช Testing

`walkdir_minimal` includes tests for common scenarios:

* Regular files and nested directories.
* Symbolic link loops and detection.
* Permission-denied directories.
* Broken symbolic links.
* Filtered traversals and depth limits.

Run the test suite with:

```bash
cargo test
```

## ๐Ÿงฑ Project Structure

```
walkdir_minimal/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ lib.rs           # Main crate entry
โ”‚   โ”œโ”€โ”€ entry.rs         # Defines the Entry type
โ”‚   โ”œโ”€โ”€ error.rs         # WalkError and error utilities
โ”‚   โ”œโ”€โ”€ options.rs       # WalkOptions definition
โ”‚   โ”œโ”€โ”€ tests.rs         # Unit and integration tests
โ”‚   โ””โ”€โ”€ walkdir.rs       # Core iterator implementation
โ”œโ”€โ”€ README.md            # Project documentation
โ”œโ”€โ”€ LICENSE              # License file (MIT)
โ””โ”€โ”€ Cargo.toml           # Package metadata
```

## ๐Ÿ“œ MIT License

This repository has scripts created to be free software.
Therefore, they can be distributed and/or modified within the terms of the ***MIT License***.

> ### See the [LICENSE]LICENSE file for details.

## ๐Ÿ“ฌ Contact & Support

* ๐Ÿ“ง **Email:** [m10ferrari1200@gmail.com]mailto:m10ferrari1200@gmail.com
* ๐Ÿ“ง **Email:** [contatolinuxdicaspro@gmail.com]mailto:contatolinuxdicaspro@gmail.com