new connect code
[spider.git] / perl / DXCron.pm
1 #
2 # module to timed tasks
3 #
4 # Copyright (c) 1998 - Dirk Koopman G1TLH
5 #
6 # $Id$
7 #
8
9 package DXCron;
10
11 use DXVars;
12 use DXUtil;
13 use DXM;
14 use DXDebug;
15 use IO::File;
16
17 use strict;
18
19 use vars qw{@crontab $mtime $lasttime $lastmin};
20
21 @crontab = ();
22 $mtime = 0;
23 $lasttime = 0;
24 $lastmin = 0;
25
26
27 my $fn = "$main::cmd/crontab";
28 my $localfn = "$main::localcmd/crontab";
29
30 # cron initialisation / reading in cronjobs
31 sub init
32 {
33         if ((-e $localfn && -M $localfn < $mtime) || (-e $fn && -M $fn < $mtime) || $mtime == 0) {
34                 my $t;
35                 
36                 @crontab = ();
37                 
38                 # first read in the standard one
39                 if (-e $fn) {
40                         $t = -M $fn;
41                         
42                         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                         cread($localfn);
51                         $mtime = $t if $t <= $mtime;
52                 }
53         }
54 }
55
56 # read in a cron file
57 sub cread
58 {
59         my $fn = shift;
60         my $fh = new IO::File;
61         my $line = 0;
62
63         dbg('cron', "cron: reading $fn\n");
64         open($fh, $fn) or confess("cron: can't open $fn $!");
65         while (<$fh>) {
66                 $line++;
67                 chomp;
68                 next if /^\s*#/o or /^\s*$/o;
69                 my ($min, $hour, $mday, $month, $wday, $cmd) = /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/o;
70                 next if !$min;
71                 my $ref = bless {};
72                 my $err;
73                 
74                 $err |= parse($ref, 'min', $min, 0, 60);
75                 $err |= parse($ref, 'hour', $hour, 0, 23);
76                 $err |= parse($ref, 'mday', $mday, 1, 31);
77                 $err |= parse($ref, 'month', $month, 1, 12, "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec");
78                 $err |= parse($ref, 'wday', $wday, 0, 6, "sun", "mon", "tue", "wed", "thu", "fri", "sat");
79                 if (!$err) {
80                         $ref->{cmd} = $cmd;
81                         push @crontab, $ref;
82                         dbg('cron', "cron: adding $_\n");
83                 } else {
84                         dbg('cron', "cron: error on line $line '$_'\n");
85                 }
86         }
87         close($fh);
88 }
89
90 sub parse
91 {
92         my $ref = shift;
93         my $sort = shift;
94         my $val = shift;
95         my $low = shift;
96         my $high = shift;
97         my @req;
98
99         # handle '*' values
100         if ($val eq '*') {
101                 $ref->{$sort} = 0;
102                 return 0;
103         }
104
105         # handle comma delimited values
106         my @comma = split /,/o, $val;
107         for (@comma) {
108                 my @minus = split /-/o;
109                 if (@minus == 2) {
110                         return 1 if $minus[0] < $low || $minus[0] > $high;
111                         return 1 if $minus[1] < $low || $minus[1] > $high;
112                         my $i;
113                         for ($i = $minus[0]; $i <= $minus[1]; ++$i) {
114                                 push @req, 0 + $i; 
115                         }
116                 } else {
117                         return 1 if $_ < $low || $_ > $high;
118                         push @req, 0 + $_;
119                 }
120         }
121         $ref->{$sort} = \@req;
122         
123         return 0;
124 }
125
126 # process the cronjobs
127 sub process
128 {
129         my $now = $main::systime;
130         return if $now-$lasttime < 1;
131         
132         my ($sec, $min, $hour, $mday, $mon, $wday) = (gmtime($now))[0,1,2,3,4,6];
133
134         # are we at a minute boundary?
135         if ($min != $lastmin) {
136                 
137                 # read in any changes if the modification time has changed
138                 init();
139
140                 $mon += 1;       # months otherwise go 0-11
141                 my $cron;
142                 foreach $cron (@crontab) {
143                         if ((!$cron->{min} || grep $_ eq $min, @{$cron->{min}}) &&
144                                 (!$cron->{hour} || grep $_ eq $hour, @{$cron->{hour}}) &&
145                                 (!$cron->{mday} || grep $_ eq $mday, @{$cron->{mday}}) &&
146                                 (!$cron->{mon} || grep $_ eq $mon, @{$cron->{mon}}) &&
147                                 (!$cron->{wday} || grep $_ eq $wday, @{$cron->{wday}})  ){
148                                 
149                                 if ($cron->{cmd}) {
150                                         dbg('cron', "cron: $min $hour $mday $mon $wday -> doing '$cron->{cmd}'");
151                                         eval "$cron->{cmd}";
152                                         dbg('cron', "cron: cmd error $@") if $@;
153                                 }
154                         }
155                 }
156         }
157
158         # remember when we are now
159         $lasttime = $now;
160         $lastmin = $min;
161 }
162
163
164 # these are simple stub functions to make connecting easy in DXCron contexts
165 #
166
167 # is it locally connected?
168 sub connected
169 {
170         my $call = uc shift;
171         return DXChannel->get($call);
172 }
173
174 # is it remotely connected anywhere (with exact callsign)?
175 sub present
176 {
177         my $call = uc shift;
178         return DXCluster->get_exact($call);
179 }
180
181 # is it remotely connected anywhere (ignoring SSIDS)?
182 sub presentish
183 {
184         my $call = uc shift;
185         return DXCluster->get($call);
186 }
187
188 # is it remotely connected anywhere (with exact callsign) and on node?
189 sub present_on
190 {
191         my $call = uc shift;
192         my $node = uc shift;
193         my $ref = DXCluster->get_exact($call);
194         return ($ref && $ref->mynode) ? $ref->mynode->call eq $node : undef;
195 }
196
197 # is it remotely connected anywhere (ignoring SSIDS) and on node?
198 sub presentish_on
199 {
200         my $call = uc shift;
201         my $node = uc shift;
202         my $ref = DXCluster->get($call);
203         return ($ref && $ref->mynode) ? $ref->mynode->call eq $node : undef;
204 }
205
206 # last time this thing was connected
207 sub last_connect
208 {
209         my $call = uc shift;
210         return $main::systime if DXChannel->get($call);
211         my $user = DXUser->get($call);
212         return $user ? $user->lastin : 0;
213 }
214
215 # disconnect a locally connected thing
216 sub disconnect
217 {
218         my $call = uc shift;
219         my $dxchan = DXChannel->get($call);
220         if ($dxchan) {
221                 if ($dxchan->is_ak1a) {
222                         $dxchan->send_now("D", DXProt::pc39($main::mycall, "$main::mycall DXCron"));
223                 } else {
224                         $dxchan->send_now('D', "");
225                 } 
226                 $dxchan->disconnect;
227         }
228         my $out = grep {$_->{call} eq $call} @main::outstanding_connects;
229         if ($out) {
230                 unless ($^O =~ /^MS/i) {
231                         kill 'TERM', $out->{pid};
232                 }
233                 @main::outstanding_connects = grep {$_->{call} ne $call} @main::outstanding_connects;
234         }
235 }
236
237 # start a connect process off
238 sub start_connect
239 {
240         my $call = uc shift;
241         my $lccall = lc $call;
242
243         if (grep {$_->{call} eq $call} @main::outstanding_connects) {
244                 dbg('cron', "Connect not started, outstanding connect to $call");
245                 return;
246         }
247         if (-e "$main::root/connect/$lccall") {
248                 ExtMsg::start_connect($call, "$main::root/connect/$lccall");    
249         } else {
250                 dbg('err', "Cannot find connect script for $lccall");
251         }
252 }
253
254 # spawn any old job off
255 sub spawn
256 {
257         my $line = shift;
258         
259         my $pid = fork();
260         if (defined $pid) {
261                 if (!$pid) {
262                         # in child, unset warnings, disable debugging and general clean up from us
263                         $^W = 0;
264                         eval "{ package DB; sub DB {} }";
265                         alarm(0);
266                         DXChannel::closeall();
267                         for (@main::listeners) {
268                                 $_->close_server;
269                         }
270                         unless ($^O =~ /^MS/) {
271                                 $SIG{HUP} = 'IGNORE';
272                                 $SIG{CHLD} = $SIG{TERM} = $SIG{INT} = $SIG{__WARN__} = 'DEFAULT';
273                         }
274                         exec "$line" or dbg('cron', "exec '$line' failed $!");
275                 }
276                 dbg('cron', "spawn of $line started");
277         } else {
278                 dbg('cron', "can't fork for $line $!");
279         }
280
281         # coordinate
282         sleep(1);
283 }
284
285 # do an rcmd to another cluster from the crontab
286 sub rcmd
287 {
288         my $call = uc shift;
289         my $line = shift;
290
291         # can we see it? Is it a node?
292         my $noderef = DXCluster->get_exact($call);
293         return  if !$noderef || !$noderef->pcversion;
294
295         # send it 
296         DXProt::addrcmd($DXProt::me, $call, $line);
297 }
298 1;
299 __END__