From 07f8138ce15dad16354f696d7fc3a521cea4237d Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 26 Oct 2010 22:51:20 -0400 Subject: [PATCH] detect upgrades and prompt user when we notice them if the right underlying modules are available --- Changelog | 15 ++- Crypt/Monkeysphere/MSVA.pm | 36 +++++- Crypt/Monkeysphere/MSVA/Monitor.pm | 186 +++++++++++++++++++++++++++++ 3 files changed, 227 insertions(+), 10 deletions(-) create mode 100644 Crypt/Monkeysphere/MSVA/Monitor.pm diff --git a/Changelog b/Changelog index 3f1c85d..8914598 100644 --- a/Changelog +++ b/Changelog @@ -1,13 +1,16 @@ -msva-perl (0.6~pre) unstable; urgency=low +msva-perl (0.6~pre) upstream; - * add new element to JSON syntax allowing request to override + * Add new element to JSON syntax allowing request to override keyserver_policy (closes MS #2542) - * do not kill off child handling processes on HUP -- let them finish + * Do not kill off child handling processes on HUP -- let them finish their queries. + * Refactor logging code + * If we have Gtk2, Linux::Inotify2, and AnyEvent, we should monitor for + updates and prompt the user when we notice one. (closes MS #2540) - -- Daniel Kahn Gillmor Thu, 14 Oct 2010 16:30:54 -0400 - -msva-perl (0.5) unstable; urgency=low + -- Daniel Kahn Gillmor Tue, 26 Oct 2010 22:49:40 -0400 + +msva-perl (0.5) upstream; * If ${MSVA_KEYSERVER} is unset or blank, default to using keyserver from ${GNUPGHOME}/gpg.conf if that file exists. (addresses MS #2080) diff --git a/Crypt/Monkeysphere/MSVA.pm b/Crypt/Monkeysphere/MSVA.pm index 8ccebf6..bff6088 100755 --- a/Crypt/Monkeysphere/MSVA.pm +++ b/Crypt/Monkeysphere/MSVA.pm @@ -32,6 +32,7 @@ use Config::General; use Crypt::Monkeysphere::MSVA::MarginalUI; use Crypt::Monkeysphere::MSVA::Logger; + use Crypt::Monkeysphere::MSVA::Monitor; use JSON; use POSIX qw(strftime); @@ -312,6 +313,9 @@ my $self = shift; my $cgi = shift; + # This is part of a spawned child process. We don't want the + # child process to destroy the update monitor when it terminates. + $self->{updatemonitor}->forget(); my $clientinfo = get_client_info(select); my $clientuid = $clientinfo->{uid}; @@ -608,9 +612,31 @@ msvalog('debug', "Subprocess %d terminated.\n", $pid); - if (exists $self->{child_pid} && - ($self->{child_pid} == 0 || - $self->{child_pid} == $pid)) { + if (exists $self->{updatemonitor} && + defined $self->{updatemonitor}->getchildpid() && + $self->{updatemonitor}->getchildpid() == $pid) { + my $exitstatus = POSIX::WEXITSTATUS($?); + msvalog('verbose', "Update monitoring process (%d) terminated with code %d.\n", $pid, $exitstatus); + if (0 == $exitstatus) { + msvalog('info', "Reloading MSVA due to update request.\n"); + # sending self a SIGHUP: + kill(1, $$); + } else { + msvalog('error', "Update monitoring process (%d) died unexpectedly with code %d.\nNo longer monitoring for updates; please send HUP manually.\n", $pid, $exitstatus); + # it died for some other weird reason; should we respawn it? + + # FIXME: i'm worried that re-spawning would create a + # potentially abusive loop, if there are legit, repeatable + # reasons for the failure. + +# $self->{updatemonitor}->spawn(); + + # instead, we'll just avoid trying to kill the next process with this PID: + $self->{updatemonitor}->forget(); + } + } elsif (exists $self->{child_pid} && + ($self->{child_pid} == 0 || + $self->{child_pid} == $pid)) { my $exitstatus = POSIX::WEXITSTATUS($?); msvalog('verbose', "Subprocess %d terminated; exiting %d.\n", $pid, $exitstatus); $server->set_exit_status($exitstatus); @@ -654,7 +680,7 @@ if ((exists $ENV{MSVA_CHILD_PID}) && ($ENV{MSVA_CHILD_PID} ne '')) { # this is most likely a re-exec. - msvalog('info', "This appears to be a re-exec, monitoring child pid %d\n", $ENV{MSVA_CHILD_PID}); + msvalog('info', "This appears to be a re-exec, continuing with child pid %d\n", $ENV{MSVA_CHILD_PID}); $self->{child_pid} = $ENV{MSVA_CHILD_PID} + 0; } elsif ($#ARGV >= 0) { $self->{child_pid} = 0; # indicate that we are planning to fork. @@ -692,6 +718,8 @@ # ssh-agent. maybe avoid backgrounding by setting # MSVA_NO_BACKGROUND. }; + + $self->{updatemonitor} = Crypt::Monkeysphere::MSVA::Monitor->new($logger); } sub extracerts { diff --git a/Crypt/Monkeysphere/MSVA/Monitor.pm b/Crypt/Monkeysphere/MSVA/Monitor.pm new file mode 100644 index 0000000..aad42b7 --- /dev/null +++ b/Crypt/Monkeysphere/MSVA/Monitor.pm @@ -0,0 +1,186 @@ +#---------------------------------------------------------------------- +# Monkeysphere Validation Agent, Perl version +# Marginal User Interface for reasonable prompting +# Copyright © 2010 Daniel Kahn Gillmor , +# Matthew James Goins , +# Jameson Graef Rollins , +# Elliot Winard +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#---------------------------------------------------------------------- + +{ package Crypt::Monkeysphere::MSVA::Monitor; + + use strict; + use warnings; + + sub createwindow { + my $self = shift; + + require Gtk2; + Gtk2->init(); + $self->{dialog} = Gtk2::Dialog->new("Monkeysphere Validation Agent updated!", + undef, + [], + 'gtk-no' => 'cancel', + 'gtk-yes' => 'ok'); + + my $icon_file = '/usr/share/pixmaps/monkeysphere-icon.png'; + + $self->{dialog}->set_default_icon_from_file($icon_file) + if (-r $icon_file); + $self->{dialog}->set_default_response('ok'); + my $label = Gtk2::Label->new("Some components of the running Monkeysphere +Validation Agent have been updated. + +Would you like to restart the validation agent?"); + $label->show(); + $self->{dialog}->get_content_area()->add($label); + $self->{dialog}->signal_connect(response => sub { my ($dialog,$resp) = @_; $self->button_clicked($resp); }); + $self->{dialog}->signal_connect(delete_event => sub { $self->button_clicked('cancel'); return 1; }); + } + + sub button_clicked { + my $self = shift; + my $resp = shift; + if ($resp eq 'ok') { + # if the user wants to restart the validation agent, we should terminate + # so that our parent gets a SIGCHLD. + exit 0; + } else { + $self->{dialog}->hide(); + } + } + + sub prompt { + my $self = shift; + $self->{dialog}->show(); + } + + sub spawn { + my $self = shift; + if (! Module::Load::Conditional::can_load('modules' => { 'Gtk2' => undef, + 'AnyEvent' => undef, + 'Linux::Inotify2' => undef, + })) { + $self->{logger}->log('info', "Not spawning a monitoring process; issue 'kill -s HUP %d' to restart after upgrades.\nInstall Perl modules Gtk2, AnyEvent, and Linux::Inotify2 for automated restarts on upgrades.\n", $$); + return; + } + my $fork = fork(); + if (! defined $fork) { + $self->{logger}->log('error', "Failed to spawn monitoring process\n"); + return; + } + if ($fork) { + $self->{monitorpid} = $fork; + $self->{logger}->log('debug', "spawned monitoring process pid %d\n", $self->{monitorpid}); + return; + } else { + $self->childmain(); + } + } + + sub childmain { + my $self = shift; + + $self->{files} = [ $0, values(%INC) ]; + $self->{logger}->log('debug3', "setting up monitoring on these files:\n%s\n", join("\n", @{$self->{files}})); + + # close all filedescriptors except for std{in,out,err}: + # see http://markmail.org/message/mlbnvfa7ds25az2u + close $_ for map { /^(?:ARGV|std(?:err|out|in)|STD(?:ERR|OUT|IN))$/ ? () : *{$::{$_}}{IO} || () } keys %::; + + $self->createwindow(); + + require Linux::Inotify2; + + $self->{inotify} = new Linux::Inotify2 + or die "unable to create new inotify object: $!"; + + my $flags = 0xc06; + # FIXME: couldn't figure out how to get these to work in "strict subs" mode: + # my $flags = Linux::Inotify2::IN_MODIFY | + # Linux::Inotify2::IN_ATTRIB | + # Linux::Inotify2::IN_DELETE_SELF | + # Linux::Inotify2::IN_MOVE_SELF; + + foreach my $file (@{$self->{files}}) { + $self->{inotify}->watch($file, + $flags, + sub { + $self->prompt(); + }); + } + + require AnyEvent; + my $inotify_w = AnyEvent->io ( + fh => $self->{inotify}->fileno, + poll => 'r', + cb => sub { $self->changed }, + ); + my $w = AnyEvent->signal(signal => 'TERM', cb => sub { exit 1; }); + + Gtk2->main(); + $self->{logger}->log('error', "Got to the end of the monitor process somehow\n"); + # if we get here, we want to terminate with non-zero + exit 1; + } + + + sub changed { + my $self = shift; + + $self->{logger}->log('debug', "changed!\n"); + $self->{inotify}->poll(); + } + + # forget about cleaning up the monitoring child (e.g. we only want + # the parent process to know about this) + sub forget { + my $self = shift; + undef $self->{monitorpid}; + } + + sub getchildpid { + my $self = shift; + return $self->{monitorpid}; + } + + sub DESTROY { + my $self = shift; + if (defined $self->{monitorpid}) { + # SIGTERM is 15: + kill(15, $self->{monitorpid}); + waitpid($self->{monitorpid}, 0); + undef($self->{monitorpid}); + } + } + + sub new { + my $class = shift; + my $logger = shift; + + my $self = { monitorpid => undef, + logger => $logger, + }; + + bless ($self, $class); + + $self->spawn(); + return $self; + } + + 1; +} -- 2.26.2