From 96eac913ce32af23709e34ca224e4044fba34c93 Mon Sep 17 00:00:00 2001 From: Toddr Bot Date: Tue, 21 Jul 2026 15:23:18 +0000 Subject: [PATCH] fix: add O_NONBLOCK, O_NOCTTY, O_CLOEXEC to SUPPORTED_SYSOPEN_MODES sysopen() with O_NONBLOCK fatally croaked because the flag was absent from the SUPPORTED_SYSOPEN_MODES whitelist. These flags are harmless for in-memory mocked files (accept and ignore). O_CLOEXEC is guarded with eval since it is not available on all platforms. Closes #412 Co-Authored-By: Claude Opus 4.6 --- lib/Test/MockFile.pm | 2 +- t/sysopen.t | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/Test/MockFile.pm b/lib/Test/MockFile.pm index 6c5d4cf..3b69df0 100644 --- a/lib/Test/MockFile.pm +++ b/lib/Test/MockFile.pm @@ -14,7 +14,7 @@ use warnings; # perl -MFcntl -E'eval "say q{$_: } . $_" foreach sort {eval "$a" <=> eval "$b"} qw/O_RDONLY O_WRONLY O_RDWR O_CREAT O_EXCL O_NOCTTY O_TRUNC O_APPEND O_NONBLOCK O_NDELAY O_EXLOCK O_SHLOCK O_DIRECTORY O_NOFOLLOW O_SYNC O_BINARY O_LARGEFILE/' use Fcntl; # O_RDONLY, etc. -use constant SUPPORTED_SYSOPEN_MODES => O_RDONLY | O_WRONLY | O_RDWR | O_APPEND | O_TRUNC | O_EXCL | O_CREAT | O_NOFOLLOW; +use constant SUPPORTED_SYSOPEN_MODES => O_RDONLY | O_WRONLY | O_RDWR | O_APPEND | O_TRUNC | O_EXCL | O_CREAT | O_NOFOLLOW | O_NONBLOCK | O_NOCTTY | ( eval { O_CLOEXEC() } // 0 ); use constant BROKEN_SYMLINK => bless {}, "A::BROKEN::SYMLINK"; use constant CIRCULAR_SYMLINK => bless {}, "A::CIRCULAR::SYMLINK"; diff --git a/t/sysopen.t b/t/sysopen.t index ee18228..ff0e61d 100644 --- a/t/sysopen.t +++ b/t/sysopen.t @@ -303,5 +303,45 @@ note "sysopen failure returns undef in list context (single-element list)"; ok( !defined $ret[0], 'sysopen failure element is undef (not "undef" string)' ); } +note "sysopen with O_NONBLOCK succeeds (flag accepted and ignored)"; +{ + my $mock = Test::MockFile->file('/nonblock_test'); + is( sysopen( my $fh, '/nonblock_test', O_WRONLY | O_CREAT | O_NONBLOCK, 0600 ), 1, + 'sysopen O_WRONLY|O_CREAT|O_NONBLOCK succeeds' ); + ok( -e '/nonblock_test', 'file created with O_NONBLOCK' ); + is( syswrite( $fh, "hello" ), 5, 'write to O_NONBLOCK-opened file works' ); + close $fh; + is( $mock->contents, "hello", 'contents correct after O_NONBLOCK open' ); +} + +note "sysopen with O_NOCTTY succeeds (flag accepted and ignored)"; +{ + my $mock = Test::MockFile->file('/noctty_test'); + is( sysopen( my $fh, '/noctty_test', O_WRONLY | O_CREAT | O_NOCTTY, 0600 ), 1, + 'sysopen O_WRONLY|O_CREAT|O_NOCTTY succeeds' ); + ok( -e '/noctty_test', 'file created with O_NOCTTY' ); + close $fh; +} + +note "sysopen with O_NONBLOCK|O_EXCL combined (issue #412 reproduction)"; +{ + my $mock = Test::MockFile->file('/nonblock_excl_test'); + is( sysopen( my $fh, '/nonblock_excl_test', O_WRONLY | O_CREAT | O_EXCL | O_NONBLOCK, 0600 ), 1, + 'sysopen O_WRONLY|O_CREAT|O_EXCL|O_NONBLOCK succeeds' ); + ok( -e '/nonblock_excl_test', 'file created with O_NONBLOCK|O_EXCL' ); + close $fh; +} + +SKIP: { + skip "O_CLOEXEC not available on this system", 2 unless eval { O_CLOEXEC(); 1 }; + + note "sysopen with O_CLOEXEC succeeds (flag accepted and ignored)"; + my $mock = Test::MockFile->file('/cloexec_test'); + is( sysopen( my $fh, '/cloexec_test', O_WRONLY | O_CREAT | O_CLOEXEC(), 0600 ), 1, + 'sysopen O_WRONLY|O_CREAT|O_CLOEXEC succeeds' ); + ok( -e '/cloexec_test', 'file created with O_CLOEXEC' ); + close $fh; +} + done_testing(); exit;