3 # Test inheriting file descriptors across exec (close-on-exec).
5 # perlvar describes $^F aka $SYSTEM_FD_MAX as follows:
7 # The maximum system file descriptor, ordinarily 2. System file
8 # descriptors are passed to exec()ed processes, while higher file
9 # descriptors are not. Also, during an open(), system file descriptors
10 # are preserved even if the open() fails. (Ordinary file descriptors
11 # are closed before the open() is attempted.) The close-on-exec
12 # status of a file descriptor will be decided according to the value of
13 # C<$^F> when the corresponding file, pipe, or socket was opened, not
14 # the time of the exec().
16 # This documented close-on-exec behaviour is typically implemented in
17 # various places (e.g. pp_sys.c) with code something like:
19 # #if defined(HAS_FCNTL) && defined(F_SETFD)
20 # fcntl(fd, F_SETFD, fd > PL_maxsysfd); /* ensure close-on-exec */
23 # This behaviour, therefore, is only currently implemented for platforms
26 # a) HAS_FCNTL and F_SETFD are both defined
27 # b) Integer fds are native OS handles
29 # ... which is typically just the Unix-like platforms.
31 # Notice that though integer fds are supported by the C runtime library
32 # on Windows, they are not native OS handles, and so are not inherited
33 # across an exec (though native Windows file handles are).
39 if (!$Config::Config{'d_fcntl'}) {
40 print("1..0 # Skip: fcntl() is not available\n");
50 my $Is_VMS = $^O eq 'VMS';
51 my $Is_MacOS = $^O eq 'MacOS';
52 my $Is_Win32 = $^O eq 'MSWin32';
54 # When in doubt, skip.
55 skip_all("MacOS") if $Is_MacOS;
56 skip_all("VMS") if $Is_VMS;
57 skip_all("Win32") if $Is_Win32;
60 my ($fname, $fcontents) = @_;
62 open FHTMP, ">$fname" or die "open '$fname': $!";
63 print FHTMP $fcontents or die "print '$fname': $!";
64 close FHTMP or die "close '$fname': $!";
67 my $Perl = which_perl();
68 my $quote = $Is_VMS || $Is_Win32 ? '"' : "'";
70 my $tmperr = tempfile();
71 my $tmpfile1 = tempfile();
72 my $tmpfile2 = tempfile();
73 my $tmpfile1_contents = "tmpfile1 line 1\ntmpfile1 line 2\n";
74 my $tmpfile2_contents = "tmpfile2 line 1\ntmpfile2 line 2\n";
75 make_tmp_file($tmpfile1, $tmpfile1_contents);
76 make_tmp_file($tmpfile2, $tmpfile2_contents);
78 # $Child_prog is the program run by the child that inherits the fd.
79 # Note: avoid using ' or " in $Child_prog since it is run with -e
80 my $Child_prog = <<'CHILD_PROG';
82 print qq{childfd=$fd\n};
83 open INHERIT, qq{<&=$fd} or die qq{open $fd: $!};
85 close INHERIT or die qq{close $fd: $!};
88 $Child_prog =~ tr/\n//d;
92 sub test_not_inherited {
93 my $expected_fd = shift;
94 ok( -f $tmpfile2, "tmpfile '$tmpfile2' exists" );
95 my $cmd = qq{$Perl -e $quote$Child_prog$quote $expected_fd};
96 # Expect 'Bad file descriptor' or similar to be written to STDERR.
97 local *SAVERR; open SAVERR, ">&STDERR"; # save original STDERR
98 open STDERR, ">$tmperr" or die "open '$tmperr': $!";
101 open STDERR, ">&SAVERR" or die "error: restore STDERR: $!";
102 close SAVERR or die "error: close SAVERR: $!";
103 # XXX: it seems one cannot rely on a non-zero return code,
104 # at least not on Tru64.
105 # cmp_ok( $rc, '!=', 0,
106 # "child return code=$rc (non-zero means cannot inherit fd=$expected_fd)" );
107 cmp_ok( $out =~ tr/\n//, '==', 1,
108 "child stdout: has 1 newline (rc=$rc, should be non-zero)" );
109 is( $out, "childfd=$expected_fd\n", 'child stdout: fd' );
113 my $expected_fd = shift;
114 ok( -f $tmpfile1, "tmpfile '$tmpfile1' exists" );
115 my $cmd = qq{$Perl -e $quote$Child_prog$quote $expected_fd};
118 cmp_ok( $rc, '==', 0,
119 "child return code=$rc (zero means inherited fd=$expected_fd ok)" );
120 my @lines = split(/^/, $out);
121 cmp_ok( $out =~ tr/\n//, '==', 2, 'child stdout: has 2 newlines' );
122 cmp_ok( scalar(@lines), '==', 2, 'child stdout: split into 2 lines' );
123 is( $lines[0], "childfd=$expected_fd\n", 'child stdout: fd' );
124 is( $lines[1], "tmpfile1 line 1\n", 'child stdout: line 1' );
127 $^F == 2 or print STDERR "# warning: \$^F is $^F (not 2)\n";
129 # Should not be able to inherit > $^F in the default case.
130 open FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!";
131 my $parentfd2 = fileno FHPARENT2;
132 defined $parentfd2 or die "fileno: $!";
133 cmp_ok( $parentfd2, '>', $^F, "parent open fd=$parentfd2 (\$^F=$^F)" );
134 test_not_inherited($parentfd2);
135 close FHPARENT2 or die "close '$tmpfile2': $!";
137 # Should be able to inherit $^F after setting to $parentfd2
138 # Need to set $^F before open because close-on-exec set at time of open.
140 open FHPARENT1, "<$tmpfile1" or die "open '$tmpfile1': $!";
141 my $parentfd1 = fileno FHPARENT1;
142 defined $parentfd1 or die "fileno: $!";
143 cmp_ok( $parentfd1, '<=', $^F, "parent open fd=$parentfd1 (\$^F=$^F)" );
144 test_inherited($parentfd1);
145 close FHPARENT1 or die "close '$tmpfile1': $!";
147 # ... and test that you cannot inherit fd = $^F+n.
148 open FHPARENT1, "<$tmpfile1" or die "open '$tmpfile1': $!";
149 open FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!";
150 $parentfd2 = fileno FHPARENT2;
151 defined $parentfd2 or die "fileno: $!";
152 cmp_ok( $parentfd2, '>', $^F, "parent open fd=$parentfd2 (\$^F=$^F)" );
153 test_not_inherited($parentfd2);
154 close FHPARENT2 or die "close '$tmpfile2': $!";
155 close FHPARENT1 or die "close '$tmpfile1': $!";
157 # ... and now you can inherit after incrementing.
159 open FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!";
160 open FHPARENT1, "<$tmpfile1" or die "open '$tmpfile1': $!";
161 $parentfd1 = fileno FHPARENT1;
162 defined $parentfd1 or die "fileno: $!";
163 cmp_ok( $parentfd1, '<=', $^F, "parent open fd=$parentfd1 (\$^F=$^F)" );
164 test_inherited($parentfd1);
165 close FHPARENT1 or die "close '$tmpfile1': $!";
166 close FHPARENT2 or die "close '$tmpfile2': $!";