added needed use directive
[monkeysphere-validation-agent.git] / Crypt / Monkeysphere / MSVA / Monitor.pm
1 #----------------------------------------------------------------------
2 # Monkeysphere Validation Agent, Perl version
3 # Marginal User Interface for reasonable prompting
4 # Copyright © 2010 Daniel Kahn Gillmor <dkg@fifthhorseman.net>,
5 #                  Matthew James Goins <mjgoins@openflows.com>,
6 #                  Jameson Graef Rollins <jrollins@finestructure.net>,
7 #                  Elliot Winard <enw@caveteen.com>
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 #
22 #----------------------------------------------------------------------
23
24 { package Crypt::Monkeysphere::MSVA::Monitor;
25
26   use Module::Load::Conditional;
27   use strict;
28   use warnings;
29
30   sub createwindow {
31     my $self = shift;
32
33     require Gtk2;
34     Gtk2->init();
35     $self->{dialog} = Gtk2::Dialog->new("Monkeysphere Validation Agent updated!",
36                                         undef,
37                                         [],
38                                         'gtk-no' => 'cancel',
39                                         'gtk-yes' => 'ok');
40
41     my $icon_file = '/usr/share/pixmaps/monkeysphere-icon.png';
42
43     $self->{dialog}->set_default_icon_from_file($icon_file)
44       if (-r $icon_file);
45     $self->{dialog}->set_default_response('ok');
46     my $label = Gtk2::Label->new("Some components of the running Monkeysphere
47 Validation Agent have been updated.
48
49 Would you like to restart the validation agent?");
50     $label->show();
51     $self->{dialog}->get_content_area()->add($label);
52     $self->{dialog}->signal_connect(response => sub { my ($dialog,$resp) = @_; $self->button_clicked($resp); });
53     $self->{dialog}->signal_connect(delete_event => sub { $self->button_clicked('cancel'); return 1; });
54   }
55
56   sub button_clicked {
57     my $self = shift;
58     my $resp = shift;
59     if ($resp eq 'ok') {
60       # if the user wants to restart the validation agent, we should terminate
61       # so that our parent gets a SIGCHLD.
62       exit 0;
63     } else {
64       $self->{dialog}->hide();
65     }
66   }
67
68   sub prompt {
69     my $self = shift;
70     $self->{dialog}->show();
71   }
72
73   sub spawn {
74     my $self = shift;
75     if (! Module::Load::Conditional::can_load('modules' => { 'Gtk2' => undef,
76                                                              'AnyEvent' => undef,
77                                                              'Linux::Inotify2' => undef,
78                                                            })) {
79       $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", $$);
80       return;
81     }
82     my $fork = fork();
83     if (! defined $fork) {
84       $self->{logger}->log('error', "Failed to spawn monitoring process\n");
85       return;
86     }
87     if ($fork) {
88       $self->{monitorpid} = $fork;
89       $self->{logger}->log('debug', "spawned monitoring process pid %d\n", $self->{monitorpid});
90       return;
91     } else {
92       $self->childmain();
93     }
94   }
95
96   sub childmain {
97     my $self = shift;
98
99     $0 = 'MSVA (perl) Upgrade Monitor';
100
101     $self->{files} = [ $0, values(%INC) ];
102     $self->{logger}->log('debug3', "setting up monitoring on these files:\n%s\n", join("\n", @{$self->{files}}));
103
104     # close all filedescriptors except for std{in,out,err}:
105     # see http://markmail.org/message/mlbnvfa7ds25az2u
106     close $_ for map { /^(?:ARGV|std(?:err|out|in)|STD(?:ERR|OUT|IN))$/ ? () : *{$::{$_}}{IO} || () } keys %::;
107
108     $self->createwindow();
109
110     require Linux::Inotify2;
111
112     $self->{inotify} = Linux::Inotify2::->new()
113       or die "unable to create new inotify object: $!";
114
115     my $flags = 0xc06;
116     # FIXME: couldn't figure out how to get these to work in "strict subs" mode:
117     # my $flags = Linux::Inotify2::IN_MODIFY |
118                 # Linux::Inotify2::IN_ATTRIB |
119                 # Linux::Inotify2::IN_DELETE_SELF |
120                 # Linux::Inotify2::IN_MOVE_SELF;
121
122     foreach my $file (@{$self->{files}}) {
123       $self->{inotify}->watch($file,
124                               $flags,
125                               sub {
126                                 $self->prompt();
127                               });
128     }
129
130     require AnyEvent;
131     my $inotify_w = AnyEvent->io (
132                                   fh => $self->{inotify}->fileno,
133                                   poll => 'r',
134                                   cb => sub { $self->changed },
135                                  );
136     my $w = AnyEvent->signal(signal => 'TERM', cb => sub { exit 1; });
137
138     Gtk2->main();
139     $self->{logger}->log('error', "Got to the end of the monitor process somehow\n");
140     # if we get here, we want to terminate with non-zero
141     exit 1;
142   }
143
144
145   sub changed {
146     my $self = shift;
147
148     $self->{logger}->log('debug', "changed!\n");
149     $self->{inotify}->poll();
150   }
151
152   # forget about cleaning up the monitoring child (e.g. we only want
153   # the parent process to know about this)
154   sub forget {
155     my $self = shift;
156     undef $self->{monitorpid};
157   }
158
159   sub getchildpid {
160     my $self = shift;
161     return $self->{monitorpid};
162   }
163
164   sub DESTROY {
165     my $self = shift;
166     if (defined $self->{monitorpid}) {
167       kill('TERM', $self->{monitorpid});
168       my $oldexit = $?;
169       waitpid($self->{monitorpid}, 0);
170       $? = $oldexit;
171       undef($self->{monitorpid});
172     }
173   }
174
175   sub new {
176     my $class = shift;
177     my $logger = shift;
178
179     my $self = { monitorpid => undef,
180                  logger => $logger,
181                };
182
183     bless ($self, $class);
184
185     $self->spawn();
186     return $self;
187   }
188
189   1;
190 }