File Coverage

blib/lib/Test/NoTty.pm
Criterion Covered Total %
statement 31 45 68.8
branch 9 22 40.9
condition 3 5 60.0
subroutine 5 6 83.3
pod 1 1 100.0
total 49 79 62.0


line stmt bran cond sub pod time code
1             #!perl
2              
3 2     2   149007 use strict;
  2         9  
  2         55  
4 2     2   10 use warnings;
  2         4  
  2         65  
5              
6             package Test::NoTty;
7              
8 2     2   10 use parent qw(Exporter);
  2         6  
  2         26  
9 2     2   1163 use POSIX qw(setsid _exit WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG);
  2         13089  
  2         10  
10              
11             our @EXPORT = 'without_tty';
12             our $VERSION = '0.01';
13              
14             sub without_tty(&@) {
15 6     6 1 47182 my ($code, @args) = @_;
16 6 50       363 pipe my $reader, my $writer
17             or die "Can't pipe: $!";
18              
19             # So, "how to detach from your controlling terminal" is a subset of the "how
20             # to start a daemon" dance. In (reverse) you
21             #
22             # 2) Call setsid when your process is not a process group leader.
23             # This detaches you from any controlling terminal
24             # 1) fork, as the child process won't be a process group leader.
25             # (Your parent might be, and certainly will be if run interactively)
26             #
27             # The fun and games ensues because the code needs to run in the child, but
28             # really we'd like to fake it (as much as possible) that the code is running
29             # in the parent.
30              
31             # I'm not quite sure if how we deal with this correctly. Of if we really
32             # can. A child process is really supposed to call `exec` or `_exit`. But
33             # there's a chance here that we want to have real output
34              
35 6         101 STDOUT->flush;
36 6         49 STDERR->flush;
37 6         5786 my $pid = fork;
38 6 50       323 die "Couldn't fork: $!"
39             unless defined $pid;
40              
41 6 50       180 unless ($pid) {
42             # We are in the child
43              
44             # We use the pipe to send (and rethrow) any regular exception.
45             # By implication, we can't deal with exception objects.
46 0         0 close $reader;
47              
48 0         0 eval {
49 0 0       0 die "setsid failed: $!"
50             unless setsid;
51              
52             # Likewise, a limitation is that the only function return value we
53             # can easily support is an integer process exit code:
54 0         0 my $exitcode = $code->(@args);
55 0         0 STDOUT->flush;
56 0         0 STDERR->flush;
57 0   0     0 _exit($exitcode // 0);
58             };
59              
60             # If you get here it's an error:
61 0 0       0 print $writer $@
62             or warn "print to error message handle failed: $!";
63 0 0       0 close $writer
64             or warn "close error message handle failed: $!";
65 0         0 STDOUT->flush;
66 0         0 STDERR->flush;
67 0         0 kill 'ABRT', $$;
68             }
69             # We are in the parent
70              
71             # Try very hard to relay signals to the child. For example, if it sleeps or
72             # churns forever, we want ^C to interrupt it, not take us out but leave it
73             # running in the background. This isn't foolproof, but seems better than
74             # doing nothing:
75              
76 6   100     1280 my @sigs = grep { !/^__/ && !/^CH?LD$/ } keys %SIG;
  414         2205  
77 6         4737 local @SIG{@sigs};
78 6         87 for my $sig (@sigs) {
79             $SIG{$sig} = sub {
80 0 0   0   0 kill $sig, $pid
81             or warn "kill $sig $pid failed: $!";
82 396         5746 };
83             }
84 6         103 close $writer;
85              
86             # "Setup" done. Let's see what the child tried to tell us:
87 6 50       600020 waitpid $pid, 0
88             or die "waitpid $pid, 0 failed: $!";
89 6         232 local $/;
90 6         390 my $error = <$reader>;
91 6 100       2748 die $error
92             if length $error;
93              
94             # This is the common case:
95 4 100       2026 return WEXITSTATUS(${^CHILD_ERROR_NATIVE})
96             if WIFEXITED(${^CHILD_ERROR_NATIVE});
97              
98 1 50       689 die "Code called by without_tty() died with signal " . WTERMSIG(${^CHILD_ERROR_NATIVE})
99             if WTERMSIG(${^CHILD_ERROR_NATIVE});
100              
101 0           die "Code called by without_tty() exited with unknown status ${^CHILD_ERROR_NATIVE}";
102             }
103              
104             1;
105              
106             __END__