Skip to content

Case-only renames rejected on case-insensitive filesystems (macOS) #4

Description

@darkone-linux

Summary

On a case-insensitive filesystem (macOS/APFS and HFS+ by default, and Windows),
a rename that only changes the case of a name is rejected with
File name already exists. Since lowercasing is one of the core
transformations, this makes the tool largely unusable on those platforms for
names that differ from their slug by case alone.

$ ls
README.MD
$ rename-simple README.MD
[E] .: README.MD -> File name already exists
1 entry matched, 0 entry renamed, 1 error.

The rename is perfectly legal there — mv README.MD readme.md works — and it
cannot lose data: source and destination are the same inode, so the operation
is a pure rename.

Cause

filter_conflicts treats any existing destination as a conflict
(src/main.rs:328). On a case-insensitive filesystem readme.md resolves to
the very same entry as README.MD, so exists() answers true.

resolve_existing then classifies it via compare_entries, which reports
EntryMatch::SameEntry because the (dev, ino) pair matches
(src/lib.rs:742), and same-entry clashes are reported as a plain error
(src/main.rs:365). That branch is correct for hard links — deleting or
renaming there would be destructive — but a case-only rename of a single entry
is not the same situation.

Suggested fix

EntryMatch::SameEntry already carries the information needed: when the
destination is the source, no data can be lost. The rename can be attempted
instead of refused, letting the filesystem arbitrate.

The catch is rename_no_clobber (src/main.rs:386): on Linux it uses
renameat2(RENAME_NOREPLACE), which fails with EEXIST on a same-entry
rename, so the fallback path (try_exists + fs::rename) is the one that
would have to run for this case. Hard links must keep failing — a fix must
distinguish "the destination path resolves to the source" (safe, and what
happens on a case-insensitive filesystem) from "two distinct names for one
inode" (link1link2, must stay an error).

Why this is not fixed yet

CI runs on ubuntu-latest only (.github/workflows/ci.yml), and the bug
cannot be reproduced on a case-sensitive filesystem. Fixing it blind would mean
shipping an untested change to the only code path that is allowed to touch the
filesystem. Adding a macos-latest job to the CI matrix is a prerequisite.

Found during the code review that led to 0.7.1. Pre-existing; not a regression
from the -D work.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions