xref: /openbsd-src/gnu/usr.bin/perl/t/run/cloexec.t (revision b8851fcc53cbe24fd20b090f26dd149e353f6174)
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