Found by Codex during the course of creating 100% test coverage infrastructure for my fork of SIMH. I've verified the bug is real.
Summary
sim_copyfile(const char *source_file, const char *dest_file, t_bool overwrite_existing) did not honor the overwrite_existing
parameter on the POSIX implementation in src/runtime/sim_fio.c.
On Windows, the corresponding implementation already passed the correct policy through to CopyFileA, so this was a POSIX-only behavior bug.
Broken behavior
Before the fix, the POSIX path always opened the destination with
"wb":
fOut = sim_fopen (dest_file, "wb");
That truncates and replaces an existing destination unconditionally. As a result, this call:
sim_copyfile(source, dest, FALSE)
would still overwrite dest if it already existed.
Why this is a real bug
- the function API explicitly carries an
overwrite_existing flag
- the Windows implementation already respects that flag
- the POSIX implementation silently ignored it
So the two host implementations disagreed on a user-visible behavior, and the POSIX version could destroy existing output files even when the caller explicitly requested that it not do so.
Fix
The POSIX implementation now uses exclusive-create open mode on modern BSD/Linux/macOS hosts:
fOut = sim_fopen (dest_file, overwrite_existing ? "wb" : "xb");
That makes the no-overwrite path atomic on those systems and aligns the behavior with the documented contract.
For any remaining non-Windows platforms that might not yet use exclusive create mode, the fallback is still to pre-check for an existing destination before opening it for write. In theory configuration before compilation could do this better, and I'll probably add such a thing eventually in my fork.
Minimal patch
+#if defined (__linux) || defined (__linux__) || defined (__APPLE__) || \
+ defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__)
+fOut = sim_fopen (dest_file, overwrite_existing ? "wb" : "xb");
+#else
+if (!overwrite_existing && (sim_stat (dest_file, &statb) == 0)) {
+ st = sim_messagef (SCPE_ARG, "Can't open '%s' for output: %s\n",
+ dest_file, strerror (EEXIST));
+ goto Cleanup_Return;
+ }
+fOut = sim_fopen (dest_file, "wb");
+#endif
Found by Codex during the course of creating 100% test coverage infrastructure for my fork of SIMH. I've verified the bug is real.
Summary
sim_copyfile(const char *source_file, const char *dest_file, t_bool overwrite_existing)did not honor theoverwrite_existingparameter on the POSIX implementation in
src/runtime/sim_fio.c.On Windows, the corresponding implementation already passed the correct policy through to
CopyFileA, so this was a POSIX-only behavior bug.Broken behavior
Before the fix, the POSIX path always opened the destination with
"wb":That truncates and replaces an existing destination unconditionally. As a result, this call:
would still overwrite
destif it already existed.Why this is a real bug
overwrite_existingflagSo the two host implementations disagreed on a user-visible behavior, and the POSIX version could destroy existing output files even when the caller explicitly requested that it not do so.
Fix
The POSIX implementation now uses exclusive-create open mode on modern BSD/Linux/macOS hosts:
That makes the no-overwrite path atomic on those systems and aligns the behavior with the documented contract.
For any remaining non-Windows platforms that might not yet use exclusive create mode, the fallback is still to pre-check for an existing destination before opening it for write. In theory configuration before compilation could do this better, and I'll probably add such a thing eventually in my fork.
Minimal patch