added run_cmd to the cron system to run any DXCluster command
[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 =  shift;
219         run_cmd("disconnect $call");
220 }
221
222 # start a connect process off
223 sub start_connect
224 {
225         my $call = shift;
226         # connecting is now done in one place - Yeah!
227         run_cmd("connect $call");
228 }
229
230 # spawn any old job off
231 sub spawn
232 {
233         my $line = shift;
234         
235         my $pid = fork();
236         if (defined $pid) {
237                 if (!$pid) {
238                         # in child, unset warnings, disable debugging and general clean up from us
239                         $^W = 0;
240                         eval "{ package DB; sub DB {} }";
241                         DXChannel::closeall();
242                         for (@main::listeners) {
243                                 $_->close_server;
244                         }
245                         unless ($main::is_win) {
246                                 $SIG{HUP} = 'IGNORE';
247                                 $SIG{CHLD} = $SIG{TERM} = $SIG{INT} = $SIG{__WARN__} = 'DEFAULT';
248                                 alarm(0);
249                         }
250                         exec "$line" or dbg('cron', "exec '$line' failed $!");
251                 }
252                 dbg('cron', "spawn of $line started");
253         } else {
254                 dbg('cron', "can't fork for $line $!");
255         }
256
257         # coordinate
258         sleep(1);
259 }
260
261 # do an rcmd to another cluster from the crontab
262 sub rcmd
263 {
264         my $call = uc shift;
265         my $line = shift;
266
267         # can we see it? Is it a node?
268         my $noderef = DXCluster->get_exact($call);
269         return  if !$noderef || !$noderef->pcversion;
270
271         # send it 
272         DXProt::addrcmd($DXProt::me, $call, $line);
273 }
274
275 sub run_cmd
276 {
277         my $line = shift;
278         my @in = DXCommandmode::run_cmd($DXProt::me, $line);
279         dbg('cron', "cmd run: $line");
280         for (@in) {
281                 s/\s*$//og;
282                 dbg('cron', "cmd out: $_");
283         }
284 }
285 1;
286 __END__