1850e2753Smillert#!./perl 2850e2753Smillert# 3850e2753Smillert# Test inheriting file descriptors across exec (close-on-exec). 4850e2753Smillert# 5850e2753Smillert# perlvar describes $^F aka $SYSTEM_FD_MAX as follows: 6850e2753Smillert# 7850e2753Smillert# The maximum system file descriptor, ordinarily 2. System file 8850e2753Smillert# descriptors are passed to exec()ed processes, while higher file 9850e2753Smillert# descriptors are not. Also, during an open(), system file descriptors 10850e2753Smillert# are preserved even if the open() fails. (Ordinary file descriptors 11850e2753Smillert# are closed before the open() is attempted.) The close-on-exec 12850e2753Smillert# status of a file descriptor will be decided according to the value of 13850e2753Smillert# C<$^F> when the corresponding file, pipe, or socket was opened, not 14850e2753Smillert# the time of the exec(). 15850e2753Smillert# 16850e2753Smillert# This documented close-on-exec behaviour is typically implemented in 17850e2753Smillert# various places (e.g. pp_sys.c) with code something like: 18850e2753Smillert# 19850e2753Smillert# #if defined(HAS_FCNTL) && defined(F_SETFD) 20850e2753Smillert# fcntl(fd, F_SETFD, fd > PL_maxsysfd); /* ensure close-on-exec */ 21850e2753Smillert# #endif 22850e2753Smillert# 23850e2753Smillert# This behaviour, therefore, is only currently implemented for platforms 24850e2753Smillert# where: 25850e2753Smillert# 26850e2753Smillert# a) HAS_FCNTL and F_SETFD are both defined 27850e2753Smillert# b) Integer fds are native OS handles 28850e2753Smillert# 29850e2753Smillert# ... which is typically just the Unix-like platforms. 30850e2753Smillert# 31850e2753Smillert# Notice that though integer fds are supported by the C runtime library 32850e2753Smillert# on Windows, they are not native OS handles, and so are not inherited 33850e2753Smillert# across an exec (though native Windows file handles are). 34850e2753Smillert 35850e2753SmillertBEGIN { 36850e2753Smillert chdir 't' if -d 't'; 37850e2753Smillert @INC = '../lib'; 38850e2753Smillert require './test.pl'; 39898184e3Ssthen skip_all_without_config('d_fcntl'); 40850e2753Smillert} 41850e2753Smillert 42850e2753Smillertuse strict; 43850e2753Smillert 44850e2753Smillert$|=1; 45850e2753Smillert 46850e2753Smillert# When in doubt, skip. 47898184e3Ssthenskip_all($^O) 48*b8851fccSafresh1 if $^O eq 'VMS' or $^O eq 'MSWin32' or $^O eq 'amigaos'; 49850e2753Smillert 50850e2753Smillertsub make_tmp_file { 51850e2753Smillert my ($fname, $fcontents) = @_; 52850e2753Smillert local *FHTMP; 53850e2753Smillert open FHTMP, ">$fname" or die "open '$fname': $!"; 54850e2753Smillert print FHTMP $fcontents or die "print '$fname': $!"; 55850e2753Smillert close FHTMP or die "close '$fname': $!"; 56850e2753Smillert} 57850e2753Smillert 58850e2753Smillertmy $Perl = which_perl(); 59898184e3Ssthenmy $quote = "'"; 60850e2753Smillert 6143003dfeSmillertmy $tmperr = tempfile(); 6243003dfeSmillertmy $tmpfile1 = tempfile(); 6343003dfeSmillertmy $tmpfile2 = tempfile(); 64850e2753Smillertmy $tmpfile1_contents = "tmpfile1 line 1\ntmpfile1 line 2\n"; 65850e2753Smillertmy $tmpfile2_contents = "tmpfile2 line 1\ntmpfile2 line 2\n"; 66850e2753Smillertmake_tmp_file($tmpfile1, $tmpfile1_contents); 67850e2753Smillertmake_tmp_file($tmpfile2, $tmpfile2_contents); 68850e2753Smillert 69850e2753Smillert# $Child_prog is the program run by the child that inherits the fd. 70850e2753Smillert# Note: avoid using ' or " in $Child_prog since it is run with -e 71850e2753Smillertmy $Child_prog = <<'CHILD_PROG'; 72850e2753Smillertmy $fd = shift; 73850e2753Smillertprint qq{childfd=$fd\n}; 74850e2753Smillertopen INHERIT, qq{<&=$fd} or die qq{open $fd: $!}; 75850e2753Smillertmy $line = <INHERIT>; 76850e2753Smillertclose INHERIT or die qq{close $fd: $!}; 77850e2753Smillertprint $line 78850e2753SmillertCHILD_PROG 79850e2753Smillert$Child_prog =~ tr/\n//d; 80850e2753Smillert 81850e2753Smillertplan(tests => 22); 82850e2753Smillert 83850e2753Smillertsub test_not_inherited { 84850e2753Smillert my $expected_fd = shift; 85850e2753Smillert ok( -f $tmpfile2, "tmpfile '$tmpfile2' exists" ); 86850e2753Smillert my $cmd = qq{$Perl -e $quote$Child_prog$quote $expected_fd}; 87850e2753Smillert # Expect 'Bad file descriptor' or similar to be written to STDERR. 88850e2753Smillert local *SAVERR; open SAVERR, ">&STDERR"; # save original STDERR 89850e2753Smillert open STDERR, ">$tmperr" or die "open '$tmperr': $!"; 90850e2753Smillert my $out = `$cmd`; 91850e2753Smillert my $rc = $? >> 8; 92850e2753Smillert open STDERR, ">&SAVERR" or die "error: restore STDERR: $!"; 93850e2753Smillert close SAVERR or die "error: close SAVERR: $!"; 94850e2753Smillert # XXX: it seems one cannot rely on a non-zero return code, 95850e2753Smillert # at least not on Tru64. 96850e2753Smillert # cmp_ok( $rc, '!=', 0, 97850e2753Smillert # "child return code=$rc (non-zero means cannot inherit fd=$expected_fd)" ); 98850e2753Smillert cmp_ok( $out =~ tr/\n//, '==', 1, 99850e2753Smillert "child stdout: has 1 newline (rc=$rc, should be non-zero)" ); 100850e2753Smillert is( $out, "childfd=$expected_fd\n", 'child stdout: fd' ); 101850e2753Smillert} 102850e2753Smillert 103850e2753Smillertsub test_inherited { 104850e2753Smillert my $expected_fd = shift; 105850e2753Smillert ok( -f $tmpfile1, "tmpfile '$tmpfile1' exists" ); 106850e2753Smillert my $cmd = qq{$Perl -e $quote$Child_prog$quote $expected_fd}; 107850e2753Smillert my $out = `$cmd`; 108850e2753Smillert my $rc = $? >> 8; 109850e2753Smillert cmp_ok( $rc, '==', 0, 110850e2753Smillert "child return code=$rc (zero means inherited fd=$expected_fd ok)" ); 111850e2753Smillert my @lines = split(/^/, $out); 112850e2753Smillert cmp_ok( $out =~ tr/\n//, '==', 2, 'child stdout: has 2 newlines' ); 113850e2753Smillert cmp_ok( scalar(@lines), '==', 2, 'child stdout: split into 2 lines' ); 114850e2753Smillert is( $lines[0], "childfd=$expected_fd\n", 'child stdout: fd' ); 115850e2753Smillert is( $lines[1], "tmpfile1 line 1\n", 'child stdout: line 1' ); 116850e2753Smillert} 117850e2753Smillert 118850e2753Smillert$^F == 2 or print STDERR "# warning: \$^F is $^F (not 2)\n"; 119850e2753Smillert 120850e2753Smillert# Should not be able to inherit > $^F in the default case. 121850e2753Smillertopen FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!"; 122850e2753Smillertmy $parentfd2 = fileno FHPARENT2; 123850e2753Smillertdefined $parentfd2 or die "fileno: $!"; 124850e2753Smillertcmp_ok( $parentfd2, '>', $^F, "parent open fd=$parentfd2 (\$^F=$^F)" ); 125850e2753Smillerttest_not_inherited($parentfd2); 126850e2753Smillertclose FHPARENT2 or die "close '$tmpfile2': $!"; 127850e2753Smillert 128850e2753Smillert# Should be able to inherit $^F after setting to $parentfd2 129850e2753Smillert# Need to set $^F before open because close-on-exec set at time of open. 130850e2753Smillert$^F = $parentfd2; 131850e2753Smillertopen FHPARENT1, "<$tmpfile1" or die "open '$tmpfile1': $!"; 132850e2753Smillertmy $parentfd1 = fileno FHPARENT1; 133850e2753Smillertdefined $parentfd1 or die "fileno: $!"; 134850e2753Smillertcmp_ok( $parentfd1, '<=', $^F, "parent open fd=$parentfd1 (\$^F=$^F)" ); 135850e2753Smillerttest_inherited($parentfd1); 136850e2753Smillertclose FHPARENT1 or die "close '$tmpfile1': $!"; 137850e2753Smillert 138850e2753Smillert# ... and test that you cannot inherit fd = $^F+n. 139850e2753Smillertopen FHPARENT1, "<$tmpfile1" or die "open '$tmpfile1': $!"; 140850e2753Smillertopen FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!"; 141850e2753Smillert$parentfd2 = fileno FHPARENT2; 142850e2753Smillertdefined $parentfd2 or die "fileno: $!"; 143850e2753Smillertcmp_ok( $parentfd2, '>', $^F, "parent open fd=$parentfd2 (\$^F=$^F)" ); 144850e2753Smillerttest_not_inherited($parentfd2); 145850e2753Smillertclose FHPARENT2 or die "close '$tmpfile2': $!"; 146850e2753Smillertclose FHPARENT1 or die "close '$tmpfile1': $!"; 147850e2753Smillert 148850e2753Smillert# ... and now you can inherit after incrementing. 149850e2753Smillert$^F = $parentfd2; 150850e2753Smillertopen FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!"; 151850e2753Smillertopen FHPARENT1, "<$tmpfile1" or die "open '$tmpfile1': $!"; 152850e2753Smillert$parentfd1 = fileno FHPARENT1; 153850e2753Smillertdefined $parentfd1 or die "fileno: $!"; 154850e2753Smillertcmp_ok( $parentfd1, '<=', $^F, "parent open fd=$parentfd1 (\$^F=$^F)" ); 155850e2753Smillerttest_inherited($parentfd1); 156850e2753Smillertclose FHPARENT1 or die "close '$tmpfile1': $!"; 157850e2753Smillertclose FHPARENT2 or die "close '$tmpfile2': $!"; 158