Skip to content

Commit 773a2da

Browse files
jgarzikclaude
andcommitted
rt: open files non-inheritable on Windows; fix libc_sig's doc comment
The intermittent Windows CI failure -- "Permission denied" from inside the runtime's unit tests, first seen at 4ad0166 -- is now named, by the panic its fatal error makes in a test: a_field_window_outlives_its_file closing a file and opening it again. The C library's handles are inheritable by default on Windows, and the unit tests that expect a fatal error run in child processes, beside the file tests. A child started while a file was open for writing kept a write handle of its own to it, so reopening the file -- which refuses other writers (_SH_DENYWR, as the assembly runtime's CreateFileA did) -- failed for as long as the child lived. sys::open now adds MSVC's N mode flag, so the handle stays in the process that opened it. The same would have bitten a program the compiler runs, had the compiler started a child. an_open_file_is_not_inherited_by_a_child reproduces it: under Wine it failed with Permission denied before, and passes now. Also: LIBC_FUNCTIONS had taken libc_sig's doc comment above its own; each has its own again (review feedback). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 3ea783a commit 773a2da

3 files changed

Lines changed: 45 additions & 2 deletions

File tree

‎rt/src/file.rs‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,31 @@ mod tests {
13431343
);
13441344
}
13451345

1346+
/// A file the program has open is not handed to a child process the
1347+
/// compiler or a test starts meanwhile. On Windows the C library's
1348+
/// handles were inheritable, so a child started while a file was open
1349+
/// for writing kept its own write handle to it; the file, closed and
1350+
/// opened again (which refuses other writers), was then "Permission
1351+
/// denied" for as long as the child lived. CI hit it when the unit tests
1352+
/// that run in child processes ran beside a file test.
1353+
#[cfg(windows)]
1354+
#[test]
1355+
fn an_open_file_is_not_inherited_by_a_child() {
1356+
let _g = serial();
1357+
let (_dir, path) = temp("inherit.txt");
1358+
open(&path, OUTPUT, 9);
1359+
put(9, "x");
1360+
// A child that outlives the close, started while the file is open.
1361+
let mut child = std::process::Command::new("cmd")
1362+
.args(["/c", "ping -n 3 127.0.0.1 >nul"])
1363+
.spawn()
1364+
.unwrap();
1365+
unsafe { _rt_file_close(9) };
1366+
open(&path, OUTPUT, 9);
1367+
unsafe { _rt_file_close(9) };
1368+
let _ = child.wait();
1369+
}
1370+
13461371
/// A FIELD variable is a window onto its file's record buffer, and
13471372
/// outlives the OPEN that made it: a re-OPEN of the number, sequential or
13481373
/// RANDOM, freed the buffer, and LSET then wrote freed memory and PRINT

‎rt/src/sys.rs‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,11 +550,29 @@ mod windows {
550550
/// assembly runtime's CreateFileA did with FILE_SHARE_READ: a second
551551
/// writer is refused, which is "Permission denied".
552552
///
553+
/// And not inherited (`N`): the C library's handles are inheritable by
554+
/// default, so a child process started while the file was open -- by a
555+
/// test, or by the compiler around a program it runs -- kept a write
556+
/// handle of its own, and the program's next OPEN of the file, refusing
557+
/// other writers, was "Permission denied" while the child lived.
558+
///
553559
/// # Safety
554560
/// Both must be NUL-terminated.
555561
pub unsafe fn open(name: *const c_char, mode: *const c_char) -> *mut FILE {
556562
const _SH_DENYWR: c_int = 0x20;
557-
unsafe { _fsopen(name, mode, _SH_DENYWR) }
563+
// The mode, then N. Modes are a few letters ("r+b" at most).
564+
let mut with_n = [0u8; 8];
565+
let mut n = 0;
566+
while n < with_n.len() - 2 {
567+
let c = unsafe { *mode.add(n) } as u8;
568+
if c == 0 {
569+
break;
570+
}
571+
with_n[n] = c;
572+
n += 1;
573+
}
574+
with_n[n] = b'N';
575+
unsafe { _fsopen(name, with_n.as_ptr().cast(), _SH_DENYWR) }
558576
}
559577

560578
/// Why the open just made failed. errno alone cannot say: the CRT maps

‎src/codegen/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,13 +538,13 @@ fn rt_type(t: rtsig::Ty) -> Type {
538538
}
539539
}
540540

541-
/// The C library functions generated code calls, with their signatures.
542541
/// The C library functions generated code calls, all of them.
543542
pub(crate) const LIBC_FUNCTIONS: [&str; 12] = [
544543
"pow", "sin", "cos", "tan", "atan", "exp", "log", "calloc", "realloc", "free", "memset",
545544
"memcpy",
546545
];
547546

547+
/// The C library functions generated code calls, with their signatures.
548548
fn libc_sig(name: &str) -> (&'static [Type], &'static [Type]) {
549549
debug_assert!(LIBC_FUNCTIONS.contains(&name), "{name} is not listed");
550550
const F: &[Type] = &[types::F64];

0 commit comments

Comments
 (0)