adeff2162d477618093ef22f9b6ef3c6e3e4877f
[spider.git] / perl / DXCron.pm
1 #
2 # module to timed tasks
3 #
4 # Copyright (c) 1998 - Dirk Koopman G1TLH
5 #
6 #
7 #
8
9 package DXCron;
10
11 use DXVars;
12 use DXUtil;
13 use DXM;
14 use DXDebug;
15 use IO::File;
16 use DXLog;
17 use Time::HiRes qw(gettimeofday tv_interval);
18 use DXSubprocess;
19
20 use strict;
21
22 use vars qw{@crontab @lcrontab @scrontab $mtime $lasttime $lastmin $use_localtime};
23
24 $mtime = 0;
25 $lasttime = 0;
26 $lastmin = 0;
27 $use_localtime = 0;
28
29 my $fn = "$main::cmd/crontab";
30 my $localfn = "$main::localcmd/crontab";
31
32 # cron initialisation / reading in cronjobs
33 sub init
34 {
35         if ((-e $localfn && -M $localfn < $mtime) || (-e $fn && -M $fn < $mtime) || $mtime == 0) {
36                 my $t;
37                 
38                 # first read in the standard one
39                 if (-e $fn) {
40                         $t = -M $fn;
41                         
42                         @scrontab = cread($fn);
43                         $mtime = $t if  !$mtime || $t <= $mtime;
44                 }
45
46                 # then read in any local ones
47                 if (-e $localfn) {
48                         $t = -M $localfn;
49                         
50                         @lcrontab = cread($localfn);
51                         $mtime = $t if $t <= $mtime;
52                 }
53                 @crontab = (@scrontab, @lcrontab);
54         }
55 }
56
57 # read in a cron file
58 sub cread
59 {
60         my $fn = shift;
61         my $fh = new IO::File;
62         my $line = 0;
63         my @out;
64
65         dbg("DXCron::cread reading $fn\n") if isdbg('cron');
66         open($fh, $fn) or confess("cron: can't open $fn $!");
67         while (my $l = <$fh>) {
68                 $line++;
69                 chomp $l;
70                 next if $l =~ /^\s*#/ or $l =~ /^\s*$/;
71                 if (my ($ts) = $l =~/^\s*LOCALE\s*=\s*(UTC|LOCAL)/i) {
72                         $ts = uc $ts;
73                         if ($ts eq 'UTC') {
74                                 $use_localtime = 0;
75                         } elsif ($ts eq 'LOCAL') {
76                                 $use_localtime = 1;
77                         }
78                         dbg("DXCron: LOCALE set to $ts") if isdbg('cron');
79                 }
80                 my ($min, $hour, $mday, $month, $wday, $cmd) = $l =~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/;
81                 next unless defined $min;
82                 my $ref = bless {};
83                 my $err = '';
84
85                 if (defined $min && defined $hour && defined $cmd) { # it isn't all of them, but should be enough to tell if this is a real line
86                         $err .= parse($ref, 'min', $min, 0, 60);
87                         $err .= parse($ref, 'hour', $hour, 0, 23);
88                         $err .= parse($ref, 'mday', $mday, 1, 31);
89                         $err .= parse($ref, 'month', $month, 1, 12, "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec");
90                         $err .= parse($ref, 'wday', $wday, 0, 6, "sun", "mon", "tue", "wed", "thu", "fri", "sat");
91                         if (!$err) {
92                                 $ref->{cmd} = $cmd;
93                                 push @out, $ref;
94                                 dbg("DXCron::cread: adding $l\n") if isdbg('cron');
95                         } else {
96                                 $err =~ s/^, //;
97                                 LogDbg('cron', "DXCron::cread: error $err on line $line '$l'");
98                         }
99                 } else {
100                         LogDbg('cron', "DXCron::cread error on line $line '$l'");
101                         my @s = ($min, $hour, $mday, $month, $wday, $cmd);
102                         my $s = "line $line splits as " . join(', ', (map {defined $_ ? qq{$_} : q{'undef'}} @s));
103                         LogDbg('cron', $s);
104                 }
105         }
106         close($fh);
107         return @out;
108 }
109
110 sub parse
111 {
112         my $ref = shift;
113         my $sort = shift;
114         my $val = shift;
115         my $low = shift;
116         my $high = shift;
117         my @req;
118
119         # handle '*' values
120         if ($val eq '*') {
121                 $ref->{$sort} = 0;
122                 return '';
123         }
124
125         # handle comma delimited values
126         my @comma = split /,/o, $val;
127         for (@comma) {
128                 my @minus = split /-/o;
129                 if (@minus == 2) {
130                         return  ", $sort should be $low >= $minus[0] <= $high" if $minus[0] < $low || $minus[0] > $high;
131                         return  ", $sort should be $low >= $minus[1] <= $high" if $minus[1] < $low || $minus[1] > $high;
132                         my $i;
133                         for ($i = $minus[0]; $i <= $minus[1]; ++$i) {
134                                 push @req, 0 + $i; 
135                         }
136                 } else {
137                         return ", $sort should be $low >= $val <= $high" if $_ < $low || $_ > $high;
138                         push @req, 0 + $_;
139                 }
140         }
141         $ref->{$sort} = \@req;
142         
143         return '';
144 }
145
146 # process the cronjobs
147 sub process
148 {
149         my $now = $main::systime;
150         return if $now-$lasttime < 1;
151         
152         my ($sec, $min, $hour, $mday, $mon, $wday);
153         if ($use_localtime) {
154                 ($sec, $min, $hour, $mday, $mon, $wday) = (localtime($now))[0,1,2,3,4,6];
155         } else {
156                 ($sec, $min, $hour, $mday, $mon, $wday) = (gmtime($now))[0,1,2,3,4,6];
157         }
158
159         # are we at a minute boundary?
160         if ($min != $lastmin) {
161                 
162                 # read in any changes if the modification time has changed
163                 init();
164
165                 $mon += 1;       # months otherwise go 0-11
166                 my $cron;
167                 foreach $cron (@crontab) {
168                         if ((!$cron->{min} || grep $_ eq $min, @{$cron->{min}}) &&
169                                 (!$cron->{hour} || grep $_ eq $hour, @{$cron->{hour}}) &&
170                                 (!$cron->{mday} || grep $_ eq $mday, @{$cron->{mday}}) &&
171                                 (!$cron->{mon} || grep $_ eq $mon, @{$cron->{mon}}) &&
172                                 (!$cron->{wday} || grep $_ eq $wday, @{$cron->{wday}})  ){
173                                 
174                                 if ($cron->{cmd}) {
175                                         my $s = $use_localtime ? "LOCALTIME" : "UTC"; 
176                                         dbg("cron: $s $min $hour $mday $mon $wday -> doing '$cron->{cmd}'") if isdbg('cron');
177                                         eval $cron->{cmd};
178                                         dbg("cron: cmd error $@") if $@ && isdbg('cron');
179                                 }
180                         }
181                 }
182         }
183
184         # remember when we are now
185         $lasttime = $now;
186         $lastmin = $min;
187 }
188
189
190 # these are simple stub functions to make connecting easy in DXCron contexts
191 #
192
193 # is it locally connected?
194 sub connected
195 {
196         my $call = uc shift;
197         return DXChannel::get($call);
198 }
199
200 # is it remotely connected anywhere (with exact callsign)?
201 sub present
202 {
203         my $call = uc shift;
204         return Route::get($call);
205 }
206
207 # is it remotely connected anywhere (ignoring SSIDS)?
208 sub presentish
209 {
210         my $call = uc shift;
211         my $c = Route::get($call);
212         unless ($c) {
213                 for (1..15) {
214                         $c = Route::get("$call-$_");
215                         last if $c;
216                 }
217         }
218         return $c;
219 }
220
221 # is it remotely connected anywhere (with exact callsign) and on node?
222 sub present_on
223 {
224         my $call = uc shift;
225         my $ncall = uc shift;
226         my $node = Route::Node::get($ncall);
227         return ($node) ? grep $call eq $_, $node->users : undef;
228 }
229
230 # is it remotely connected (ignoring SSIDS) and on node?
231 sub presentish_on
232 {
233         my $call = uc shift;
234         my $ncall = uc shift;
235         my $node = Route::Node::get($ncall);
236         my $present;
237         if ($node) {
238                 $present = grep {/^$call/ } $node->users;
239         }
240         return $present;
241 }
242
243 # last time this thing was connected
244 sub last_connect
245 {
246         my $call = uc shift;
247         return $main::systime if DXChannel::get($call);
248         my $user = DXUser::get($call);
249         return $user ? $user->lastin : 0;
250 }
251
252 # disconnect a locally connected thing
253 sub disconnect
254 {
255         my $call =  shift;
256         run_cmd("disconnect $call");
257 }
258
259 # start a connect process off
260 sub start_connect
261 {
262         my $call = shift;
263         # connecting is now done in one place - Yeah!
264         run_cmd("connect $call");
265 }
266
267 # spawn any old job off
268 sub spawn
269 {
270         my $line = shift;
271         my $t0 = [gettimeofday];
272
273         dbg("DXCron::spawn: $line") if isdbg("cron");
274         my $fc = DXSubprocess->new();
275         $fc->run(
276                          sub {
277                                  my @res = `$line`;
278 #                                diffms("DXCron spawn 1", $line, $t0, scalar @res) if isdbg('chan');
279                                  return @res
280                          },
281                          sub {
282                                  my ($fc, $err, @res) = @_; 
283                                  if ($err) {
284                                          my $s = "DXCron::spawn: error $err";
285                                          dbg($s);
286                                          return;
287                                  }
288                                  for (@res) {
289                                          chomp;
290                                          dbg("DXCron::spawn: $_") if isdbg("cron");
291                                  }
292                                  diffms("by DXCron::spawn", $line, $t0, scalar @res) if isdbg('progress');
293                          }
294                         );
295 }
296
297 sub spawn_cmd
298 {
299         my $line = shift;
300         my $t0 = [gettimeofday];
301
302         dbg("DXCron::spawn_cmd run: $line") if isdbg('cron');
303         my $fc = DXSubprocess->new();
304         $fc->run(
305                          sub {
306                                  ++$main::me->{_nospawn};
307                                  my @res = $main::me->run_cmd($line);
308 #                                diffms("DXCron spawn_cmd 1", $line, $t0, scalar @res) if isdbg('chan');
309                                  return @res;
310                          },
311                          sub {
312                                  my ($fc, $err, @res) = @_; 
313                                  --$main::me->{_nospawn};
314                                  delete $main::me->{_nospawn} if exists $main::me->{_nospawn} && $main::me->{_nospawn} <= 0;
315                                  if ($err) {
316                                          my $s = "DXCron::spawn_cmd: error $err";
317                                          dbg($s);
318                                  }
319                                  for (@res) {
320                                          chomp;
321                                          dbg("DXCron::spawn_cmd: $_") if isdbg("cron");
322                                  }
323                                  diffms("by DXCron::spawn_cmd", $line, $t0, scalar @res) if isdbg('progress');
324                          }
325                         );
326 }
327
328 # do an rcmd to another cluster from the crontab
329 sub rcmd
330 {
331         my $call = uc shift;
332         my $line = shift;
333
334         # can we see it? Is it a node?
335         my $noderef = Route::Node::get($call);
336         return  unless $noderef && $noderef->version;
337
338         # send it 
339         DXProt::addrcmd($main::me, $call, $line);
340 }
341
342 sub run_cmd
343 {
344         my $line = shift;
345         dbg("DXCron::run_cmd: $line") if isdbg('cron');
346         my @in = $main::me->run_cmd($line);
347         for (@in) {
348                 s/\s*$//;
349                 dbg("DXCron::cmd out: $_") if isdbg('cron');
350         }
351 }
352
353 1;
354 __END__