1*441954edSanton# $OpenBSD: Proc.pm,v 1.3 2021/10/05 17:40:08 anton Exp $ 253d930aeSreyk 353d930aeSreyk# Copyright (c) 2010-2014 Alexander Bluhm <bluhm@openbsd.org> 453d930aeSreyk# 553d930aeSreyk# Permission to use, copy, modify, and distribute this software for any 653d930aeSreyk# purpose with or without fee is hereby granted, provided that the above 753d930aeSreyk# copyright notice and this permission notice appear in all copies. 853d930aeSreyk# 953d930aeSreyk# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1053d930aeSreyk# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1153d930aeSreyk# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1253d930aeSreyk# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1353d930aeSreyk# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1453d930aeSreyk# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1553d930aeSreyk# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1653d930aeSreyk 1753d930aeSreykuse strict; 1853d930aeSreykuse warnings; 1953d930aeSreyk 2053d930aeSreykpackage Proc; 2153d930aeSreykuse Carp; 2253d930aeSreykuse Errno; 2353d930aeSreykuse File::Basename; 2453d930aeSreykuse IO::File; 2553d930aeSreykuse POSIX; 2653d930aeSreykuse Time::HiRes qw(time alarm sleep); 2753d930aeSreyk 2853d930aeSreykmy %CHILDREN; 2953d930aeSreyk 3053d930aeSreyksub kill_children { 3153d930aeSreyk my @pids = @_ ? @_ : keys %CHILDREN 3253d930aeSreyk or return; 3353d930aeSreyk my @perms; 3453d930aeSreyk foreach my $pid (@pids) { 3553d930aeSreyk if (kill(TERM => $pid) != 1 and $!{EPERM}) { 3653d930aeSreyk push @perms, $pid; 3753d930aeSreyk } 3853d930aeSreyk } 39*441954edSanton if (my @sudo = split(' ', $ENV{SUDO}) and @perms) { 4053d930aeSreyk local $?; # do not modify during END block 41*441954edSanton my @cmd = (@sudo, '/bin/kill', '-TERM', @perms); 4253d930aeSreyk system(@cmd); 4353d930aeSreyk } 4453d930aeSreyk delete @CHILDREN{@pids}; 4553d930aeSreyk} 4653d930aeSreyk 4753d930aeSreykBEGIN { 4853d930aeSreyk $SIG{TERM} = $SIG{INT} = sub { 4953d930aeSreyk my $sig = shift; 5053d930aeSreyk kill_children(); 5153d930aeSreyk $SIG{TERM} = $SIG{INT} = 'DEFAULT'; 5253d930aeSreyk POSIX::raise($sig); 5353d930aeSreyk }; 5453d930aeSreyk} 5553d930aeSreyk 5653d930aeSreykEND { 5753d930aeSreyk kill_children(); 5853d930aeSreyk $SIG{TERM} = $SIG{INT} = 'DEFAULT'; 5953d930aeSreyk} 6053d930aeSreyk 6153d930aeSreyksub new { 6253d930aeSreyk my $class = shift; 6353d930aeSreyk my $self = { @_ }; 6453d930aeSreyk $self->{down} ||= "Shutdown"; 6553d930aeSreyk $self->{func} && ref($self->{func}) eq 'CODE' 6653d930aeSreyk or croak "$class func not given"; 6753d930aeSreyk $self->{logfile} 6853d930aeSreyk or croak "$class log file not given"; 6953d930aeSreyk open(my $fh, '>', $self->{logfile}) 7053d930aeSreyk or die "$class log file $self->{logfile} create failed: $!"; 7153d930aeSreyk $fh->autoflush; 7253d930aeSreyk $self->{log} = $fh; 7353d930aeSreyk return bless $self, $class; 7453d930aeSreyk} 7553d930aeSreyk 7653d930aeSreyksub run { 7753d930aeSreyk my $self = shift; 7853d930aeSreyk 7953d930aeSreyk pipe(my $reader, my $writer) 8053d930aeSreyk or die ref($self), " pipe to child failed: $!"; 8153d930aeSreyk defined(my $pid = fork()) 8253d930aeSreyk or die ref($self), " fork child failed: $!"; 8353d930aeSreyk if ($pid) { 8453d930aeSreyk $CHILDREN{$pid} = 1; 8553d930aeSreyk $self->{pid} = $pid; 8653d930aeSreyk close($reader); 8753d930aeSreyk $self->{pipe} = $writer; 8853d930aeSreyk return $self; 8953d930aeSreyk } 9053d930aeSreyk %CHILDREN = (); 9153d930aeSreyk $SIG{TERM} = $SIG{INT} = 'DEFAULT'; 9253d930aeSreyk $SIG{__DIE__} = sub { 9353d930aeSreyk die @_ if $^S; 9453d930aeSreyk warn @_; 9553d930aeSreyk IO::Handle::flush(\*STDERR); 9653d930aeSreyk POSIX::_exit(255); 9753d930aeSreyk }; 9853d930aeSreyk open(STDERR, '>&', $self->{log}) 9953d930aeSreyk or die ref($self), " dup STDERR failed: $!"; 10053d930aeSreyk close($writer); 10153d930aeSreyk open(STDIN, '<&', $reader) 10253d930aeSreyk or die ref($self), " dup STDIN failed: $!"; 10353d930aeSreyk close($reader); 10453d930aeSreyk 10553d930aeSreyk do { 10653d930aeSreyk $self->child(); 10753d930aeSreyk print STDERR $self->{up}, "\n"; 10853d930aeSreyk $self->{begin} = time(); 10953d930aeSreyk $self->{func}->($self); 11053d930aeSreyk } while ($self->{redo}); 11153d930aeSreyk $self->{end} = time(); 11253d930aeSreyk print STDERR "Shutdown", "\n"; 11353d930aeSreyk if ($self->{timefile}) { 11453d930aeSreyk open(my $fh, '>>', $self->{timefile}) 11553d930aeSreyk or die ref($self), " open $self->{timefile} failed: $!"; 11653d930aeSreyk printf $fh "time='%s' duration='%.10g' ". 11753d930aeSreyk "test='%s'\n", 11853d930aeSreyk scalar(localtime(time())), $self->{end} - $self->{begin}, 11953d930aeSreyk basename($self->{testfile}); 12053d930aeSreyk } 12153d930aeSreyk 12253d930aeSreyk IO::Handle::flush(\*STDOUT); 12353d930aeSreyk IO::Handle::flush(\*STDERR); 12453d930aeSreyk POSIX::_exit(0); 12553d930aeSreyk} 12653d930aeSreyk 12753d930aeSreyksub wait { 12853d930aeSreyk my $self = shift; 12953d930aeSreyk my $flags = shift; 13053d930aeSreyk 13153d930aeSreyk my $pid = $self->{pid} 13253d930aeSreyk or croak ref($self), " no child pid"; 13353d930aeSreyk my $kid = waitpid($pid, $flags); 13453d930aeSreyk if ($kid > 0) { 13553d930aeSreyk my $status = $?; 13653d930aeSreyk my $code; 13753d930aeSreyk $code = "exit: ". WEXITSTATUS($?) if WIFEXITED($?); 13853d930aeSreyk $code = "signal: ". WTERMSIG($?) if WIFSIGNALED($?); 13953d930aeSreyk $code = "stop: ". WSTOPSIG($?) if WIFSTOPPED($?); 14053d930aeSreyk delete $CHILDREN{$pid} if WIFEXITED($?) || WIFSIGNALED($?); 14153d930aeSreyk return wantarray ? ($kid, $status, $code) : $kid; 14253d930aeSreyk } 14353d930aeSreyk return $kid; 14453d930aeSreyk} 14553d930aeSreyk 14653d930aeSreyksub loggrep { 14753d930aeSreyk my $self = shift; 14853d930aeSreyk my($regex, $timeout) = @_; 14953d930aeSreyk 150f8f8530cSbluhm my $end; 151f8f8530cSbluhm $end = time() + $timeout if $timeout; 15253d930aeSreyk 15353d930aeSreyk do { 15453d930aeSreyk my($kid, $status, $code) = $self->wait(WNOHANG); 15553d930aeSreyk if ($kid > 0 && $status != 0 && !$self->{dryrun}) { 15653d930aeSreyk # child terminated with failure 15753d930aeSreyk die ref($self), " child status: $status $code"; 15853d930aeSreyk } 15953d930aeSreyk open(my $fh, '<', $self->{logfile}) 16053d930aeSreyk or die ref($self), " log file open failed: $!"; 16153d930aeSreyk my @match = grep { /$regex/ } <$fh>; 16253d930aeSreyk return wantarray ? @match : $match[0] if @match; 16353d930aeSreyk close($fh); 16453d930aeSreyk # pattern not found 16553d930aeSreyk if ($kid == 0) { 16653d930aeSreyk # child still running, wait for log data 16753d930aeSreyk sleep .1; 16853d930aeSreyk } else { 16953d930aeSreyk # child terminated, no new log data possible 17053d930aeSreyk return; 17153d930aeSreyk } 17253d930aeSreyk } while ($timeout and time() < $end); 17353d930aeSreyk 17453d930aeSreyk return; 17553d930aeSreyk} 17653d930aeSreyk 17753d930aeSreyksub up { 17853d930aeSreyk my $self = shift; 17953d930aeSreyk my $timeout = shift || 10; 18053d930aeSreyk $self->loggrep(qr/$self->{up}/, $timeout) 18153d930aeSreyk or croak ref($self), " no '$self->{up}' in $self->{logfile} ". 18253d930aeSreyk "after $timeout seconds"; 18353d930aeSreyk return $self; 18453d930aeSreyk} 18553d930aeSreyk 18653d930aeSreyksub down { 18753d930aeSreyk my $self = shift; 18853d930aeSreyk my $timeout = shift || 300; 18953d930aeSreyk $self->loggrep(qr/$self->{down}/, $timeout) 19053d930aeSreyk or croak ref($self), " no '$self->{down}' in $self->{logfile} ". 19153d930aeSreyk "after $timeout seconds"; 19253d930aeSreyk return $self; 19353d930aeSreyk} 19453d930aeSreyk 19553d930aeSreyksub kill_child { 19653d930aeSreyk my $self = shift; 19753d930aeSreyk kill_children($self->{pid}); 19853d930aeSreyk return $self; 19953d930aeSreyk} 20053d930aeSreyk 20153d930aeSreyk1; 202