make getpeername() return the original socket address which before it was intercepted
[hband-tools.git] / user-tools / stdfilt
blob3b496dedb2ba0e08f945aaa3e952b319db264878
1 #!/usr/bin/env perl
3 use Data::Dumper;
4 use Getopt::Long qw/:config no_ignore_case bundling no_getopt_compat/;
5 use Fcntl qw/F_GETFL F_SETFL O_NONBLOCK/;
6 use POSIX;
7 use Pod::Usage;
10 $Filters = {
11 'out' => [],
12 'err' => [],
14 @savedARGV = @ARGV;
17 sub process_args
19 @ARGV = @savedARGV;
20 pop @{$Filters->{'out'}} while scalar @{$Filters->{'out'}};
21 pop @{$Filters->{'err'}} while scalar @{$Filters->{'err'}};
22 return GetOptions(
23 'F|filter-file=s@' => sub { process_filter_file($_[1]); },
24 'o|po|pass-stdout=s@' => sub { push @{$Filters->{'out'}}, {'pass'=>1, 'pattern'=>$_[1]}; },
25 'e|pe|pass-stderr=s@' => sub { push @{$Filters->{'err'}}, {'pass'=>1, 'pattern'=>$_[1]}; },
26 'O|fo|filter-stdout=s@' => sub { push @{$Filters->{'out'}}, {'pass'=>0, 'pattern'=>$_[1]}; },
27 'E|fe|filter-stderr=s@' => sub { push @{$Filters->{'err'}}, {'pass'=>0, 'pattern'=>$_[1]}; },
28 'p|pass=s@' => sub { push @{$Filters->{$_}}, {'pass'=>1, 'pattern'=>$_[1]} for ('out', 'err'); },
29 'f|filter=s@' => sub { push @{$Filters->{$_}}, {'pass'=>0, 'pattern'=>$_[1]} for ('out', 'err'); },
34 process_args or pod2usage(-exitval=>2, -verbose=>99);
36 if(not @ARGV)
38 pod2usage(-exitval=>2, -verbose=>99);
42 sub process_filter_file
44 my $filepath = shift;
45 open my $fh, '<', $filepath or die "$filepath: $!\n";
46 my @channels = ('out', 'err');
47 while(<$fh>)
49 chomp;
50 s/^\s*//;
51 next if /^(#|$)/;
52 if(/^\[std(out|err)\]$/)
54 @channels = ($1);
55 next;
57 if(/^\[\*\]$/)
59 @channels = ('out', 'err');
60 next;
62 my $pass = 0;
63 $pass = 1 if s/^!\s*//;
64 my $pattern = $_;
65 push @{$Filters->{$_}}, {'pass'=>$pass, 'pattern'=>$pattern} for @channels;
67 close $fh;
71 sub filter_pass
73 my $line = shift;
74 my $filters = shift;
75 my $pass = 1;
77 for my $filt (@$filters)
79 if($line =~ $filt->{'pattern'})
81 $pass = $filt->{'pass'};
84 return $pass;
87 sub process_line
89 my $line = shift;
90 my $stream = shift;
91 print {$stream->{'write_fh'}} $line if filter_pass $line, $stream->{'filter'};
94 sub process_stream
96 my $stream = shift;
97 while(1)
99 my $nl = index $stream->{'buf'}, "\n";
100 last if $nl == -1;
101 $nl++;
103 my $line = substr $stream->{'buf'}, 0, $nl;
104 process_line $line, $stream;
106 $stream->{'buf'} = substr $stream->{'buf'}, $nl;
112 pipe($stdout_r, $stdout_w) or die "$0: pipe: $!\n";
113 pipe($stderr_r, $stderr_w) or die "$0: pipe: $!\n";
116 my $child_pid = fork // die "$0: fork: $!\n";
118 if($child_pid == 0)
120 open STDOUT, '>&', $stdout_w or die "$0: replace stdout: $!\n";
121 open STDERR, '>&', $stderr_w or die "$0: replace stderr: $!\n";
122 select STDERR; $|++;
123 select STDOUT; $|++;
125 exec {$ARGV[0]} @ARGV;
126 my ($errno, $errstr) = (int $!, $!);
127 warn "$0: ${ARGV[0]}: $errstr\n";
128 exit 125+$errno;
132 my $child_status = undef;
133 close STDIN;
134 close $stdout_w;
135 close $stderr_w;
136 fcntl($stdout_r, F_SETFL, fcntl($stdout_r, F_GETFL, 0) | O_NONBLOCK);
137 fcntl($stderr_r, F_SETFL, fcntl($stderr_r, F_GETFL, 0) | O_NONBLOCK);
138 select STDERR; $|++;
139 select STDOUT; $|++;
142 $pipe = {
143 'out' => {
144 'fh' => $stdout_r,
145 'write_fh' => \*STDOUT,
146 'fd' => fileno $stdout_r,
147 'buf' => '',
148 'filter' => $Filters->{'out'},
150 'err' => {
151 'fh' => $stderr_r,
152 'write_fh' => \*STDERR,
153 'fd' => fileno $stderr_r,
154 'buf' => '',
155 'filter' => $Filters->{'err'},
160 $SIG{'HUP'} = sub { $do_reload = 1; };
163 while(1)
165 $fds = '';
166 for my $n ('out', 'err')
168 vec($fds, $pipe->{$n}->{'fd'}, 1) = 1 if defined $pipe->{$n}->{'fh'};
170 last if $fds eq '';
172 $! = 0;
173 select($fds, undef, undef, undef);
174 my $errno = int $!;
176 if($do_reload)
178 $do_reload = 0;
179 process_args;
182 next if $errno;
184 for my $n ('out', 'err')
186 if(vec($fds, $pipe->{$n}->{'fd'}, 1) == 1)
188 my $bytes = sysread $pipe->{$n}->{'fh'}, $pipe->{$n}->{'buf'}, 1024, length $pipe->{$n}->{'buf'};
189 if($bytes)
191 process_stream($pipe->{$n});
193 else
195 # this stream is closed.
196 undef $pipe->{$n}->{'fh'};
197 # process last unterminated line (if any)
198 process_line($pipe->{$n}->{'buf'}, $pipe->{$n}) if length $pipe->{$n}->{'buf'};
204 waitpid($child_pid, 0);
205 $child_status = $?;
206 $exit_status = WEXITSTATUS($child_status);
207 $exit_status = 128 + WTERMSIG($child_status) if WIFSIGNALED($child_status);
208 exit $exit_status;
211 __END__
213 =pod
215 =head1 NAME
217 stdfilt - Run a command but filter its STDOUT and STDERR
219 =head1 SYNOPSIS
221 stdfilt [I<OPTIONS>] [--] I<COMMAND> [I<ARGS>]
223 =head1 OPTIONS
225 =over 4
227 =item -F, --filter-file I<FILE>
229 =item -o, -po, --pass-stdout I<PATTERN>
231 =item -e, -pe, --pass-stderr I<PATTERN>
233 =item -p, --pass I<PATTERN>
235 =item -O, -fo, --filter-stdout I<PATTERN>
237 =item -E, -fe, --filter-stderr I<PATTERN>
239 =item -f, --filter I<PATTERN>
241 =back
243 =head1 DESCRIPTION
245 Run I<COMMAND> and match each of its output lines (both stdout and stderr separately) against
246 the given perl-compatible regexp (see perlre(1)) patterns.
248 =head1 FILTER FILE FORMAT
250 Empty and comment lines (starting with C<#> hashmark) are ignored as well as leading whitespace.
251 Each line is either a filter pattern,
252 or the literal string C<[stdout]>, C<[stderr]>, or C<[*]>,
253 which indicates which standard fd will be filtered through the subsequent patterns.
254 If not indicated, C<[*]> (all fd) is implied.
256 A pattern may start with an C<!> exclamation mark, in which case, the lines matching it,
257 will be passed, not filtered.
259 =head1 SIGNALS
261 HUP - re-read filter files given at command line
263 =head1 SEE ALSO
265 grep(1), stdbuf(1), logwall(8), perlre(1)
267 =cut