UR-06: File::Open mode 1 creates the file it tests for
Mode 1 is the open-an-existing-file mode, but it carries O_CREAT. The rename probe therefore creates the destination file and reports the name as taken.
File::Open mode 1 is supposed to open a file that exists and fail when it's
absent. Today it carries O_CREAT, so it creates the file instead.
Build used for the measurements below:
b0319704f3072d6948a66026a35af5eb0af12b48d70986783c293e7c77e98483.
Function 771, File::Open(path, mode, err) -> handle.
Fix: low effort, medium blast radius — the change is one flag in the mode table, but every mode 1 caller changes behaviour. Code that depends on the current create-on-open breaks; that's the correct outcome, but the mode 1 callers need a check. We identified two: function 9753 (template read) and function 9757 (rename probe).
Mode 1 is the open-an-existing-file mode. Function 9753 uses it to read a template, and function 9757 uses it to ask whether a rename destination is already taken:
if (!allowOverwrite) {
handle = File::Open(path, 1, NULL);
if (handle) return false; // the name is already in use
}
handle = File::Open(path, 2, NULL); // create and writeWhat the client does now
Both modes reach openat with identical flags. We captured a save and then a
rename on a live client:
save openat flags:32834 access:2 create:true errno:0 -> fd_pwrite 24/24 -> fd_close
rename openat flags:32834 access:2 create:true errno:0 -> fd_closeThe value 32834 is O_RDWR | O_CREAT | O_LARGEFILE.
Mode 1 therefore creates the very file it's testing for — the probe then sees that the file exists and reports the name as taken.
What the player sees
- Every rename fails, for every destination name.
- The client shows "The attempt to rename the Skills Template named … failed".
- A file of zero length stays at the destination path.
- A load of an absent template creates an empty file instead of a failure.
Expected behaviour
Mode 1 opens an existing file, without O_CREAT, and fails when the file is
absent. Mode 2 keeps O_CREAT.
Proposed mode table
// File::Open(path, mode, err)
//
// mode 1 open existing O_RDONLY (or O_RDWR) no O_CREAT
// mode 2 create and write O_RDWR | O_CREAT | O_TRUNCWe don't know the full mode table. We report modes 1 and 2 only, because those are the two the template paths use.
O_RDWR today, switching to O_RDONLY also changes
behaviour on a read-only file. Either choice works with our acceptance
criteria — just decide which one you want.Acceptance criteria
File::Open(L"Templates/Skills/Absent.txt", 1, NULL)returns a null handle.- After that call,
Templates/Skills/Absent.txtdoes not exist. File::Openmode 2 on the same path creates the file, as it does today.- A rename to an unused name completes.
- A rename to a used name fails, and the existing file stays unchanged.
- A load of an absent template fails and creates no file.
How to reproduce
Prepare a template
Use a build in which the save path works. Save one template.
Rename it
Rename the template to any unused name. The rename fails.
Look at the directory
A file of zero length is now at the destination name.
Our workaround today
The host adds a forwarder for the probe call site only. It asks the host
whether the file exists and calls the real File::Open only when it does; the
write call site and the load path keep the real function. A corrected mode
table retires the forwarder.