X-Git-Url: https://git.proxmox.com/?p=pve-common.git;a=blobdiff_plain;f=src%2FPVE%2FRESTEnvironment.pm;h=b32c452947fdbdbecd6c4a5582718fdf021fa3a2;hp=0ad6dbaefc28c96067cae96901ebb845be570c9c;hb=61aca93afb586c57d0135036b763b3aa67968084;hpb=95109cc44eb72e0b1b1a66b800a3f3cb99980eea diff --git a/src/PVE/RESTEnvironment.pm b/src/PVE/RESTEnvironment.pm index 0ad6dba..b32c452 100644 --- a/src/PVE/RESTEnvironment.pm +++ b/src/PVE/RESTEnvironment.pm @@ -26,7 +26,7 @@ my $rest_env; # and register forked processes with &$register_worker(pid) # Note: using $SIG{CHLD} = 'IGNORE' or $SIG{CHLD} = sub { wait (); } or ... # has serious side effects, because perls built in system() and open() -# functions can't get the correct exit status of a child. So we cant use +# functions can't get the correct exit status of a child. So we can't use # that (also see perlipc) my $WORKER_PIDS; @@ -217,6 +217,20 @@ sub get_user { die "user name not set\n"; } +sub set_u2f_challenge { + my ($self, $challenge) = @_; + + $self->{u2f_challenge} = $challenge; +} + +sub get_u2f_challenge { + my ($self, $noerr) = @_; + + return $self->{u2f_challenge} if defined($self->{u2f_challenge}) || $noerr; + + die "no active u2f challenge\n"; +} + sub is_worker { my ($class) = @_; @@ -270,7 +284,7 @@ sub active_workers { } - @ta = sort { $b->{starttime} cmp $a->{starttime} } @ta; + @ta = sort { $b->{starttime} <=> $a->{starttime} } @ta; my $save = defined($new_upid); @@ -373,6 +387,78 @@ sub check_worker { return 1; } +# acts almost as tee: writes an output both to STDOUT and a task log, +# we differ as we're worker aware and look also at the childs control pipe, +# so we know if the function could be executed successfully or not. +my $tee_worker = sub { + my ($childfd, $ctrlfd, $taskfh, $cpid) = @_; + + eval { + my $int_count = 0; + local $SIG{INT} = local $SIG{QUIT} = local $SIG{TERM} = sub { + # always send signal to all pgrp members + my $kpid = -$cpid; + if ($int_count < 3) { + kill(15, $kpid); # send TERM signal + } else { + kill(9, $kpid); # send KILL signal + } + $int_count++; + }; + local $SIG{PIPE} = sub { die "broken pipe\n"; }; + + my $select = new IO::Select; + my $fh = IO::Handle->new_from_fd($childfd, 'r'); + $select->add($fh); + + my $readbuf = ''; + my $count; + while ($select->count) { + my @handles = $select->can_read(1); + if (scalar(@handles)) { + my $count = sysread ($handles[0], $readbuf, 4096); + if (!defined ($count)) { + my $err = $!; + die "sync pipe read error: $err\n"; + } + last if $count == 0; # eof + + print $readbuf; + select->flush(); + + print $taskfh $readbuf; + $taskfh->flush(); + } else { + # some commands daemonize without closing stdout + last if !PVE::ProcFSTools::check_process_running($cpid); + } + } + + # get status (error or OK) + POSIX::read($ctrlfd, $readbuf, 4096); + if ($readbuf =~ m/^TASK OK\n?$/) { + # skip printing to stdout + print $taskfh $readbuf; + } elsif ($readbuf =~ m/^TASK ERROR: (.*)\n?$/) { + print STDERR "$1\n"; + print $taskfh "\n$readbuf"; # ensure start on new line for webUI + } else { + die "got unexpected control message: $readbuf\n"; + } + $taskfh->flush(); + }; + my $err = $@; + + POSIX::close($childfd); + POSIX::close($ctrlfd); + + if ($err) { + $err =~ s/\n/ /mg; + print STDERR "$err\n"; + print $taskfh "TASK ERROR: $err\n"; + } +}; + # start long running workers # STDIN is redirected to /dev/null # STDOUT,STDERR are redirected to the filename returned by upid_decode @@ -396,6 +482,7 @@ sub fork_worker { my @psync = POSIX::pipe(); my @csync = POSIX::pipe(); + my @ctrlfd = POSIX::pipe() if $sync; my $node = $self->{nodename}; @@ -421,15 +508,26 @@ sub fork_worker { $SIG{INT} = $SIG{QUIT} = $SIG{TERM} = sub { die "received interrupt\n"; }; $SIG{CHLD} = $SIG{PIPE} = 'DEFAULT'; - - # set sess/process group - we want to be able to kill the - # whole process group - POSIX::setsid(); + $SIG{TTOU} = 'IGNORE'; + + my $ppgid; + # set session/process group allows to kill the process group + if ($sync && -t STDIN) { + # some sync'ed workers operate on the tty but setsid sessions lose + # the tty, so just create a new pgroup and give it the tty + $ppgid = POSIX::getpgrp() or die "failed to get old pgid: $!\n"; + POSIX::setpgid(0, 0) or die "failed to setpgid: $!\n"; + POSIX::tcsetpgrp(fileno(STDIN), $$) or die "failed to tcsetpgrp: $!\n"; + } else { + POSIX::setsid(); + } POSIX::close ($psync[0]); + POSIX::close ($ctrlfd[0]) if $sync; POSIX::close ($csync[1]); $outfh = $sync ? $psync[1] : undef; + my $resfh = $sync ? $ctrlfd[1] : undef; eval { PVE::INotify::inotify_close(); @@ -438,7 +536,7 @@ sub fork_worker { &$atfork(); } - # same algorythm as used inside SA + # same algorithm as used inside SA # STDIN = /dev/null my $fd = fileno (STDIN); @@ -450,6 +548,7 @@ sub fork_worker { if !open(STDIN, "{type} eq 'ha') { - print "task started by HA resource agent\n"; - } - eval { &$function($upid); }; + if ($self->{type} eq 'ha') { + print "task started by HA resource agent\n"; + } + &$function($upid); + }; + my ($msg, $exitcode); my $err = $@; if ($err) { chomp $err; $err =~ s/\n/ /mg; syslog('err', $err); - print STDERR "TASK ERROR: $err\n"; - POSIX::_exit(-1); + $msg = "TASK ERROR: $err\n"; + $exitcode = -1; } else { - print STDERR "TASK OK\n"; - POSIX::_exit(0); + $msg = "TASK OK\n"; + $exitcode = 0; + } + POSIX::write($resfh, $msg, length($msg)); + + if ($sync) { + POSIX::close($resfh); + if ( -t STDIN) { + POSIX::tcsetpgrp(fileno(STDIN), $ppgid) or + die "failed to tcsetpgrp to parent: $!\n"; + } } - kill(-9, $$); + POSIX::_exit($exitcode); + kill(-9, $$); # not really needed, just to be sure } # parent POSIX::close ($psync[1]); + POSIX::close ($ctrlfd[1]) if $sync; POSIX::close ($csync[0]); my $readbuf = ''; @@ -561,76 +674,8 @@ sub fork_worker { my $res = 0; if ($sync) { - my $count; - my $outbuf = ''; - my $int_count = 0; - eval { - local $SIG{INT} = local $SIG{QUIT} = local $SIG{TERM} = sub { - # always send signal to all pgrp members - my $kpid = -$cpid; - if ($int_count < 3) { - kill(15, $kpid); # send TERM signal - } else { - kill(9, $kpid); # send KILL signal - } - $int_count++; - }; - local $SIG{PIPE} = sub { die "broken pipe\n"; }; - - my $select = new IO::Select; - my $fh = IO::Handle->new_from_fd($psync[0], 'r'); - $select->add($fh); - - while ($select->count) { - my @handles = $select->can_read(1); - if (scalar(@handles)) { - my $count = sysread ($handles[0], $readbuf, 4096); - if (!defined ($count)) { - my $err = $!; - die "sync pipe read error: $err\n"; - } - last if $count == 0; # eof - - $outbuf .= $readbuf; - while ($outbuf =~ s/^(([^\010\r\n]*)(\r|\n|(\010)+|\r\n))//s) { - my $line = $1; - my $data = $2; - if ($data =~ m/^TASK OK$/) { - # skip - } elsif ($data =~ m/^TASK ERROR: (.+)$/) { - print STDERR "$1\n"; - } else { - print $line; - } - if ($outfh) { - print $outfh $line; - $outfh->flush(); - } - } - } else { - # some commands daemonize without closing stdout - last if !PVE::ProcFSTools::check_process_running($cpid); - } - } - }; - my $err = $@; - - POSIX::close($psync[0]); - - if ($outbuf) { # just to be sure - print $outbuf; - if ($outfh) { - print $outfh $outbuf; - } - } - if ($err) { - $err =~ s/\n/ /mg; - print STDERR "$err\n"; - if ($outfh) { - print $outfh "TASK ERROR: $err\n"; - } - } + $tee_worker->($psync[0], $ctrlfd[0], $outfh, $cpid); &$kill_process_group($cpid, $pstart); # make sure it gets killed