Implement rename - #632
Open
mmclinton wants to merge 2 commits into
Open
Conversation
added 2 commits
August 19, 2026 21:46
rename replaces a substring in filenames. The option surface follows rename(1): -v, -s, -n, -a, -l, -o and -i, with all three operands required. Behavior throughout is what util-linux 2.42.2 and 2.40.4 were both observed doing. The parts a reader should not have to reverse engineer: - The exit status is two counters rather than bit flags. 0 means something was renamed, 1 that everything failed, 2 a mix, and 4 that nothing matched. An operand that matched nothing, that -o skipped, or that was declined at an -i prompt counts as neither, so it can neither degrade a success nor promote a failure. - Only the final path component is rewritten, unless either operand holds a separator, which widens the scope to the whole path. Trailing separators are stripped from the operand, but the existence check still sees the operand exactly as it was typed. - An empty substring inserts at every code-unit boundary rather than matching nowhere, and a substring equal to its replacement exits 4 before any syscall runs. - The two overwrite guards ask different questions deliberately. The default path uses a check that follows symlinks, so a dangling destination is clobbered; -s lstats the new target name, so a dangling entry blocks it. - Filenames are carried as OsStr and the substitution engine is generic over the code unit: bytes on unix, UTF-16 units on Windows. A name that is not valid Unicode survives the report and the diagnostics without being replaced. Known divergences, each structural to clap and Rust rather than a defect in the port: the --help and --version layout, ANSI escapes on a terminal, the eager mutual-exclusion check, the rpmatch answer set, and SIGPIPE, which Rust ignores and the reference dies of.
Sixty-four tests: the exit-status tally, the three substitution modes, the path-scope rule, both overwrite guards, symlink mode, byte-oriented names, and the test_invalid_arg smoke test every other util carries. They defend the decisions a refactor would plausibly undo rather than restating what clap already guarantees. The two overwrite predicates and their opposite treatment of a dangling destination, the short circuit that runs before any syscall, and the order of stripping against the existence check each have a test whose only job is to fail if someone tidies them away. Diagnostics are asserted only as far as we write them. The errno text after our half belongs to libc, so the suite does not pin strerror strings on platforms it cannot run. The substitution engine keeps its unit tests beside it in subst.rs. `cargo test` from the workspace root does not run them, because the root package is the only default member; `cargo test -p uu_rename` does.
mmclinton
force-pushed
the
rename-implement
branch
from
August 20, 2026 18:02
44bba04 to
d4627bd
Compare
Contributor
Author
|
Fixed the initial macOS CI failure.
So on macOS the call succeeded, The fix was trivial: gated that test to Linux, utility unchanged. All checks now pass. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #629.
renamereplaces a substring in filenames. This is the whole of rename(1):-v,-s,-n,-a,-l,-o,-i, the three required operands, and the documented exit statuses. No option is half implemented.I built this against util-linux 2.42.2 and cross checked it against 2.40.4, comparing stdout, stderr, exit status and the resulting directory tree over roughly a thousand invocations.
Decisions I made inside the port
POSIXLY_CORRECTis honored by rewriting argv. With that variable set, option permutation stops, so everything after the first non option is a filename however much it looks like a flag. clap has no concept of this, soargv.rsinserts a--where getopt would have stopped scanning and hands the result to clap. It makes rename the only utility in this project that touches argv before clap sees it. Dropping it costs nothing except that rename then diverges here the way every other utility already does.Filenames are
OsStrend to end, and the substitution engine is generic over the code unit:u8on unix,u16on Windows. That is whatencoding.rsis for. A name that is not valid Unicode is exactly the kind of name people reach for rename to fix, so it has to survive the match, the rename, the-vline and the error message; carrying it through aStringwould replace it with U+FFFD.Diagnostics are written as bytes rather than through
show_error!, for the same reason.Displaywrites&str, so a message that carries a filename losslessly and aDisplayimpl are mutually exclusive.RenameErrortherefore has noDisplayand writes its ownrename:prefix.stdout is block buffered when it is not a terminal. C util-linux's stdout is line buffered on a tty and block buffered anywhere else, and Rust's is line buffered everywhere. That is not only a syscall count: under a write limit, line buffering stops our loop part way through a run C util-linux completes.
-iaccepts the C locale answer set. C util-linux takes the answers it accepts from the locale'sYESEXPR; we accept an answer whose first character isyorY. It is two sided: under a locale whoseYESEXPRis^[qQ]C util-linux acceptsqand rejectsy. UnderLC_ALL=Cthe two are identical. Matching it means declaring an rpmatch extern ourselves and callingsetlocale, which is process global state inside a utility that is supposed to stay embeddable.Exit status 64
rename(1) documents 64 as "unanticipated error occurred" and names nothing that produces it. This port cannot return it: exit status is set in four places and the reachable values are 0, 1, 2 and 4. I also never saw C util-linux return it, including from a deliberate hunt (memory exhaustion, a fifo, a dangling link,
/proc/self/mem), so there was no case to map onto it. I am not claiming C util-linux cannot produce a 64, only that I could not find what does. If someone knows, I will wire it up.What the tests do not cover
64 integration tests, plus 24 unit tests on the substitution engine. Three deficiencies worth naming:
cargo testfrom the workspace root, because the root package is the only default member.cargo test -p uu_renameruns them. This is not specific to rename;lscpuandlsmemhave the same blind spot.#[cfg(unix)], because uutests' symlink helpersunwrapand Windows needsSeCreateSymbolicLinkPrivilegeor Developer Mode, so on a runner without it the fixture would panic rather than skip.-sbehavior and the Windows arm ofencoding::symlinkare compiled but have never been executed anywhere.-iprompt decides how to read stdin from atcgetattrprobe at startup. Nothing exercises that probe, because it needs a pty and there is no test in this project that drives one.