# Cross-Platform Compatibility Assessment
## RustDupe Duplicate File Finder
**Document Version:** 1.0
**Date:** 2026-02-05
**Applies To:** RustDupe v0.2.0+
---
## Table of Contents
1. [Executive Summary](#executive-summary)
2. [Platform Support Matrix](#platform-support-matrix)
3. [Filesystem Compatibility Analysis](#filesystem-compatibility-analysis)
4. [Permission Model Analysis](#permission-model-analysis)
5. [Trash/Deletion Compatibility](#trashdeletion-compatibility)
6. [Terminal/TUI Compatibility](#terminaltui-compatibility)
7. [Build and Distribution](#build-and-distribution)
8. [Known Issues and Risks](#known-issues-and-risks)
9. [Testing Recommendations](#testing-recommendations)
10. [Recommendations](#recommendations)
---
## Executive Summary
RustDupe demonstrates **strong cross-platform maturity** with comprehensive support for Windows, macOS, and Linux. The project leverages well-maintained Rust ecosystem crates to abstract platform differences, resulting in consistent behavior across all target platforms.
### Current Maturity: 8/10
**Strengths:**
- ✅ Full CI/CD coverage for all three platforms (GitHub Actions matrix)
- ✅ Cross-platform trash/recycle bin support via `trash` crate
- ✅ Unicode path normalization (NFC/NFD) implemented for macOS compatibility
- ✅ Windows long path support (>260 chars) via embedded manifest
- ✅ Cross-platform TUI using `ratatui` + `crossterm`
- ✅ Parallel directory traversal with `jwalk` (rayon-backed)
**Key Issues:**
- ⚠️ **Windows hardlink detection not implemented** (`src/scanner/hardlink.rs`)
- ⚠️ **No platform-specific test isolation** in CI
- ⚠️ **Locked file handling** could be more robust on Windows
- ⚠️ **Limited elevated privilege detection** for protected directories
### Critical Gaps
| Windows hardlink detection | False duplicates reported | ❌ Not implemented |
| Windows junction point handling | May miss duplicates | ⚠️ Partial (follows as symlinks) |
| Extended attribute handling | Metadata loss on move | ⚠️ Not addressed |
| Network drive trash support | Deletion may fail | ⚠️ Platform dependent |
---
## Platform Support Matrix
| **Directory Scanning** | ✅ Full | ✅ Full | ✅ Full | jwalk parallel traversal (4x faster than walkdir) |
| **File Hashing (BLAKE3)** | ✅ Full | ✅ Full | ✅ Full | Streaming hash, 8.4-92 GB/s |
| **Trash/Recycle Bin** | ✅ Full | ✅ Full | ✅ Full | trash crate v5.x (IFileOperation/NSFileManager/Freedesktop) |
| **Permanent Delete** | ✅ Full | ✅ Full | ✅ Full | Standard fs::remove_file |
| **Unicode Path Support** | ✅ Full | ✅ Full | ✅ Full | NFC normalization via unicode-normalization |
| **Long Path Support** | ✅ Full* | ✅ Full | ✅ Full | *Requires Windows 10+ with registry setting |
| **Hidden File Filtering** | ✅ Full | ✅ Full | ✅ Full | Dot-prefix detection |
| **Symlink Following** | ✅ Full | ✅ Full | ✅ Full | Configurable via --follow-symlinks |
| **Hardlink Detection** | ❌ None | ✅ Full | ✅ Full | Windows stubbed (see section 3.4) |
| **TUI/Interactive Mode** | ✅ Full | ✅ Full | ✅ Full | ratatui + crossterm |
| **Progress Bars** | ✅ Full | ✅ Full | ✅ Full | indicatif with rayon integration |
| **Signal Handling** | ✅ Full | ✅ Full | ✅ Full | ctrlc crate (SIGINT/SIGTERM) |
| **Cache/Config Directory** | ✅ Full | ✅ Full | ✅ Full | directories crate (AppData/XDG) |
| **Gitignore Support** | ✅ Full | ✅ Full | ✅ Full | ignore crate (ripgrep's engine) |
| **Case-Sensitive Comparison** | ✅ Partial | ✅ Partial | ✅ Full | See section 3.1 |
| **Windows Junction Points** | ⚠️ As symlinks | N/A | N/A | No special handling |
| **Extended Attributes** | ❌ None | ❌ None | ❌ None | Not implemented |
| **Resource Forks (macOS)** | N/A | ❌ None | N/A | Not implemented |
### Legend
- ✅ **Full**: Fully implemented and tested
- ⚠️ **Partial**: Basic functionality works, edge cases may exist
- ❌ **None**: Not implemented or unsupported
- N/A: Not applicable to platform
---
## Filesystem Compatibility Analysis
### 3.1 Case Sensitivity Handling
RustDupe handles case sensitivity appropriately for duplicate detection but **does not normalize filenames for comparison**.
| **NTFS (Windows)** | Case-insensitive, case-preserving | Files compared by content hash only (case irrelevant) |
| **APFS (macOS)** | Case-insensitive, case-preserving | Same as Windows |
| **ext4 (Linux)** | Case-sensitive | Same files with different cases treated as distinct |
**Current Implementation:**
- Files are compared by **content hash (BLAKE3)** and **size**, not filename
- This makes case sensitivity a non-issue for duplicate detection
- However, "same name" duplicate detection would need case normalization
**Code Location:** `src/duplicates/finder.rs`
**Recommendation:** If implementing "same name" duplicate detection:
```rust
// Normalize for comparison on case-insensitive filesystems
#[cfg(any(target_os = "windows", target_os = "macos"))]
fn filenames_equal(a: &str, b: &str) -> bool {
a.eq_ignore_ascii_case(b)
}
```
### 3.2 Unicode Normalization (NFC/NFD)
**Status: ✅ FULLY IMPLEMENTED**
RustDupe properly handles Unicode normalization differences between platforms:
| Windows | Preserves input | All paths normalized to NFC |
| macOS (APFS) | Normalization-insensitive | All paths normalized to NFC |
| Linux | Byte-preserving | All paths normalized to NFC |
**Implementation Details:**
The `path_utils` module (`src/scanner/path_utils.rs`) provides NFC normalization:
```rust
use unicode_normalization::UnicodeNormalization;
pub fn normalize_path_str(s: &str) -> String {
s.nfc().collect()
}
pub fn paths_equal(a: &str, b: &str) -> bool {
normalize_path_str(a) == normalize_path_str(b)
}
```
**The "Café Problem" Solved:**
- NFC: `café.txt` = U+00E9 (single code point) = 2 bytes
- NFD: `café.txt` = U+0065 + U+0301 (e + combining accent) = 3 bytes
- After normalization: Both become NFC form, compare equal
**Test Coverage:** `src/scanner/path_utils.rs` lines 246-380 include comprehensive tests for:
- NFC unchanged
- NFD to NFC conversion
- Multiple combining characters (résumé)
- Korean Hangul normalization
### 3.3 Path Length Limits
| **Windows** | 260 chars (MAX_PATH) | 32,767 chars | ✅ Manifest + build.rs |
| **macOS** | 1,024 chars (PATH_MAX) | N/A | ✅ Native support |
| **Linux** | 4,096 chars (PATH_MAX) | Filesystem dependent | ✅ Native support |
| **Filename** | 255 bytes | N/A | ✅ Standard limit |
**Windows Long Path Support:**
RustDupe implements Windows long path support via:
1. **Application Manifest** (`rustdupe.manifest`):
```xml
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
```
2. **Build Script** (`build.rs`):
```rust
#[cfg(windows)]
{
embed_resource::compile("rustdupe.rc", embed_resource::NONE);
}
```
3. **Resource Script** (`rustdupe.rc`):
```c
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "rustdupe.manifest"
```
**Requirements for Users:**
- Windows 10 version 1607 (Anniversary Update) or later
- Registry setting: `HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled = 1`
- Or Group Policy: Computer Configuration > Administrative Templates > System > Filesystem > Enable Win32 long paths
**⚠️ Limitation:** The `\?\` prefix is not explicitly added to paths. If users encounter long path issues, they should enable the registry setting above.
### 3.4 Symbolic Links, Hardlinks, and Junctions
#### Symbolic Links
| Windows | ✅ Supported | ✅ Supported | Developer Mode or Admin (Vista+) |
| macOS | ✅ Supported | ✅ Supported | Standard permissions |
| Linux | ✅ Supported | ✅ Supported | Standard permissions |
**Implementation:** `src/scanner/walker.rs` lines 282-283
```rust
let walk_dir = WalkDir::new(&self.root)
.follow_links(self.config.follow_symlinks)
```
**Configuration:** CLI flag `--follow-symlinks` (default: false)
#### Hardlinks
| Unix (macOS/Linux) | ✅ Full | ✅ Implemented via (dev, inode) |
| Windows (NTFS) | ✅ Full | ❌ **NOT IMPLEMENTED** |
**Unix Implementation:** `src/scanner/hardlink.rs` lines 52-59
```rust
#[cfg(unix)]
fn from_metadata(metadata: &Metadata) -> Option<Self> {
use std::os::unix::fs::MetadataExt;
Some(Self {
dev: metadata.dev(),
ino: metadata.ino(),
})
}
```
**Windows Stub:** `src/scanner/hardlink.rs` lines 62-75
```rust
#[cfg(windows)]
fn from_metadata(_metadata: &Metadata) -> Option<Self> {
// Windows metadata doesn't expose file_index directly.
// Would need to open file handle and call GetFileInformationByHandle
None // Hardlinks treated as separate files
}
```
**Impact:** On Windows, hardlinked files will be hashed separately but produce the same hash. They will be reported as duplicates even though they share the same underlying data.
**Recommended Fix:**
```rust
#[cfg(windows)]
fn from_metadata(path: &Path) -> Option<Self> {
use std::os::windows::fs::OpenOptionsExt;
use winapi::um::fileapi::GetFileInformationByHandle;
let file = std::fs::OpenOptions::new()
.read(true)
.open(path)
.ok()?;
// Get file_index and volume_serial from handle
// ...
}
```
#### Junction Points (Windows)
**Status:** ⚠️ Treated as symlinks when following is enabled
Windows junction points are directory reparse points that work similarly to directory symlinks but don't require special privileges to create. RustDupe will follow them if `--follow-symlinks` is enabled.
**⚠️ Risk:** Junction cycles could cause infinite loops if not handled by jwalk's cycle detection.
### 3.5 Extended Attributes
| Creation Time | ✅ NTFS | ✅ HFS+/APFS | ⚠️ Filesystem dependent | ❌ Not used |
| Access Time | ✅ | ✅ | ✅ (may be disabled) | ❌ Not used |
| Extended Attributes (xattr) | NTFS streams | ✅ | ✅ | ❌ Not used |
| Resource Forks | N/A | ✅ (legacy) | N/A | ❌ Not used |
| ACLs | NTFS ACL | POSIX + NFSv4 | POSIX ACL | ❌ Not used |
**Design Decision:** RustDupe intentionally avoids extended attributes for duplicate detection to ensure cross-platform consistency. Detection relies on:
1. File size (fast filter)
2. Content hash (BLAKE3 - definitive)
3. Modification time (cache validation)
**Future Consideration:** Extended attributes could be used for advanced features like preserving macOS resource forks when moving files to trash.
---
## Permission Model Analysis
### 4.1 Windows ACL vs POSIX Permissions
| **Granularity** | Per-user/group entries (13+ rights) | Owner/Group/Other (3 modes) |
| **Inheritance** | Complex tree inheritance | umask at creation |
| **Rust Access** | Try to open file | Check permission bits |
**Current Implementation:**
RustDupe uses the standard Rust approach - attempt the operation and handle errors:
```rust
// From src/scanner/walker.rs
match std::fs::metadata(&path) {
Ok(m) => m,
Err(e) => {
match e.kind() {
ErrorKind::PermissionDenied => {
log::warn!("Permission denied: {}", path.display());
Err(ScanError::PermissionDenied(path.to_path_buf()))
}
// ...
}
}
}
```
This is the **recommended cross-platform approach** because:
- ACL evaluation is complex and platform-specific
- Actual access can depend on factors beyond simple permission bits
- Race conditions exist between check and use (TOCTOU)
### 4.2 Elevated Permissions Requirements
| Scan system folders | Admin/SYSTEM | root | root |
| Delete protected files | TrustedInstaller | root | root |
| Read all files | Admin | root | root |
| Access other users' files | Admin | root | root |
**Current Behavior:**
- RustDupe gracefully skips inaccessible files
- Permission errors are logged as warnings, not failures
- Batch operations continue on error (configurable)
**Code:** `src/scanner/walker.rs` lines 442-458
### 4.3 Locked File Handling
**Windows-Specific Challenge:**
Unlike POSIX systems, Windows doesn't allow deleting files that are open by another process. This causes issues with:
- Files open in applications
- System files locked by TrustedInstaller
- Anti-virus scanning locks
**Current Implementation:**
No special handling - the trash crate or fs::remove_file will return an error:
```rust
// From src/actions/delete.rs
pub fn delete_to_trash(path: &Path) -> Result<DeleteResult, DeleteError> {
// ...
trash::delete(path).map_err(|e| {
DeleteError::TrashFailed {
path: path.to_path_buf(),
message: e.to_string(),
}
})
}
```
**Recommended Enhancement:**
```rust
#[cfg(windows)]
fn can_delete_file(path: &Path) -> bool {
use std::fs::OpenOptions;
// Try to open with delete sharing
OpenOptions::new()
.read(true)
.share_mode(FILE_SHARE_DELETE)
.open(path)
.is_ok()
}
```
### 4.4 Permission Error Reporting
RustDupe provides clear error messages for permission issues:
```rust
#[derive(Debug, Error)]
pub enum DeleteError {
#[error("permission denied: {0}")]
PermissionDenied(PathBuf),
// ...
}
#[derive(thiserror::Error, Debug)]
pub enum ScanError {
#[error("Permission denied: {0}")]
PermissionDenied(PathBuf),
// ...
}
```
Users see clear messages like:
```
Warning: Permission denied: C:\Windows\System32\some_file.dll
```
---
## Trash/Deletion Compatibility
### 5.1 The `trash` Crate
RustDupe uses the `trash` crate v5.x for cross-platform trash/recycle bin support:
| **Windows** | IFileOperation API | Recycle bin, undo support |
| **macOS** | NSFileManager | Trash, metadata preservation |
| **Linux** | Freedesktop.org Trash Spec v1.0 | $XDG_DATA_HOME/Trash |
**Dependencies:** `Cargo.toml` line 50
```toml
trash = "5"
```
### 5.2 Platform-Specific Details
#### Windows: IFileOperation
Uses the modern Windows Shell API:
- FOF_ALLOWUNDO: Enable undo (recycle bin)
- FOFX_RECYCLEONDELETE: Force recycle
- FOF_NOCONFIRMATION: No UI prompts
- FOF_SILENT: No progress UI
#### macOS: NSFileManager
Uses Objective-C bindings via the `objc` crate:
```objc
[[NSFileManager defaultManager]
trashItemAtURL:fileURL
resultingItemURL:&resultURL
error:&error];
```
#### Linux: Freedesktop.org Trash Spec
Directory structure:
```
$XDG_DATA_HOME/Trash/ (~/.local/share/Trash/)
├── files/ # Actual file content
│ └── deleted_file.txt
└── info/ # Metadata
└── deleted_file.txt.trashinfo
```
.trashinfo format:
```ini
[Trash Info]
Path=/original/path/to/file.txt
DeletionDate=2026-02-05T10:30:00
```
### 5.3 Known Limitations
| Network drives | ⚠️ May fail | ✅ Usually works | ✅ Usually works | Trash folder created on drive |
| External drives | ✅ Supported | ✅ Supported | ✅ Supported | Uses $topdir/.Trash-$uid/ |
| Permission denied | Returns error | Returns error | Returns error | File not moved |
| Locked file | Returns error | May succeed | Usually succeeds | Platform-specific |
| Large batches | ✅ Supported | ✅ Supported | ✅ Supported | trash::delete_all() |
**⚠️ Linux Thread Safety Note:**
The trash crate has a known caveat on Linux/FreeBSD due to non-threadsafe libc calls when querying mount points. The crate mitigates this with an internal Mutex, but concurrent trash operations from multiple threads could be slower than expected.
### 5.4 Permanent Deletion
Always available as a fallback:
```rust
pub fn permanent_delete(path: &Path) -> Result<DeleteResult, DeleteError> {
fs::remove_file(path).map_err(|e| {
DeleteError::PermanentDeleteFailed {
path: path.to_path_buf(),
message: e.to_string(),
}
})
}
```
---
## Terminal/TUI Compatibility
### 6.1 crossterm Platform Support
RustDupe uses `crossterm` 0.28 as the terminal backend:
| **Raw Mode** | ✅ | ✅ | ✅ | Unbuffered input |
| **Alternate Screen** | ✅ | ✅ | ✅ | Clean exit |
| **Mouse Support** | ✅ | ✅ | ✅ | Optional |
| **Colors (16)** | ✅ | ✅ | ✅ | Standard |
| **Colors (256)** | ⚠️ | ✅ | ✅ | Terminal dependent |
| **True Color (24-bit)** | ⚠️ Windows Terminal | ✅ | ✅ | Terminal dependent |
| **Unicode** | ✅ | ✅ | ✅ | UTF-8 |
### 6.2 Known Terminal Compatibility Issues
| Windows Terminal | ✅ | Full support, recommended |
| ConHost (cmd.exe) | ⚠️ | Limited color support |
| PowerShell | ✅ | Good support |
| Windows 7/8 console | ❌ | No ANSI support |
| Terminal.app | ✅ | Full support |
| iTerm2 | ✅ | Full support, best experience |
| Alacritty | ✅ | Full support |
| GNOME Terminal | ✅ | Full support |
| Konsole | ✅ | Full support |
| xterm | ⚠️ | Limited color support |
| tmux | ✅ | Full support |
| screen | ⚠️ | May need terminfo update |
### 6.3 Color/Theme Support
The theme module (`src/tui/theme.rs`) provides consistent colors:
```rust
pub struct Theme {
pub primary: Color, // Cyan
pub secondary: Color, // Blue
pub success: Color, // Green
pub warning: Color, // Yellow
pub error: Color, // Red
pub highlight: Color, // Magenta
pub muted: Color, // Gray
}
```
**Fallback Behavior:**
- If terminal doesn't support colors: falls back to basic styling
- If terminal doesn't support Unicode: ASCII alternatives used
- If terminal is too small: error message displayed
### 6.4 Event Handling
Cross-platform keyboard handling:
```rust
// From src/tui/events.rs
use crossterm::event::{Event, KeyCode, KeyEvent};
match key.code {
KeyCode::Char('q') | KeyCode::Esc => Action::Quit,
KeyCode::Up | KeyCode::Char('k') => Action::NavigateUp,
KeyCode::Down | KeyCode::Char('j') => Action::NavigateDown,
// ...
}
```
**Platform Differences:**
- Ctrl+C handling: Interrupt on all platforms
- Arrow keys: Standard on all platforms
- Function keys: May vary by terminal
- Mouse: Supported where terminal allows
---
## Build and Distribution
### 7.1 CI/CD Setup
**GitHub Actions Workflows:**
1. **CI Workflow** (`.github/workflows/ci.yml`):
- Triggers: Push/PR to master
- Platforms: ubuntu-latest, macos-latest, windows-latest
- Steps: fmt check, clippy, tests
2. **Release Workflow** (`.github/workflows/release.yml`):
- Triggers: Tag push (v*), workflow_dispatch
- Build matrix:
| x86_64-unknown-linux-gnu | Ubuntu | x86_64 | rustdupe |
| x86_64-unknown-linux-musl | Ubuntu | x86_64 (static) | rustdupe |
| x86_64-pc-windows-msvc | Windows | x86_64 | rustdupe.exe |
| x86_64-apple-darwin | macOS | x86_64 | rustdupe |
| aarch64-apple-darwin | macOS | ARM64 (Apple Silicon) | rustdupe |
### 7.2 Binary Distribution
**Archive Formats:**
- Windows: `.zip` (via 7z)
- macOS/Linux: `.tar.gz` (via tar)
- Checksums: `.sha256` for all files
**Installation Methods:**
| Download from GitHub Releases | All | ✅ Available |
| Cargo install | All | ⚠️ Not published yet |
| Homebrew (macOS/Linux) | macOS, Linux | ❌ Not available |
| Chocolatey (Windows) | Windows | ❌ Not available |
| apt/yum/etc. | Linux | ❌ Not available |
### 7.3 Dependencies and Static Linking
| MSVC Runtime | Dynamic | N/A | N/A |
| libc | N/A | Dynamic | Dynamic (glibc) or Static (musl) |
| SQLite | Static (bundled) | Static (bundled) | Static (bundled) |
| OpenSSL | N/A | System | System |
**Key Configuration:**
```toml
# Cargo.toml
rusqlite = { version = "0.31", features = ["bundled"] }
```
The `bundled` feature ensures SQLite is statically linked, avoiding system dependency issues.
### 7.4 Minimum Supported Rust Version (MSRV)
- **Current MSRV:** 1.85
- **Specified in:** `Cargo.toml` line 5
- **Rationale:** Required for ratatui 0.28 compatibility
### 7.5 Code Signing
| Windows | ❌ Unsigned | Users see SmartScreen warning |
| macOS | ❌ Unsigned | Users must run `xattr -d com.apple.quarantine` |
| Linux | N/A | No signing standard |
**Recommendation:** Consider code signing for production releases.
---
## Known Issues and Risks
### 8.1 Issue Summary Table
| **Windows hardlink detection missing** | Windows | 🔴 High | Hardlinks hashed separately, reported as duplicates | ❌ Open |
| **Locked file deletion fails** | Windows | 🟡 Medium | Graceful error handling, user must close files | ⚠️ Partial |
| **No Windows junction point special handling** | Windows | 🟡 Medium | Treated as symlinks (usually fine) | ⚠️ Acceptable |
| **Limited elevated permission detection** | All | 🟢 Low | Graceful skip with warnings | ✅ Mitigated |
| **Network drive trash may fail** | All | 🟡 Medium | Falls back to permanent delete | ⚠️ Partial |
| **Unicode combining chars in terminal** | All | 🟢 Low | Path display may vary by terminal | ✅ Acceptable |
| **Case-sensitive filesystem edge cases** | Linux | 🟢 Low | Content-based detection unaffected | ✅ Acceptable |
| **External drive trash path issues** | Linux | 🟡 Medium | trash crate handles per Freedesktop spec | ⚠️ Monitor |
| **macOS quarantine attribute** | macOS | 🟡 Medium | Documented in release notes | ⚠️ Documented |
| **No code signing** | Windows, macOS | 🟡 Medium | SmartScreen/gatekeeper warnings | ⚠️ Known |
### 8.2 Detailed Risk Analysis
#### 🔴 High: Windows Hardlink Detection
**Risk:** Users may accidentally delete hardlinked files thinking they're duplicates.
**Impact:** Data loss (one path deleted, other paths still exist but point to freed inode).
**Mitigation:**
- Hardlinks detected by same hash (both produce identical BLAKE3)
- At least one copy preservation enforced
- Better handling would skip hardlinks entirely
**Fix Priority:** High
#### 🟡 Medium: Locked File Handling
**Risk:** Batch deletions may fail on Windows if files are open.
**Impact:** Incomplete deletion operations.
**Mitigation:**
- Batch operations continue on error (configurable)
- Clear error messages indicate locked files
- User can retry after closing applications
**Fix Priority:** Medium
#### 🟡 Medium: Code Signing
**Risk:** Users may be discouraged by security warnings.
**Impact:** Lower adoption, support requests.
**Mitigation:**
- Clear documentation in release notes
- SHA256 checksums for verification
- Open source (users can build from source)
**Fix Priority:** Low (cost/benefit)
---
## Testing Recommendations
### 9.1 Platform-Specific Test Cases Needed
| **Case sensitivity** | Verify case-insensitive comparison | Verify APFS behavior | Verify case-sensitive | Medium |
| **Unicode normalization** | Store both NFC/NFD | Verify APFS matches both | Test byte-level names | High |
| **Long paths** | Test >260 chars | Test >1024 chars | Test >4096 chars | High |
| **Symlink handling** | Create requires privileges | Standard test | Standard test | Medium |
| **Junction points** | Test traversal | N/A | N/A | Low |
| **Hardlinks** | ❌ **NEEDS IMPLEMENTATION** | Test inode detection | Test inode detection | 🔴 Critical |
| **Locked files** | Test open file deletion | Test open file behavior | Test (usually succeeds) | Medium |
| **Trash operations** | Test recycle bin | Test Trash | Test Freedesktop spec | High |
| **Network drives** | Test trash on SMB share | Test AFP/SMB | Test NFS | Medium |
| **External drives** | Test USB drive trash | Test USB drive trash | Test USB drive trash | Medium |
| **Permissions** | Test system folder access | Test root-owned files | Test root-owned files | Medium |
| **TUI terminal compat** | Test Windows Terminal, cmd | Test Terminal, iTerm2 | Test GNOME Terminal, Konsole | Low |
### 9.2 CI/CD Improvements
**Current Gaps:**
1. No platform-specific test isolation
2. No integration tests for trash operations
3. No tests for long paths
4. No Windows hardlink tests
**Recommended Additions:**
```yaml
# Add to .github/workflows/ci.yml
- name: Test trash operations (Unix)
if: runner.os != 'Windows'
run: cargo test --test trash_tests
- name: Test long paths (Windows)
if: runner.os == 'Windows'
run: cargo test --test longpath_tests
- name: Test hardlink detection (Unix)
if: runner.os != 'Windows'
run: cargo test hardlink
- name: Enable Windows long path support
if: runner.os == 'Windows'
run: |
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
-Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
```
### 9.3 Manual Testing Checklist
Before each release, test on real hardware:
- [ ] **Windows 10/11:** Scan user profile, delete to trash, verify in Recycle Bin
- [ ] **Windows:** Test with deeply nested node_modules (long paths)
- [ ] **macOS:** Scan /Users, delete to trash, verify in Trash
- [ ] **macOS:** Test with NFD filenames (copy from HFS+ drive)
- [ ] **Linux:** Scan home directory, delete to trash, verify ~/.local/share/Trash
- [ ] **Linux:** Test on ext4 and btrfs filesystems
- [ ] **All:** Test with external USB drive
- [ ] **All:** Test with network share (if available)
---
## Recommendations
### 10.1 Priority 1: Critical (Next Release)
#### 1. Implement Windows Hardlink Detection
**File:** `src/scanner/hardlink.rs`
**Implementation:**
```rust
#[cfg(windows)]
fn from_metadata(path: &Path) -> Option<Self> {
use std::os::windows::fs::OpenOptionsExt;
use std::os::windows::raw::HANDLE;
use winapi::um::fileapi::GetFileInformationByHandle;
use winapi::um::winnt::FILE_SHARE_READ;
let file = std::fs::OpenOptions::new()
.read(true)
.share_mode(FILE_SHARE_READ)
.open(path)
.ok()?;
let handle = file.as_raw_handle() as HANDLE;
let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
if unsafe { GetFileInformationByHandle(handle, &mut info) } != 0 {
Some(Self {
volume_serial: info.dwVolumeSerialNumber,
file_index: ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64),
})
} else {
None
}
}
```
**Dependencies:** Add `winapi` crate with `fileapi` feature
#### 2. Add Platform-Specific Test Suites
Create test files:
- `tests/platform/windows_tests.rs`
- `tests/platform/macos_tests.rs`
- `tests/platform/linux_tests.rs`
Use conditional compilation:
```rust
#[cfg(all(test, target_os = "windows"))]
mod windows_tests {
// Windows-specific tests
}
```
### 10.2 Priority 2: High (Following Release)
#### 3. Implement Locked File Detection on Windows
```rust
#[cfg(windows)]
pub fn can_delete_file(path: &Path) -> bool {
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
use winapi::um::winnt::FILE_SHARE_DELETE;
OpenOptions::new()
.read(true)
.share_mode(FILE_SHARE_DELETE)
.open(path)
.is_ok()
}
```
#### 4. Add Long Path Explicit Support
```rust
pub fn to_extended_path(path: &Path) -> PathBuf {
#[cfg(windows)]
{
let path_str = path.to_string_lossy();
if path.is_absolute() && !path_str.starts_with(r"\\?\") {
return PathBuf::from(format!(r"\\?\{}", path_str));
}
}
path.to_path_buf()
}
```
#### 5. Improve CI Matrix
Add to CI:
```yaml
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
name: linux-arm64
- target: x86_64-pc-windows-gnu
os: windows-latest
name: windows-x86_64-mingw
```
### 10.3 Priority 3: Medium (Backlog)
#### 6. Consider Code Signing
| Platform | Service | Cost |
|----------|---------|------|
| Windows | Azure Code Signing | ~$100/year |
| Windows | DigiCert OV | ~$200/year |
| macOS | Apple Developer | $99/year |
#### 7. Package Manager Distribution
- **Homebrew:** Create tap formula
- **Chocolatey:** Create package
- **Cargo:** Publish to crates.io
- **AUR:** Create PKGBUILD for Arch
#### 8. Extended Attribute Preservation
When moving files to trash on macOS, optionally preserve:
- Resource forks
- Extended attributes
- Custom icons
### 10.4 Priority 4: Low (Nice to Have)
#### 9. Junction Point Special Handling
Detect Windows junction points and:
- Report as "junction" rather than "symlink"
- Option to skip all junctions (safer default)
#### 10. Windows ACL Preservation
When deleting/restoring files, optionally preserve:
- NTFS ACLs
- Inheritance flags
---
## Appendix A: Related Files
| File | Purpose |
|------|---------|
| `src/scanner/hardlink.rs` | Hardlink detection (Windows stubbed) |
| `src/scanner/path_utils.rs` | Unicode normalization |
| `src/scanner/walker.rs` | Directory traversal |
| `src/actions/delete.rs` | Trash/permanent deletion |
| `src/tui/run.rs` | Terminal setup |
| `build.rs` | Windows manifest embedding |
| `rustdupe.manifest` | Windows long path declaration |
| `rustdupe.rc` | Windows resource script |
| `.github/workflows/ci.yml` | CI configuration |
| `.github/workflows/release.yml` | Release builds |
| `docs/research/cross-platform-file-management-2026-02-05.md` | Background research |
## Appendix B: Dependency Versions
| Crate | Version | Purpose |
|-------|---------|---------|
| trash | 5.x | Cross-platform trash |
| crossterm | 0.28 | Terminal control |
| ratatui | 0.28 | TUI framework |
| jwalk | 0.8 | Parallel directory walking |
| unicode-normalization | 0.1 | NFC normalization |
| embed-resource | 2.x | Windows manifest embedding |
| directories | 5.x | Config/cache directories |
| ctrlc | 3.4 | Signal handling |
---
## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | 2026-02-05 | Documentation Agent | Initial assessment |
---
*This document is part of the RustDupe strategic documentation suite.*