New test for close-on-exec ($^F): t/run/cloexec.t
[p5sagit/p5-mst-13.2.git] / t / run / cloexec.t
1 #!./perl
2 #
3 # Test inheriting file descriptors across exec (close-on-exec).
4 #
5 # perlvar describes $^F aka $SYSTEM_FD_MAX as follows:
6 #
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().
15 #
16 # This documented close-on-exec behaviour is typically implemented in
17 # various places (e.g. pp_sys.c) with code something like:
18 #
19 #  #if defined(HAS_FCNTL) && defined(F_SETFD)
20 #      fcntl(fd, F_SETFD, fd > PL_maxsysfd);  /* ensure close-on-exec */
21 #  #endif
22 #
23 # This behaviour, therefore, is only currently implemented for platforms
24 # where:
25 #
26 #  a) HAS_FCNTL and F_SETFD are both defined
27 #  b) Integer fds are native OS handles
28 #
29 # ... which is typically just the Unix-like platforms.
30 #
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).
34
35 BEGIN {
36     chdir 't' if -d 't';
37     @INC = '../lib';
38     use Config;
39     if (!$Config{'d_fcntl'}) {
40         print("1..0 # Skip: fcntl() is not available\n");
41         exit(0);
42     }
43     require './test.pl';
44 }
45
46 use strict;
47
48 $|=1;
49
50 my $Is_VMS      = $^O eq 'VMS';
51 my $Is_MacOS    = $^O eq 'MacOS';
52 my $Is_Win32    = $^O eq 'MSWin32';
53 my $Is_Cygwin   = $^O eq 'cygwin';
54
55 # When in doubt, skip.
56 skip_all("MacOS")    if $Is_MacOS;
57 skip_all("VMS")      if $Is_VMS;
58 skip_all("cygwin")   if $Is_Cygwin;
59 skip_all("Win32")    if $Is_Win32;
60
61 sub make_tmp_file {
62     my ($fname, $fcontents) = @_;
63     local *FHTMP;
64     open   FHTMP, ">$fname"  or die "open  '$fname': $!";
65     print  FHTMP $fcontents  or die "print '$fname': $!";
66     close  FHTMP             or die "close '$fname': $!";
67 }
68
69 my $Perl = which_perl();
70 my $quote = $Is_VMS || $Is_Win32 ? '"' : "'";
71
72 my $tmperr             = 'cloexece.tmp';
73 my $tmpfile1           = 'cloexec1.tmp';
74 my $tmpfile2           = 'cloexec2.tmp';
75 my $tmpfile1_contents  = "tmpfile1 line 1\ntmpfile1 line 2\n";
76 my $tmpfile2_contents  = "tmpfile2 line 1\ntmpfile2 line 2\n";
77 make_tmp_file($tmpfile1, $tmpfile1_contents);
78 make_tmp_file($tmpfile2, $tmpfile2_contents);
79
80 # $Child_prog is the program run by the child that inherits the fd.
81 # Note: avoid using ' or " in $Child_prog since it is run with -e
82 my $Child_prog = <<'CHILD_PROG';
83 my $fd = shift;
84 print qq{childfd=$fd\n};
85 open INHERIT, qq{<&=$fd} or die qq{open $fd: $!};
86 my $line = <INHERIT>;
87 close INHERIT or die qq{close $fd: $!};
88 print $line
89 CHILD_PROG
90 $Child_prog =~ tr/\n//d;
91
92 plan(tests => 29);
93
94 sub test_not_inherited {
95     my $expected_fd = shift;
96     ok( -f $tmpfile2, "tmpfile '$tmpfile2' exists" );
97     local *FHPARENT2;
98     open FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!";
99     my $parentfd = fileno FHPARENT2;
100     defined $parentfd or die "fileno: $!";
101     cmp_ok( $parentfd, '==', $expected_fd, "parent open fd=$parentfd" );
102     my $cmd = qq{$Perl -e $quote$Child_prog$quote $parentfd};
103     # Expect 'Bad file descriptor' or similar to be written to STDERR.
104     local *SAVERR; open SAVERR, ">&STDERR";  # save original STDERR
105     open STDERR, ">$tmperr" or die "open '$tmperr': $!";
106     my $out = `$cmd`;
107     my $rc  = $? >> 8;
108     open STDERR, ">&SAVERR" or die "error: restore STDERR: $!";
109     close SAVERR or die "error: close SAVERR: $!";
110     cmp_ok( $rc, '!=', 0,
111         "child return code=$rc (non-zero means cannot inherit fd=$parentfd)" );
112     cmp_ok( $out =~ tr/\n//, '==', 1,   'child stdout: has 1 newline' );
113     is( $out, "childfd=$expected_fd\n", 'child stdout: fd' );
114     close FHPARENT2 or die "close '$tmpfile2': $!";
115 }
116
117 sub test_inherited {
118     my $expected_fd = shift;
119     ok( -f $tmpfile1, "tmpfile '$tmpfile1' exists" );
120     local *FHPARENT1;
121     open FHPARENT1, "<$tmpfile1" or die "open-1 '$tmpfile1': $!";
122     my $parentfd = fileno FHPARENT1;
123     defined $parentfd or die "fileno: $!";
124     cmp_ok( $parentfd, '==', $expected_fd, "parent open fd=$parentfd" );
125     my $cmd = qq{$Perl -e $quote$Child_prog$quote $parentfd};
126     my $out = `$cmd`;
127     my $rc  = $? >> 8;
128     cmp_ok( $rc, '==', 0,
129         "child return code=$rc (zero means inherited fd=$parentfd ok)" );
130     my @lines = split(/^/, $out);
131     cmp_ok( $out =~ tr/\n//, '==', 2, 'child stdout: has 2 newlines' );
132     cmp_ok( scalar(@lines),  '==', 2, 'child stdout: split into 2 lines' );
133     is( $lines[0], "childfd=$expected_fd\n", 'child stdout: fd' );
134     is( $lines[1], "tmpfile1 line 1\n",      'child stdout: line 1' );
135     close FHPARENT1 or die "close '$tmpfile1': $!";
136 }
137
138 $^F == 2 or print STDERR "# warning: \$^F is $^F (not 2)\n";
139
140 # Should not be able to inherit $^F+1 in the default case.
141 test_not_inherited($^F+1);
142
143 # Should be able to inherit $^F after incrementing it.
144 ++$^F;
145 test_inherited($^F);
146 # ... and test that you cannot inherit fd = $^F+1.
147 open FHPARENT1, "<$tmpfile1" or die "open '$tmpfile1': $!";
148 test_not_inherited($^F+1);
149 close FHPARENT1 or die "close '$tmpfile1': $!";
150 # ... and now you can inherit after incrementing.
151 ++$^F;
152 open FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!";
153 test_inherited($^F);
154 close FHPARENT2 or die "close '$tmpfile2': $!";
155
156 # Re-test default case after decrementing.
157 --$^F; --$^F;
158 test_not_inherited($^F+1);
159
160 END {
161     defined $tmperr   and unlink($tmperr);
162     defined $tmpfile1 and unlink($tmpfile1);
163     defined $tmpfile2 and unlink($tmpfile2);
164 }