Skip to content

Windows file replacement goes through a delete, so an interrupted retry loses the file #791

Description

@xroche

On Windows hts_rename_over() (src/htstools.c) replaces a file by removing the target and renaming over the hole, because the CRT's rename() refuses an existing target. That window is not recoverable: if the second rename fails, the old file is already gone and nothing put the new one in its place. #779 narrowed the cases that reach it (only EEXIST, only with a source that exists), but the window itself is still there.

Win32 has had the right primitive since forever. MoveFileExW(old, new, MOVEFILE_REPLACE_EXISTING) replaces atomically, exactly as POSIX rename() does, and would let the fallback go away entirely: hts_rename_over() collapses back to a single call, and hts_rename_utf8() stops being the odd one out that fails where the POSIX build succeeds.

The catch is that hts_rename_utf8() backs the RENAME macro everywhere, so this changes rename semantics across the engine on Windows, and none of it can be exercised on the Linux or macOS runners. Worth doing, but as its own change with the Windows job watching it, not folded into a fix for something else.

Found while reviewing #779: an adversarial pass on the invariant "no call to this helper can destroy a file it did not successfully replace" turned up a source held by an antivirus or indexer as the way to reach the window in practice. #784 refuses that case (the CRT reports it as EACCES, not EEXIST, and only EEXIST is accepted), so it is covered in the shape we know about, not in general.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions