detect upgrades and prompt user when we notice them if the right underlying modules...
authorDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Wed, 27 Oct 2010 02:51:20 +0000 (22:51 -0400)
committerDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Wed, 27 Oct 2010 02:51:20 +0000 (22:51 -0400)
Changelog
Crypt/Monkeysphere/MSVA.pm
Crypt/Monkeysphere/MSVA/Monitor.pm [new file with mode: 0644]

index 3f1c85d16d00f8e7d475190b236fcae3fd87f4cd..89145989c9772fe0c5b9e5ef1390691b40c08d32 100644 (file)
--- 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 <dkg@fifthhorseman.net>  Thu, 14 Oct 2010 16:30:54 -0400
-
-msva-perl (0.5) unstable; urgency=low
+ -- Daniel Kahn Gillmor <dkg@fifthhorseman.net>  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)
index 8ccebf649f618a2e6db1de09ee9b5a5c06d9d164..bff6088722a46c8b542049abacbc1baf184e0e19 100755 (executable)
@@ -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);
     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};
 
 
     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);
 
     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.
       # 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 (file)
index 0000000..aad42b7
--- /dev/null
@@ -0,0 +1,186 @@
+#----------------------------------------------------------------------
+# Monkeysphere Validation Agent, Perl version
+# Marginal User Interface for reasonable prompting
+# Copyright © 2010 Daniel Kahn Gillmor <dkg@fifthhorseman.net>,
+#                  Matthew James Goins <mjgoins@openflows.com>,
+#                  Jameson Graef Rollins <jrollins@finestructure.net>,
+#                  Elliot Winard <enw@caveteen.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+#----------------------------------------------------------------------
+
+{ 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;
+}