summaryrefslogtreecommitdiff
path: root/gnu/usr.bin/perl/t/run/cloexec.t
blob: 52003771652a869c3e7855313c7e374f514cd770 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!./perl
#
# Test inheriting file descriptors across exec (close-on-exec).
#
# perlvar describes $^F aka $SYSTEM_FD_MAX as follows:
#
#  The maximum system file descriptor, ordinarily 2.  System file
#  descriptors are passed to exec()ed processes, while higher file
#  descriptors are not.  Also, during an open(), system file descriptors
#  are preserved even if the open() fails.  (Ordinary file descriptors
#  are closed before the open() is attempted.)  The close-on-exec
#  status of a file descriptor will be decided according to the value of
#  C<$^F> when the corresponding file, pipe, or socket was opened, not
#  the time of the exec().
#
# This documented close-on-exec behaviour is typically implemented in
# various places (e.g. pp_sys.c) with code something like:
#
#  #if defined(HAS_FCNTL) && defined(F_SETFD)
#      fcntl(fd, F_SETFD, fd > PL_maxsysfd);  /* ensure close-on-exec */
#  #endif
#
# This behaviour, therefore, is only currently implemented for platforms
# where:
#
#  a) HAS_FCNTL and F_SETFD are both defined
#  b) Integer fds are native OS handles
#
# ... which is typically just the Unix-like platforms.
#
# Notice that though integer fds are supported by the C runtime library
# on Windows, they are not native OS handles, and so are not inherited
# across an exec (though native Windows file handles are).

BEGIN {
    chdir 't' if -d 't';
    @INC = '../lib';
    require './test.pl';
    skip_all_without_config('d_fcntl');
}

use strict;

$|=1;

# When in doubt, skip.
skip_all($^O)
    if $^O eq 'VMS' or $^O eq 'MSWin32';

sub make_tmp_file {
    my ($fname, $fcontents) = @_;
    local *FHTMP;
    open   FHTMP, ">$fname"  or die "open  '$fname': $!";
    print  FHTMP $fcontents  or die "print '$fname': $!";
    close  FHTMP             or die "close '$fname': $!";
}

my $Perl = which_perl();
my $quote = "'";

my $tmperr             = tempfile();
my $tmpfile1           = tempfile();
my $tmpfile2           = tempfile();
my $tmpfile1_contents  = "tmpfile1 line 1\ntmpfile1 line 2\n";
my $tmpfile2_contents  = "tmpfile2 line 1\ntmpfile2 line 2\n";
make_tmp_file($tmpfile1, $tmpfile1_contents);
make_tmp_file($tmpfile2, $tmpfile2_contents);

# $Child_prog is the program run by the child that inherits the fd.
# Note: avoid using ' or " in $Child_prog since it is run with -e
my $Child_prog = <<'CHILD_PROG';
my $fd = shift;
print qq{childfd=$fd\n};
open INHERIT, qq{<&=$fd} or die qq{open $fd: $!};
my $line = <INHERIT>;
close INHERIT or die qq{close $fd: $!};
print $line
CHILD_PROG
$Child_prog =~ tr/\n//d;

plan(tests => 22);

sub test_not_inherited {
    my $expected_fd = shift;
    ok( -f $tmpfile2, "tmpfile '$tmpfile2' exists" );
    my $cmd = qq{$Perl -e $quote$Child_prog$quote $expected_fd};
    # Expect 'Bad file descriptor' or similar to be written to STDERR.
    local *SAVERR; open SAVERR, ">&STDERR";  # save original STDERR
    open STDERR, ">$tmperr" or die "open '$tmperr': $!";
    my $out = `$cmd`;
    my $rc  = $? >> 8;
    open STDERR, ">&SAVERR" or die "error: restore STDERR: $!";
    close SAVERR or die "error: close SAVERR: $!";
    # XXX: it seems one cannot rely on a non-zero return code,
    # at least not on Tru64.
    # cmp_ok( $rc, '!=', 0,
    #     "child return code=$rc (non-zero means cannot inherit fd=$expected_fd)" );
    cmp_ok( $out =~ tr/\n//, '==', 1,
        "child stdout: has 1 newline (rc=$rc, should be non-zero)" );
    is( $out, "childfd=$expected_fd\n", 'child stdout: fd' );
}

sub test_inherited {
    my $expected_fd = shift;
    ok( -f $tmpfile1, "tmpfile '$tmpfile1' exists" );
    my $cmd = qq{$Perl -e $quote$Child_prog$quote $expected_fd};
    my $out = `$cmd`;
    my $rc  = $? >> 8;
    cmp_ok( $rc, '==', 0,
        "child return code=$rc (zero means inherited fd=$expected_fd ok)" );
    my @lines = split(/^/, $out);
    cmp_ok( $out =~ tr/\n//, '==', 2, 'child stdout: has 2 newlines' );
    cmp_ok( scalar(@lines),  '==', 2, 'child stdout: split into 2 lines' );
    is( $lines[0], "childfd=$expected_fd\n", 'child stdout: fd' );
    is( $lines[1], "tmpfile1 line 1\n",      'child stdout: line 1' );
}

$^F == 2 or print STDERR "# warning: \$^F is $^F (not 2)\n";

# Should not be able to inherit > $^F in the default case.
open FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!";
my $parentfd2 = fileno FHPARENT2;
defined $parentfd2 or die "fileno: $!";
cmp_ok( $parentfd2, '>', $^F, "parent open fd=$parentfd2 (\$^F=$^F)" );
test_not_inherited($parentfd2);
close FHPARENT2 or die "close '$tmpfile2': $!";

# Should be able to inherit $^F after setting to $parentfd2
# Need to set $^F before open because close-on-exec set at time of open.
$^F = $parentfd2;
open FHPARENT1, "<$tmpfile1" or die "open '$tmpfile1': $!";
my $parentfd1 = fileno FHPARENT1;
defined $parentfd1 or die "fileno: $!";
cmp_ok( $parentfd1, '<=', $^F, "parent open fd=$parentfd1 (\$^F=$^F)" );
test_inherited($parentfd1);
close FHPARENT1 or die "close '$tmpfile1': $!";

# ... and test that you cannot inherit fd = $^F+n.
open FHPARENT1, "<$tmpfile1" or die "open '$tmpfile1': $!";
open FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!";
$parentfd2 = fileno FHPARENT2;
defined $parentfd2 or die "fileno: $!";
cmp_ok( $parentfd2, '>', $^F, "parent open fd=$parentfd2 (\$^F=$^F)" );
test_not_inherited($parentfd2);
close FHPARENT2 or die "close '$tmpfile2': $!";
close FHPARENT1 or die "close '$tmpfile1': $!";

# ... and now you can inherit after incrementing.
$^F = $parentfd2;
open FHPARENT2, "<$tmpfile2" or die "open '$tmpfile2': $!";
open FHPARENT1, "<$tmpfile1" or die "open '$tmpfile1': $!";
$parentfd1 = fileno FHPARENT1;
defined $parentfd1 or die "fileno: $!";
cmp_ok( $parentfd1, '<=', $^F, "parent open fd=$parentfd1 (\$^F=$^F)" );
test_inherited($parentfd1);
close FHPARENT1 or die "close '$tmpfile1': $!";
close FHPARENT2 or die "close '$tmpfile2': $!";